Documentation
Advanced
Foundry

Foundry

This guide describes how to integrate Ponder and Foundry during local development.

Foundry projects follow various development workflows (test-driven, deploy to a fresh chain, deploy to a fork, etc). Rather than a one-size-fits-all integration, this page offers patterns that you can adapt to your workflow.

Configure the anvil network

Disable caching

Ponder's RPC request cache works well for live networks where the chain is generally immutable, but causes issues when indexing a local chain that "resets".

Use the disableCache option to disable RPC request caching for the Anvil network. With this option set to true, Ponder will clear the cache on start up and between hot reloads.

ponder.config.ts
import { createConfig } from "@ponder/core";
import { http } from "viem";
 
export default createConfig({
  networks: {
    anvil: { 
      chainId: 31337,
      transport: http("http://127.0.0.1:8545"),
      disableCache: true,
    },
  },
  // ...
});

Chain ID

We recommend using 31337 (the default Anvil chain ID) even when forking a live chain. This avoids common footguns when working with multiple networks.

Mining mode

We recommend using interval mining with a block time of ~2 seconds. This better simulates a live network.

⚠️
Known issue: When indexing Anvil with auto mining enabled in an app with multiple networks, indexing progress will get "stuck" at the timestamp of the latest Anvil block.

Generate ABI files

To enable end-to-end type safety, the contract ABIs generated by Foundry must be copied into TypeScript (.ts) source files.

Wagmi CLI

The Wagmi CLI Foundry plugin is an excellent tool to automate tedious ABI file management. For more information, visit the Wagmi CLI documentation.

Here is the Wagmi CLI config file used by the Foundry example project.

wagmi.config.ts
import { defineConfig } from "@wagmi/cli";
import { foundry } from "@wagmi/cli/plugins";
 
export default defineConfig({
  out: "abis/CounterAbi.ts",
  plugins: [
    foundry({
      project: "foundry",
      include: ["Counter.sol/**"],
    }),
  ],
});

Import broadcast files

Foundry scripts write transaction inputs and receipts to JSON files in the broadcast directory. You can import these files directly into ponder.config.ts to automate address management and enable hot reloading.

Remember to enable broadcast so that forge script submits transactions to Anvil.

Automate address management

To read the contract address and deployment block number from a broadcast file, import the file directly into ponder.config.ts and access properties from the JSON object.

The ponder.config.ts file from the Foundry example project demonstrates this pattern. Here, the first transaction in the broadcast file deployed the Counter.sol contract. The location of the contract address and start block within the broadcast file depends on the order and number of transactions in your deployment script.

ponder.config.ts
import { createConfig } from "@ponder/core";
import { http, getAddress, hexToNumber } from "viem";
import { counterABI } from "../abis/CounterAbi";
import CounterDeploy from "../foundry/broadcast/Deploy.s.sol/31337/run-latest.json";
 
const address = getAddress(CounterDeploy.transactions[0]!.contractAddress);
const startBlock = hexToNumber(CounterDeploy.receipts[0]!.blockNumber);
 
export default createConfig({
  networks: {
    anvil: {
      chainId: 31337,
      transport: http("http://127.0.0.1:8545"),
      disableCache: true,
    },
  },
  contracts: {
    Counter: {
      network: "anvil",
      abi: counterABI,
      address,
      startBlock,
    },
  },
});

Enable hot reloading

If you import a JSON broadcast file in ponder.config.ts, the dev server will reload each time that file changes. This is a simple way to ensure that Ponder reloads every time you run a Foundry deployment script.

ponder.config.ts
import { createConfig } from "@ponder/core";
import { http } from "viem";
import CounterDeploy from "../foundry/broadcast/Deploy.s.sol/31337/run-latest.json";
// ^ The development server detects changes to this file and triggers a hot reload.
 
export default createConfig({
  // ...
});