Skip to content

centrifuge/api-v3

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

431 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🌀 Centrifuge API V3

Deploy Pipeline Centrifuge embrio.tech

A blockchain event indexer for the Centrifuge protocol, built with Ponder.


Overview

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 vs services

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.

Getting started

Prerequisites

  • Node.js 22+
  • pnpm (Corepack is enough: corepack enable)
  • Docker (for local PostgreSQL)

Local database (Docker Compose)

Development uses a Postgres 16 container defined in compose.yaml (postgres user/password/database, port 5432).

  • Starting the dev server runs docker compose up -d automatically (predev in package.json).
  • When you stop pnpm dev, postdev runs docker 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/postgres

Install and run

pnpm 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

Configuration notes

  • REGISTRY_URL / IPFS_GATEWAY: optional overrides for where the registry JSON is fetched from (see scripts/fetch-registry.mjs).
  • REGISTRY_<version>_… / REGISTRY_ALL_…: optional local patches applied when running pnpm update-registry (per-version slug, or the same path on every registry version for REGISTRY_ALL_; version-specific keys override REGISTRY_ALL_ on the same path).
  • SELECTED_REGISTRY_VERSIONS: optional comma-separated registry slugs to emit (e.g. v3_1,v3_2). When unset, every version in the resolved chain is generated. Order follows the chain (oldest → newest), not the env var order.
  • IPFS_HASH (optional): if set, the registry is loaded from {IPFS_GATEWAY}/{IPFS_HASH} instead of the default REGISTRY_URL for the network.
  • ENVIRONMENT: mainnet or testnet—chooses the default registry host (https://registry.centrifuge.io/ vs https://registry.testnet.centrifuge.io/). Defaults to mainnet if unset.
  • PONDER_RPC_URL_<chainId>: optional comma-separated RPC URLs per chain (see src/chains.ts); default provider endpoints are appended as fallbacks.
  • PONDER_RPC_TIMEOUT_MS: JSON-RPC timeout for Ponder sync (default 60000). Uses a custom viem transport in src/helpers/ponderRpcTransport.ts because Ponder’s built-in HTTP client defaults to 10s.

Updating registry data

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-registry

This refreshes generated registry TypeScript under generated/.

Production-style run (local)

pnpm build
pnpm start

(prestart runs update-registry before ponder start.)

Ponder sync (pnpm sync)

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), pnpm sync:push (publish a snapshot to GHCR), and pnpm sync:invalidate-factories (clear factory discovery cache while keeping RPC block/log cache — see scripts/sync-invalidate-factories.mjs and AGENTS.md).

Database schema

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.

API

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.

Smoke tests

Operational integrity checks compare the GraphQL indexer to on-chain view functions via eth_call (no log replay). Specs and design principles live in test/smoke/specs/README.md.

pnpm smoke list
pnpm smoke --only issuance,onramp --mismatches-only
pnpm smoke issuance --symbol ACRDX --chain plume

Optional ERPC_BASE_URL (e.g. https://erpc.cfg.embrio.tech/main) and ERPC_API_KEY in .env.local for RPC ({base}/evm/<chainId>?secret=…); smokes try eRPC first, then Chainlist public endpoints as fallbacks (test/smoke/lib/rpc.mjs). Use --graphql to point at a local or staging indexer.

CI: .github/workflows/smoke.yml runs pnpm update-registry (mainnet generated/ is not committed), sets ERPC_BASE_URL / ERPC_API_KEY from GitHub secrets, then runs smokes against EU and US staging. Results are posted as a single PR comment on release-please PRs (updated in place on each run). Add Smoke tests / Release PR staging (eu) and (us) as required checks before merging a release.

Deployment environments

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.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT License

About

Ponder based indexing logic

Resources

License

Stars

4 stars

Watchers

4 watching

Forks

Packages

 
 
 

Contributors