v5-spot-indexer API
Read-only REST API over indexed SwapShell.SwapExecuted events, with execution-quality and average-cost realized PnL.
Overview
- Read-only. All endpoints are
GET; no authentication. - Finalized data only. Only logs from finalized blocks are stored, so rows never change or roll back.
- All swaps, including failures (
success: falsewith a decodedreason).
Chains & contracts
| Chain | chainId | SwapShell |
|---|---|---|
| Arbitrum | 42161 | 0xF8698CA109583Ea2CBc57EF560d4Be2E0DF349A2 |
| Robinhood Testnet | 46630 | 0x528B30910B3ef5a615cDC3847F273947dc474519 |
| Robinhood Mainnet | 4663 | 0x4262efBd176F02824af27010bEa218429c33c7E8 |
Which chains are live depends on deployment config — call /stats to see what's actually indexed.
Conventions
These apply to every endpoint — read this first.
- Big numbers are strings. Token amounts (
uint256) and block numbers are returned as decimal strings, never JSON numbers. Parse with a bigint/decimal library. - Raw token amounts.
amountIn/amountOutetc. are in the token's smallest unit — divide by10^decimals(from thetokensmap). e.g."1500000"USDC (decimals 6) = 1.50 USDC. - USD fields are human decimals. Anything ending in
Usdis already a normal decimal string like"12.34". - Addresses are lowercase (query params are lowercased server-side).
- Timestamps are ISO 8601 UTC.
- Token maps. Any response with token addresses also includes a
tokensobject keyed by"<chainId>:<address>"resolvingsymbol/decimals/name. - Query booleans are the literal strings
true/false.
Pagination & errors
List endpoints use keyset (cursor) pagination, newest first: limit (default 50, max 500) and an opaque nextCursor you pass back as cursor. nextCursor: null means the end.
curl -s "/trades?chainId=42161&limit=100"
curl -s "/trades?chainId=42161&limit=100&cursor=<nextCursor>"
400 = invalid query/params; 404 = not found.
Endpoints
Per-chain indexing status: last indexed block, finalized head, lag (blocks behind), and trade count.
{
"chains": [
{ "chainId": 42161, "name": "arbitrum",
"lastIndexedBlock": "472437134", "finalizedHead": "472440000",
"lag": "2866", "trades": 114 }
]
}
Platform-wide daily spot metrics over a trailing window of whole UTC calendar days, oldest first. One row per day (days with no activity are included as zeros).
| Metric | Definition |
|---|---|
transactions | Executed (successful) swaps that day |
activeAddresses | Distinct takers with ≥1 executed swap that day |
newAddresses | Takers whose first-ever executed swap landed that day |
| Param | Type | Description |
|---|---|---|
days | int | Trailing UTC days to return, incl. today (default 30, max 365) |
chainId | int | Restrict to one chain (default: all indexed). With a chain set, newAddresses is first-ever on that chain. |
curl -s "/stats/daily?days=7"
{
"segment": "spot", "chainId": null, "timezone": "UTC", "days": 7,
"rows": [
{ "date": "2026-07-09", "activeAddresses": 812, "newAddresses": 143, "transactions": 5127 },
{ "date": "2026-07-10", "activeAddresses": 934, "newAddresses": 121, "transactions": 6011 }
]
}
Only executed fills count — reverted swaps (and non-swap events the indexer never stores: cancels, transfers, deposits/withdrawals) are excluded. Today's row and short windows trail real time by the finality lag.
The trade feed. All filters optional, combined with AND.
| Param | Type | Description |
|---|---|---|
chainId | int | Restrict to one chain |
taker | address | Trades by this taker |
tokenIn / tokenOut | address | Filter by input/output token |
router | address | Underlying router/venue |
routeTag | string | Decoded route tag |
success | bool | true = successful only |
priced | bool | true = USD-priced (stablecoin leg) only |
fromBlock / toBlock | int | Inclusive block range |
cursor | string | Pagination cursor |
limit | int | Page size (default 50, max 500) |
Returns { trades: Trade[], tokens: {…}, nextCursor }. See Data model.
curl -s "/trades?chainId=42161&success=true&limit=20"
All swaps emitted in a single transaction.
curl -s "/trades/42161/0xabc...def"
Trades for one taker, newest first. Supports cursor and limit.
curl -s "/takers/42161/0x51cc...410c/trades?limit=50"
Current inventory per token for a taker, with average cost and PnL.
{
"positions": [
{ "token": "0xf97f4d...39fb4", "qty": "0.2551457815815209",
"avgCostUsd": "7.838655954266621", "realizedPnlUsd": "0",
"markPriceUsd": "7.84725886703017", "unrealizedPnlUsd": "0.0021949969",
"lastBlockNumber": "472437134" }
],
"tokens": { "42161:0xf97f4d...39fb4": { "symbol": "LINK", "decimals": 18, "…": "…" } }
}
| Field | Description |
|---|---|
qty | Remaining held quantity (human units) |
avgCostUsd | Average cost basis per unit, USD |
realizedPnlUsd | Cumulative realized PnL for this token |
markPriceUsd | Latest observed execution price |
unrealizedPnlUsd | (markPrice - avgCost) * qty |
lastBlockNumber | Block of the most recent trade in the position |
Historic total portfolio value (USD) for a taker, as a time series for charts (oldest first). A background job snapshots every tracked wallet on a fixed cadence (default 30 min), pricing each held token once via the chain's router and valuing the taker's positions. Cached (~5 min) with a Cache-Control header.
| Param | Type | Description |
|---|---|---|
window | enum | 1d | 7d | 30d | 90d | all (default 30d) |
curl -s "/takers/42161/0x51cc...410c/portfolio?window=30d"
{
"chainId": 42161, "address": "0x51cc...410c", "window": "30d",
"points": [
{ "capturedAt": "2026-06-16T00:00:00.000Z", "totalValueUsd": "1234.56",
"pricedTokenCount": 3, "totalTokenCount": 4 }
]
}
Value is based on the derived positions (net tokens acquired via SwapShell), not on-chain balances. When pricedTokenCount < totalTokenCount the value is partial — some held tokens had no router liquidity to price.
Aggregate PnL/quality metrics, grouped. groupBy is required: taker, routeTag, tokenIn, or tokenOut. Accepts the same optional filters as /trades, plus limit.
curl -s "/pnl/summary?groupBy=taker&taker=0x51cc...410c"
{
"groupBy": "taker",
"groups": [
{ "key": "0x51cc...410c", "trades": 2, "successful": 2, "priced": 2,
"usdNotional": "2.00", "priceImprovementUsd": "0.000294",
"avgSlippageHeadroomUsd": "0.005120", "realizedPnlUsd": null }
],
"tokens": {}
}
When grouping by a token, the tokens map resolves the address keys; for taker/routeTag it's empty.
Spot trading stats for a single account. The address is a query param (not chain-path scoped like /takers/…).
| Param | Type | Description |
|---|---|---|
address | address | Required. Account to report on |
chainId | int | Restrict to one chain (default: all enabled) |
window | enum | 1d | 7d | 14d | 30d | all (default all) |
curl -s "/trading-stats?address=0x51cc...410c&window=all"
{
"address": "0x51cc...410c", "chainId": null, "window": "all",
"spot": { "volumeUsd": "10280000.00", "feesUsd": "0", "trades": 128 }
}
volumeUsd = total USD notional of priced swaps in the window; feesUsd is currently always 0.
Spot trading stats for a list of accounts. Send a JSON body; get the combined total across all addresses plus a per-address breakdown. Addresses with no trades in the window are returned with zeros.
| Field | Type | Description |
|---|---|---|
addresses | address[] | Required. 1–1000 accounts (de-duplicated) |
chainId | int | Restrict to one chain (default: all enabled) |
window | enum | 1d | 7d | 14d | 30d | all (default all) |
curl -s -X POST "/trading-stats/batch" -H 'content-type: application/json' -d '{"addresses":["0x51cc...410c","0xabcd...1234"],"window":"all"}'
{
"chainId": null, "window": "all", "addressCount": 2,
"total": { "volumeUsd": "10530000.00", "feesUsd": "0", "trades": 140 },
"byAddress": [
{ "address": "0x51cc...410c", "volumeUsd": "10280000.00", "feesUsd": "0", "trades": 128 },
{ "address": "0xabcd...1234", "volumeUsd": "250000.00", "feesUsd": "0", "trades": 12 }
]
}
total.volumeUsd is summed in Postgres for full precision; per-address volumeUsd = total USD notional of priced swaps in the window.
Platform-wide PnL leaderboard, aggregated per taker over a time window. When chainId is omitted, a taker's activity is summed across all enabled chains.
| Param | Type | Description |
|---|---|---|
window | enum | 1d | 7d | 14d | 30d | all (default 30d) |
chainId | int | Restrict to one chain (default: all enabled) |
orderBy | enum | pnl (default) or volume |
limit | int | Page size (default 50, max 500) |
offset | int | Rows to skip — for numbered pages |
curl -s "/leaderboard?window=30d&limit=10&offset=0"
{
"window": "30d", "chainId": null, "orderBy": "pnl",
"total": 247, "limit": 10, "offset": 0,
"rows": [
{ "trader": "0x51cc...410c", "volumeUsd": "1203000.00",
"feesUsd": "0", "realizedPnlUsd": "1230000.00", "trades": 42 }
]
}
PnL semantics: realized only — average-cost realized gains/losses from sells within the window. A buy-and-hold trader shows realizedPnlUsd: null for the window (their unrealized PnL is on /takers/…/positions). Short windows trail real time by the finality lag.
USD trading volume per token over a window (default the last 24 hours), ranked highest first. Each priced swap's usdNotional is attributed to its non-stable base token (the asset bought/sold against a stablecoin), so each trade counts once. Stablecoins, token-to-token swaps, and failed swaps have no base token / USD value and are excluded. Fetch a single token's 24h volume by passing its contract address (address/ca) or ticker symbol (symbol/ticker). Heavily cached (~5m).
| Param | Type | Description |
|---|---|---|
window | enum | 1d | 7d | 14d | 30d | all (default 1d) |
chainId | int | Restrict to one chain (default: all enabled) |
address / ca | address | Only this base-token contract address |
symbol / ticker | string | Only base tokens with this symbol (case-insensitive) |
limit | int | Max tokens (default 50, max 500) |
curl -s "/tokens/volume?ticker=WETH&window=1d"
{
"window": "1d", "chainId": null, "address": null, "symbol": "WETH",
"rows": [
{ "chainId": 42161, "token": "0x82af49...fbab1",
"volumeUsd": "1840293.55", "trades": 312 }
],
"tokens": { "42161:0x82af49...fbab1": { "symbol": "WETH", "decimals": 18, "…": "…" } }
}
With no chainId, the same token on different chains appears as separate rows — and a shared symbol can match several tokens. Short windows trail real time by the finality lag.
Cached metadata for one token (404 if unseen).
{ "chainId": 42161, "address": "0xaf88...5831",
"symbol": "USDC", "name": "USD Coin", "decimals": 6,
"underlying": null, // wrapped token's underlying, else null
"firstSeenBlock": "472212554" }
Metadata for every tracked token in the served categories (meme and pToken by default), in one call. The token list is owned by the router (its /tokens route); this endpoint fetches it, keeps the served categories, and resolves each token's market metadata from the same source as /token-metadata/{address}. Each entry is an identical metadata record. priceUsd (with marketCap/fdv rescaled to match) is overlaid live from the streaming price source — see /token-metadata/{address}.
resolved vs requested. A 502 is returned only when the router token list itself can't be fetched.curl -s "/token-metadata"
{
"requested": 30, "resolved": 27,
"tokens": [
{ "address": "0x020bfc...018b4", "symbol": "CASHCAT", "name": "Cash Cat",
"priceUsd": "0.1231", "marketCap": 122122047, "liquidityUsd": 6147985.84,
"imageUrl": "/token-images/a1b2c3...", "source": { "score": 1, "…": "…" } }
]
}
Each element of tokens has the same shape as /token-metadata/{address} (see there for field details and image handling).
One authoritative metadata record for a token (price, liquidity, 24h volume, market cap, socials, logo), aggregated from public market data. priceUsd is overlaid live from the streaming price source (the same feed behind /token-price and /ws), with marketCap/fdv rescaled to match; the slower-moving fields (liquidity, socials, images) are cached per token (~60s) within a rate-limited budget. Display data only — not for settlement.
pairAddress is informational), but we still must pick which pool's metadata to trust. We rank all pools whose base token is the given address by economic activity — USD liquidity (dominant), 24h volume, total 24h transactions, and two-sidedness (min(buys,sells)/max(buys,sells)) — and return the winner. source.liquidityDominance (winner's share of total liquidity) and source.score (0–1 composite) express confidence.| Param | Type | Description |
|---|---|---|
address | address | Required. Base-token contract address |
curl -s "/token-metadata/0x020bfc650a365f8bb26819deaabf3e21291018b4"
{
"address": "0x020bfc...018b4",
"chainId": "robinhood",
"name": "Cash Cat", "symbol": "CASHCAT",
"priceUsd": "0.1231", "priceNative": "0.00007077",
"marketCap": 122122047, "fdv": 122122047,
"liquidityUsd": 6147985.84, "volume24h": 100354315.08,
"txns24h": { "buys": 32200, "sells": 25238 },
"imageUrl": "/token-images/a1b2c3...", "header": "/token-images/d4e5f6...", "openGraph": "/token-images/7890ab...",
"websites": [ { "url": "https://cashcattoken.xyz/", "label": "Website" } ],
"socials": [ { "type": "twitter", "url": "https://x.com/cashcat_token" } ],
"source": {
"dexId": "uniswap", "pairAddress": "0xa70f...e313", "labels": ["v3"],
"score": 1, "liquidityDominance": 0.99998, "pairsConsidered": 5
}
}
Numeric market fields (liquidityUsd, volume24h, marketCap, fdv) are JSON numbers; prices are strings. Covers every known chain/DEX (not limited to indexed chains). 404 when the token has no market data; 502 if the upstream is unreachable.
Image fields (imageUrl, header, openGraph) are same-origin /token-images/{id} paths served by the endpoint below — resolve them against this host. null when the token has no such asset.
Streams a token image referenced by /token-metadata. The bytes are cached hard (~24h, immutable) and served from this origin. Not called directly — use the imageUrl/header/openGraph paths returned by /token-metadata.
404 if the id is unknown or expired (re-fetch the token's metadata for a fresh path); 502 if the image can't be retrieved.
Current USD price for a token, served from the live streaming price cache. A token someone is watching answers instantly from memory; a cold token costs one upstream query and joins the live subscription set so subsequent reads are instant. For a push feed instead of polling, use /ws.
| Param | Type | Description |
|---|---|---|
networkId | int | Price-provider network id (defaults to the configured chain) |
curl -s "/token-price/0x020bfc650a365f8bb26819deaabf3e21291018b4"
{
"address": "0x020bfc...018b4", "networkId": 4663,
"priceUsd": 0.15149986707, "timestamp": 1783662870, "source": "ws"
}
timestamp is the upstream price observation (unix seconds). source is ws when the value came off the live stream, query for a cold/fallback read. 503 when live prices aren't configured; 404 when the token can't be priced; 502 if the upstream is unreachable.
Point-chart data for a token — one close price (USD) and volume per time bucket, oldest first. Meant for a simple line chart; for candlesticks use /token-candles.
| Param | Type | Description |
|---|---|---|
resolution | enum | 1m | 5m | 15m | 1h | 1d (default 1h) |
networkId | int | Chart-provider network id (defaults to the configured chain) |
from / to | int | Unix seconds. Default: the most recent 300 buckets. The span is clamped to 5000 points. |
curl -s "/token-chart/0x020bfc650a365f8bb26819deaabf3e21291018b4?resolution=1h&networkId=4663"
{
"address": "0x020bfc...018b4", "networkId": 4663, "resolution": "1h",
"from": 1782856800, "to": 1783537200,
"points": [
{ "t": 1782856800, "price": 0.0912, "volume": 48210.44 },
{ "t": 1782860400, "price": 0.0987, "volume": 51877.10 }
]
}
t is the bucket start (unix seconds); price is the bucket close; volume is quote volume. 503 when charting isn't configured; 400 for a bad range; 502 if the upstream is unreachable.
Full OHLCV candles for a token, oldest first — same windowing, parameters, and smart caching as /token-chart, but each bucket keeps its open/high/low/close. Pair this (history) with the /ws candles channel (live updates) to drive a candlestick chart.
| Param | Type | Description |
|---|---|---|
resolution | enum | 1m | 5m | 15m | 1h | 1d (default 1h) |
networkId | int | Chart-provider network id (defaults to the configured chain) |
from / to | int | Unix seconds. Default: the most recent 300 buckets. The span is clamped to 5000 points. |
curl -s "/token-candles/0x020bfc650a365f8bb26819deaabf3e21291018b4?resolution=1m"
{
"address": "0x020bfc...018b4", "networkId": 4663, "resolution": "1m",
"from": 1783644881, "to": 1783662881,
"candles": [
{ "t": 1783644900, "o": 0.001408, "h": 0.001409, "l": 0.001394, "c": 0.001394, "volume": 497.97 },
{ "t": 1783644960, "o": 0.001394, "h": 0.001401, "l": 0.001394, "c": 0.001401, "volume": 285.05 }
]
}
Buckets with no trades are omitted. Same error semantics as /token-chart.
Live streaming — WS /ws
WebSocket endpoint for live prices and candles. Connect to wss://<host>/ws and exchange JSON messages; one connection multiplexes any number of subscriptions (up to the per-connection cap, default 50). Upstream market-data subscriptions are shared across all clients watching the same token, so subscribing is cheap — never poll the REST endpoints when you can hold a socket.
Client → server
{ "op": "subscribe", "channel": "price", "address": "0x…" }
{ "op": "subscribe", "channel": "candles", "address": "0x…", "resolution": "1m" }
{ "op": "unsubscribe", "channel": "price", "address": "0x…" }
{ "op": "unsubscribe", "channel": "candles", "address": "0x…", "resolution": "1m" }
{ "op": "ping" }
resolution ∈ 1m | 5m | 15m | 1h | 1d (required for candles). networkId is optional and defaults to the configured chain. Addresses must be tokens from the router catalog — unknown addresses are rejected with unknown_token.
Server → client
// acks and errors
{ "op": "subscribed", "channel": "price", "address": "0x…", "networkId": 4663 }
{ "op": "unsubscribed", "channel": "candles", "address": "0x…", "networkId": 4663, "resolution": "1m" }
{ "op": "pong" }
{ "op": "error", "code": "unknown_token", "message": "address is not a tracked token" }
// data — price: pushed on every price change (plus a snapshot on subscribe)
{ "channel": "price", "address": "0x…", "networkId": 4663,
"data": { "priceUsd": 0.001651, "timestamp": 1783663007 } }
// data — candles: the currently-forming bar, resent on every update;
// a new "t" means the previous bar closed
{ "channel": "candles", "address": "0x…", "networkId": 4663, "resolution": "1m",
"data": { "t": 1783662900, "o": 0.001651, "h": 0.001653, "l": 0.001650, "c": 0.001652, "volume": 120.5 } }
Error codes: bad_message, bad_network, unknown_token, over_limit, over_capacity, validation_unavailable, not_configured, internal. On subscribe you get an immediate snapshot when one is available (always for price; for candles once the stream has produced a bar — render REST history first). The server pings every 30s; unresponsive connections are dropped, so reconnect with backoff and re-subscribe.
CoinGecko
Public, no-auth endpoints in CoinGecko's Spot Exchanges API shape, serving a single spot chain (the Robinhood mainnet market). Prices are quoted in the market's stablecoin (the "target"), computed directly from each swap's two legs, so they're independent of any USD peg. As an RFQ/aggregator (no pool, no native order book) we omit pool_id, liquidity_in_usd, and the /orderbook endpoint, and don't report bid/ask — confirmed acceptable by CoinGecko. Only successful, finalized, single-stablecoin-leg swaps appear; wrapped legs are resolved to their underlying, so markets are keyed by the real asset.
24-hour market stats for every market that has ever traded, most-active first. last_price is the most recent trade ever (so a market idle in the last 24h still reports a price); base_volume/target_volume/high/low are over the trailing 24h (high/low are null with no 24h trades). Cached ~30s.
[
{ "ticker_id": "0x1f9840...1f984_0x5fc536...1d168",
"base_currency": "0x1f9840...1f984",
"target_currency": "0x5fc536...1d168",
"last_price": "231.55", "base_volume": "1240.5", "target_volume": "287000.0",
"high": "233.10", "low": "229.80" }
]
ticker_id = <baseAddress>_<targetAddress> (lowercased). All amounts are decimal strings.
Completed trades for one market, newest first, split into buy/sell arrays.
| Param | Type | Description |
|---|---|---|
ticker_id | string | Required. <baseAddress>_<targetAddress> |
type | enum | buy | sell — restrict to one side |
limit | int | Max trades (default 200, max 1000) |
start_time / end_time | int | Unix seconds; inclusive time range |
curl -s "/historical_trades?ticker_id=0x1f9840...1f984_0x5fc536...1d168&limit=2"
{
"buy": [
{ "trade_id": 84213, "price": "231.55", "base_volume": "5.0",
"target_volume": "1157.75", "trade_timestamp": 1782438650123, "type": "buy" }
],
"sell": []
}
trade_timestamp is Unix milliseconds; trade_id is the indexer's stable row id. type: buy = base bought with the stablecoin, sell = base sold for it.
CoinMarketCap
Public, no-auth endpoints in CoinMarketCap's Section A — Spot Exchanges shape, serving the same single spot chain as the CoinGecko endpoints. Endpoints are versioned per CMC's integration standards — /cmc/v1/… is canonical (unversioned /cmc/… aliases also work). Pairs are symbol-based (e.g. GLW_USDG): tokens are ranked by 24h activity and the busiest token wins its symbol; a token whose symbol is already taken (or that has no on-chain symbol) is excluded rather than served ambiguously. As an RFQ/aggregator there is no resting order book, so the /orderbook endpoint is not provided; summary's lowest_ask/highest_bid are instead synthesized from live router quotes (see below).
Overview of every market: 24h stats plus price_change_percent_24h (last price vs. the last trade at-or-before 24h ago, falling back to the first trade inside the window; null when no reference exists). last_price is the most recent trade ever; highest_price_24h/lowest_price_24h are null with no 24h trades. Cached ~30s.
lowest_ask/highest_bid are the live executable RFQ prices for a ~$100-notional swap (configurable via CMC_BBO_NOTIONAL_USD) in each direction, quoted through the chain's router at snapshot build time — the honest equivalent of a top-of-book on a venue with no resting orders. Either side is null when no venue quotes it (or BBO is disabled via CMC_BBO_ENABLED=false).
[
{ "trading_pairs": "GLW_USDG",
"base_currency": "GLW", "quote_currency": "USDG",
"last_price": "231.55", "lowest_ask": "231.98", "highest_bid": "231.12",
"base_volume": "1240.5", "quote_volume": "287000.0",
"price_change_percent_24h": -1.32,
"highest_price_24h": "233.10", "lowest_price_24h": "229.80" }
]
Amounts are decimal strings; price_change_percent_24h is a JSON number.
Every currency participating in a served market, keyed by symbol. Assets settle on-chain via swaps (no custody gate, no exchange-level fees), so can_deposit/can_withdraw are always "true" and maker_fee/taker_fee are "0". contractAddressUrl appears only when an explorer URL is configured (CMC_EXPLORER_TOKEN_URL). Cached ~30s.
{
"GLW": { "name": "Glow", "contractAddress": "0x1f9840...1f984",
"can_deposit": "true", "can_withdraw": "true",
"maker_fee": "0", "taker_fee": "0" }
}
24h pricing/volume per market, keyed by pair. Same data as /cmc/v1/summary in CMC's ticker shape. Cached ~30s.
{
"GLW_USDG": { "last_price": "231.55",
"base_volume": "1240.5", "quote_volume": "287000.0", "isFrozen": "0" }
}
Recently completed trades for one market, newest first, as a flat array (default 1000 — comfortably more than 24h of fills).
| Param | Type | Description |
|---|---|---|
market_pair | string | Required (path). Symbol pair, e.g. GLW_USDG (case-insensitive) |
type | enum | buy | sell — restrict to one side |
limit | int | Max trades (default 1000, max 1000) |
curl -s "/cmc/v1/trades/GLW_USDG?limit=2"
[
{ "trade_id": 84213, "price": "231.55", "base_volume": "5.0",
"quote_volume": "1157.75", "timestamp": 1782438650123, "type": "buy" }
]
timestamp is Unix milliseconds; trade_id is the indexer's stable row id. type: buy = base bought with the stablecoin, sell = base sold for it. 400 for an unknown pair.
Data model
Trade object
{
"id": "1234", // stable unique row id (string)
"chainId": 42161,
"blockNumber": "472437134",
"blockTimestamp": "2026-06-16T02:38:50.775Z",
"txHash": "0x...", "logIndex": 3,
"swapShell": "0x...", // emitting contract
"taker": "0x...",
"tokenIn": "0x...", "tokenOut": "0x...", // underlying (wrapped legs resolved)
"tokenInWrapped": false, "tokenOutWrapped": true, // which legs settled wrapped
"rawTokenIn": "0x...", "rawTokenOut": "0x...", // raw on-chain legs (wrapped addr when *Wrapped)
"minAmountOut": "...", // signed slippage floor (raw)
"amountIn": "...", "amountOut": "...", // raw base units (amountIn as emitted)
"effectiveAmountIn": "...", // raw input the taker actually spent; = amountIn unless the
// router refunded unused input in the same tx. All USD
// economics (usdNotional, tokenExecPriceUsd, baseQty, PnL)
// derive from this, not amountIn.
"quotedAmountIn": "...", "quotedAmountOut": "...",
"tokenInBenchmarkPrice": "...", "tokenOutBenchmarkPrice": "...", // often 0/unreliable
"router": "0x...",
"routeTag": "zerox", // decoded, or null
"routeTagRaw": "0x...",
"success": true, "reason": "",
"pnl": { /* see below */ }
}
The pnl object (per trade)
USD value is anchored to the swap's stablecoin leg (configured per chain), since the event's benchmark prices are often zero.
"pnl": {
"priced": true, // false if no stablecoin leg / failed / unknown decimals
"usdNotional": "151.90", // trade size in USD
"feeUsd": "0", // per-trade USD fee (0 until a fee source exists)
"tokenExecPriceUsd": "7.84", // implied USD price of the non-stable token
"priceImprovementUsd": "0.0003", // amountOut - quotedAmountOut, in USD
"slippageHeadroomUsd": "0.005", // output above signed minAmountOut, in USD
"side": "buy", // "buy" | "sell" | null
"baseToken": "0x...", "baseQty": "0.255",
"realizedPnlUsd": "1.20", // average-cost realized PnL (null until accounted)
"avgCostUsd": "7.50",
"matchedQty": "0.1", // qty matched against inventory on a sell
"positionQtyAfter": "0.155"
}
Side: buy = stablecoin sold for the token; sell = token sold for a stablecoin; null = stable-stable or token-token swap.
Wrapped tokens: a wrapped fill settles the taker in a non-transferable wrapped representation (e.g. wTSLA) of the token actually bought (TSLA); the real underlying is delivered later off the SwapExecuted path. The indexer normalizes it — tokenIn/tokenOut, baseToken, side, pricing, positions, and volume all use the underlying (so a wrapped buy nets against a later real sell). tokenInWrapped/tokenOutWrapped flag which legs settled wrapped, and rawTokenIn/rawTokenOut keep the raw on-chain address.
PnL explained
There are two distinct concepts:
Execution PnL (per trade, stateless)
priced, usdNotional, tokenExecPriceUsd, priceImprovementUsd, slippageHeadroomUsd describe the quality of a single execution. Always available for priced trades, including backfilled ones.
Realized PnL (average cost, stateful)
realizedPnlUsd, avgCostUsd, matchedQty, positionQtyAfter (and the /positions endpoint) track profit/loss over a sequence of trades per (taker, token): a buy updates the average cost; a sell realizes (execPrice - avgCost) * matchedQty. Computed by a stateful pass over each key's full history, so it stays correct even when older trades are backfilled later.
Caveats
- Partial positions. Only
SwapShellswaps are observed. Tokens acquired/disposed elsewhere aren't counted; sells beyond observed inventory match only the held qty and the position floors at zero (no shorts). priced: falsewhen a swap failed, has no configured stablecoin leg, or the token's decimals aren't known — USD fields arenull.- Benchmark prices from the event are frequently zero/unreliable; prefer the stablecoin-anchored
pnl.*Usdfields. - Lag. Only finalized blocks are indexed; newest trades appear after finality. Check /stats.