Ponder Indexer Setup
🏠 Home > Indexer > Ponder Setup
Aurellion uses Ponder as its event indexer. Ponder listens to all Diamond contract events, stores them in PostgreSQL, and exposes a GraphQL API that the frontend queries instead of hitting the blockchain directly.
Why Ponder?
| Feature | The Graph | Ponder |
|---|---|---|
| Language | AssemblyScript | TypeScript |
| Local dev | Difficult | First-class support |
| Debugging | Standard Node.js tools | Standard Node.js tools |
| Type safety | Limited | Full TypeScript types |
| Custom API | Schema only | Full HTTP handler support |
Ponder gives Aurellion full TypeScript across the entire stack — same language as the frontend and domain layer.
Indexer Structure
indexer/
├── abis/ ← Diamond ABI fragments
├── src/
│ └── handlers/ ← Event handler functions
│ ├── index.ts ← Handler registry
│ └── *.ts ← Per-domain handlers
├── test/
│ └── smoke/ ← Vitest smoke tests
├── generated-schema.ts ← Auto-generated (DO NOT EDIT)
├── ponder.config.ts ← Main Ponder configuration
├── ponder.config.generated.ts ← Auto-generated config
├── ponder-env.d.ts ← TypeScript env types
├── Dockerfile ← Multi-stage bun build
├── docker-compose.yml ← Local dev (Postgres + pgAdmin)
├── docker-compose.prod.yml ← Production (Postgres + Indexer + Traefik)
├── healthcheck.sh ← Docker HEALTHCHECK script
└── package.json ← Indexer-specific dependenciesRuntime Stack
| Component | Technology | Why |
|---|---|---|
| Runtime | Bun (oven/bun:1) |
Fast JS/TS runtime, built-in bundler |
| Package manager | bun | Monorepo root uses bun@1.2.8 |
| Containers | Docker Compose | Both dev and production |
| Database | PostgreSQL 16 Alpine | Ponder persistence layer |
| Reverse proxy (prod) | Traefik | SSL termination, routing |
| Registry | GHCR (ghcr.io/aurellionlabs/web/indexer) |
GitHub Container Registry |
| CI/CD | GitHub Actions | Build → push → SSH deploy |
Configuration (ponder.config.ts)
import { createConfig } from '@ponder/core';
import { http } from 'viem';
export default createConfig({
networks: {
baseSepolia: {
chainId: 84532,
transport: http(process.env.NEXT_PUBLIC_RPC_URL_84532),
},
},
contracts: {
Diamond: {
network: 'baseSepolia',
abi: diamondAbi,
address: process.env.NEXT_PUBLIC_DIAMOND_ADDRESS,
startBlock: DIAMOND_DEPLOY_BLOCK, // 36030424
},
},
});The auto-generated config (ponder.config.generated.ts) is produced by:
bun scripts/generate-indexer.tsand includes all facet ABIs concatenated.
Schema Architecture
Current: Dumb Indexer Pattern
Raw events stored as-is. No aggregation in the indexer — the repository layer handles state derivation:
// generated-schema.ts
export const mintedAssetEvents = onchainTable('mintedAssetEvents', (t) => ({
id: t.text().primaryKey(),
account: t.hex(),
hash: t.hex(),
tokenId: t.bigint(),
name: t.text(),
assetClass: t.text(),
className: t.text(),
block_timestamp: t.bigint(),
transaction_hash: t.hex(),
}));Table name = camelCase event name (Ponder pluralises it, creating the double ss in queries):
MintedAssetevent →mintedAssetEventstable →mintedAssetEventssin GraphQL
Indexed Events
Asset Events
| Event | Table |
|---|---|
MintedAsset |
mintedAssetEventss |
CustodyEstablished |
custodyEstablishedEventss |
SupportedClassAdded |
supportedClassAddedEventss |
Node Events
| Event | Table |
|---|---|
NodeRegistered |
nodeRegisteredEventss |
SupportedAssetAdded |
supportedAssetAddedEventss |
NodeCapacityUpdated |
nodeCapacityUpdatedEventss |
CLOB Events
| Event | Table |
|---|---|
RouterOrderPlaced |
routerOrderPlacedEventss |
TradeExecuted |
tradeExecutedEventss |
MarketDepthChanged |
marketDepthChangedEventss |
PoolCreated |
poolCreatedEventss |
LiquidityAdded |
liquidityAddedEventss |
Bridge / Logistics Events
| Event | Table |
|---|---|
UnifiedOrderCreated |
unifiedOrderCreatedEventss |
LogisticsOrderCreated |
logisticsOrderCreatedEventss |
JourneyStatusUpdated |
journeyStatusUpdatedEventss |
OrderSettled |
orderSettledEventss |
AuSys Events
| Event | Table |
|---|---|
AuSysOrderCreated |
auSysOrderCreatedEventss |
JourneyCreated |
journeyCreatedEventss |
DriverAssigned |
driverAssignedEventss |
EmitSig |
emitSigEventss |
AuSysJourneyStatusUpdated |
auSysJourneyStatusUpdatedEventss |
P2POfferCreated |
p2POfferCreatedEventss |
RWY Events
| Event | Table |
|---|---|
OpportunityCreated |
opportunityCreatedEventss |
CommodityStaked |
commodityStakedEventss |
ProfitDistributed |
profitDistributedEventss |
Running Locally
cd indexer
# 1. Start PostgreSQL (and pgAdmin on port 5050)
docker compose up -d
# Postgres available at:
# host: localhost:5432
# user: postgres
# password: aurellion_secure_2026
# database: ponder_indexer
# 2. Install indexer dependencies
bun install
# 3. Regenerate schema if contracts changed
bun run generate:indexer # or: cd .. && bun scripts/generate-indexer.ts
# 4. Start Ponder dev server (hot reload)
ponder dev
# GraphQL playground: http://localhost:42069/graphql
# REST endpoint: http://localhost:42069Running Tests
cd indexer
# Run Vitest smoke tests
vitest run
# Watch mode
vitestSmoke tests gracefully skip if the indexer isn't running — they're designed to be run after ponder dev is up.
Production Architecture
GitHub Actions (CI)
1. bun install
2. bunx hardhat compile
3. bun scripts/gen-all.ts
4. docker build → push → ghcr.io/aurellionlabs/web/indexer:<branch>
GitHub Actions (Deploy) via SSH to /srv/Web
1. git pull origin <branch>
2. docker compose -f docker-compose.prod.yml pull indexer
3. docker compose stop indexer
4. DROP/recreate public schema in Postgres
5. docker compose up -d indexer
6. Health check loop (90s timeout, HTTP on :42069)
7. Auto-rollback to previous image on failureDocker Setup
Dockerfile (Multi-stage Bun Build)
# Stage 1: Build — compiles contracts, generates ABIs
FROM oven/bun:1 AS builder
WORKDIR /app
# Copies monorepo root + indexer/, compiles contracts via hardhat
RUN bunx --bun hardhat compile
RUN bun scripts/gen-all.ts
# Stage 2: Runner — minimal Alpine image
FROM oven/bun:1-alpine AS runner
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 ponder
# Non-root user: ponder
EXPOSE 42069
CMD ["bun", "run", "--cwd", "indexer", "start"]Production Compose Services
| Service | Image | Port | Notes |
|---|---|---|---|
postgres |
postgres:16-alpine |
5432 | Persistent volume postgres_data |
indexer |
ghcr.io/aurellionlabs/web/indexer:<tag> |
42069 | Depends on postgres healthy |
traefik |
traefik:v3.0 |
80, 443 | Optional, profile with-traefik |
Health Check
Docker uses healthcheck.sh as its HEALTHCHECK directive (interval 30s, timeout 15s, 5 retries). The script hits http://localhost:42069/ and validates a 200 response.
# Check container health from host
docker compose -f docker-compose.prod.yml ps
# View logs
docker compose -f docker-compose.prod.yml logs -f indexer
# Manual health check
docker exec aurellion_indexer curl -sf http://localhost:42069/Important: Schema Reset on Every Deploy
The CI deploy script drops and recreates the PostgreSQL public schema on every deployment:
docker compose exec -T postgres psql -U postgres -d ponder_indexer \
-c "DROP SCHEMA IF EXISTS public CASCADE; CREATE SCHEMA public;"Why: Ponder stores a hash of its config/schema. If it detects a mismatch with the existing database (e.g., new events added), it refuses to start. Resetting the schema forces a clean re-sync from startBlock.
Implication: Every production deployment re-indexes from block 36030424. On Base Sepolia this takes a few minutes depending on event volume and RPC throughput.
Resetting Locally
# Full reset: stop containers, remove volumes, restart
docker compose down -v
docker compose up -d
ponder dev # Re-syncs from startBlockEnvironment Variables
| Variable | Required | Description |
|---|---|---|
NEXT_PUBLIC_RPC_URL_84532 |
✅ | Base Sepolia RPC URL |
POSTGRES_PASSWORD |
✅ (prod) | Postgres password |
DATABASE_URL |
✅ | Full postgres connection string |
DATABASE_SCHEMA |
Optional | Default: public |
PONDER_NETWORK |
Optional | Default: baseSepolia |
PONDER_LOG_LEVEL |
Optional | Default: info |
NEXT_PUBLIC_DIAMOND_ADDRESS |
✅ | Diamond proxy address |
Schema Generation
# From monorepo root:
bun scripts/generate-indexer.ts
# Or via npm script alias (calls the same thing):
bun run generate:indexerNever manually edit generated-schema.ts — overwritten on next generation run.