Skip to main content

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 by pool_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). Raw uint256 amounts are stored as text in ClickHouse (they exceed numeric precision); USD columns are nullable so an unpriced token leaves them empty.
The 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.
MySQL is the same shape with 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):
24h USD volume per pool (sign-aware, cheaper than FINAL):
Postgres / MySQL:

Modes

Shipped defaults: ClickHouse hybrid (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 same logIndex 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_usd are NULL when the relevant token had no in-block tokenPriceUpdate (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, so fee_amount / fee_token / fee_usd are NULL on source_kind='aggregator' rows.
  • One trade can appear under two pool_address values 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 distinct vendor_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.
This endpoint needs a combination of recipes, not one. The pool list comes from the pairAddressLookup registry recipe (pair_address_lookup_observations, one row per pool with exchange, protocol, fee, and both tokens); this recipe’s swaps table supplies the 24h volume; add Pair Reserves for liquidity, Token Prices for usd_price and the 24h change, and Token Metadata for symbols and pair_label, then join them in one view.
The composed view (Postgres, query it with WHERE base_token = $1 OR quote_token = $1 ORDER BY liquidity_usd DESC NULLS LAST):
Gotchas
  • 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 realtime plus a 24-hour window. See History & backfill.
  • The volume join keys on the pair address, so it matches this recipe’s pool_fill rows only, aggregator-routed fills carry the aggregator’s address in pool_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_logo and token_logo are off-chain images, layer them in from your own source keyed by address, or drop them.

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.