Track Uniswap V3 swap events to extract real-time price data, calculate trading volumes, and analyze on-chain liquidity dynamics. Unlike centralized exchanges, DEX data is verifiable and tamper-proof—ideal for building trustless price oracles and analytics.
Use Case
DEX swap indexing unlocks powerful analytics:
- Price Discovery: Extract real-time token prices from swap amounts without relying on third-party APIs
- Volume Analysis: Calculate 24h trading volumes, identify trending pairs, and detect unusual activity
- Liquidity Tracking: Monitor pool depth changes and identify liquidity migration patterns
- MEV Detection: Analyze sandwich attacks, arbitrage opportunities, and front-running patterns by correlating swaps within blocks
Code Example
curl --compress -X POST 'https://portal.sqd.dev/datasets/ethereum-mainnet/stream' \
-H 'Content-Type: application/json' \
-d '{
"type": "evm",
"fromBlock": 12369621,
"toBlock": 12400000,
"fields": {
"block": {
"number": true,
"timestamp": true
},
"log": {
"address": true,
"topics": true,
"data": true,
"transactionHash": true
}
},
"logs": [{
"topic0": ["0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67"]
}]
}'
Try it yourself with the interactive query interface below:
Key Parameters
| Parameter | Description |
|---|
topic0 | Swap event signature (Swap(address,address,int256,int256,uint160,uint128,int24)) |
topics[1] | Sender address (who initiated the swap) |
topics[2] | Recipient address (who receives the output) |
data | Swap amounts and other parameters (amount0, amount1, sqrtPriceX96, liquidity, tick) |
address | Pool contract address |
The Swap event signature varies by DEX. Uniswap V3 uses a different signature than Uniswap V2 or other DEXs.
Expected Output
{
"header": {
"number": 12369650,
"timestamp": 1620054321
},
"logs": [
{
"address": "0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8",
"topics": [
"0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67",
"0x00000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45",
"0x00000000000000000000000028c6c06298d514db089934071355e5743bf21d60"
],
"data": "0x...",
"transactionHash": "0x123..."
}
]
}
Track Specific Pool
Monitor swaps for a specific trading pair:
curl --compress -X POST 'https://portal.sqd.dev/datasets/ethereum-mainnet/stream' \
-H 'Content-Type: application/json' \
-d '{
"type": "evm",
"fromBlock": 18000000,
"toBlock": 18010000,
"fields": {
"log": {
"address": true,
"topics": true,
"data": true
}
},
"logs": [{
"address": ["0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8"],
"topic0": ["0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67"]
}]
}'
Uniswap V2 Swaps
For Uniswap V2, use the V2 Swap event signature:
// Uniswap V2 Swap event: Swap(address,uint256,uint256,uint256,uint256,address)
const V2_SWAP_TOPIC = "0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822";
const blocks = await dataSource.getBlocks({
from: 18000000,
to: 18010000,
fields: {
log: { address: true, topics: true, data: true },
},
logs: [
{
topic0: [V2_SWAP_TOPIC],
},
],
});
Different DEX protocols use different event signatures. Check the contract ABI to find the correct signature for your target DEX.
Decode Swap Amounts and Calculate Prices
The real value in DEX data comes from decoding swap amounts to calculate prices. Here’s how to extract and interpret Uniswap V3 swap data:
import { ethers } from "ethers";
// Uniswap V3 Swap event ABI
const swapEventAbi = [
"event Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick)"
];
const iface = new ethers.Interface(swapEventAbi);
for (const block of blocks) {
for (const log of block.logs) {
const decoded = iface.parseLog({
topics: log.topics,
data: log.data,
});
const amount0 = decoded.args.amount0;
const amount1 = decoded.args.amount1;
const sqrtPriceX96 = decoded.args.sqrtPriceX96;
// Calculate the actual price from sqrtPriceX96
// price = (sqrtPriceX96 / 2^96)^2
const price = (Number(sqrtPriceX96) / (2 ** 96)) ** 2;
// Determine swap direction and calculate effective price
const isBuyingToken0 = amount0 < 0n;
const effectivePrice = isBuyingToken0
? Math.abs(Number(amount1)) / Math.abs(Number(amount0))
: Math.abs(Number(amount0)) / Math.abs(Number(amount1));
console.log({
pool: log.address,
blockNumber: block.header.number,
timestamp: block.header.timestamp,
amount0: amount0.toString(),
amount1: amount1.toString(),
currentPrice: price,
effectivePrice: effectivePrice,
liquidity: decoded.args.liquidity.toString(),
tick: decoded.args.tick,
});
}
}
Advanced Analysis: Detecting MEV Sandwich Attacks
Sandwich attacks are a common MEV strategy where an attacker places trades before and after a victim’s transaction to profit from price impact. By analyzing swap sequences within blocks, you can detect these patterns:
// Query swaps with transaction details to detect sandwich attacks
const blocks = await dataSource.getBlocks({
from: 18000000,
to: 18010000,
fields: {
block: { number: true, timestamp: true },
transaction: { hash: true, from: true, to: true, transactionIndex: true },
log: { address: true, topics: true, data: true, transactionHash: true, logIndex: true },
},
logs: [{ topic0: [SWAP_TOPIC] }],
includeAllBlocks: false,
});
// Detect sandwich attacks by analyzing swap sequences
for (const block of blocks) {
// Group swaps by pool address
const poolSwaps = new Map();
for (const log of block.logs) {
const tx = block.transactions.find(t => t.hash === log.transactionHash);
if (!tx) continue;
if (!poolSwaps.has(log.address)) {
poolSwaps.set(log.address, []);
}
poolSwaps.get(log.address).push({
txIndex: tx.transactionIndex,
txHash: tx.hash,
trader: tx.from,
log: log,
});
}
// Check for sandwich pattern: same trader with swaps before and after victim
for (const [pool, swaps] of poolSwaps) {
if (swaps.length < 3) continue;
swaps.sort((a, b) => a.txIndex - b.txIndex);
for (let i = 0; i < swaps.length - 2; i++) {
const first = swaps[i];
const middle = swaps[i + 1];
const last = swaps[i + 2];
// Sandwich pattern: same trader surrounds victim's trade
if (first.trader === last.trader && middle.trader !== first.trader) {
console.log({
block: block.header.number,
pool: pool,
attacker: first.trader,
victim: middle.trader,
frontrunTx: first.txHash,
victimTx: middle.txHash,
backrunTx: last.txHash,
});
}
}
}
}
MEV detection requires analyzing transaction ordering within blocks. Make sure to request transaction fields and transactionIndex to properly sequence trades.
Advanced Analysis: Multi-Hop Swaps and Arbitrage
Many swaps route through multiple pools via aggregators like 1inch or Uniswap’s Universal Router. By combining swap events with traces, you can reconstruct complete arbitrage paths and calculate profitability:
// Query both swap events and traces to reconstruct multi-hop routes
const blocks = await dataSource.getBlocks({
from: 18000000,
to: 18010000,
fields: {
block: { number: true, timestamp: true },
transaction: { hash: true, from: true, to: true },
log: { address: true, topics: true, data: true, transactionHash: true },
trace: { callFrom: true, callTo: true, callValue: true, transactionHash: true },
},
logs: [{ topic0: [SWAP_TOPIC] }],
traces: [{ type: ["call"] }],
});
// Group swaps by transaction to identify arbitrage opportunities
const swapsByTx = new Map();
for (const block of blocks) {
for (const log of block.logs) {
if (!swapsByTx.has(log.transactionHash)) {
swapsByTx.set(log.transactionHash, {
swaps: [],
traces: [],
tx: block.transactions.find(t => t.hash === log.transactionHash),
});
}
swapsByTx.get(log.transactionHash).swaps.push(log);
}
// Add traces for each transaction
for (const trace of block.traces) {
if (swapsByTx.has(trace.transactionHash)) {
swapsByTx.get(trace.transactionHash).traces.push(trace);
}
}
}
// Analyze multi-hop swaps and detect circular arbitrage
for (const [txHash, data] of swapsByTx) {
if (data.swaps.length > 1) {
const pools = data.swaps.map(s => s.address);
const uniquePools = new Set(pools);
// Circular arbitrage: starts and ends with same token
if (uniquePools.size >= 3) {
console.log({
txHash,
trader: data.tx?.from,
pools: pools,
hopCount: data.swaps.length,
type: 'potential-arbitrage',
});
}
}
}
Combine swap events with traces to capture the full execution path, including intermediate token approvals and transfers that don’t emit swap events.
- Filter by pool address: Query specific high-volume pools (USDC/WETH, WETH/USDT) instead of all swaps
- Use block ranges: Process 10k-50k block batches for optimal throughput
- Batch decode: Decode multiple events in parallel rather than sequentially
- Cache pool metadata: Store token decimals and symbols to avoid repeated lookups
- Combine with traces: Track multi-hop swaps and MEV activity by analyzing call traces alongside events