> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sqd.dev/llms.txt
> Use this file to discover all available pages before exploring further.

<AgentInstructions>

## Submitting Feedback

If you encounter incorrect, outdated, or confusing documentation on this page, submit feedback:

POST https://docs.sqd.dev/feedback

```json
{
  "path": "/en/portal/evm/examples/dex-swaps",
  "feedback": "Description of the issue"
}
```

Only submit feedback when you have something specific and actionable to report.

</AgentInstructions>

# Index EVM DEX Swaps

> Track EVM DEX trading activity through the SQD Portal — sample queries for Uniswap, SushiSwap, and other decentralized exchange swap events.

export const QueryInterfaceDexSwaps = () => {
  const useCasesConfig = [{
    id: "uniswap-v3-swaps",
    name: "Track Uniswap V3 Swap events",
    network: "ethereum-mainnet",
    payload: {
      type: "evm",
      fromBlock: 20000000,
      toBlock: 20000003,
      fields: {
        block: {
          number: true,
          timestamp: true
        },
        log: {
          address: true,
          topics: true,
          data: true,
          transactionHash: true
        }
      },
      logs: [{
        topic0: ["0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67"]
      }]
    }
  }];
  const [queryFormat, setQueryFormat] = useState("curl");
  const [useCase, setUseCase] = useState("uniswap-v3-swaps");
  const [response, setResponse] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [viewMode, setViewMode] = useState("code");
  const [copied, setCopied] = useState(false);
  const generateCurlCommand = config => {
    const url = `https://portal.sqd.dev/datasets/${config.network}/stream`;
    const payload = JSON.stringify(config.payload, null, 2);
    return `curl --compressed -X POST '${url}' \\\n  -H 'Content-Type: application/json' \\\n  -d '${payload}'`;
  };
  useEffect(() => {
    setViewMode("code");
    setResponse("");
    setError(null);
  }, [useCase]);
  const handleRun = async () => {
    const selectedUseCase = useCasesConfig.find(uc => uc.id === useCase);
    if (!selectedUseCase) return;
    setViewMode("output");
    setLoading(true);
    setError(null);
    setResponse('');
    try {
      const url = `https://portal.sqd.dev/datasets/${selectedUseCase.network}/stream`;
      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(selectedUseCase.payload)
      });
      if (!response.ok) {
        throw new Error(`API request failed: ${response.status} ${response.statusText}`);
      }
      const text = await response.text();
      if (text.trim()) {
        const lines = text.trim().split('\n');
        const parsedData = lines.map((line, index) => {
          try {
            return JSON.parse(line);
          } catch (e) {
            return {
              error: `Failed to parse line ${index + 1}`,
              raw: line
            };
          }
        });
        const formattedOutput = parsedData.map(obj => JSON.stringify(obj, null, 2)).join('\n\n');
        setResponse(formattedOutput);
      } else {
        setResponse('No data returned from API');
      }
    } catch (err) {
      setError(err.message || 'An error occurred while fetching data');
      setResponse('');
    } finally {
      setLoading(false);
    }
  };
  return <div className="not-prose w-full my-6 sm:my-8">

      <div className="bg-white dark:bg-gray-950 rounded-xl p-4 sm:p-6 border border-gray-200 dark:border-gray-800 shadow-sm">
        <div className="flex flex-col sm:flex-row items-start sm:items-center gap-3 sm:gap-4 mb-4">
          <label className="text-sm font-semibold text-gray-700 dark:text-gray-300 whitespace-nowrap">
            Response
          </label>

          <div className="flex flex-wrap items-center gap-3 flex-1 w-full">
            <div className="relative flex-shrink-0">
              <select value={queryFormat} onChange={e => setQueryFormat(e.target.value)} className="appearance-none bg-gray-50 dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded-xl px-4 py-2.5 pr-10 text-sm font-medium text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-[#2563eb] dark:focus:ring-[#2563eb] cursor-pointer transition-all">
                <option value="curl">curl</option>
              </select>
              <div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
                <svg className="w-4 h-4 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
                </svg>
              </div>
            </div>

            <div className="relative flex-shrink-0 flex-1 min-w-[200px]">
              <select value={useCase} onChange={e => setUseCase(e.target.value)} className="appearance-none w-full bg-gray-50 dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded-xl px-4 py-2.5 pr-10 text-sm font-medium text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-[#2563eb] dark:focus:ring-[#2563eb] cursor-pointer transition-all">
                {useCasesConfig.map(uc => <option key={uc.id} value={uc.id}>
                    {uc.name}
                  </option>)}
              </select>
              <div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
                <svg className="w-4 h-4 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
                </svg>
              </div>
            </div>

            <button onClick={handleRun} type="button" disabled={loading} className="flex items-center gap-2 bg-[#2563eb] hover:bg-[#1e40af] dark:bg-[#2563eb] dark:hover:bg-[#1e40af] disabled:bg-gray-400 disabled:cursor-not-allowed text-white font-semibold px-6 py-2.5 rounded-xl transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-[#2563eb] focus:ring-offset-2 dark:focus:ring-offset-gray-950 whitespace-nowrap shadow-sm hover:shadow-md">
              {loading ? <>
                  <svg className="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
                    <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
                    <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
                  </svg>
                  Loading...
                </> : <>
                  <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
                    <path d="M6.3 2.841A1.5 1.5 0 004 4.11V15.89a1.5 1.5 0 002.3 1.269l9.344-5.89a1.5 1.5 0 000-2.538L6.3 2.84z" />
                  </svg>
                  RUN
                </>}
            </button>
          </div>
        </div>

        {error && <div className="mt-4 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-xl">
            <p className="text-sm text-red-800 dark:text-red-200 font-medium">Error: {error}</p>
          </div>}

        <div className="mt-4 bg-gray-50 dark:bg-gray-950 border border-gray-200 dark:border-gray-800 rounded-xl overflow-hidden h-[450px] flex flex-col">
          <div className="bg-gray-100 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800 px-4 py-2.5 flex items-center justify-between">
            <span className="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wide">
              {viewMode === "code" ? "Request" : "Response"}
            </span>
            <button onClick={() => {
    const textToCopy = viewMode === "code" ? generateCurlCommand(useCasesConfig.find(uc => uc.id === useCase)) : response;
    navigator.clipboard.writeText(textToCopy);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  }} className={`flex items-center gap-1.5 transition-colors ${copied ? "text-[#2563eb] dark:text-[#60a5fa]" : "text-gray-500 hover:text-[#2563eb] dark:text-gray-400 dark:hover:text-[#60a5fa]"}`} title={copied ? "Copied!" : "Copy to clipboard"}>
              {copied ? <>
                  <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
                  </svg>
                  <span className="text-xs font-medium">Copied!</span>
                </> : <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
                </svg>}
            </button>
          </div>
          <div className="flex-1 overflow-auto p-3 bg-gray-50 dark:bg-gray-950">
            {loading ? <div className="flex items-center justify-center h-full">
                <div className="text-sm text-gray-500 dark:text-gray-400">Fetching data from Portal API...</div>
              </div> : viewMode === "code" ? <div key={`code-${useCase}`} style={{
    position: 'relative'
  }}>
                <pre style={{
    margin: 0,
    padding: '1rem',
    background: 'var(--hl-bg)',
    borderRadius: '0.75rem',
    overflow: 'auto',
    fontSize: '0.875rem',
    lineHeight: '1.5',
    fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
    color: 'var(--hl-cmd)'
  }}>
                  <code key={`curl-code-${useCase}`} style={{
    counterReset: 'line'
  }}>
                    {generateCurlCommand(useCasesConfig.find(uc => uc.id === useCase)).trim().split('\n').map((line, i) => <div key={`curl-line-${i}`} style={{
    display: 'flex',
    position: 'relative',
    paddingLeft: '3.5em',
    minHeight: '1.5em',
    lineHeight: '1.5'
  }}>
                        <span style={{
    position: 'absolute',
    left: 0,
    width: '2.5em',
    textAlign: 'right',
    color: 'var(--hl-line-num)',
    userSelect: 'none',
    paddingRight: '1em',
    lineHeight: '1.5'
  }}>{i + 1}</span>
                        <span style={{
    whiteSpace: 'pre-wrap',
    wordBreak: 'break-word',
    color: 'var(--hl-cmd)',
    lineHeight: '1.5'
  }}>
                          {line.includes('curl') && <span style={{
    color: 'var(--hl-key)'
  }}>{line}</span>}
                          {!line.includes('curl') && line.match(/^\s*-[XHd]/) && <>
                              <span style={{
    color: 'var(--hl-flag)'
  }}>{line.match(/^\s*-[XHd]/)[0]}</span>
                              <span style={{
    color: 'var(--hl-cmd)'
  }}>{line.substring(line.match(/^\s*-[XHd]/)[0].length)}</span>
                            </>}
                          {!line.includes('curl') && !line.match(/^\s*-[XHd]/) && line.match(/'[^']*'/) && <>
                              {line.split(/'([^']*)'/).map((part, j) => j % 2 === 1 ? <span key={`curl-part-${i}-${j}`} style={{
    color: 'var(--hl-str)'
  }}>'{part}'</span> : <span key={`curl-part-${i}-${j}`} style={{
    color: 'var(--hl-cmd)'
  }}>{part}</span>)}
                            </>}
                          {!line.includes('curl') && !line.match(/^\s*-[XHd]/) && !line.match(/'[^']*'/) && <span style={{
    color: 'var(--hl-cmd)'
  }}>{line}</span>}
                        </span>
                      </div>)}
                  </code>
                </pre>
              </div> : response ? <div key={`response-${useCase}-${response.length}`} style={{
    position: 'relative'
  }}>
                <pre style={{
    margin: 0,
    padding: '1rem',
    background: 'var(--hl-bg)',
    borderRadius: '0.75rem',
    overflow: 'auto',
    fontSize: '0.875rem',
    lineHeight: '1.5',
    fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
    color: 'var(--hl-cmd)'
  }}>
                  <code key={`response-code-${useCase}-${response.length}`}>
                    {response.trim().split('\n').map((line, i) => <div key={`response-line-${i}`} style={{
    display: 'flex',
    position: 'relative',
    paddingLeft: '3.5em',
    minHeight: '1.5em',
    lineHeight: '1.5'
  }}>
                        <span style={{
    position: 'absolute',
    left: 0,
    width: '2.5em',
    textAlign: 'right',
    color: 'var(--hl-line-num)',
    userSelect: 'none',
    paddingRight: '1em',
    lineHeight: '1.5'
  }}>{i + 1}</span>
                        <span style={{
    whiteSpace: 'pre-wrap',
    wordBreak: 'break-word',
    color: 'var(--hl-cmd)',
    lineHeight: '1.5'
  }}>
                          {line.match(/"[^"]+":/) ? <>
                              {line.split(/("[^"]+":)/).map((part, j) => part.match(/^"[^"]+":$/) ? <span key={`resp-part-${i}-${j}`} style={{
    color: 'var(--hl-flag)'
  }}>{part}</span> : part.match(/^"[^"]+"$/) ? <span key={`resp-part-${i}-${j}`} style={{
    color: 'var(--hl-str)'
  }}>{part}</span> : <span key={`resp-part-${i}-${j}`} style={{
    color: 'var(--hl-cmd)'
  }}>{part}</span>)}
                            </> : <span style={{
    color: 'var(--hl-cmd)'
  }}>{line}</span>}
                        </span>
                      </div>)}
                  </code>
                </pre>
              </div> : <div className="text-sm text-gray-500 dark:text-gray-400 text-center h-full flex items-center justify-center">
                No data returned from API
              </div>}
          </div>
        </div>
      </div>
    </div>;
};

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

<CodeGroup>
  ```bash curl theme={"system"}
  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"]
      }]
    }'
  ```

  ```typescript TypeScript theme={"system"}
  import { DataSource } from "@subsquid/portal-client";

  const dataSource = new DataSource({
    network: "ethereum-mainnet",
  });

  // Uniswap V3 Swap event signature
  const SWAP_TOPIC = "0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67";

  const blocks = await dataSource.getBlocks({
    from: 12369621, // Uniswap V3 deployment block
    to: 12400000,
    fields: {
      block: { number: true, timestamp: true },
      log: {
        address: true,
        topics: true,
        data: true,
        transactionHash: true,
      },
    },
    logs: [
      {
        topic0: [SWAP_TOPIC],
      },
    ],
  });

  // Process swaps
  for (const block of blocks) {
    for (const log of block.logs) {
      const sender = `0x${log.topics[1].slice(26)}`;
      const recipient = `0x${log.topics[2].slice(26)}`;
      
      console.log({
        blockNumber: block.header.number,
        timestamp: block.header.timestamp,
        pool: log.address,
        sender,
        recipient,
        txHash: log.transactionHash,
        // Decode amounts from data field
      });
    }
  }
  ```

  ```python Python theme={"system"}
  import requests
  import json

  url = "https://portal.sqd.dev/datasets/ethereum-mainnet/stream"
  headers = {"Content-Type": "application/json"}

  # Uniswap V3 Swap event
  swap_topic = "0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67"

  payload = {
      "type": "evm",
      "fromBlock": 12369621,  # Uniswap V3 deployment
      "toBlock": 12400000,
      "fields": {
          "block": {
              "number": True,
              "timestamp": True
          },
          "log": {
              "address": True,
              "topics": True,
              "data": True,
              "transactionHash": True
          }
      },
      "logs": [{
          "topic0": [swap_topic]
      }]
  }

  response = requests.post(url, headers=headers, json=payload)

  for line in response.text.strip().split('\n'):
      block = json.loads(line)
      for log in block.get('logs', []):
          sender = "0x" + log['topics'][1][26:]
          recipient = "0x" + log['topics'][2][26:]
          
          print({
              "blockNumber": block['header']['number'],
              "timestamp": block['header']['timestamp'],
              "pool": log['address'],
              "sender": sender,
              "recipient": recipient,
              "txHash": log['transactionHash']
          })
  ```
</CodeGroup>

<Info>Try it yourself with the interactive query interface below:</Info>

<QueryInterfaceDexSwaps />

## 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                                                               |

<Note>
  The Swap event signature varies by DEX. Uniswap V3 uses a different signature than Uniswap V2 or other DEXs.
</Note>

## Expected Output

```json theme={"system"}
{
  "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:

<CodeGroup>
  ```bash curl theme={"system"}
  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"]
      }]
    }'
  ```

  ```typescript TypeScript theme={"system"}
  const USDC_WETH_POOL = "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8"; // Uniswap V3 USDC/WETH

  const blocks = await dataSource.getBlocks({
    from: 18000000,
    to: 18010000,
    fields: {
      log: { address: true, topics: true, data: true },
    },
    logs: [
      {
        address: [USDC_WETH_POOL],
        topic0: ["0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67"],
      },
    ],
  });
  ```

  ```python Python theme={"system"}
  usdc_weth_pool = "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8"

  payload = {
      "type": "evm",
      "fromBlock": 18000000,
      "toBlock": 18010000,
      "fields": {
          "log": {
              "address": True,
              "topics": True,
              "data": True
          }
      },
      "logs": [{
          "address": [usdc_weth_pool],
          "topic0": ["0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67"]
      }]
  }
  ```
</CodeGroup>

## Uniswap V2 Swaps

For Uniswap V2, use the V2 Swap event signature:

<CodeGroup>
  ```typescript TypeScript theme={"system"}
  // 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],
      },
    ],
  });
  ```
</CodeGroup>

<Tip>
  Different DEX protocols use different event signatures. Check the contract ABI to find the correct signature for your target DEX.
</Tip>

## 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:

<CodeGroup>
  ```typescript TypeScript theme={"system"}
  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,
      });
    }
  }
  ```

  ```python Python theme={"system"}
  from eth_abi import decode

  # Decode data field (amounts and other params)
  # Uniswap V3: (int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick)
  types = ['int256', 'int256', 'uint160', 'uint128', 'int24']

  for line in response.text.strip().split('\n'):
      block = json.loads(line)
      for log in block.get('logs', []):
          decoded = decode(types, bytes.fromhex(log['data'][2:]))
          amount0, amount1, sqrtPriceX96, liquidity, tick = decoded
          
          print({
              "amount0": amount0,
              "amount1": amount1,
              "sqrtPriceX96": sqrtPriceX96,
              "liquidity": liquidity,
              "tick": tick
          })
  ```
</CodeGroup>

## 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:

```typescript theme={"system"}
// 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,
        });
      }
    }
  }
}
```

<Warning>
  MEV detection requires analyzing transaction ordering within blocks. Make sure to request `transaction` fields and `transactionIndex` to properly sequence trades.
</Warning>

## 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:

```typescript theme={"system"}
// 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',
      });
    }
  }
}
```

<Tip>
  Combine swap events with traces to capture the full execution path, including intermediate token approvals and transfers that don't emit swap events.
</Tip>

## Performance Tips

1. **Filter by pool address**: Query specific high-volume pools (USDC/WETH, WETH/USDT) instead of all swaps
2. **Use block ranges**: Process 10k-50k block batches for optimal throughput
3. **Batch decode**: Decode multiple events in parallel rather than sequentially
4. **Cache pool metadata**: Store token decimals and symbols to avoid repeated lookups
5. **Combine with traces**: Track multi-hop swaps and MEV activity by analyzing call traces alongside events

## Related Examples

<CardGroup cols={2}>
  <Card title="Query Event Logs" icon="file-lines" href="/en/portal/evm/examples/query-logs">
    General event log querying
  </Card>

  <Card title="Query Traces" icon="diagram-project" href="/en/portal/evm/examples/query-traces">
    Track multi-hop swaps with traces
  </Card>

  <Card title="Track NFT Transfers" icon="image" href="/en/portal/evm/examples/nft-transfers">
    Monitor NFT marketplace activity
  </Card>

  <Card title="API Reference" icon="book" href="/en/portal/evm/api">
    View complete API docs
  </Card>
</CardGroup>
