Home / Solutions / Decentralized Exchanges
Decentralized Exchanges

Pools, listings, and order flow: the data layer for running a DEX

The chain already maintains your exchange's core objects: a factory event registers every pool, and each pool's log stream carries its trades. SQD serves the PairCreated, PoolCreated, and V4 Initialize events that build the pool registry, plus the swap, mint, burn, and sync streams behind charts and price APIs, across 225+ networks. A deployment on another chain is the same handler with the dataset name and factory address swapped.

225+ Networks
V2/V3/V4 Pool discovery
Genesis Full history
Logs + traces One endpoint
The problem

A DEX front end is a data product, and the chain is the only source of truth

Every pool in the list, every candle, every open position is read back off the chain. Rebuilt the naive way, that means paginating millions of blocks over eth_getLogs against per-provider caps, standing up a separate indexer for each chain the protocol deploys to, and polling slot0 or getReserves per pool per block for state. All of it is onchain already; the cost is in how you read it.

How it works

Your pool list is onchain: one factory event per pool

The root object a DEX renders is the pool registry, and the chain already maintains it: a factory emits exactly one creation event per pool. PairCreated on Uniswap V2 forks, PoolCreated on V3 forks, Initialize on the V4 PoolManager. Filter the factory's address and that event's topic0, and the stream from the factory's deployment block to the chain head is your registry.

The Uniswap V3 factory (0x1f98…f984) emitted its first PoolCreated at block 12,369,739 on 2021-05-04, registering the UNI/WETH 0.3% pool. 10,230,365 blocks later, at block 22,600,104, the same filter returns a new 1% pool:

FieldArrives inValue
  • token0topic 10x701b…ece0
  • token1topic 20xe994…cf4d
  • feetopic 30x2710 = 10000 (the 1% tier)
  • tickSpacingdata, word 10xc8 = 200
  • pooldata, word 20x2c28…3fcd
PoolCreated at block 22,600,104, decoded from the topics and data of the returned log

The new pool is 0x2c28…3fcd, and the same transaction (0x0cf7…f996) carries the pool contract's create trace, which the Portal serves from the same endpoint. This page is the exchange operator's data stack; for the consumer side, liquidations, MEV, and aggregated markets, see DeFi & Trading.

The registry row above comes back from one bounded query. Set fromBlock to 12,369,739 and drop toBlock, and the same filter is the factory's complete history:

your terminal
curl -s -X POST https://portal.sqd.dev/datasets/ethereum-mainnet/stream \
-H 'content-type: application/json' \
-d '{
"type": "evm",
"fromBlock": 22600104, "toBlock": 22600104,
"logs": [{
"address": ["0x1f98431c8ad98523631ae4a59f267346ea31f984"],
"topic0": ["0x783cca1c0412dd0d695e784568c96da2e9c22ff989357a2e8b1d9b2b4e6b7118"]
}],
"fields": { "log": { "topics": true, "data": true, "transactionHash": true }, "block": { "number": true, "timestamp": true } }
}'

One gotcha: the field a registry actually needs, the pool address, is not in the topics. PoolCreated indexes token0, token1, and fee, and leaves tickSpacing and the pool address ABI-encoded in data. A topics-only pipeline decodes everything except the address it came for; word 2 of data is the pool.

the returned block · one NDJSON line, formatted
{
"header": { "number": 22600104, "timestamp": 1748663015 },
"logs": [{
"transactionHash": "0x0cf792b4df8ee4b45d7ff819b0a84c0cb4c7afe124e9484cc02dee6224cef996",
"data": "0x00000000000000000000000000000000000000000000000000000000000000c80000000000000000000000002c28560dabd1cb9c2a2f0ea09b67d2ecf11e3fcd",
"topics": [
"0x783cca1c0412dd0d695e784568c96da2e9c22ff989357a2e8b1d9b2b4e6b7118",
"0x000000000000000000000000701bec7d1361ebb9a4004fe8acd8529204feece0",
"0x000000000000000000000000e9942930a2e3a8a096a8a9637d62219f3159cf4d",
"0x0000000000000000000000000000000000000000000000000000000000002710"
]
}]
}

Rebuilding this registry over eth_getLogs means paginating those 10.2 million blocks against per-provider window caps, some as small as 50 blocks per call (the verified caps). The Portal takes the whole range in one query shape and streams back only the blocks that match; the Squid and Pipes SDKs decode against the ABI and resume the stream for you.

The same handler on the next chain

A deployment elsewhere is the dataset name and the factory address swapped, not a second indexer. On base-mainnet, the identical query shape with Aerodrome's factory (0x420d…40da) and its own PoolCreated topic0 returns pool 0xfe7f…3330 at block 31,000,872, with token0 the canonical Base WETH and the factory's running pool count, 6,627, in the second data word. PancakeSwap runs this pattern in production across BNB Chain, Ethereum, Base, Arbitrum One, and more.

Where V4 breaks the pattern

Uniswap V4 has no factory and deploys no pool contracts: every pool lives inside one PoolManager (0x0000…8a90) and is a bytes32 id, not an address. Discovery reads the PoolManager's Initialize events: at block 22,600,073 the first id in our scan window was 0xa036…d815. A registry keyed on addresses needs a second key type for V4, and every pool's events now land in a single contract's log stream. That concentration is what moved UNCX's locker indexing to SQD.

Order flow

Charts and price APIs read from the swap stream

Once the registry knows a pool, the pool's own log stream carries the order flow. One block after the registry row above, at block 22,600,105, the USDC/WETH 0.05% pool (0x88e6…5640) settled a single swap (0x61fe…a07c). Its five data words carry the whole trade and the pool's state after it:

FieldDecodedMeaning
  • amount0+34,401.181986USDC into the pool
  • amount1-13.703287655962952718WETH out of the pool
  • sqrtPriceX961581529408292132230018060326655895post-trade price: 2,509.60 USDC per WETH
  • liquidity4,070,587,857,482,289,567in-range liquidity after the swap
  • tick0x30599 = 198041the tick the pool ended on
The Swap event at block 22,600,105, decoded. Negative amounts flow out of the pool; USDC has 6 decimals, WETH 18.

The two prices this row yields must reconcile, and they do: 34,401.181986 USDC over 13.703287655962952718 WETH is a paid average of 2,510.43, while sqrtPriceX96 works out to 2,509.60. The first includes the pool's 0.05 percent fee and averages across the range the trade crossed; the second is the pool's marginal price after the trade. A candle API stores the second; the first is what the trader paid.

The query is the discovery filter with the address and topic0 swapped, the same shape on every pool and every chain:

your terminal
curl -s -X POST https://portal.sqd.dev/datasets/ethereum-mainnet/stream \
-H 'content-type: application/json' \
-d '{
"type": "evm",
"fromBlock": 22600105, "toBlock": 22600105,
"logs": [{
"address": ["0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640"],
"topic0": ["0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67"]
}],
"fields": { "log": { "data": true, "transactionHash": true }, "block": { "number": true, "timestamp": true } }
}'

Two mechanics to respect. topic0 is global: every V3 fork, and any unrelated contract, can emit an event with this exact signature, so a production feed filters by the pool set discovered from the factory, never by topic0 alone. And on V2-style pools one swap lands as three logs in one transaction, Fees, Sync, and Swap (verified on an Aerodrome pool at Base block 31,000,280, 0x4bdf…957e): Sync holds the post-trade reserves, so reserve and TVL series ride the same stream.

The alternative is polling: a chart backend on raw RPC calls slot0 or getReserves per pool per block, multiplied by every deployment. Here the executed price, size, and block timestamp arrive in one row, and candles are an aggregation of the stream. GMX runs its multichain perp DEX data layer on SQD; the traces guide covers the create-trace and internal-call side.

Playground

Try the Portal API

Live queries against real blockchain data. Pick a network, choose a query, hit run.

The public endpoint is free for development. See plans for dedicated portals and higher limits.

Network+200 more
Query
curl --compressed -X POST \
'https://portal.sqd.dev/datasets/base-mainnet/stream' \
-H 'Content-Type: application/json' \
-d '{
"type": "evm",
"fromBlock": 23000000,
"toBlock": 23000010,
"fields": {
"block": {"number":true,"timestamp":true},
"transaction": {
"hash": true,
"from": true,
"to": true,
"value": true,
"sighash": true
}
},
"transactions": [
{
"to": [
"0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43"
]
}
]
}'
Basebase-mainnet/stream
Transactions on Base's largest DEX
Capabilities

Built for Decentralized Exchanges at scale

Pool discovery from factory events

One filter on the factory address and the creation event's topic0 returns every PairCreated or PoolCreated from the deployment block to head. The gotcha is handled downstream: the new pool's address arrives ABI-encoded in the data section, not in a topic.

Swap, mint, burn, and sync streams

A pool's log stream carries its order flow: executed price and size in the Swap data words, post-trade reserves in a V2-style Sync. Candles, volume, and TVL series are aggregations of that stream, not per-block slot0 or getReserves polling.

One handler across deployments

The event ABI is identical wherever the same contracts are deployed, so a multi-chain DEX is one handler with the dataset name and factory address swapped per chain. PancakeSwap runs this pattern across BNB Chain, Ethereum, Base, Arbitrum One, and more.

V4 and singleton designs covered

Uniswap V4 deploys no pool contracts: every pool is a bytes32 id inside one PoolManager, and all its events land in a single log stream. Discovery reads the PoolManager's Initialize events; the Portal serves that stream the same way it serves a factory's.

Why SQD

What you would build yourself

The events, transactions, and traces are public. The work a provider saves you is in the reading, and it compounds with every pool and every chain.

  • Pool discovery
    Yourself Paginate 10.2M blocks over eth_getLogs against per-provider window caps, some as small as 50 blocks per call.
    With SQD One query shape takes the whole range and streams back only the blocks that match.
  • Multi-chain deployment
    Yourself A separate indexer and pipeline for each chain the DEX ships to.
    With SQD One handler with the dataset name and factory address swapped per chain.
  • Price and TVL series
    Yourself Poll slot0 or getReserves per pool per block, multiplied by every deployment.
    With SQD Executed price, size, and block timestamp arrive in one row; candles are an aggregation.
  • Uniswap V4
    Yourself Address-keyed registries break: every pool is a bytes32 id inside one contract.
    With SQD The concentrated log stream is one filter on the PoolManager, served like any other.

See this on your own contracts

Tell us the chains and contracts you care about, and we will show you the query that returns them.

Use cases

What teams build with SQD

  • Pool registries built from factory event streams
  • Price and candle APIs aggregated from Swap events
  • TVL and reserve series from Mint, Burn, and Sync
  • New-listing detection at the chain head
  • Cross-chain deployments served by one handler
  • LP position dashboards from per-pool event history

Frequently asked questions

How do I track pool creation events on a DEX?
Filter the factory contract's address plus the creation event's topic0: PairCreated on Uniswap V2 forks, PoolCreated on V3 forks, and Initialize on the Uniswap V4 PoolManager, where a pool is a bytes32 id rather than a deployed contract. Note that V2- and V3-style events put the new pool's address in the ABI-encoded data section, not in a topic. One filter shape covers a factory's entire history from its deployment block (the Squid and Pipes SDKs resume the stream across response boundaries), and the same filter at the chain head surfaces new listings as they are created.
What Uniswap data does SQD serve?
The raw record the protocol writes onchain: every log its contracts emit (PoolCreated, Initialize, Swap, Mint, Burn, Collect), plus the transactions, execution traces, and state diffs around them, from genesis to head. Pool state such as slot0 or reserves is not returned as a field; it is reconstructed downstream from those events, which is what a Swap event's sqrtPriceX96 and a V2-style Sync's reserves are for. The same applies to any V2, V3, or V4 fork on the 225+ supported networks.
Can one indexer cover a DEX deployed on several chains?
Yes. The event ABI is identical wherever the same contracts are deployed, so a multi-chain deployment is one handler with the dataset name and factory address swapped per chain; this page runs the same query shape against Uniswap V3 on ethereum-mainnet and Aerodrome on base-mainnet. In production, PancakeSwap indexes trading and liquidity data across BNB Chain, Ethereum, Base, Arbitrum One, and more on SQD, and GMX runs its multichain perp DEX data layer the same way. Solana AMMs use the Solana instruction shape rather than EVM logs.
Get started

Your blockchain data infrastructure, handled.

Private Portal. Dedicated. Validated. Managed. Tell us what you're building, we'll show you what it looks like on SQD.