Building a Production Custom Blockchain Indexer with Ponder
An advanced guide to building a production-grade custom blockchain indexer using Ponder — schema design, handler patterns, deployment, and operational concerns.
Subgraphs solve 80% of indexing problems well. The remaining 20% — multi-contract state reconstruction, complex business logic in handlers, data shapes that subgraph schema cannot cleanly express, or requirements for custom API logic — is where teams reach for a custom indexer. Ponder is currently the most mature TypeScript-native framework for this work.
This is an advanced guide for teams that have already evaluated hosted subgraphs and decided they need more control.
When a Custom Indexer Is the Right Call
Before investing in a custom indexer, confirm that subgraphs genuinely cannot meet your requirements. The most common legitimate reasons to build custom:
- Your handler logic requires conditional branching, external lookups, or business logic that is difficult to express in AssemblyScript
- You need to expose a custom REST or WebSocket API alongside GraphQL
- Your data model requires joins or aggregations that subgraph GraphQL does not support efficiently
- You are indexing a chain or contract type not well-supported by The Graph
- You need to combine on-chain data with off-chain data sources in the same query layer
If your reason is "we want TypeScript instead of AssemblyScript," Envio is a managed alternative worth evaluating first — it gives you TypeScript handlers with managed infrastructure.
What Ponder Is
Ponder is an open-source framework for building custom blockchain indexers in TypeScript. You define your schema using Ponder's schema DSL, write event handlers in TypeScript, and run the indexer locally or in a production environment. Ponder handles:
- Block ingestion from your RPC endpoints
- Event decoding using viem and your contract ABIs
- Database writes via Drizzle ORM to a SQLite (local) or Postgres (production) backend
- A generated GraphQL API for the entities in your schema
- Reorg detection and rollback
What Ponder does not handle: running the indexer in a managed cloud environment, scaling the sync horizontally across multiple workers, or alerting on data drift. That is operational work you own.
Building a Custom Blockchain Indexer: Schema Design in Ponder
Ponder's schema is defined in ponder.schema.ts using a TypeScript DSL:
import { createSchema } from "@ponder/core";
export default createSchema((p) => ({
Pool: p.createTable({
id: p.string(),
token0: p.string(),
token1: p.string(),
totalVolumeUSD: p.float(),
swapCount: p.int(),
createdAt: p.int(),
}),
Swap: p.createTable({
id: p.string(),
poolId: p.string().references("Pool.id"),
amountInUSD: p.float(),
amountOutUSD: p.float(),
timestamp: p.int(),
blockNumber: p.int(),
}),
}));The generated Drizzle schema maps directly to Postgres tables. This means you can query your indexed data with raw SQL or any Postgres-compatible analytics tool alongside the generated GraphQL endpoint.
Handler Patterns in Ponder
Handlers in Ponder are TypeScript functions. The framework provides typed event objects based on your contract ABIs:
import { ponder } from "@/generated";
ponder.on("UniswapV3Pool:Swap", async ({ event, context }) => {
const { Pool, Swap } = context.db;
const pool = await Pool.findUnique({ id: event.log.address });
if (!pool) return;
await Pool.update({
id: event.log.address,
data: {
swapCount: pool.swapCount + 1,
totalVolumeUSD: pool.totalVolumeUSD + calculateUSD(event.args),
},
});
await Swap.create({
id: `${event.transaction.hash}-${event.log.logIndex}`,
data: {
poolId: event.log.address,
amountInUSD: calculateUSD(event.args),
amountOutUSD: calculateUSD(event.args),
timestamp: Number(event.block.timestamp),
blockNumber: Number(event.block.number),
},
});
});Because handlers are plain TypeScript, you can import utility libraries, call your own modules, and write conditional logic without the constraints of AssemblyScript. This is the primary advantage over subgraphs.
Handling Invalid RPC Data
One operational risk with custom indexers: RPC providers occasionally return malformed or invalid responses — incorrect transaction receipts, missing logs, or inconsistent block data. Unlike a managed subgraph platform that may absorb these errors silently, a custom indexer will crash or write corrupt state if you do not validate inputs.
Defensive handler patterns:
- Validate that numeric values are within expected bounds before writing to the database
- Wrap handler logic in try-catch and log errors with full context (block number, transaction hash, event name) for investigation
- For critical numeric operations, check for negative values, zero divisors, and overflow conditions explicitly
Deployment Architecture
Ponder runs as a long-lived Node.js process. For production deployment:
Compute: A single Ponder process on a dedicated VPS or container is sufficient for most protocols. AWS EC2 t3.medium or c5.large covers the majority of cases. CPU and memory requirements scale with event volume per block.
Database: Use a managed Postgres instance. PlanetScale, Supabase, Neon, or AWS RDS all work. Ponder's Drizzle layer handles the schema migration. Enable connection pooling (PgBouncer or equivalent) if you have many concurrent readers hitting the database.
Restart strategy: Ponder checkpoints its sync progress. On restart, it resumes from the last indexed block. Use a process supervisor (PM2, systemd, or a container orchestrator) to auto-restart on crash.
Monitoring: Export handler error rates and sync lag (current block minus indexed block) to your observability stack. Alert when sync lag exceeds two minutes for a live chain, or when handler errors spike.
Reorg Handling Verification
Ponder's reorg handling is built in, but verify it explicitly before launch. Run your indexer against a testnet or local Anvil fork, manually cause a reorg by mining competing blocks, and confirm that entity state rolls back correctly. The specific behavior to verify: an entity updated in block N should revert to its pre-block-N value after a reorg that removes block N.
When to Switch Away from Ponder
Ponder is the right tool when you need custom handler logic and are willing to own the operational surface. You may outgrow it when:
- Your event volume is high enough that a single Node.js process cannot keep up with chain tip
- You need horizontal scale across multiple indexer workers
- Your team does not have the capacity to maintain the deployment and monitoring
At that point, evaluate whether a managed service with custom handler support (such as Goldsky Mirror feeding into your own database) meets the requirement with lower operational overhead.
Custom blockchain indexers give you complete control over your data layer at the cost of ownership. If your team is evaluating whether to build custom or use a managed service — or needs help designing and deploying a production Ponder setup — Clixo builds production indexing infrastructure for protocols at scale.