Indexing functions

Indexing function API

Indexing functions are user-defined functions that process blockchain data. They can be registered within any .ts file inside the src/ directory. There are two kinds of events: log events and the "setup" event.

Registration

To register an indexing function, use the .on() method of the ponder object exported from "@/generated".

Ponder uses

Values returned by indexing functions are ignored.

src/index.ts
import { ponder } from "@/generated";
 
ponder.on("ContractName:EventName", async ({ event, context }) => {
  const { params, log, block, transaction } = event;
  const { models, network, client, contracts } = context;
 
  // ...
});

Event

The event argument passed to each indexing function contains raw event data.

type Event = {
  name: string;
  params: Params;
  log: Log;
  block: Block;
  transaction: Transaction;
};

Params

The event.params object contains event log arguments decoded using the contract ABI.

type Params =

Block, transaction, and log

The event.block, event.transaction, and event.log objects contain raw blockchain data.

Block, transaction, and log types
/** The block containing the transaction that emitted the log being processed. */
type Block = {
  /** Base fee per gas */
  baseFeePerGas: bigint | null;
  /** "Extra data" field of this block */
  extraData: `0x${string}`;
  /** Maximum gas allowed in this block */
  gasLimit: bigint;
  /** Total used gas by all transactions in this block */
  gasUsed: bigint;
  /** Block hash */
  hash: `0x${string}`;
  /** Logs bloom filter */
  logsBloom: `0x${string}`;
  /** Address that received this block’s mining rewards */
  miner: `0x${string}`;
  /** Block number */
  number: bigint;
  /** Parent block hash */
  parentHash: `0x${string}`;
  /** Root of the this block’s receipts trie */
  receiptsRoot: `0x${string}`;
  /** Size of this block in bytes */
  size: bigint;
  /** Root of this block’s final state trie */
  stateRoot: `0x${string}`;
  /** Unix timestamp of when this block was collated */
  timestamp: bigint;
  /** Total difficulty of the chain until this block */
  totalDifficulty: bigint | null;
  /** Root of this block’s transaction trie */
  transactionsRoot: `0x${string}`;
};
 
/** The transaction that emitted the log being processed. */
type Transaction = {
  /** Hash of block containing this transaction */
  blockHash: `0x${string}`;
  /** Number of block containing this transaction */
  blockNumber: bigint;
  /** Transaction sender */
  from: `0x${string}`;
  /** Gas provided for transaction execution */
  gas: bigint;
  /** Base fee per gas. */
  gasPrice?: bigint | undefined;
  /** Hash of this transaction */
  hash: `0x${string}`;
  /** Contract code or a hashed method call */
  input: `0x${string}`;
  /** Total fee per gas in wei (gasPrice/baseFeePerGas + maxPriorityFeePerGas). */
  maxFeePerGas?: bigint | undefined;
  /** Max priority fee per gas (in wei). */
  maxPriorityFeePerGas?: bigint | undefined;
  /** Unique number identifying this transaction */
  nonce: number;
  /** Transaction recipient or `null` if deploying a contract */
  to: `0x${string}` | null;
  /** Index of this transaction in the block */
  transactionIndex: number;
  /** Value in wei sent with this transaction */
  value: bigint;
};
 
/** The log being processed. */
type Log = {
  /** Globally unique identifier for this log (`${blockHash}-${logIndex}`). */
  id: string;
  /** The address from which this log originated */
  address: `0x${string}`;
  /** Hash of block containing this log */
  blockHash: `0x${string}`;
  /** Number of block containing this log */
  blockNumber: bigint;
  /** Contains the non-indexed arguments of the log */
  data: `0x${string}`;
  /** Index of this log within its block */
  logIndex: number;
  /** `true` if this log has been removed in a chain reorganization */
  removed: boolean;
  /** List of order-dependent topics */
  topics: [`0x${string}`, ...`0x${string}`[]] | [];
  /** Hash of the transaction that created this log */
  transactionHash: `0x${string}`;
  /** Index of the transaction that created this log */
  transactionIndex: number;
};

Context

The context argument passed to each indexing function contains database model objects and helper objects based on your config.

At runtime, the indexing engine uses a different context object depending on the network the current event was emitted on. The TypeScript types for the context object reflect this by creating a union of possible types for context.network and context.contracts.

ponder.config.ts
import { createConfig } from "@ponder/core";
import { http } from "viem";
 
import { UniswapV3FactoryAbi } from "./abis/UniswapV3Factory";
 
export default createConfig({
  networks: {
    mainnet: { chainId: 1, transport: http(process.env.PONDER_RPC_URL_1) },
    base: { chainId: 8453, transport: http(process.env.PONDER_RPC_URL_8453) },
  },
  contracts: {
    UniswapV3Factory: {
      abi: UniswapV3FactoryAbi,
      network: {
        mainnet: {
          address: "0x1F98431c8aD98523631AE4a59f267346ea31F984",
          startBlock: 12369621,
        },
        base: {
          address: "0x33128a8fC17869897dcE68Ed026d694621f6FDfD",
          startBlock: 1371680,
        },
      },
    },
  },
});
src/index.ts
ponder.on("UniswapV3Factory:Ownership", async ({ event, context }) => {
  context.network;
  //      ^? { name: "mainnet", chainId 1 } | { name: "base", chainId 8453 }
 
  event.log.address;
  //        ^? "0x1F98431c8aD98523631AE4a59f267346ea31F984" | "0x33128a8fC17869897dcE68Ed026d694621f6FDfD"
 
  if (context.network.name === "mainnet") {
    // Do mainnet-specific stuff!
  }
});

Models

The context.models object contains ORM-style models that can be used to create, read, update, and delete database records.

ponder.schema.ts
import { p } from "@ponder/core";
 
export default p.createSchema({
  Person: p.createTable({
    id: p.string(),
    age: p.int().optional(),
  }),
  Dog: p.createTable({
    id: p.bigint(),
    ownerId: p.string().references("Person.id"),
  }),
});
src/index.ts
import { ponder } from "@/generated";
 
ponder.on("UniswapV3Factory:Ownership", async ({ event, context }) => {
  context.models.Person;
  //             ^? Model<{ id: string; age?: number }>;
  context.models.Dog;
  //             ^? Model<{ id: bigint; ownerId: string; }>;
});

Network

The context.network object contains information about the network that the current event was emitted on. The name and chain ID properties are strictly typed as a union of possible values based on the networks that the contract is configured to run on.

src/index.ts
ponder.on("UniswapV3Factory:Ownership", async ({ event, context }) => {
  context.network;
  //      ^? { name: "mainnet", chainId 1 } | { name: "base", chainId 8453 }
 
  if (context.network.name === "mainnet") {
    // Do mainnet-specific stuff!
  }
});

Client

See the Read contract data guide for more details.

Contracts

Context

This object contains CRUD objects for the tables defined in ponder.schema.ts, and a read-only contract object for each contract specified in ponder.config.ts.

type Context = {
  // Keyed by table names from ponder.schema.ts
  models: Record<string, Model>;
  // Keyed by contract names from ponder.config.ts
  contracts: Record<string, ReadOnlyContract>;
};

Model

These objects are used to create, read, update, and delete database records. context.models contains a Model object for each table defined in schema.ponder.ts.

See Create & update records for a complete API reference.

ReadOnlyContract

See the read contract data guide for more details.

ReadOnlyContract objects are used to read data directly from a contract. These objects have a method for each read-only function present in the contract's ABI (functions with state mutability of "pure" or "view"). The context.contracts object has a ReadOnlyContract for each contract defined in ponder.config.ts.

A ReadOnlyContract is a viem Contract Instance (opens in a new tab) that has been modified to cache contract read results. By default, contract reads use the eth_call RPC method with blockNumber set to the block number of the event being processed (event.block.number). You can read the contract at a different block number (e.g. the contract deployment block number or "latest") by passing the blockNumber or blockTag option, but this will disable caching.

src/index.ts
import { ponder } from "@/generated";
 
ponder.on("MyERC20:Transfer", async ({ event, context }) => {
  const { MyERC20 } = context.contracts;
 
  // This read will occur at the block number of the event being
  // processed (event.block.number) and will be served from the cache
  // on hot reloads / reployments.
  const totalSupply = await MyERC20.read.totalSupply();
 
  // This read will occur at the latest block number when this function
  // runs, and will not be cached. Avoid this pattern.
  const currentBalance = await MyERC20.read.balanceOf("0xFa3...", {
    blockTag: "latest",
  });
});

The "setup" event

You can also define an indexing function for a special event called "setup" that runs before all other events.

src/index.ts
import { ponder } from "@/generated";
 
ponder.on("setup", async ({ context }) => {
  const { models, contracts } = context;
 
  // ...
});

Options

namedescription
contextGlobal resources (model objects, read-only contract objects)