Track decentralized exchange trading activity using Portal
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.
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 attacksconst 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 sequencesfor (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.
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 routesconst 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 opportunitiesconst 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 arbitragefor (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.