CtrlK
BlogDocsLog inGet started
Tessl Logo

hefgi/ponder

Build EVM blockchain data indexers using Ponder (ponder.sh) - an open-source TypeScript framework for indexing smart contract events, transactions, and traces into custom database schemas with type-safe APIs. Use when the user mentions ponder, blockchain/EVM indexing, onchain data pipelines, subgraph replacement, or wants to index smart contract events into a queryable database.

98

1.25x
Quality

99%

Does it follow best practices?

Impact

98%

1.25x

Average score across 5 eval scenarios

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

config.mdreferences/

Ponder Configuration Reference

Table of Contents

  • createConfig
  • Chains
  • Contracts
  • Factory Contracts
  • mergeAbis
  • Accounts
  • Block Intervals
  • Database
  • Ordering Modes
  • Multi-Chain Example
  • Factory Example

createConfig

import { createConfig } from "ponder";
import { http, fallback, loadBalance } from "viem";

export default createConfig({
  ordering?: "multichain" | "omnichain" | "experimental_isolated",
  database?: { ... },
  chains: { ... },
  contracts?: { ... },
  accounts?: { ... },
  blocks?: { ... },
});

Chains

Each chain needs an id (EVM chain ID) and an rpc endpoint.

chains: {
  mainnet: {
    id: 1,
    rpc: process.env.PONDER_RPC_URL_1,
    // Or use an array for automatic fallback:
    // rpc: [process.env.PONDER_RPC_URL_1, process.env.PONDER_RPC_URL_1_FALLBACK],
  },
  base: {
    id: 8453,
    rpc: process.env.PONDER_RPC_URL_8453,
  },
}

Chain Options

OptionTypeDefaultDescription
idnumberrequiredEVM chain ID
rpcstring | string[] | TransportrequiredRPC endpoint(s) or viem Transport
wsstring-WebSocket RPC endpoint
pollingIntervalnumber1000Milliseconds between polls for new blocks
disableCachebooleanfalseDisable RPC cache. Set true for Anvil/Hardhat.
ethGetLogsBlockRangenumberautoOverride max block range for eth_getLogs

Advanced RPC Configuration

import { http, fallback, loadBalance } from "viem";

chains: {
  mainnet: {
    id: 1,
    // Fallback: tries each in order
    rpc: fallback([
      http(process.env.PONDER_RPC_URL_1_PRIMARY),
      http(process.env.PONDER_RPC_URL_1_FALLBACK),
    ]),
    // Or load balance across multiple endpoints:
    // rpc: loadBalance([
    //   http(process.env.PONDER_RPC_URL_1_A),
    //   http(process.env.PONDER_RPC_URL_1_B),
    // ]),
  },
}

Contracts

contracts: {
  MyContract: {
    abi: MyContractAbi,                  // Must use `as const`
    chain: "mainnet",                    // Single chain
    address: "0x...",                    // Single address (lowercase)
    startBlock: 12345678,                // Contract deployment block
    endBlock?: 13000000,                 // Optional: stop indexing at this block
    filter?: { ... },                    // Optional: filter specific events/args
    includeCallTraces?: false,           // Optional: index function calls
    includeTransactionReceipts?: false,  // Optional: include tx receipts
  },
}

Multi-Chain Contract

Same contract on multiple chains:

contracts: {
  WETH: {
    abi: WETHAbi,
    chain: {
      mainnet: {
        address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
        startBlock: 4719568,
      },
      base: {
        address: "0x4200000000000000000000000000000000000006",
        startBlock: 1,
      },
      optimism: {
        address: "0x4200000000000000000000000000000000000006",
        startBlock: 1,
      },
    },
  },
}

Multiple Addresses (Same Chain)

contracts: {
  Tokens: {
    abi: ERC20Abi,
    chain: "mainnet",
    address: [
      "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC
      "0xdac17f958d2ee523a2206206994597c13d831ec7", // USDT
    ],
    startBlock: 6082465,
  },
}

Event Filtering

contracts: {
  USDC: {
    abi: ERC20Abi,
    chain: "mainnet",
    address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
    startBlock: 6082465,
    filter: {
      event: "Transfer",                      // Single event name
      // event: ["Transfer", "Approval"],      // Or multiple events
      args: {
        from: "0x...",                         // Filter by indexed arg
        // from: ["0x...", "0x..."],           // Or array of values
      },
    },
  },
}

Factory Contracts

For contracts deployed dynamically by a factory:

import { createConfig, factory } from "ponder";
import { parseAbiItem } from "viem";

export default createConfig({
  contracts: {
    UniswapV3Pool: {
      abi: PoolAbi,
      chain: "mainnet",
      address: factory({
        address: "0x1f98431c8ad98523631ae4a59f267346ea31f984",
        event: parseAbiItem(
          "event PoolCreated(address indexed token0, address indexed token1, uint24 indexed fee, int24 tickSpacing, address pool)"
        ),
        parameter: "pool",
      }),
      startBlock: 12369621,
    },
  },
});

factory() Options

OptionTypeDescription
addressstringFactory contract address (lowercase)
eventAbiEventABI item for the creation event (use parseAbiItem)
parameterstringName of the event parameter containing the child address
startBlocknumberOptional: override child contract start block
endBlocknumberOptional: stop indexing child contracts at this block

mergeAbis

Combine proxy + implementation ABIs:

import { createConfig, mergeAbis } from "ponder";

export default createConfig({
  contracts: {
    MyProxy: {
      abi: mergeAbis([ProxyAbi, ImplementationAbi]),
      chain: "mainnet",
      address: "0x...",
      startBlock: 12345678,
    },
  },
});

Accounts

Index account-level activity (transactions and native transfers):

accounts: {
  Vitalik: {
    chain: "mainnet",
    address: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
    startBlock: 0,
  },
}

Account Handler Types

HandlerTrigger
"Account:transaction:from"Transactions sent by the account
"Account:transaction:to"Transactions received by the account
"Account:transfer:from"Native ETH sent by the account
"Account:transfer:to"Native ETH received by the account

Block Intervals

Execute a handler at fixed block intervals:

blocks: {
  PriceOracle: {
    chain: "mainnet",
    interval: 5,          // Every 5 blocks
    startBlock: 18000000,
    endBlock?: 19000000,  // Optional
  },
}

Database

PGlite (Development)

Automatic in ponder dev. No config needed. To customize:

database: {
  kind: "pglite",
  directory: ".ponder/pglite",
}

Postgres (Production)

database: {
  kind: "postgres",
  connectionString: process.env.DATABASE_URL,
  poolConfig: {
    max: 30,
    ssl: { rejectUnauthorized: false },
  },
}

Ordering Modes

ModeOrdering GuaranteePerformanceUse Case
multichainPer-chain only. Events from different chains interleave freely.GoodDefault. Most projects.
omnichainGlobal order across all chains by block timestamp.SlowerBridges, cross-chain aggregators.
experimental_isolatedPer-chain only. Each chain indexes independently with max parallelism.BestHigh throughput. Requires chainId in all PKs.
export default createConfig({
  ordering: "experimental_isolated",
  // ...
});

Multi-Chain Example

WETH on mainnet + Base + Optimism:

import { createConfig } from "ponder";
import { WETHAbi } from "./abis/WETHAbi";

export default createConfig({
  ordering: "experimental_isolated",
  chains: {
    mainnet: { id: 1, rpc: process.env.PONDER_RPC_URL_1 },
    base: { id: 8453, rpc: process.env.PONDER_RPC_URL_8453 },
    optimism: { id: 10, rpc: process.env.PONDER_RPC_URL_10 },
  },
  contracts: {
    WETH: {
      abi: WETHAbi,
      chain: {
        mainnet: {
          address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
          startBlock: 4719568,
        },
        base: {
          address: "0x4200000000000000000000000000000000000006",
          startBlock: 1,
        },
        optimism: {
          address: "0x4200000000000000000000000000000000000006",
          startBlock: 1,
        },
      },
    },
  },
});

Factory Example

Uniswap V3 pools:

import { createConfig, factory } from "ponder";
import { parseAbiItem } from "viem";
import { PoolAbi } from "./abis/PoolAbi";

export default createConfig({
  chains: {
    mainnet: { id: 1, rpc: process.env.PONDER_RPC_URL_1 },
  },
  contracts: {
    UniswapV3Pool: {
      abi: PoolAbi,
      chain: "mainnet",
      address: factory({
        address: "0x1f98431c8ad98523631ae4a59f267346ea31f984",
        event: parseAbiItem(
          "event PoolCreated(address indexed token0, address indexed token1, uint24 indexed fee, int24 tickSpacing, address pool)"
        ),
        parameter: "pool",
      }),
      startBlock: 12369621,
    },
  },
});

SKILL.md

tile.json