Question it answers
“What are the currentMirrors Moralisreserve0/reserve1of DEX pair 0x…?”
GET /{pair_address}/reserves → { reserve0, reserve1 }. The recipe lands the pool’s absolute token balances and keeps the latest observation per pair, so a single read returns each pair’s current reserves.
What you get
The recipe lands one observation per swap, the pool’s absolute token balances captured after each trade, and the current reserves of a pair are the highest-(block_number, log_index) observation for that pair:
These are absolute snapshots, not deltas, no running sum, no pre-window baseline, no genesis requirement. The latest snapshot in any window is the answer (window-safe).
Source
The faithful source for reserves istokenSwaps. Each swap carries token0PostBalance / token1PostBalance, the pool’s absolute balances after the trade, which are exactly the pair’s reserves. The transform projects one reserve observation per swap, keyed by pair.
pairLiquidityChanges is not used: it only fires on add/remove liquidity, so a running sum of its deltas misses all swap-driven reserve movement and is window-relative. Aggregated swaps are also not used, they carry an aggregator address, not a pair_address, so their post-balances can’t be attributed to a specific pool.
Destination
ClickHouse uses the collapsing log-table pattern (see the recipes overview) so chain reorganizations self-correct: a reorg negates the rolled-back block’s observations (
sign = -1) and re-emits the corrected ones. Read with FINAL to collapse the ±1 pair, then argMax to pick the latest observation, never a bare WHERE sign = 1.
Postgres and MySQL append append-only observations and derive a latest-wins current-reserves surface per pair: Postgres via a DISTINCT ON (pair_address) … ORDER BY block_number DESC, log_index DESC materialized view, MySQL via an AFTER INSERT upsert trigger.
Full schema
The ClickHouse fact table is the per-pair observation table; the current reserves are anargMax over it. Reserves are raw uint256 stored as text (matching the API’s string reserves), argMax over text ordered by the numeric recency key is exact. Keep the columns you need (see Schema & flexibility).
ClickHouse, fact_pair_reserves
ClickHouse, fact_pair_reserves
sign column drives reorg collapsing, read with FINAL then argMax. A single-node setup can use CollapsingMergeTree(sign) without the replication path.Postgres, pair_reserves
Postgres, pair_reserves
pair_reserve_observations table plus a pair_reserves table kept current by an AFTER INSERT latest-wins upsert trigger, with reserve0 / reserve1 as DECIMAL(65,0). position is the block-level cursor used during backfill.Example reads
Current reserves of a pair, latest observation;FINAL collapses reorg ±1 pairs before argMax (ClickHouse):
reserve0 (ClickHouse):
Modes
Shipped defaults: ClickHousehybrid (backfill → realtime), Postgres / MySQL historical (one-shot backfill). For live/reorg-safe ingestion, use ClickHouse, see the overview.
The Postgres / MySQL realtime reorg path needs a single-column
UNIQUE on the position column, but position is block-level. Run realtime / hybrid on ClickHouse (the collapsing log table corrects reorgs per block); the Postgres / MySQL configs target historical backfill and re-derive their current-reserves surface on refresh.Multichain
The recipe is chain-parametrized via thechain setting, point it at any supported EVM chain or Solana. On Solana, the event identity already folds in (pair_address, token0PostBalance, token1PostBalance) on top of (transaction_hash, log_index), so it stays row-unique despite Solana’s repeated log indices within an instruction.
Fidelity gaps
- Only pairs with a swap in-window get current reserves. Reserves are read from swap post-balances, so a pair appears only if it had at least one swap with resolved post-balances in the ingested window. A pool that is quiet (no swaps) for the whole window won’t be present, but quiet pools have unchanged reserves by definition, so widening the window picks them up.
- Swaps without resolved post-balances are skipped.
token0PostBalance/token1PostBalanceare nullable; rows where either is null/empty, or where the swap has no pair address, are filtered out. - V4 pair addresses arrive as
poolId_0xaddress(~109 chars) and are stored raw for a key that’s consistent across destinations. MySQL capspair_addressatVARCHAR(160); PostgresTEXTand ClickHouseStringare unbounded. - MySQL precision ceiling. MySQL
DECIMAL(65,0)cannot represent a full 78-digituint256, though no real reserve approaches that magnitude. Postgres (NUMERIC(76,0)) and ClickHouse (String) carry the larger range.
Related
Swaps by Pair
Every DEX trade for a pair, the same swap stream these reserves derive from.
Pair OHLCV
Per-pair price candles for charting.

