CtrlK
BlogDocsLog inGet started
Tessl Logo

hefgi/ponder

Build EVM blockchain data indexers using Ponder (ponder.sh) - an open-source TypeScript framework for indexing smart contract events, transactions, and traces into custom database schemas with type-safe APIs. Use when the user mentions ponder, blockchain/EVM indexing, onchain data pipelines, subgraph replacement, or wants to index smart contract events into a queryable database.

98

1.25x
Quality

99%

Does it follow best practices?

Impact

98%

1.25x

Average score across 5 eval scenarios

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

production.mdreferences/

Ponder Production Reference

Table of Contents

  • Postgres Requirements
  • Database Configuration
  • Schema Isolation
  • Zero-Downtime Deployments
  • Health Checks
  • Crash Recovery
  • Scaling
  • Resource Configuration
  • RPC Requirements
  • Monitoring
  • Debugging Checklist
  • Migration Notes

Postgres Requirements

  • Version: 14, 15, 16, or 17
  • Latency: <50ms from application server (same private network recommended)
  • Connection limit: at least 30 connections available for Ponder

Database Configuration

// ponder.config.ts
export default createConfig({
  database: {
    kind: "postgres",
    connectionString: process.env.DATABASE_URL,
    poolConfig: {
      max: 30,
      ssl: { rejectUnauthorized: false }, // If using SSL
    },
  },
  // ...
});

Or rely on the DATABASE_URL environment variable (auto-detected when set).

Schema Isolation

Production requires schema isolation to prevent conflicts between deployments:

# Via CLI flag:
ponder start --schema my_app_v1

# Or via environment variable:
DATABASE_SCHEMA=my_app_v1 ponder start

Each schema name creates isolated Postgres schemas. Multiple instances can share the same database with different schema names.

Managing Schemas

# List all Ponder schemas in the database:
ponder db list

# Remove unused schemas:
ponder db prune

Zero-Downtime Deployments

Use the views pattern to swap between schema versions without downtime:

# Deploy new version:
ponder start --schema my_app_v2 --views-schema my_app_views

# The --views-schema creates Postgres views that point to the active schema.
# When v2 finishes backfill, the views automatically switch from v1 to v2.
# Your API (ponder serve) reads from the views schema, so it serves v1 data
# until v2 is ready, then seamlessly serves v2 data.

Steps for Zero-Downtime Deploy

  1. API servers: ponder serve --schema my_app_views (reads from views)
  2. Current indexer: ponder start --schema my_app_v1 --views-schema my_app_views
  3. Deploy new indexer: ponder start --schema my_app_v2 --views-schema my_app_views
  4. When v2 catches up, views switch. Stop v1.

Health Checks

EndpointBehaviorUse For
/healthAlways returns HTTP 200Liveness probes (is the process alive?)
/readyReturns 503 during backfill, 200 when caught upReadiness probes (is the API serving current data?)
/statusReturns JSON with indexing progress per chainMonitoring dashboards

/status Response Example

{
  "mainnet": {
    "ready": false,
    "block": {
      "current": 18500000,
      "target": 19000000
    },
    "progress": 0.974
  }
}

Crash Recovery

When Ponder restarts with the same schema name, it automatically resumes from the last checkpoint:

  • No data loss between checkpoints
  • No re-processing of already-indexed events
  • Checkpoint frequency is automatic (approximately every few seconds)

This means ponder start --schema X is safe to run with auto-restart supervisors (systemd, Docker restart policies, Kubernetes, Railway, etc.).

Scaling

Architecture

  • One indexer: ponder start --schema X (writes data, runs indexing functions)
  • N API replicas: ponder serve --schema X (read-only API, no indexing)

ponder serve is stateless and horizontally scalable. It reads from the same Postgres database that the indexer writes to.

# Indexer (single instance):
ponder start --schema my_app

# API replicas (scale horizontally):
ponder serve --schema my_app

With Views Pattern

# Indexer:
ponder start --schema my_app_v1 --views-schema my_app_views

# API replicas (point to views):
ponder serve --schema my_app_views

Resource Configuration

Memory

For large indexing jobs, increase Node.js memory:

NODE_OPTIONS="--max-old-space-size=8192" ponder start --schema my_app

CPU

  • multichain and omnichain ordering: single-threaded indexing
  • experimental_isolated ordering: uses up to 4 cores (one per chain, parallelized)

RPC Requirements

Node Type

  • Archive node: Required for historical data (reading state at past blocks). Most indexing requires this.
  • Full node: Only sufficient if your startBlock is recent and you don't use readContract.

Required Methods

MethodRequired For
eth_getLogsAll indexing (core requirement)
eth_getBlockByNumberAll indexing (core requirement)
eth_callreadContract in handlers
debug_traceBlockByNumberincludeCallTraces: true

Rate Limits

  • Recommended: 50-100 requests/second
  • Ponder automatically manages rate limiting and retries
  • Providers: Alchemy, QuickNode, Infura, Ankr, dRPC

eth_getLogs Block Range

Different providers have different max block ranges for eth_getLogs. Ponder auto-detects this, but you can override:

chains: {
  mainnet: {
    id: 1,
    rpc: process.env.PONDER_RPC_URL_1,
    ethGetLogsBlockRange: 2000, // Override if auto-detection is wrong
  },
}

Load Balancing / Fallback

import { http, fallback, loadBalance } from "viem";

chains: {
  mainnet: {
    id: 1,
    // Fallback: tries each in order, moves to next on failure
    rpc: fallback([
      http(process.env.PONDER_RPC_URL_1_PRIMARY),
      http(process.env.PONDER_RPC_URL_1_FALLBACK),
    ]),
  },
  base: {
    id: 8453,
    // Load balance: distributes requests across endpoints
    rpc: loadBalance([
      http(process.env.PONDER_RPC_URL_8453_A),
      http(process.env.PONDER_RPC_URL_8453_B),
    ]),
  },
}

Or simply use an array of URLs (automatic fallback):

chains: {
  mainnet: {
    id: 1,
    rpc: [
      process.env.PONDER_RPC_URL_1_PRIMARY,
      process.env.PONDER_RPC_URL_1_FALLBACK,
    ],
  },
}

Monitoring

Prometheus Metrics

Available at /metrics in Prometheus exposition format:

curl http://localhost:42069/metrics

Key metrics:

  • ponder_indexing_completed_events - Total events processed
  • ponder_indexing_completed_seconds - Time spent in handlers
  • ponder_historical_total_blocks - Total blocks to process
  • ponder_historical_completed_blocks - Blocks processed so far

Logging

# Log levels: silent, error, warn, info, debug, trace
ponder start --schema my_app --log-level debug

# JSON format (for log aggregation):
ponder start --schema my_app --log-format json

Use --log-level trace for maximum detail when debugging RPC or database issues.

Debugging Checklist

Slow Indexing

  1. Check startBlock: Should be the contract deployment block, not 0
  2. RPC rate limits: Check provider dashboard. Add fallback URLs.
  3. Complex handlers: Profile readContract calls. Use cache: "immutable" for static values.
  4. Database latency: Postgres should be <50ms from app. Check poolConfig.max.
  5. Ordering mode: Switch to experimental_isolated for max throughput if cross-chain ordering isn't needed.

RPC Errors

  1. "429 Too Many Requests": Lower request rate or upgrade provider plan. Add fallback URLs.
  2. "missing trie node": Need an archive node. Full nodes don't have historical state.
  3. Timeout errors: Add fallback URLs. Check provider status page.
  4. "block range too large": Set ethGetLogsBlockRange to a smaller value.

Database Errors

  1. Connection refused: Check DATABASE_URL and network connectivity
  2. Too many connections: Increase Postgres max_connections or reduce poolConfig.max
  3. Schema already exists: Another instance is using this schema name. Use a different name or stop the other instance.
  4. Slow queries: Add indexes on columns used in WHERE clauses and GraphQL filters

Fatal Exit (Code 1)

  1. Check logs for the error message
  2. Common causes: constraint violations, invalid ABI, missing ponder-env.d.ts
  3. Fix the issue and restart

Retryable Exit (Code 75)

  1. Transient error (RPC timeout, DB connection drop)
  2. Safe to auto-restart. Ponder resumes from checkpoint.
  3. If persistent, check RPC and DB connectivity.

Migration Notes

Key breaking changes across Ponder versions:

VersionChange
0.8Package renamed from @ponder/core to ponder
0.9API file (src/api/index.ts) required. Must export default Hono app.
0.11Config: networks renamed to chains. transport renamed to rpc.
0.12All addresses normalized to lowercase. Remove checksums everywhere.
0.16Table/schema names limited to 45 characters.

SKILL.md

tile.json