A blockchain event indexer for the Centrifuge protocol, built with Ponder.
This project indexes EVM events from smart contracts in the Centrifuge protocol, maintains a PostgreSQL database of pools, vaults, share classes, holdings, cross-chain messaging, and related entities, and exposes a GraphQL API.
Handlers are Ponder entry points: they subscribe to contract logs (and blocks where used), decode event.args, and orchestrate what happens for each on-chain event. They live under src/handlers/ and should stay thin—parse the event, load any needed context, then delegate to services.
Services are the domain and persistence layer. Each service typically wraps one Ponder/Drizzle table (or a focused set of operations) with typed helpers: query, get-or-create, updates, and business rules shared across handlers. They live under src/services/, usually as class X extends Service<typeof Table> with static readonly entityTable and entityName so shared statics (insert, get, saveMany, …) stay correctly typed per entity.
In short: handlers react to the chain; services own the database model and reusable logic.
- Node.js 22+
- pnpm (Corepack is enough:
corepack enable) - Docker (for local PostgreSQL)
Development uses a Postgres 16 container defined in compose.yaml (postgres user/password/database, port 5432).
- Starting the dev server runs
docker compose up -dautomatically (predevinpackage.json). - When you stop
pnpm dev,postdevrunsdocker compose down.
To manage the database manually:
docker compose up -d # start Postgres
docker compose down # stop and remove containers (volume persists unless removed)Point Ponder at the database with DATABASE_URL in .env or .env.local, for example:
DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgrespnpm install --frozen-lockfile
# Environment (see .env.example): RPC keys, DATABASE_URL, optional ENVIRONMENT, etc.
cp .env.example .env.local
# Registry and ABIs (compile-time); set ENVIRONMENT=mainnet|testnet as needed
pnpm run update-registry
pnpm run codegen
# Starts Docker Postgres, then Ponder dev (port 8000, UI disabled)
pnpm dev- REGISTRY_URL / IPFS_GATEWAY: optional overrides for where the registry JSON is fetched from (see
scripts/fetch-registry.mjs). - IPFS_HASH (optional): if set, the registry is loaded from
{IPFS_GATEWAY}/{IPFS_HASH}instead of the defaultREGISTRY_URLfor the network. - ENVIRONMENT:
mainnetortestnet—chooses the default registry host (https://registry.centrifuge.io/vshttps://registry.testnet.centrifuge.io/). Defaults tomainnetif unset.
Registry data is fetched at build time (and on container start in production), not at runtime, so types and ABIs stay stable.
pnpm run update-registry
# or: IPFS_HASH=<cid> pnpm run update-registryThis refreshes generated registry TypeScript under generated/.
pnpm build
pnpm start(prestart runs update-registry before ponder start.)
pnpm sync runs scripts/cache.mjs: it downloads the published ponder-sync snapshot from GitHub Container Registry (ghcr.io, artifact name ponder-sync) and restores it into the database given by DATABASE_URL.
Mainnet only. The script requires ENVIRONMENT=mainnet. If ENVIRONMENT is missing or set to anything else (including testnet), it prints Skipping cache download: ENVIRONMENT is not mainnet. and exits without downloading or restoring. This is stricter than update-registry, which defaults to mainnet when ENVIRONMENT is unset.
Optional artifact tag: pass --tag <tag>, -t <tag>, or a single positional argument; latest or a numeric tag (see script validation).
Related maintainer scripts: pnpm sync:export (dump ponder_sync schema from the local Docker Postgres) and pnpm sync:push (publish a snapshot to GHCR).
The indexer maintains structured tables for pools, tokens, vaults, epochs, investor flows, holdings, escrows, cross-chain payloads, and related entities. See the Ponder schema in the repo and the GraphQL schema Ponder serves when running.
While the process is running, Ponder serves a GraphQL API; the URL is printed in the logs (dev default port 8000).
The public production GraphQL endpoint used for schema checks in this repo is https://api.centrifuge.io.
Helm values in environments/ describe how the app is deployed (indexer + query, ingress hosts, env vars). Summary:
| Environment | Network | GraphQL host (ingress) |
|---|---|---|
main |
mainnet |
api-v3-main.cfg.embrio.tech |
main-s |
mainnet |
api-v3-main-s.cfg.embrio.tech |
test |
testnet |
api-v3-test.cfg.embrio.tech |
test-s |
testnet |
api-v3-test-s.cfg.embrio.tech |
Container images are built and pushed to GitHub Container Registry (ghcr.io/centrifuge/api-v3) on pushes to main and on releases (see .github/workflows/docker-build.yml). The production image sets DATABASE_SCHEMA=app and runs fetch-registry on entry.
Contributions are welcome! Please feel free to submit a Pull Request.
MIT License