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
.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
@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
http://localhost:42069.Commands
Addresses and start block
Addresses come frompackages/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:
- Environment overrides:
FACTORY_ADDRESS,REGISTRY_ADDRESS,KEEPER_ORACLE_ADDRESS,POOL_MANAGER_ADDRESS,VAULT_ADDRESSES(comma separated), andSTART_BLOCK_<chainId>thenSTART_BLOCK(a block number orlatest). - The deployment file (or the one named by
DEPLOYMENTS_FILE), including itsstartBlockordeployBlockfield when present. - Zero addresses and block 0.
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-filteredeth_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
WithoutDATABASE_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 toNEXT_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:
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.