Call traces

Call traces

As of 0.4.26, Ponder supports call trace indexing. Call traces are similar to logs, but they represent a function call instead of an event log.


Call traces are slower, more expensive, and less widely supported than logs. For new chains, you might struggle to find an RPC provider that supports the trace_filter and trace_block methods.

Register an indexing function

Call trace indexing is disabled by default. To enable it for a contract, set the includeCallTraces option to true.

After enabling this option, each function in the contract ABI will become available as an indexing function event name using the "ContractName.functionName()" scheme.

import { createConfig } from "@ponder/core";
import { BlitmapAbi } from "./abis/Blitmap";
export default createConfig({
  contracts: {
    Blitmap: {
      abi: BlitmapAbi,
      network: "mainnet",
      address: "0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63",
      startBlock: 12439123,
      includeCallTraces: true,
  // ...
import { ponder } from "@/generated";
ponder.on("Blitmap.mintOriginal()", async ({ event }) => {
  //    ^? [tokenData: Hex, name: string]
  //          ^? bigint

What is a call trace?

Let's define call traces from three different perspectives:

  • Ponder: A call trace is similar to a log, but it represents a function call instead of an event log. You can register an indexing function that will run whenever a specific function on one of your contracts gets called.
  • Solidity: A call trace records a function call. For example, whenever someone calls the transfer(address to, uint256 amount) function of an ERC20 token contract, it produces a call trace.
  • EVM: A call trace records the execution of the CALL, STATICCALL, DELEGATECALL, or CALLCODE opcode within a transaction.

Top-level vs. internal calls

A call trace can be a top-level call or an internal call. A top-level call is from an externally-owned account, and an internal call is from another contract.

To determine if a specific call trace is a top-level call, use event.trace.traceAddress.length === 0. Top-level calls also always have ===, but this can also be true for internal calls.

ponder.on("ERC20.transfer()", async ({ event }) => {
  const isTopLevelCall = event.trace.traceAddress.length === 0;
  // ...

eth_call and view functions

The eth_call RPC method does not produce a call trace. These calls do not occur during the execution of a transaction, so they are not recorded as call traces.

However, calls made to view or pure functions do produce call traces if they are made during the execution of a transaction. These call traces are rarely useful for indexing, but they do happen.

RPC methods

RPC methods
Historicaltrace_filter and eth_getTransactionReceipt
Realtimetrace_block and eth_getTransactionReceipt

The sync engine fetches call traces using the trace_filter and trace_block RPC methods, and uses eth_getTransactionReceipt to confirm that a specific call trace did not occur within a reverted transaction.