Question it answers
“Give me every DEX trade on pool/pair 0x…, newest first, each row is one trade with both token legs, the side, the protocol, and a USD notional.”Same trade projection as Swaps by Token, but keyed by the pool/pair address with one row per trade (no per-token-side unpivot).
pool_address is the pair address for direct pool fills and the aggregator address for aggregator-routed trades.
What you get
One row per trade, keyed bypool_address:
Source
The transform reads two per-block trade arrays and projects them into one feed:tokenSwaps (direct pool fills, source_kind='pool_fill', pool_address = pairAddress) · aggregateTokenSwaps (aggregator-routed trades, source_kind='aggregator', pool_address = aggregatorAddress)
The two branches are disjoint by construction, together they give every fill exactly once. USD values are computed inline from the same block’s tokenPriceUpdates (the chronologically-last update wins on duplicate keys), no separate price join at read time.
Destination
ClickHouse uses the collapsing log-table pattern (see the recipes overview) so chain reorganizations self-correct, the
+1/−1 reorg pair for a trade shares an identical key and collapses cleanly. The fact table’s sort key is pool-first, so a pair’s full trade feed is a contiguous range read.
Full schema
The complete read table this recipe produces, one row per trade. Keep the columns you need and drop the rest (see Schema & flexibility). Rawuint256 amounts are stored as text in ClickHouse (they exceed numeric precision); USD columns are nullable so an unpriced token leaves them empty.
ClickHouse, fact_swaps_by_pair
ClickHouse, fact_swaps_by_pair
sign column drives reorg collapsing, read with FINAL or sum(sign), never a bare WHERE sign=1. A single-node setup can use CollapsingMergeTree(sign) without the replication path.Postgres, swaps
Postgres, swaps
DECIMAL(38,18) for the USD columns. Amounts use NUMERIC(76,0) so large raw uint256 balances never overflow. position is the block-level cursor used during backfill.Example reads
All trades on a pool/pair, newest first (ClickHouse):FINAL):
Modes
Shipped defaults: ClickHousehybrid (backfill → realtime), Postgres / MySQL historical (one-shot backfill). For live/reorg-safe ingestion use ClickHouse, see the overview.
The realtime reorg path needs a single-column
UNIQUE on the position column, but position is block-level (many trades per block), so array-expanded trade rows can only carry a composite unique. Run realtime/hybrid on ClickHouse (the log table corrects reorgs per-block via the collapsing companion table); the Postgres / MySQL configs are intended for historical backfill.Multichain
The recipe is chain-parametrized, point it at any supported EVM chain or Solana. On Solana, the samelogIndex can be assigned to multiple events in one instruction, so for production the pool-fill event identity is widened with (token0_address, token1_address, token0_amount) to keep rows distinct; the trade feed it produces is identical in shape.
Fidelity gaps
price_usd/notional_usd/fee_usdareNULLwhen the relevant token had no in-blocktokenPriceUpdate(illiquid or brand-new tokens). The recipe enriches only from same-block price updates, there is no cross-block carry-forward.fee_*is populated for pool fills only. Aggregator-routed trades carry no per-fill fee, sofee_amount/fee_token/fee_usdareNULLonsource_kind='aggregator'rows.- One trade can appear under two
pool_addressvalues when it is observed both as a direct pool fill (the pair address) and as part of an aggregator route (the aggregator address). These are distinct events with distinctvendor_event_ids by design, the two source arrays are disjoint per event.
Migrating from the REST API
This recipe supplies the volume leg of the token-pairs REST endpoint. That endpoint composes a pool registry with live numbers, so there is no 1:1 replacement, you land the pieces and join them in one view. Fidelity labels: exact = straight from the chain, calculated = derived from real DEX trades (very close, compare with a tolerance), add yourself = off-chain signal not in the feed.GET /erc20/:token_address/pairs
Lists every DEX pool that trades a token, with price, liquidity, and 24h volume per pool. The pool list itself comes through exactly from a purpose-built registry; the live figures are computed from the same onchain trades the old endpoint uses.
The composed view (Postgres, query it with
WHERE base_token = $1 OR quote_token = $1 ORDER BY liquidity_usd DESC NULLS LAST):
- A token trades in many pools (fee tiers, DEXes), list them by matching either side of the registry, and sort by liquidity to surface the meaningful ones.
- Backfill is mixed: the pool list needs history far enough back to see each pool’s deployment (full history for completeness), while price, liquidity, and 24h volume only need
realtimeplus a 24-hour window. See History & backfill. - The volume join keys on the pair address, so it matches this recipe’s
pool_fillrows only, aggregator-routed fills carry the aggregator’s address inpool_address(see Fidelity gaps). - The live figures track the old endpoint closely but come from a pricing computation, compare with a tolerance, not bit-for-bit.
exchange_logoandtoken_logoare off-chain images, layer them in from your own source keyed by address, or drop them.
Related
Swaps by Token
The same trade projection, keyed by token with one row per token side.
Pair OHLCV
Roll a pair’s trades into candlesticks for charting.
Trading & Charting
The use case this per-pair trade feed powers.
Token Analytics
Pool-level volume and trade flow for token analytics.

