Skip to main content

evmDecoder

Decode smart contract events as a pipe.
import { evmDecoder, commonAbis } from "@subsquid/pipes/evm";

async function main() {
  const decoder = evmDecoder({
    range: { from: 20000000, to: 20100000 },
    contracts: ["0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"],
    events: {
      transfer: commonAbis.erc20.events.Transfer,
    },
  });

  await source.pipe(decoder).pipeTo(target);
}

void main();

Configuration

Range

Specify which blocks to decode:
// Fixed range
{ range: { from: 20000000, to: 20100000 } }

// From latest
{ range: { from: 'latest' } }

// From specific block
{ range: { from: 20000000 } }

Contracts

Array of contract addresses:
{
  contracts: [
    "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC
    "0xdac17f958d2ee523a2206206994597c13d831ec7", // USDT
    "0x6b175474e89094c44da98b954eedeac495271d0f", // DAI
  ];
}

Events

Map event names to ABI objects:
{
  events: {
    transfer: commonAbis.erc20.events.Transfer,
    approval: commonAbis.erc20.events.Approval
  }
}

Error Handling

Custom error handler (optional, default: throws error):
{
  onError: (ctx, error) => {
    ctx.logger.error({ error }, 'Decoding failed');
    // Return undefined to skip the failed event
    // Or throw to stop processing
  }
}

Common ABIs

Pre-defined ABIs for standard contracts:
import { commonAbis } from "@subsquid/pipes/evm";

// ERC20
commonAbis.erc20.events.Transfer;
commonAbis.erc20.events.Approval;
Currently, only ERC20 ABIs are available in commonAbis. For ERC721 and other token standards, use @subsquid/evm-typegen to generate custom ABIs from your contract JSON files.

Custom ABIs

Generate ABIs from contract JSON:
npx @subsquid/evm-typegen src/abi abi/uniswapV3Pool.json
Using @subsquid/evm-typegen provides full type safety for your event data and eliminates the need to manually look up raw event signatures (topic0 hashes). The generated types ensure compile-time safety when accessing event fields.
Use generated ABIs:
import * as uniswapAbi from "./abi/uniswapV3Pool";

const decoder = evmDecoder({
  range: { from: 12369621 },
  contracts: ["0x..."],
  events: {
    swap: uniswapAbi.events.Swap,
    mint: uniswapAbi.events.Mint,
    burn: uniswapAbi.events.Burn,
  },
});
The property names in the events object (like swap, mint, burn) are arbitrary. You can use any names you prefer—these are just labels that help you organize and access the decoded events in your code.

Decoded Event Structure

{
  blockNumber: 20000000,
  timestamp: Date,
  contract: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
  transactionHash: '0x...',
  logIndex: 123,
  rawEvent: {
    address: '0x...',
    data: '0x...',
    topics: ['0x...']
  },
  event: {
    from: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
    to: '0x8e23Ee67d1332aD560396262C48ffbB273f626a4',
    value: 1000000n
  }
}
Access decoded data:
async function main() {
  for await (const { data } of source.pipe(decoder)) {
    for (const transfer of data.transfer) {
      console.log(transfer.event.from);
      console.log(transfer.event.to);
      console.log(transfer.event.value);
      console.log(transfer.block.number);
      console.log(transfer.timestamp);
    }
  }
}

void main();

Multiple Event Types

Decode multiple events from same contract:
async function main() {
  const decoder = evmDecoder({
    range: { from: 20000000 },
    contracts: ["0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"],
    events: {
      transfer: commonAbis.erc20.events.Transfer,
      approval: commonAbis.erc20.events.Approval,
    },
  });

  for await (const { data } of source.pipe(decoder)) {
    console.log(`Transfers: ${data.transfer.length}`);
    console.log(`Approvals: ${data.approval.length}`);
  }
}

void main();

Composite Decoders

Decode from multiple contracts simultaneously:
async function main() {
  const pipeline = source.pipeComposite({
    usdc: evmDecoder({
      range: { from: 20000000 },
      contracts: ["0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"],
      events: { transfer: commonAbis.erc20.events.Transfer },
    }),
    usdt: evmDecoder({
      range: { from: 20000000 },
      contracts: ["0xdac17f958d2ee523a2206206994597c13d831ec7"],
      events: { transfer: commonAbis.erc20.events.Transfer },
    }),
  });

  for await (const { data } of pipeline) {
    console.log(`USDC: ${data.usdc.transfer.length}`);
    console.log(`USDT: ${data.usdt.transfer.length}`);
  }
}

void main();

Profiler Integration

const decoder = evmDecoder({
  profiler: { id: "ERC20 decoding" },
  range: { from: 20000000 },
  contracts: ["0x..."],
  events: { transfer: commonAbis.erc20.events.Transfer },
});

Data Flow

Complete Example

import {
  evmPortalSource,
  evmDecoder,
  commonAbis,
} from "@subsquid/pipes/evm";

async function main() {
  const source = evmPortalSource({
    portal: "https://portal.sqd.dev/datasets/ethereum-mainnet",
  });

  const decoder = evmDecoder({
    range: { from: 20000000, to: 20000100 },
    contracts: ["0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"],
    events: {
      transfer: commonAbis.erc20.events.Transfer,
    },
  });

  for await (const { data } of source.pipe(decoder)) {
    for (const transfer of data.transfer) {
      console.log({
        from: transfer.event.from,
        to: transfer.event.to,
        amount: transfer.event.value.toString(),
        block: transfer.block.number,
      });
    }
  }
}

void main();