Documentation
Block intervals

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.

ponder.config.ts
import { createConfig } from "ponder";
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.

src/index.ts
import { prices } from "ponder:schema";
import { ponder } from "ponder:registry";
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 row into the prices table.
  await context.db.insert(prices).values({
    id: event.block.timestamp,
    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.

ponder.config.ts
import { createConfig } from "ponder";
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.

ponder.config.ts
import { createConfig } from "ponder";
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.

src/index.ts
import { ponder } from "ponder:registry";
 
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.

ponder.config.ts
import { createConfig } from "ponder";
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,
    },
  },
});