Skip to main content
The indexer is a Ponder app in packages/indexer. One instance follows one chain, selected by CHAIN_ID, and serves the GraphQL API described in the API reference on port 42069. Every chain the web app offers needs its own instance.

What it indexes

The PoolManager source is dropped entirely when INDEX_POOL_SWAPS=false (see below).

Setup

1

Install and configure

Ponder reads .env.local. Set CHAIN_ID, the RPC for that chain and the start block (the deployment block; see “Addresses and start block”).
2

Generate the ABIs

This re-exports the JSON ABIs from @launchpad/abis as as const TypeScript in abis/index.ts and writes ponder-env.d.ts. abis/index.ts is also regenerated by postinstall, so run pnpm install or pnpm codegen again after pnpm abis (repository root) changes the ABIs.
3

Run

GraphiQL opens at http://localhost:42069.

Commands

Addresses and start block

Addresses come from packages/abis/deployments/<CHAIN_ID>.json, the file written by contracts/script/Deploy.s.sol (its shape is the Deployment type in @launchpad/abis). It is loaded lazily, so the indexer typechecks and boots without it; without a file or overrides every address is zero, a warning is logged and nothing is indexed. A file whose chainId differs from CHAIN_ID is rejected at startup. Resolution order, highest priority first:
  1. Environment overrides: FACTORY_ADDRESS, REGISTRY_ADDRESS, KEEPER_ORACLE_ADDRESS, POOL_MANAGER_ADDRESS, VAULT_ADDRESSES (comma separated), and START_BLOCK_<chainId> then START_BLOCK (a block number or latest).
  2. The deployment file (or the one named by DEPLOYMENTS_FILE), including its startBlock or deployBlock field when present.
  3. Zero addresses and block 0.
Set the start block. scripts/deploy-mainnet.sh records the deploy block as startBlock in the deployment file, but the testnet script does not, and syncing from block 0 on a chain with 0.5 s blocks takes days. Use the block of the deployment transaction. The suffixed form START_BLOCK_<chainId> lets one .env.local serve an indexer per chain.
When no deployment file and no VAULT_ADDRESSES are available, vaults are discovered from the registry’s SyntheticCreated event instead.

Environment

RPC requirements

The indexer needs an endpoint that answers topic-filtered eth_getLogs at a steady rate and, because the handlers read contract state at the event’s block (vault.status() for snapshots, for instance), archive state. Most public endpoints fail one of the two.
The .env.example still lists PONDER_RPC_URL_5042002=https://rpc.testnet.arc.io as a starting point. Replace it with the mirrors before indexing anything on Arc testnet.

Database

Without DATABASE_URL, Ponder keeps an embedded PGlite database under packages/indexer/.ponder/pglite-<CHAIN_ID>, so two instances for two chains never share a directory. With Postgres (a docker run postgres:16 is fine), give each instance its own schema: DATABASE_SCHEMA or ponder start --schema <name>. Backfill sits around 600 to 700 MB of RAM per instance; the production pm2 entries restart an indexer above 1200 MB.

Endpoints

The API answers CORS (access-control-allow-origin: *), so the browser can query it from another origin without proxy rules.

One instance per chain

The web app talks to NEXT_PUBLIC_INDEXER_URL_<chainId> for each chain in its switcher, so run one indexer per chain from the same package with a different CHAIN_ID and PORT. Locally:
The production box runs three through deploy/ecosystem.config.cjs: The three share one .env.local: the RPC and start-block variables carry the chain id, and deploy/ecosystem.config.cjs sets CHAIN_ID, PORT and the per-chain overrides on top. pm2 runs node_modules/ponder/dist/esm/bin/ponder.js start --schema <schema> directly rather than the .bin shim, which pnpm writes as a shell script. A chain whose indexer is down still works in the web app from on-chain reads, with “indexer offline” in place of charts, trades and holders.