Block intervals
To run indexing functions on a schedule, use a block interval. Block intervals are great for "cron" workloads, because they run at a consistent frequency regardless of contract activity or transactions.
This guide describes how to configure block intervals, and suggests patterns for common use cases. See also the ponder.config.ts
API reference page.
Name
Every block interval must have a name, provided as a key to the blocks
object. The name must be unique across blocks
and contracts
.
import { createConfig } from "@ponder/core";
import { http } from "viem";
export default createConfig({
networks: {
mainnet: { chainId: 1, transport: http(process.env.PONDER_RPC_URL_1) },
},
blocks: {
ChainlinkOracleUpdate: {
network: "mainnet",
startBlock: 19783636,
interval: 60 / 12, // Every 60 seconds
},
},
});
Example block event indexing function
This indexing function uses block events to index price chart data by reading the latest price from a Chainlink oracle contract every minute.
import { ponder } from "@/generated";
import { ChainlinkOracleAbi } from "../abis/ChainlinkOracle.ts";
ponder.on("ChainlinkOracleUpdate:block", async ({ event, context }) => {
// Fetch the price at the current block height.
const latestPrice = await client.readContract({
abi: ChainlinkOracleAbi,
address: "0xD10aBbC76679a20055E167BB80A24ac851b37056",
functionName: "latestAnswer",
});
// Insert a Price record.
const token = await context.db.Price.create({
id: event.block.timestamp,
data: {
blockNumber: event.block.number,
timestamp: event.block.timestamp,
price: latestPrice,
},
});
});
Interval
The interval
option specifies how often the indexing function should run. For example, a block interval with a start block of 100
and a interval of 10
will index blocks 100
, 110
, 120
, 130
, and so on.
Block time
It's often easier to think about a time interval instead of a block interval. To convert between the two, divide the time interval by the network block time.
For example, if the network block time is 3 seconds and you want to run an indexing function once per day:
// 24 hours per day, 60 minutes per hour, 60 seconds per minute
const secondsInterval = 24 * 60 * 60;
// 3 seconds per block
const blockTime = 3
// 28800 blocks per day
const blockInterval = secondsInterval / blockTime
To find the block time of a specific chain, check the chain's documentation website or block explorer. Most Etherscan deployments have a /chart/blocktime
page.
Network
Single network
If you only need to index blocks from one network, pass the network name as a string to the network
field.
import { createConfig } from "@ponder/core";
import { http } from "viem";
export default createConfig({
networks: {
mainnet: { chainId: 1, transport: http(process.env.PONDER_RPC_URL_1) },
},
blocks: {
ChainlinkOracleUpdate: {
network: "mainnet",
startBlock: 19783636,
interval: 60 / 12, // Every 60 seconds
},
},
});
Multiple networks
If you'd like to run the same block indexing function across multiple networks, pass an object to the network
field containing network-specific options.
import { createConfig } from "@ponder/core";
import { http } from "viem";
export default createConfig({
networks: {
mainnet: { chainId: 1, transport: http(process.env.PONDER_RPC_URL_1) },
optimism: { chainId: 10, transport: http(process.env.PONDER_RPC_URL_10) },
},
blocks: {
ChainlinkOracleUpdate: {
network: {
mainnet: {
startBlock: 19783636,
interval: (60 * 60) / 12, // Every 60 minutes
},
optimism: {
startBlock: 119534316,
interval: (60 * 60) / 2, // Every 60 minutes
},
},
},
},
});
Now, the indexing functions you write for ChainlinkOracleUpdate:block
will process blocks from both mainnet and Optimism.
The context.network
object contains information about which network the current block is from.
import { ponder } from "@/generated";
ponder.on("ChainlinkOracleUpdate:block", async ({ event, context }) => {
context.network;
// ^? { name: "mainnet", chainId 1 } | { name: "optimism", chainId 10 }
if (context.network.name === "mainnet") {
// Do mainnet-specific stuff!
}
});
Just like with contracts, you can use network-specific overrides for the interval
, startBlock
and endBlock
options. Read more about network-specific overrides.
Block range
The optional startBlock
and endBlock
options specify the block range to index. The default value for startBlock
is 0
, and the default value for endBlock
is undefined
. When endBlock
is undefined
, Ponder will index the contract in realtime.
If endBlock
is defined, no events will be indexed after that block number. This option is useful if you're only interested in a slice of historical data, or to enable faster feedback loops during development where it's not necessary to index the entire history.
import { createConfig } from "@ponder/core";
import { http } from "viem";
export default createConfig({
networks: {
mainnet: { chainId: 1, transport: http(process.env.PONDER_RPC_URL_1) },
},
blocks: {
ChainlinkOracleUpdate: {
network: "mainnet",
interval: 60 / 12, // Every 60 seconds
startBlock: 19600000,
endBlock: 19700000,
},
},
});