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.
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.
/** 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
.
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,
},
},
},
},
});
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.
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"),
}),
});
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.
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.
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.
import { ponder } from "@/generated";
ponder.on("setup", async ({ context }) => {
const { models, contracts } = context;
// ...
});
Options
name | description |
---|---|
context | Global resources (model objects, read-only contract objects) |