# Table of Contents - [Telemetry – Ponder](#telemetry-ponder) - [Get started – Ponder](#get-started-ponder) - [Why Ponder](#why-ponder) - [Database – Ponder](#database-ponder) - [Requirements – Ponder](#requirements-ponder) - [Chains – Ponder](#chains-ponder) - [Migration guide – Ponder](#migration-guide-ponder) - [Accounts – Ponder](#accounts-ponder) - [Contracts – Ponder](#contracts-ponder) - [Block intervals – Ponder](#block-intervals-ponder) - [Indexing – Ponder](#indexing-ponder) - [Write to the database – Ponder](#write-to-the-database-ponder) - [Read contract data – Ponder](#read-contract-data-ponder) - [SQL over HTTP – Ponder](#sql-over-http-ponder) - [API endpoints – Ponder](#api-endpoints-ponder) - [GraphQL – Ponder](#graphql-ponder) - [Direct SQL – Ponder](#direct-sql-ponder) - [Deploy on Marble – Ponder](#deploy-on-marble-ponder) - [Deploy on Railway – Ponder](#deploy-on-railway-ponder) - [Self-hosting – Ponder](#self-hosting-ponder) - [Factory pattern – Ponder](#factory-pattern-ponder) - [Isolated indexing – Ponder](#isolated-indexing-ponder) - [Call traces – Ponder](#call-traces-ponder) - [Transaction receipts – Ponder](#transaction-receipts-ponder) - [Offchain data – Ponder](#offchain-data-ponder) - [Foundry – Ponder](#foundry-ponder) - [Time-series data – Ponder](#time-series-data-ponder) - [Custom views – Ponder](#custom-views-ponder) - [Relations – Ponder](#relations-ponder) - [create-ponder – Ponder](#create-ponder-ponder) - [Bun – Ponder](#bun-ponder) - [ponder.config.ts – Ponder](#ponder-config-ts-ponder) - [ponder – Ponder](#ponder-ponder) - [Database reference – Ponder](#database-reference-ponder) - [Tables – Ponder](#tables-ponder) - [ponder.schema.ts – Ponder](#ponder-schema-ts-ponder) - [Indexing functions – Ponder](#indexing-functions-ponder) - [@ponder/client – Ponder](#-ponder-client-ponder) - [API endpoints – Ponder](#api-endpoints-ponder) - [@ponder/react – Ponder](#-ponder-react-ponder) - [@ponder/utils – Ponder](#-ponder-utils-ponder) - [Observability – Ponder](#observability-ponder) --- # Telemetry – Ponder [Skip to content](https://ponder.sh/docs/advanced/telemetry#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Telemetry On this page Chevron Right Ponder collects **completely anonymous** telemetry data about general usage. The developers use this data to prioritize new feature development, identify bugs, and improve performance & stability. Opt out[](https://ponder.sh/docs/advanced/telemetry#opt-out) --------------------------------------------------------------- To opt out of telemetry, set the `PONDER_TELEMETRY_DISABLED` environment variable. File .env.local Copy `PONDER_TELEMETRY_DISABLED = true` Implementation[](https://ponder.sh/docs/advanced/telemetry#implementation) ----------------------------------------------------------------------------- Ponder's telemetry implementation is 100% open-source. The [telemetry service](https://github.com/ponder-sh/ponder/blob/main/packages/core/src/common/telemetry.ts#L47) (part of `ponder`) runs on the user's device and submits event data via HTTP POST requests to the [telemetry collection endpoint](https://github.com/ponder-sh/ponder/blob/main/docs/pages/api/telemetry/index.ts) hosted at `https://ponder.sh/api/telemetry`. The implementation generates a stable anonymous unique identifier for the user's device and stores it at the [system default user config directory](https://github.com/sindresorhus/env-paths#pathsconfig) . This config also stores the user's opt-out preference and a stable salt used to hash potentially sensitive data such as file paths and the git remote URL. --- # Get started – Ponder [Skip to content](https://ponder.sh/docs/get-started#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Get started On this page Chevron Right What is Ponder?[](https://ponder.sh/docs/get-started#what-is-ponder) ----------------------------------------------------------------------- Ponder is an open-source framework for custom Ethereum indexing. You write TypeScript code to transform onchain data into your application's schema. Then, Ponder fetches data from the chain, runs your indexing logic, and writes the result to Postgres. Once indexed, you can query the data through GraphQL, SQL over HTTP, or directly in Postgres. Quickstart[](https://ponder.sh/docs/get-started#quickstart) -------------------------------------------------------------- ### Run `create-ponder`[](https://ponder.sh/docs/get-started#run-create-ponder) The quickest way to create a new Ponder project is `create-ponder`, which sets up everything automatically for you. pnpmyarnnpmbun Terminal pnpm Copy `pnpm create ponder` On installation, you'll see a few prompts. DefaultERC-20 example Default Copy `✔ What's the name of your project? › new-project ✔ Which template would you like to use? › Default ✔ Installed packages with pnpm. ✔ Initialized git repository.` This guide follows the ERC-20 example, which indexes a token contract on Ethereum mainnet. ### Start the dev server[](https://ponder.sh/docs/get-started#start-the-dev-server) After installation, start the local development server. pnpmyarnnpmbun Terminal pnpm Copy `pnpm dev` Ponder will connect to the database, start the HTTP server, and begin indexing. LogsTerminal UI Logs Copy `12:16:42.845 INFO Connected to database type=postgres database=localhost:5432/demo (35ms) 12:16:42.934 INFO Connected to JSON-RPC chain=mainnet hostnames=["eth-mainnet.g.alchemy.com"] (85ms) 12:16:43.199 INFO Created database tables count=4 tables=["account","transfer_event","allowance","approval_event"] (17ms) 12:16:43.324 INFO Created HTTP server port=42069 (5ms) 12:16:43.325 INFO Started returning 200 responses endpoint=/health 12:16:43.553 INFO Started backfill indexing chain=mainnet block_range=[13142655,13150000] 12:16:43.555 INFO Started fetching backfill JSON-RPC data chain=mainnet cached_block=13145448 cache_rate=38.0% 12:16:43.796 INFO Indexed block range chain=mainnet event_count=4259 block_range=[13142655,13145448] (164ms) 12:16:43.840 INFO Indexed block range chain=mainnet event_count=33 block_range=[13145449,13145474] (4ms)` ### Query the database[](https://ponder.sh/docs/get-started#query-the-database) Visit [localhost:42069/graphql](http://localhost:42069/graphql) in your browser to explore the auto-generated GraphQL API. Here's a query for the top accounts by balance, along with the total number of accounts. QueryResult Query Copy `query { accounts(orderBy: "balance", orderDirection: "desc", limit: 2) { items { address balance } totalCount } }` ### Customize the schema[](https://ponder.sh/docs/get-started#customize-the-schema) Let's add a new column to a table in `ponder.schema.ts`. We want to track which accounts are an owner of the token contract. File ponder.schema.ts Copy `import { index, onchainTable, primaryKey, relations } from "ponder"; export const account = onchainTable("account", (t) => ({ address: t.hex().primaryKey(), balance: t.bigint().notNull(), isOwner: t.boolean().notNull(), })); // ...` Immediately, there's a type error in `src/index.ts` and a runtime error in the terminal. We added a required column, but our indexing logic doesn't include it. Terminal Copy `12:16:16 PM ERROR indexing Error while processing 'ERC20:Transfer' event NotNullConstraintError: Column 'account.isOwner' violates not-null constraint. at /workspace/new-project/src/index.ts:10:3 8 | 9 | ponder.on("ERC20:Transfer", async ({ event, context }) => { > 10 | await context.db | ^ 11 | .insert(account) 12 | .values({ address: event.args.from, balance: 0n }) 13 | .onConflictDoUpdate((row) => ({` ### Update indexing logic[](https://ponder.sh/docs/get-started#update-indexing-logic) Update the indexing logic to include `isOwner` when inserting new rows into the `account` table. File src/index.ts Copy `import { ponder } from "ponder:registry"; import { account } from "ponder:schema"; const OWNER_ADDRESS = "0x3bf93770f2d4a794c3d9ebefbaebae2a8f09a5e5"; ponder.on("ERC20:Transfer", async ({ event, context }) => { await context.db .insert(account) .values({ address: event.args.from, balance: 0n, isOwner: event.args.from === OWNER_ADDRESS, }) .onConflictDoUpdate((row) => ({ // ... })` As soon as we save the file, the dev server hot reloads and finishes indexing successfully. LogsTerminal UI Logs Copy `12:19:31.629 INFO Hot reload "src/index.ts" 12:19:31.889 WARN Dropped existing database tables count=4 tables=["account","transfer_event","allowance","approval_event"] (3ms) 12:19:31.901 INFO Created database tables count=4 tables=["account","transfer_event","allowance","approval_event"] (12ms) 12:19:32.168 INFO Started backfill indexing chain=mainnet block_range=[13142655,13150000] 12:19:32.169 INFO Started fetching backfill JSON-RPC data chain=mainnet cached_block=13147325 cache_rate=63.6% 12:19:32.447 INFO Indexed block range chain=mainnet event_count=6004 block_range=[13142655,13146396] (199ms) 12:19:32.551 INFO Indexed block range chain=mainnet event_count=3607 block_range=[13146397,13147325] (104ms)` Next steps[](https://ponder.sh/docs/get-started#next-steps) -------------------------------------------------------------- This quickstart only scratches the surface of what Ponder can do. Take a look at the [examples directory](https://github.com/ponder-sh/ponder/tree/main/examples) for more complex projects, or the [GitHub dependents](https://github.com/ponder-sh/ponder/network/dependents?package_id=UGFja2FnZS0xMzA2OTEyMw%3D%3D) for a list of real-world repositories using Ponder. Or, continue reading the guides and API reference here on the documentation site. * [Contract configuration](https://ponder.sh/docs/config/contracts) * [Query the database directly](https://ponder.sh/docs/query/direct-sql) * [Schema design](https://ponder.sh/docs/schema/tables) --- # Why Ponder [Skip to content](https://ponder.sh/docs/why-ponder#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Why Ponder On this page Chevron Right **Ponder is an open-source framework for custom Ethereum indexing.** With Ponder, you can rapidly build & deploy an API that serves custom data from smart contracts on any EVM blockchain. Most apps need a backend[](https://ponder.sh/docs/why-ponder#most-apps-need-a-backend) ----------------------------------------------------------------------------------------- Many developers have quietly accepted that you need traditional backend web services to build great crypto apps – particularly for indexing. The standard RPC, subgraphs, and out-of-the-box API endpoints are great for getting started, but fall short as applications mature and new requirements appear. When this happens, developers often roll their own web backend, which gives them the flexibility to write custom server-side code. Unfortunately, engineering teams across the industry reinvent the wheel on common problems (reorgs, RPC error handling, etc) in closed-source repositories where best practices remain siloed. We built Ponder because **this is a framework-shaped problem**. Local development[](https://ponder.sh/docs/why-ponder#local-development) --------------------------------------------------------------------------- It’s so hard to run a subgraph locally that devs often deploy to the hosted service just to run their code. This creates long feedback loops and errors that are painful to debug. Ponder is designed from the ground up for local development. The dev server has hot reloading for every file in your project and descriptive error logs that keep you unblocked. ![Ponder dev server hot reloading](https://ponder.sh/hot-reloading.gif) Ponder’s type safety and editor autocomplete is very thorough, and works without any codegen or build step. If your code passes the type checker, it will usually run without error. Focus on web developers[](https://ponder.sh/docs/why-ponder#focus-on-web-developers) --------------------------------------------------------------------------------------- Ponder is built for web & mobile application developers. If you understand the basics of Ethereum and can write some TypeScript, you’ll be productive with Ponder in a matter of minutes. (So, data analysts, protocol developers, and researchers can use Ponder too.) Your code runs in a JavaScript runtime (Node.js) where you can import NPM packages, make HTTP requests, and connect to databases. This makes it easy to do things that are painful in constrained environments like WebAssembly sandboxes, SQL engines, and EVM runtimes. Performance[](https://ponder.sh/docs/why-ponder#performance) --------------------------------------------------------------- Long feedback loops are the kiss of death for developer productivity, and unfortunately they're commonplace in blockchain indexing tools. Ponder is fast and lean. In our initial benchmarks, Ponder indexed an ERC20 contract ~10x faster than the Graph Node from a cold start and 15x faster fully cached. ### Benchmarks 37s 6m 40s Sync (Cold) Sync (Cache) Database Size RPC Credits Ponder 37s 5s 31 MB 108k The Graph 5m 28s 1m 15s 1.1 GB 167k Results of indexing the Rocket Pool ERC20 token contract on mainnet from block 18,600,000 to 18,718,056 (latest) on an M1 MacBook Pro (8 core, 16GB RAM) against an Alchemy node on the Growth plan using a 950MB/s network connection. [Run it yourself →](https://github.com/ponder-sh/ponder/tree/main/benchmark#readme) Ponder also used fewer resources — 35x less disk space and 35% fewer RPC credits. Get started[](https://ponder.sh/docs/why-ponder#get-started) --------------------------------------------------------------- * [Quickstart](https://ponder.sh/docs/get-started) * [GitHub repo ↗](https://github.com/ponder-sh/ponder) * [Telegram chat ↗](https://t.me/pondersh) * [X (Twitter) ↗](https://x.com/ponder_sh) --- # Database – Ponder [Skip to content](https://ponder.sh/docs/database#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Database On this page Chevron Right Ponder supports two database options, [**PGlite**](https://pglite.dev/) and Postgres. * **PGlite**: An embedded Postgres database. PGlite runs in the same Node.js process as Ponder, and stores data in the `.ponder` directory. **Only suitable for local development**. * **PostgreSQL**: A traditional Postgres database server. Required for production, can be used for local development. Choose a database[](https://ponder.sh/docs/database#choose-a-database) ------------------------------------------------------------------------- Ponder uses PGlite by default. To use Postgres, set the `DATABASE_URL` environment variable to a Postgres connection string, or use explicit configuration in `ponder.config.ts`. Copy `import { createConfig } from "ponder"; export default createConfig({ database: { kind: "postgres", connectionString: "postgresql://user:password@localhost:5432/dbname", }, // ... });` [Read more](https://ponder.sh/docs/api-reference/ponder/config#database) about database configuration in the `ponder.config.ts` API reference. Database schema[](https://ponder.sh/docs/database#database-schema) --------------------------------------------------------------------- Ponder uses **database schemas** to organize data. Each instance must use a different schema. Use the `DATABASE_SCHEMA` environment variable or `--schema` CLI option to configure the database schema for an instance. This is where the app will create the tables defined in `ponder.schema.ts`. .env.localCLI Terminal .env.local Copy `DATABASE_SCHEMA=my_schema` [Read more](https://ponder.sh/docs/production/self-hosting#database-schema) about database schema selection in the self-hosting guide. ### Guidelines[](https://ponder.sh/docs/database#guidelines) Here are a few things to keep in mind when choosing a database schema. * No two Ponder instances/deployments can use the same database schema at the same time. * Tables created by `ponder start` are treated as valuable and will never be dropped automatically. * The default schema for `ponder dev` is `public`. There is no default for `ponder start`, you must explicitly set the database schema. * Use `ponder dev` for local development; `ponder start` is intended for production. --- # Requirements – Ponder [Skip to content](https://ponder.sh/docs/requirements#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Requirements On this page Chevron Right The `create-ponder` CLI is the easiest way to [get started](https://ponder.sh/docs/get-started) with Ponder. If it runs without error, your system likely meets the requirements. System requirements[](https://ponder.sh/docs/requirements#system-requirements) --------------------------------------------------------------------------------- * macOS, Linux, or Windows (including WSL). * [Node.js](https://nodejs.org/en) 18.18 or later. * [PostgreSQL](https://www.postgresql.org/download/) version 14, 15, 16 or 17. TypeScript[](https://ponder.sh/docs/requirements#typescript) --------------------------------------------------------------- Ponder uses advanced TypeScript features to offer end-to-end type safety without code generation. We **strongly** recommend taking the time to set up a working TypeScript development environment – it will pay dividends in the long run. ### Requirements[](https://ponder.sh/docs/requirements#requirements-1) * TypeScript `>=5.0.4`, viem `>=2`, and hono `>=4.5` * ABIs must be asserted `as const` following [ABIType guidelines](https://abitype.dev/guide/getting-started#usage) * The `ponder-env.d.ts` file must be present and up to date ### `ponder-env.d.ts`[](https://ponder.sh/docs/requirements#ponder-envdts) This file powers Ponder's zero-codegen type system. It contains a declaration for the `ponder:registry` virtual module which exports types derived from `ponder.config.ts` and `ponder.schema.ts`. After upgrading to a new version of `ponder`, the dev server might make changes to `ponder-env.d.ts`. When this happens, please accept and commit the changes. ### VSCode[](https://ponder.sh/docs/requirements#vscode) By default, VSCode's TypeScript language features use an internal version of TypeScript. Sometimes, this version does not meet Ponder's requirement of `>=5.0.4`. To change VSCode's TypeScript version, run `TypeScript: Select TypeScript version..."` from the command palette and select `Use Workspace Version` or [update VSCode's version](https://stackoverflow.com/questions/39668731/what-typescript-version-is-visual-studio-code-using-how-to-update-it) . --- # Chains – Ponder [Skip to content](https://ponder.sh/docs/config/chains#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Chains On this page Chevron Right Use the `chains` field in `ponder.config.ts` to configure chain IDs and names, RPC endpoints, and connection options. This guide describes each configuration option and suggests patterns for common use cases. Visit the config [API reference](https://ponder.sh/docs/api-reference/ponder/config) for more information. Example[](https://ponder.sh/docs/config/chains#example) ---------------------------------------------------------- This config sets up two chains: Ethereum mainnet and Optimism. File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ chains: { mainnet: { id: 1, rpc: process.env.PONDER_RPC_URL_1, }, optimism: { id: 10, rpc: [ process.env.PONDER_RPC_URL_10, "https://optimism.llamarpc.com", ], }, }, contracts: { /* ... */ }, });` Name[](https://ponder.sh/docs/config/chains#name) ---------------------------------------------------- Each chain must have a unique name, provided as a key to the `chains` object. The contract, account, and block interval `chain` options reference the chain name. Within indexing functions, the `context.chain.name` property contains the chain name of the current event. File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ chains: { mainnet: { id: 1, rpc: process.env.PONDER_RPC_URL_1, }, }, contracts: { Blitmap: { abi: BlitmapAbi, chain: "mainnet", address: "0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63", }, }, });` Chain ID[](https://ponder.sh/docs/config/chains#chain-id) ------------------------------------------------------------ Use the `id` field to specify a unique [Chain ID](https://chainlist.org/) for each chain. Within indexing functions, the `context.chain.id` property contains the chain ID of the current event. The indexing engine uses `id` in the cache key for RPC responses. To avoid cache issues, make sure `id` always matches the chain ID of the configured RPC endpoint. File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ chains: { mainnet: { id: 1, rpc: "https://eth.llamarpc.com", }, }, contracts: { /* ... */ }, });` RPC endpoints[](https://ponder.sh/docs/config/chains#rpc-endpoints) ---------------------------------------------------------------------- Use the `rpc` field to provide one or more RPC endpoints for each chain. Ponder dynamically adapts to provider rate limits to avoid 429 errors and maximize performance. Providing multiple endpoints enables intelligent load balancing and fallback logic to improve reliability. File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ chains: { mainnet: { id: 1, rpc: "https://eth-mainnet.g.alchemy.com/v2/...", }, }, contracts: { /* ... */ }, });` ### Custom transport[](https://ponder.sh/docs/config/chains#custom-transport) The `rpc` field also accepts a [Viem Transport](https://viem.sh/docs/clients/intro#transports) , which can be useful if you need more granular control over how RPC requests are made. File ponder.config.ts Copy `import { createConfig } from "ponder"; import { http, fallback } from "viem"; export default createConfig({ chains: { mainnet: { id: 1, rpc: fallback([ http("https://eth-mainnet.g.alchemy.com/v2/..."), http("https://quaint-large-card.quiknode.pro/..."), ]), }, }, });` Here are a few common transport options. * [`http`](https://viem.sh/docs/clients/transports/http) * [`webSocket`](https://viem.sh/docs/clients/transports/websocket) * [`fallback`](https://viem.sh/docs/clients/transports/fallback) * [`loadBalance`](https://ponder.sh/docs/api-reference/ponder-utils#loadbalance) * [`rateLimit`](https://ponder.sh/docs/api-reference/ponder-utils#ratelimit) WebSocket[](https://ponder.sh/docs/config/chains#websocket) -------------------------------------------------------------- Use the optional `ws` field to specify a WebSocket RPC endpoint for each chain. When provided, Ponder will use WebSocket connections for realtime block subscriptions instead of polling. Websocket connections typically offer lower latency and reduced RPC usage. File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ chains: { mainnet: { id: 1, rpc: "https://eth-mainnet.g.alchemy.com/v2/...", ws: "wss://eth-mainnet.g.alchemy.com/v2/...", }, }, contracts: { /* ... */ }, });` Polling interval[](https://ponder.sh/docs/config/chains#polling-interval) ---------------------------------------------------------------------------- The `pollingInterval` option controls how frequently (in milliseconds) the indexing engine checks for a new block in realtime. The default is `1000` (1 second). If you set `pollingInterval` greater than the chain's block time, it **does not reduce RPC usage**. The indexing engine still fetches every block to check for reorgs. The default is suitable for most chains. File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ chains: { mainnet: { id: 1, rpc: process.env.PONDER_RPC_URL_1, pollingInterval: 2_000, // 2 seconds }, }, });` Disable caching[](https://ponder.sh/docs/config/chains#disable-caching) -------------------------------------------------------------------------- Use the `disableCache` option to disable caching for RPC responses. The default is `false`. Set this option to `true` when indexing a development node like Anvil, where the chain state / history may change. [Read more](https://ponder.sh/docs/guides/foundry) about indexing Anvil. File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ chains: { anvil: { id: 31337, rpc: "http://127.0.0.1:8545", disableCache: true, }, }, });` `eth_getLogs` block range[](https://ponder.sh/docs/config/chains#eth_getlogs-block-range) -------------------------------------------------------------------------------------------- Ponder does its best to automatically determine the maximum block range for JSON-RPC requests with the `eth_getLogs` method. When this comes up short, you can set a manual limit with `ethGetLogsBlockRange` option. File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ chains: { mainnet: { id: 1, rpc: "https://eth-mainnet.g.alchemy.com/v2/...", ethGetLogsBlockRange: 1000, }, }, });` --- # Migration guide – Ponder [Skip to content](https://ponder.sh/docs/migration-guide#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Migration guide On this page Chevron Right 0.16[](https://ponder.sh/docs/migration-guide#016) ----------------------------------------------------- ### Breaking changes[](https://ponder.sh/docs/migration-guide#breaking-changes) #### Table and schema name length limits[](https://ponder.sh/docs/migration-guide#table-and-schema-name-length-limits) The `onchainTable` name, `--schema` CLI option, and `--views-schema` CLI option now enforce a maximum length of 45 characters. This eliminates an issue where names could conflict undetected and cause undefined behavior. Other values like column names, `onchainView` name, and index names are unaffected and will continue to use the default Postgres behavior (maximum of 63 characters with truncation). #### Removed Etherscan and subgraph templates[](https://ponder.sh/docs/migration-guide#removed-etherscan-and-subgraph-templates) The `create-ponder` CLI tool no longer supports bootstrapping a project from an Etherscan or subgraph link. Both options have been broken for some time due to breaking changes to upstream APIs. ### New features[](https://ponder.sh/docs/migration-guide#new-features) #### Bun support[](https://ponder.sh/docs/migration-guide#bun-support) Ponder now officially supports the [Bun](https://bun.sh/) runtime. [Read more](https://ponder.sh/docs/guides/bun) in the new guide. #### Live indexing performance improvement[](https://ponder.sh/docs/migration-guide#live-indexing-performance-improvement) During live indexing, the Store API now uses the same in-memory database as it uses during backfill indexing. This dramatically improves live indexing performance for high-throughput projects (~50+ database operations per second). If your project was struggling to keep up with tip during live indexing, this release may solve your problem. #### Full node-postgres `poolConfig` support[](https://ponder.sh/docs/migration-guide#full-node-postgres-poolconfig-support) Ponder now supports the full range of [node-postgres](https://node-postgres.com/apis/pool) `poolConfig` options. File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ database: { kind: "postgres", poolConfig: { // ... config options }, }, });` 0.15[](https://ponder.sh/docs/migration-guide#015) ----------------------------------------------------- ### Breaking changes[](https://ponder.sh/docs/migration-guide#breaking-changes-1) None. ### New features[](https://ponder.sh/docs/migration-guide#new-features-1) #### `ordering: "experimental_isolated"`[](https://ponder.sh/docs/migration-guide#ordering-experimental_isolated) A new ordering mode, `experimental_isolated`, is available. This mode isolates each chain, requiring each table in the schema to include a `chain_id` column in the primary key. It also takes advantage of mulitple CPU cores for better performance. Visit the [documentation](https://ponder.sh/docs/api-reference/ponder/config#ordering) for details. #### Improved SQL over HTTP scalability[](https://ponder.sh/docs/migration-guide#improved-sql-over-http-scalability) SQL over HTTP queries are cached and selectively invalidated only when a table referenced by the query is updated. Live queries are now guaranteed to be updated _only when the query result changes_. 0.14[](https://ponder.sh/docs/migration-guide#014) ----------------------------------------------------- ### Breaking changes[](https://ponder.sh/docs/migration-guide#breaking-changes-2) #### Metrics updates[](https://ponder.sh/docs/migration-guide#metrics-updates) * Removed the `ponder_historical_duration`, `ponder_indexing_has_error`, and `ponder_http_server_port` metrics. * Added a `chain` label to `ponder_historical_start_timestamp_seconds` and `ponder_historical_end_timestamp_seconds`. * Updated histogram bucket limits. ### New features[](https://ponder.sh/docs/migration-guide#new-features-2) #### Log output improvements[](https://ponder.sh/docs/migration-guide#log-output-improvements) Ponder now emits a more useful set of logs. These changes improve signal-to-noise and aim to eliminate scenarios where Ponder appears to hang without printing any logs. Highlights: * Pretty logs (the default) now use millisecond precision for timestamps, no longer include a "service" column, and use [logfmt](https://brandur.org/logfmt) formatting for extra properties. * JSON-formatted logs (`--log-format json` CLI option) now include a wider range of properties, e.g. `duration`, `block_range`, `chain_id`, and so on. The standard `service` property was removed. ![New log output screenshot](https://ponder.sh/logs-014.png) #### GraphQL offset pagination[](https://ponder.sh/docs/migration-guide#graphql-offset-pagination) The GraphQL now supports `offset` pagination for each plural query field and `many()` relationship field. [Read more](https://ponder.sh/docs/query/graphql#pagination) in the GraphQL pagination docs. #### Custom database views[](https://ponder.sh/docs/migration-guide#custom-database-views) Ponder now supports custom database views in `ponder.schema.ts` that reference other tables or views in your schema. Custom views are defined using the Drizzle query builder API. We expect this feature to be particularly useful for users who want custom query-time transformation logic but still prefer GraphQL (vs. SQL-over-HTTP or direct SQL). [Read more](https://ponder.sh/docs/schema/views) in the custom view guide. 0.13[](https://ponder.sh/docs/migration-guide#013) ----------------------------------------------------- ### Breaking changes[](https://ponder.sh/docs/migration-guide#breaking-changes-3) None. ### New features[](https://ponder.sh/docs/migration-guide#new-features-3) #### Performance[](https://ponder.sh/docs/migration-guide#performance) Ponder now queries less data from the database when reindexing against a full RPC cache. This can eliminate a significant amount of unnecessary work for apps with a large number of events where the indexing logic only accesses a few properties on the `event` object. 0.12[](https://ponder.sh/docs/migration-guide#012) ----------------------------------------------------- ### Breaking changes[](https://ponder.sh/docs/migration-guide#breaking-changes-4) #### Lowercase addresses[](https://ponder.sh/docs/migration-guide#lowercase-addresses) Address values on the `event` object are now always **lowercase**. Before, these values were always checksum encoded. This includes decoded event and trace arguments (e.g. `event.args.sender`) and these standard properties of the `event` object: * `event.block.miner` * `event.log.address` * `event.transaction.to` * `event.transaction.from` * `event.transactionReceipt.from` * `event.transactionReceipt.to` * `event.transactionReceipt.contractAddress` * `event.trace.from` * `event.trace.to` ### New features[](https://ponder.sh/docs/migration-guide#new-features-4) #### Exit code 75[](https://ponder.sh/docs/migration-guide#exit-code-75) Ponder now exits with code 75 when the instance encounters a retryable error. This includes most RPC errors and database connection issues. Exit code 1 now indicates a fatal error that is unlikely to resolve after a restart. This includes logical indexing errors (e.g. unique constraint violations). 0.11[](https://ponder.sh/docs/migration-guide#011) ----------------------------------------------------- ### Breaking changes[](https://ponder.sh/docs/migration-guide#breaking-changes-5) #### Renamed `networks` → `chains`[](https://ponder.sh/docs/migration-guide#renamed-networks--chains) The `networks` field in `ponder.config.ts` was renamed and redesigned. * `networks` → `chains` * `chainId` → `id` * `transport` → `rpc` The new `rpc` field accepts one or more RPC endpoints directly, or a Viem Transport for backwards compatibility. When multiple RPC URLS are provided, Ponder load balances across them. File ponder.config.ts Copy `import { createConfig } from "ponder"; import { http } from "viem"; export default createConfig({ networks: { mainnet: { chainId: 1, transport: http("https://eth-mainnet.g.alchemy.com/v2/your-api-key"), }, }, chains: { mainnet: { id: 1, rpc: "https://eth-mainnet.g.alchemy.com/v2/your-api-key", }, }, contracts: { Erc20: { network: "mainnet", chain: "mainnet", // ... } } });` #### Renamed `context.network` → `context.chain`[](https://ponder.sh/docs/migration-guide#renamed-contextnetwork--contextchain) The indexing function context object `context.network` was renamed to `context.chain`. #### Renamed API functions → API endpoints[](https://ponder.sh/docs/migration-guide#renamed-api-functions--api-endpoints) **API functions** were renamed to **API endpoints** throughout the documentation. #### `publicClients` now keyed by chain name[](https://ponder.sh/docs/migration-guide#publicclients-now-keyed-by-chain-name) The [`publicClients`](https://ponder.sh/docs/query/api-endpoints#rpc-requests) object (available in API endpoints) is now keyed by chain name, not chain ID. #### `/status` response type[](https://ponder.sh/docs/migration-guide#status-response-type) The response type for the `/status` endpoint and related functions from `@ponder/client` and `@ponder/react` has changed. Copy `type Status = { [chainName: string]: { ready: boolean; id: number; block: { number: number; timestamp: number }; }; };` #### Default `multichain` ordering[](https://ponder.sh/docs/migration-guide#default-multichain-ordering) The default event ordering strategy was changed from `omnichain` to `multichain`. [Read more](https://ponder.sh/docs/api-reference/ponder/config#ordering) about event ordering. ### New features[](https://ponder.sh/docs/migration-guide#new-features-5) #### Database views pattern[](https://ponder.sh/docs/migration-guide#database-views-pattern) This release introduces a new pattern for querying Ponder tables directly in Postgres. [Read more](https://ponder.sh/docs/production/self-hosting#views-pattern) about the views pattern. ##### Update start command[](https://ponder.sh/docs/migration-guide#update-start-command) To enable the views pattern on platforms like Railway, update the start command to include the new `--views-schema` flag. Terminal Start command Copy `pnpm start --schema $RAILWAY_DEPLOYMENT_ID pnpm start --schema $RAILWAY_DEPLOYMENT_ID --views-schema my_project` Whenever a deployment becomes _ready_ (historical indexing finishes), it will create views in the specified schema that "point" to its tables. ##### Query views schema[](https://ponder.sh/docs/migration-guide#query-views-schema) With this configuration, downstream applications can query the views schema directly. The views will always point at the latest deployment's tables. Copy `SELECT * FROM my_project.accounts;` 0.10[](https://ponder.sh/docs/migration-guide#010) ----------------------------------------------------- ### Breaking changes[](https://ponder.sh/docs/migration-guide#breaking-changes-6) #### `ponder_sync` database migration[](https://ponder.sh/docs/migration-guide#ponder_sync-database-migration) **WARNING**: This release includes an irreversible database migration to the RPC request cache located in the `ponder_sync` schema. Here are some details to consider when upgrading your production environment. 1. When an `0.10` instance starts up, it will attempt to run the migration against the connected database. 2. Any `<=0.9` instances currently connected to the database will crash, or the migration will fail. 3. Once the migration is complete, it's not possible to run `<=0.9` instances against the upgraded database. #### Removed `event.log.id`[](https://ponder.sh/docs/migration-guide#removed-eventlogid) The `event.log.id` and `event.trace.id` properties were removed. Replace each occurrence with the new `event.id` property (described below), or update the table definition to use a compound primary key that better represents the business logic / domain. File src/index.ts Copy `import { ponder } from "ponder:registry"; import { transferEvent } from "ponder:registry"; ponder.on("ERC20:Transfer", ({ event, context }) => { await context.db .insert(transferEvent) .values({ id: event.log.id }); .values({ id: event.id }); });` #### Removed `event.name`[](https://ponder.sh/docs/migration-guide#removed-eventname) The undocumented `event.name` property was also removed. ### New features[](https://ponder.sh/docs/migration-guide#new-features-6) #### `event.id`[](https://ponder.sh/docs/migration-guide#eventid) The new `event.id` property is a globally unique identifier for a log, block, transaction, or trace event that works across any number of chains. Each `event.id` value is a 75-digit positive integer represented as a string. #### Factory performance[](https://ponder.sh/docs/migration-guide#factory-performance) This release fixes a long-standing performance issue affecting large factory contracts (10k+ addresses). Before, a SQL query was used to dynamically generate the list of addresses for each batch of events. This did not scale well. Now, the list of addresses is materialized directly and all address filtering occurs in-memory. #### RPC request cache fixes[](https://ponder.sh/docs/migration-guide#rpc-request-cache-fixes) This release fixes two performance issues related to the ad-hoc RPC request cache. 1. **Reorg reconciliation** — Before, the query that evicted non-canonical results from the cache did not have an appropriate index. This occasionally caused timeouts leading to a crash. 2. **Large multicalls** — Before, multicall requests were treated naively as a single large `eth_call`. Now, the caching logic intelligently splits large multicall requests into smaller chunks. 0.9[](https://ponder.sh/docs/migration-guide#09) --------------------------------------------------- ### Breaking changes[](https://ponder.sh/docs/migration-guide#breaking-changes-7) #### API endpoints file is required[](https://ponder.sh/docs/migration-guide#api-endpoints-file-is-required) The Hono / API endpoints file `src/api/index.ts` is now required. The GraphQL API is no longer served by default. To achieve the same functionality as `<=0.8`, copy the following code into `src/api/index.ts`. File src/api/index.ts Copy `import { db } from "ponder:api"; import schema from "ponder:schema"; import { Hono } from "hono"; import { graphql } from "ponder"; const app = new Hono(); app.use("/", graphql({ db, schema })); app.use("/graphql", graphql({ db, schema })); export default app;` #### Removed `ponder.get()`, `post()`, `use()`[](https://ponder.sh/docs/migration-guide#removed-ponderget-post-use) This release makes custom API endpoints less opinionated. Just default export a normal Hono `App` object from the `src/api/index.ts` file, and Ponder will serve it. The `ponder.get()`, `post()`, `use()` methods were removed. Now, use Hono's built-in routing system. src/api/index.ts (0.8 and below)src/api/index.ts (0.9) File src/api/index.ts (0.8 and below) Copy `import { ponder } from "ponder:registry"; ponder.get("/hello", (c) => { return c.text("Hello, world!"); });` #### Removed `c.db`[](https://ponder.sh/docs/migration-guide#removed-cdb) The `c.db` object was removed from the Hono context. Now, use the `"ponder:api"` virtual module to access the readonly Drizzle database object. File src/api/index.ts Copy `import { db } from "ponder:api"; import schema from "ponder:schema"; import { Hono } from "hono"; const app = new Hono(); app.get("/account/:address", async (c) => { const address = c.req.param("address"); const account = await db .select() .from(schema.accounts) .where(eq(schema.accounts.address, address)) .limit(1); return c.json(account); }); export default app;` ### New features[](https://ponder.sh/docs/migration-guide#new-features-7) #### SQL over HTTP[](https://ponder.sh/docs/migration-guide#sql-over-http) The `@ponder/client` package provides a new experience for querying a Ponder app over HTTP. It's an SQL-based alternative to the GraphQL API. [Read more](https://ponder.sh/docs/query/sql-over-http) . #### `@ponder/react`[](https://ponder.sh/docs/migration-guide#ponderreact) The `@ponder/react` package uses `@ponder/client` and Tanstack Query to provide reactive live queries. [Read more](https://ponder.sh/docs/query/sql-over-http#guide-react) . #### `publicClients`[](https://ponder.sh/docs/migration-guide#publicclients) Custom API endpoint files now have access to a new `"ponder:api"` virtual module. This module contains the `db` object and a new `publicClients` object, which contains a Viem [Public Client](https://viem.sh/docs/clients/public) for each network. These clients use the transports defined in `ponder.config.ts`. File src/api/index.ts Copy `import { publicClients, db } from "ponder:api"; import schema from "ponder:schema"; import { Hono } from "hono"; const app = new Hono(); app.get("/account/:chainId/:address", async (c) => { const chainId = c.req.param("chainId"); const address = c.req.param("address"); const balance = await publicClients[chainId].getBalance({ address }); const account = await db.query.accounts.findFirst({ where: eq(schema.accounts.address, address), }); return c.json({ balance, account }); }); export default app;` #### Custom log filters[](https://ponder.sh/docs/migration-guide#custom-log-filters) The `contracts.filter` property now supports multiple log filters, and requires argument values. [Read more](https://ponder.sh/docs/config/contracts#filter) . 0.8[](https://ponder.sh/docs/migration-guide#08) --------------------------------------------------- ### Breaking changes[](https://ponder.sh/docs/migration-guide#breaking-changes-8) #### Database management[](https://ponder.sh/docs/migration-guide#database-management) Ponder now requires the database schema to be explicitly specified with an environment variable or CLI flag. **`onchainSchema()` is removed.** Terminal .env.local Copy `DATABASE_SCHEMA=my_schema` Terminal shell Copy `ponder start --schema my_schema` #### Railway[](https://ponder.sh/docs/migration-guide#railway) Railway users should [update the start command](https://ponder.sh/docs/production/railway#create-a-ponder-app-service) to include a database schema. pnpmyarnnpmbun Terminal pnpm Copy `pnpm start --schema $RAILWAY_DEPLOYMENT_ID` #### `@ponder/core` → `ponder`[](https://ponder.sh/docs/migration-guide#pondercore--ponder) New versions will be published to `ponder` and not `@ponder/core`. pnpmyarnnpmbun Terminal pnpm Copy `pnpm remove @ponder/core pnpm add ponder` #### `@/generated` → `ponder:registry`[](https://ponder.sh/docs/migration-guide#generated--ponderregistry) The virtual module `@/generated` was replaced with `ponder:registry`. File src/index.ts Copy `- import { ponder } from "@/generated"; + import { ponder } from "ponder:registry";` #### `factory()` function[](https://ponder.sh/docs/migration-guide#factory-function) The `factory()` function replaces the `factory` property in the contract config. The result should be passed to the `address` property. ponder.config.ts (0.7 and below)ponder.config.ts (0.8) File ponder.config.ts (0.7 and below) Copy `import { createConfig } from "@ponder/core"; export default createConfig({ contracts: { uniswap: { factory: { address: "0x1F98431c8aD98523631AE4a59f267346ea31F984", event: getAbiItem({ abi: UniswapV3FactoryAbi, name: "PoolCreated" }), parameter: "pool", }, }, }, });` #### `ponder-env.d.ts`[](https://ponder.sh/docs/migration-guide#ponder-envdts) This release updates the `ponder-env.d.ts` file. The new file uses [triple slash directives](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-types-) for less frequent updates. pnpmyarnnpmbun Terminal pnpm Copy `pnpm codegen` #### Removed `transactionReceipt.logs`[](https://ponder.sh/docs/migration-guide#removed-transactionreceiptlogs) The `transactionReceipt.logs` property was removed from the `event` object. #### Removed redundant properties from `event`[](https://ponder.sh/docs/migration-guide#removed-redundant-properties-from-event) The following properties were removed from the `event` object. Copy `- event.log.blockNumber; - event.log.blockHash; - event.log.transactionHash; - event.log.transactionIndex; - event.transaction.blockNumber; - event.transaction.blockHash; - event.transactionReceipt.transactionHash; - event.transactionReceipt.transactionIndex;` All of the data is still available on other properties of the `event` object, such as `event.transaction.hash` or `event.block.number`. ### New features[](https://ponder.sh/docs/migration-guide#new-features-8) #### Account indexing[](https://ponder.sh/docs/migration-guide#account-indexing) A new event source `accounts` is available. Accounts can be used to index transactions and native transfers to and from an address. [Read more](https://ponder.sh/docs/config/accounts) . #### `ponder:schema` alias[](https://ponder.sh/docs/migration-guide#ponderschema-alias) The `ponder:schema` virtual module was added. It is an alias for `ponder.schema.ts`. File src/index.ts Copy `- import { accounts } from "../ponder.schema"; + import { accounts } from "ponder:schema";` It also contains a default export of all the exported table objects from `ponder.schema.ts`. File src/index.ts Copy `import schema from "ponder:schema"; const row = await db.insert(schema.accounts).values({ address: "0x7Df1", balance: 0n });` #### `ponder db list`[](https://ponder.sh/docs/migration-guide#ponder-db-list) A new command was added for more visibility into which database schemas are being used. Terminal shell Copy `$ ponder db list │ Schema │ Active │ Last active │ Table count │ ├───────────────┼──────────┼────────────────┼─────────────┤ │ indexer_prod │ yes │ --- │ 10 │ │ test │ no │ 26m 58s ago │ 10 │ │ demo │ no │ 1 day ago │ 5 │` 0.7[](https://ponder.sh/docs/migration-guide#07) --------------------------------------------------- ### Breaking changes[](https://ponder.sh/docs/migration-guide#breaking-changes-9) This release includes several breaking changes. #### Install & run codegen[](https://ponder.sh/docs/migration-guide#install--run-codegen) pnpmyarnnpmbun Terminal pnpm Copy `pnpm add @ponder/core@0.7` To ensure strong type safety during the migration, regenerate `ponder-env.d.ts`. pnpmyarnnpmbun Terminal pnpm Copy `pnpm codegen` #### Migrate `ponder.schema.ts`[](https://ponder.sh/docs/migration-guide#migrate-ponderschemats) Here's a table defined with the new schema definition API, which uses [Drizzle](https://orm.drizzle.team/docs/overview) under the hood. File ponder.schema.ts (after) Copy `import { onchainTable } from "@ponder/core"; export const accounts = onchainTable("account", (t) => ({ address: t.hex().primaryKey(), daiBalance: t.bigint().notNull(), isAdmin: t.boolean().notNull(), graffiti: t.text(), }));` Key changes: 1. Declare tables with the `onchainTable` function exported from `@ponder/core` 2. Export all table objects from `ponder.schema.ts` 3. Use `.primaryKey()` to mark the primary key column 4. Columns are nullable by default, use `.notNull()` to add the constraint 5. The `hex` column type now uses `TEXT` instead of `BYTEA` 6. `p.float()` (`DOUBLE PRECISION`) was removed, use `t.doublePrecision()` or `t.real()` instead The new `onchainTable` function adds several new capabilities. * Custom primary key column name (other than `id`) * Composite primary keys * Default column values Here's a more advanced example with indexes and a composite primary key. File ponder.schema.ts Copy `import { onchainTable, index, primaryKey } from "@ponder/core"; export const transferEvents = onchainTable( "transfer_event", (t) => ({ id: t.text().primaryKey(), amount: t.bigint().notNull(), timestamp: t.integer().notNull(), from: t.hex().notNull(), to: t.hex().notNull(), }), (table) => ({ fromIdx: index().on(table.from), }) ); export const allowance = onchainTable( "allowance", (t) => ({ owner: t.hex().notNull(), spender: t.hex().notNull(), amount: t.bigint().notNull(), }), (table) => ({ pk: primaryKey({ columns: [table.owner, table.spender] }), }) ); export const approvalEvent = onchainTable("approval_event", (t) => ({ id: t.text().primaryKey(), amount: t.bigint().notNull(), timestamp: t.integer().notNull(), owner: t.hex().notNull(), spender: t.hex().notNull(), }));` #### Migrate indexing functions[](https://ponder.sh/docs/migration-guide#migrate-indexing-functions) This release updates the indexing function database API to offer a unified SQL experience based on Drizzle. Here's an indexing function defined with the new API, which uses the table objects exported from `ponder.schema.ts`. File src/index.ts Copy `import { ponder } from "@/generated"; import { account } from "../ponder.schema"; ponder.on("ERC20:Transfer", async ({ event, context }) => { await context.db .insert(account) .values({ address: event.args.from, balance: 0n, isOwner: false, }) .onConflictDoUpdate((row) => ({ balance: row.balance - event.args.amount, })); });` Key changes: 1. Transition from ORM pattern `db.Account.create({ ... })` to query builder pattern `db.insert(accounts, { ... })` 2. Import table objects from `ponder.schema.ts` 3. Replace `findMany` with `db.sql.select(...)` or `db.sql.query(...)` Here is a simple migration example to familiarize yourself with the API. src/index.ts (0.6 and below)src/index.ts (0.7) File src/index.ts (0.6 and below) Copy `// Create a single allowance await context.db.Allowance.create({ id: event.log.id, data: { owner: event.args.owner, spender: event.args.spender, amount: event.args.amount, }, });` Here is a reference for how to migrate each method. File src/index.ts Copy `// create -> insert await context.db.Account.create({ id: event.args.from, data: { balance: 0n }, }); await context.db.insert(account).values({ id: event.args.from, balance: 0n }); // createMany -> insert await context.db.Account.createMany({ data: [ { id: event.args.from, balance: 0n }, { id: event.args.to, balance: 0n }, ], }); await context.db.insert(account).values([ { id: event.args.from, balance: 0n }, { id: event.args.to, balance: 0n }, ]); // findUnique -> find await context.db.Account.findUnique({ id: event.args.from }); await context.db.find(account, { address: event.args.from }); // update await context.db.Account.update({ id: event.args.from, data: ({ current }) => ({ balance: current.balance + 100n }), }); await context.db .update(account, { address: event.args.from }) .set((row) => ({ balance: row.balance + 100n })); // upsert await context.db.Account.upsert({ id: event.args.from, create: { balance: 0n }, update: ({ current }) => ({ balance: current.balance + 100n }), }); await context.db .insert(account) .values({ address: event.args.from, balance: 0n }) .onConflictDoUpdate((row) => ({ balance: row.balance + 100n })); // delete await context.db.Account.delete({ id: event.args.from }); await context.db.delete(account, { address: event.args.from }); // findMany -> raw SQL select, see below await context.db.Account.findMany({ where: { balance: { gt: 100n } } }); await context.db.sql.select().from(account).where(eq(account.balance, 100n)); // updateMany -> raw SQL update, see below await context.db.Player.updateMany({ where: { id: { startsWith: "J" } }, data: { age: 50 }, }); await context.db.sql .update(player) .set({ age: 50 }) .where(like(player.id, "J%"));` Finally, another migration example for an ERC20 Transfer indexing function using `upsert`. src/index.ts (0.6 and below)src/index.ts (0.7) File src/index.ts (0.6 and below) Copy `import { ponder } from "@/generated"; ponder.on("ERC20:Transfer", async ({ event, context }) => { const { Account, TransferEvent } = context.db; await Account.upsert({ id: event.args.from, create: { balance: BigInt(0), isOwner: false, }, update: ({ current }) => ({ balance: current.balance - event.args.amount, }), }); });` #### Migrate API functions[](https://ponder.sh/docs/migration-guide#migrate-api-functions) * Removed `c.tables` in favor of importing table objects from `ponder.schema.ts` ### New features[](https://ponder.sh/docs/migration-guide#new-features-9) #### Arbitrary SQL within indexing functions[](https://ponder.sh/docs/migration-guide#arbitrary-sql-within-indexing-functions) The new `context.db.sql` interface replaces the rigid `findMany` method and supports any valid SQL `select` query. File src/index.ts Copy `import { desc } from "@ponder/core"; import { account } from "../ponder.schema"; ponder.on("...", ({ event, context }) => { const result = await context.db.sql .select() .from(account) .orderBy(desc(account.balance)) .limit(1); });` 0.6.0[](https://ponder.sh/docs/migration-guide#060) ------------------------------------------------------ ### Breaking changes[](https://ponder.sh/docs/migration-guide#breaking-changes-10) #### Updated `viem` to `>=2`[](https://ponder.sh/docs/migration-guide#updated-viem-to-2) This release updates the `viem` peer dependency requirement to `>=2`. The `context.client` action `getBytecode` was renamed to `getCode`. pnpmyarnnpmbun Terminal pnpm Copy `pnpm add viem@latest` #### Simplified Postgres schema pattern[](https://ponder.sh/docs/migration-guide#simplified-postgres-schema-pattern) Starting with this release, the indexed tables, reorg tables, and metadata table for a Ponder app are contained in one Postgres schema, specified by the user in `ponder.config.ts` (defaults to `public`). This means the shared `ponder` schema is no longer used. (Note: The `ponder_sync` schema is still in use). This release also removes the view publishing pattern and the `publishSchema` option from `ponder.config.ts`, which may disrupt production setups using horizontal scaling or direct SQL. If you relied on the publish pattern, please [get in touch on Telegram](https://t.me/kevinkoste) and we'll work to get you unblocked. ### New features[](https://ponder.sh/docs/migration-guide#new-features-10) #### Added `/ready`, updated `/health`[](https://ponder.sh/docs/migration-guide#added-ready-updated-health) The new `/ready` endpoint returns an HTTP `200` response once the app **is ready to serve requests**. This means that historical indexing is complete and the app is indexing events in realtime. The existing `/health` endpoint now returns an HTTP `200` response as soon as the process starts. (This release removes the `maxHealthcheckDuration` option, which previously governed the behavior of `/health`.) For Railway users, we now recommend using `/ready` as the health check endpoint to enable zero downtime deployments. If your app takes a while to sync, be sure to set the healthcheck timeout accordingly. Read the [Railway deployment guide](https://ponder.sh/docs/production/railway#create-a-ponder-app-service) for more details. 0.5.0[](https://ponder.sh/docs/migration-guide#050) ------------------------------------------------------ ### Breaking changes[](https://ponder.sh/docs/migration-guide#breaking-changes-11) #### `hono` peer dependency[](https://ponder.sh/docs/migration-guide#hono-peer-dependency) This release adds [Hono](https://hono.dev/) as a peer dependency. After upgrading, install `hono` in your project. pnpmyarnnpmbun Terminal pnpm Copy `pnpm add hono@latest` ### New features[](https://ponder.sh/docs/migration-guide#new-features-11) #### Introduced custom API endpoints[](https://ponder.sh/docs/migration-guide#introduced-custom-api-endpoints) This release added support for API functions. [Read more](https://ponder.sh/docs/query/api-endpoints) . 0.4.0[](https://ponder.sh/docs/migration-guide#040) ------------------------------------------------------ ### Breaking changes[](https://ponder.sh/docs/migration-guide#breaking-changes-12) This release changes the location of database tables when using both SQLite and Postgres. It **does not** require any changes to your application code, and does not bust the sync cache for SQLite or Postgres. #### New database layout[](https://ponder.sh/docs/migration-guide#new-database-layout) Please read the new docs on [direct SQL](https://ponder.sh/docs/query/direct-sql) for a detailed overview. **SQLite** Ponder now uses the `.ponder/sqlite/public.db` file for indexed tables. Before, the tables were present as views in the `.ponder/sqlite/ponder.db`. Now, the`.ponder/sqlite/ponder.db` file is only used internally by Ponder. **Postgres** Ponder now creates a table in the `public` schema for each table in `ponder.schema.ts`. Before, Ponder created them as views in the `ponder` schema. Isolation while running multiple Ponder instances against the same database also works differently. Before, Ponder used a schema with a pseudorandom name if the desired schema was in use. Now, Ponder will fail on startup with an error if it cannot acquire a lock on the desired schema. This also changes the zero-downtime behavior on platforms like Railway. For more information on how this works in `0.4`, please reference: * [Direct SQL](https://ponder.sh/docs/query/direct-sql) * [Zero-downtime deployments](https://ponder.sh/docs/production/self-hosting#database-schema) **Postgres table cleanup** After upgrading to `0.4`, you can run the following Postgres SQL script to clean up stale tables and views created by `0.3` Ponder apps. **Note:** This script could obviously be destructive, so please read it carefully before executing. File cleanup.sql Copy `DO $ DECLARE view_name TEXT; schema_name_var TEXT; BEGIN -- Drop all views from the 'ponder' schema FOR view_name IN SELECT table_name FROM information_schema.views WHERE table_schema = 'ponder' LOOP EXECUTE format('DROP VIEW IF EXISTS ponder.%I CASCADE', view_name); RAISE NOTICE 'Dropped view "ponder"."%"', view_name; END LOOP; -- Drop the 'ponder_cache' schema EXECUTE 'DROP SCHEMA IF EXISTS ponder_cache CASCADE'; RAISE NOTICE 'Dropped schema "ponder_cache"'; -- Find and drop any 'ponder_instance_*' schemas FOR schema_name_var IN SELECT schema_name AS schema_name_alias FROM information_schema.schemata WHERE schema_name LIKE 'ponder_instance_%' LOOP EXECUTE format('DROP SCHEMA IF EXISTS %I CASCADE', schema_name_var); RAISE NOTICE 'Dropped schema "%"', schema_name_var; END LOOP; END $;` 0.3.0[](https://ponder.sh/docs/migration-guide#030) ------------------------------------------------------ ### Breaking changes[](https://ponder.sh/docs/migration-guide#breaking-changes-13) #### Moved SQLite directory[](https://ponder.sh/docs/migration-guide#moved-sqlite-directory) **Note:** This release busted the SQLite sync cache. The SQLite database was moved from the `.ponder/store` directory to `.ponder/sqlite`. The old `.ponder/store` directory will still be used by older versions. #### Moved Postgres sync tables[](https://ponder.sh/docs/migration-guide#moved-postgres-sync-tables) Similar to SQLite, the sync tables for Postgres were moved from the `public` schema to `ponder_sync`. Now, Ponder does not use the `public` schema whatsoever. This change did NOT bust the sync cache; the tables were actually moved. This process emits some `WARN`\-level logs that you should see after upgrading. 0.2.0[](https://ponder.sh/docs/migration-guide#020) ------------------------------------------------------ ### Breaking changes[](https://ponder.sh/docs/migration-guide#breaking-changes-14) #### Replaced `p.bytes()` with `p.hex()`[](https://ponder.sh/docs/migration-guide#replaced-pbytes-with-phex) Removed `p.bytes()` in favor of a new `p.hex()` primitive column type. `p.hex()` is suitable for Ethereum addresses and other hex-encoded data, including EVM `bytes` types. `p.hex()` values are stored as `bytea` (Postgres) or `blob` (SQLite). To migrate, replace each occurrence of `p.bytes()` in `ponder.schema.ts` with `p.hex()`, and ensure that any values you pass into hex columns are valid hexadecimal strings. The GraphQL API returns `p.hex()` values as hexadecimal strings, and allows sorting/filtering on `p.hex()` columns using the numeric comparison operators (`gt`, `gte`, `le`, `lte`). ### New features[](https://ponder.sh/docs/migration-guide#new-features-12) #### Cursor pagination[](https://ponder.sh/docs/migration-guide#cursor-pagination) Updated the GraphQL API to use cursor pagination instead of offset pagination. Note that this change also affects the `findMany` database method. See the [GraphQL pagination docs](https://ponder.sh/docs/query/graphql#pagination) for more details. 0.1[](https://ponder.sh/docs/migration-guide#01) --------------------------------------------------- ### Breaking changes[](https://ponder.sh/docs/migration-guide#breaking-changes-15) #### Config[](https://ponder.sh/docs/migration-guide#config) * In general, `ponder.config.ts` now has much more static validation using TypeScript. This includes network names in `contracts`, ABI event names for the contract `event` and `factory` options, and more. * The `networks` and `contracts` fields were changed from an array to an object. The network or contract name is now specified using an object property name. The `name` field for both networks and contracts was removed. * The `filter` field has been removed. To index all events matching a specific signature across all contract addresses, add a contract that specifies the `event` field without specifying an `address`. * The `abi` field now requires an ABI object that has been asserted as const (cannot use a file path). See the ABIType documentation for more details. #### Schema[](https://ponder.sh/docs/migration-guide#schema) * The schema definition API was rebuilt from scratch to use a TypeScript file `ponder.schema.ts` instead of `schema.graphql`. The `ponder.schema.ts` file has static validation using TypeScript. * Note that it is possible to convert a `schema.graphql` file into a `ponder.schema.ts` file without introducing any breaking changes to the autogenerated GraphQL API schema. * Please see the `design your schema` guide for an overview of the new API. #### Indexing functions[](https://ponder.sh/docs/migration-guide#indexing-functions) * `event.params` was renamed to `event.args` to better match Ethereum terminology norms. * If a contract uses the `event` option, only the specified events will be available for registration. Before, all events in the ABI were available. * `context.models` was renamed to `context.db` * Now, a read-only Viem client is available at `context.client`. This client uses the same transport you specify in `ponder.config.ts`, except all method are cached to speed up subsequent indexing. * The `context.contracts` object now contains the contract addresses and ABIs specified in`ponder.config.ts`, typed as strictly as possible. (You should not need to copy addresses and ABIs around anymore, just use `context.contracts`). * A new `context.network` object was added which contains the network name and chain ID that the current event is from. #### Multi-chain indexing[](https://ponder.sh/docs/migration-guide#multi-chain-indexing) * The contract `network` field `ponder.config.ts` was upgraded to support an object of network-specific overrides. This is a much better DX for indexing the same contract on multiple chains. * The options that you can specify per-network are `address`, `event`, `startBlock`, `endBlock`, and `factory`. * When you add a contract on multiple networks, Ponder will sync the contract on each network you specify. Any indexing functions you register for the contract will now process events across all networks. * The `context.network` object is typed according to the networks that the current contract runs on, so you can write network-specific logic like `if (context.network.name === "optimism") { …` #### Vite[](https://ponder.sh/docs/migration-guide#vite) * Ponder now uses Vite to transform and load your code. This means you can import files from outside the project root directory. * Vite's module graph makes it possible to invalidate project files granularly, only reloading the specific parts of your app that need to be updated when a specific file changes. For example, if you save a change to one of your ABI files, `ponder.config.ts` will reload because it imports that file, but your schema will not reload. * This update also unblocks a path towards concurrent indexing and granular caching of indexing function results. --- # Accounts – Ponder [Skip to content](https://ponder.sh/docs/config/accounts#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Accounts On this page Chevron Right To index **transactions** or **native transfers** sent to (or from) an address, use the `accounts` field in `ponder.config.ts`. This guide describes each configuration option and suggests patterns for common use cases. Visit the config [API reference](https://ponder.sh/docs/api-reference/ponder/config) for more information. Example[](https://ponder.sh/docs/config/accounts#example) ------------------------------------------------------------ This config instructs the indexing engine to fetch transactions or native transfers sent by the [Beaver](https://beaverbuild.org/) block builder account. File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ chains: { mainnet: { id: 1, rpc: process.env.PONDER_RPC_URL_1 }, }, accounts: { BeaverBuild: { chain: "mainnet", address: "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5", startBlock: 20000000, }, }, });` Now, we can register an indexing function for the `transaction:from` event. The indexing engine will fetch all transactions where `from` matches the specified address, then call the indexing function for each transaction. File src/index.ts Copy `import { ponder } from "ponder:registry"; import { deposits } from "ponder:schema"; ponder.on("BeaverBuild:transaction:from", async ({ event, context }) => { await context.db.insert(deposits).values({ from: event.transaction.from, to: event.transaction.to, value: event.transaction.value, input: event.transaction.input, }); });` You can also register indexing functions for the `transaction:to`, `transfer:from`, and `transfer:to` events. [Read more](https://ponder.sh/docs/api-reference/ponder/config#accounts) about event types. Name[](https://ponder.sh/docs/config/accounts#name) ------------------------------------------------------ Every account must have a unique name, provided as a key to the `accounts` object. Names must be unique across accounts, contracts, and block intervals. File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ chains: { /* ... */ }, accounts: { BeaverBuild: { chain: "mainnet", address: "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5", startBlock: 12439123, }, }, });` Chain[](https://ponder.sh/docs/config/accounts#chain) -------------------------------------------------------- The `chain` option for accounts works the same way as it does for contracts. You can specify a different `address`, `startBlock`, and `endBlock` for each chain. [Read more](https://ponder.sh/docs/config/contracts#chain) in the contracts guide. Address[](https://ponder.sh/docs/config/accounts#address) ------------------------------------------------------------ The `address` option for accounts works the same way as it does for contracts. You can provide a single address, a list of addresses, or an address factory. You can also specify chain-specific overrides. [Read more](https://ponder.sh/docs/config/contracts#address) in the contracts guide. Block range[](https://ponder.sh/docs/config/accounts#block-range) -------------------------------------------------------------------- The `startBlock` and `endBlock` options for accounts work the same way as it does for contracts. [Read more](https://ponder.sh/docs/config/contracts#block-range) in the contracts guide. Transaction receipts[](https://ponder.sh/docs/config/accounts#transaction-receipts) -------------------------------------------------------------------------------------- The `includeTransactionReceipts` option for accounts works the same way as it does for contracts. [Read more](https://ponder.sh/docs/config/contracts#transaction-receipts) in the contracts guide. --- # Contracts – Ponder [Skip to content](https://ponder.sh/docs/config/contracts#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Contracts On this page Chevron Right To index **event logs** or **call traces** produced by a contract, use the `contracts` field in `ponder.config.ts`. This guide describes each configuration option and suggests patterns for common use cases. Visit the config [API reference](https://ponder.sh/docs/api-reference/ponder/config) for more information. Example[](https://ponder.sh/docs/config/contracts#example) ------------------------------------------------------------- This config instructs the indexing engine to fetch event logs emitted by the [Blitmap](https://blitmap.xyz/) NFT contract. File ponder.config.ts Copy `import { createConfig } from "ponder"; import { BlitmapAbi } from "./abis/Blitmap"; export default createConfig({ chains: { mainnet: { id: 1, rpc: process.env.PONDER_RPC_URL_1 }, }, contracts: { Blitmap: { abi: BlitmapAbi, chain: "mainnet", address: "0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63", startBlock: 12439123, }, }, });` Now, we can register an indexing function for the `MetadataChanged` event that will be called for each event log. In this case, the indexing function inserts or updates a row in the `tokens` table. File src/index.ts Copy `import { ponder } from "ponder:registry"; import { tokens } from "ponder:schema"; ponder.on("Blitmap:MetadataChanged", async ({ event, context }) => { await context.db .insert(tokens) .values({ id: event.args.tokenId, metadata: event.args.newMetadata, }) .onConflictDoUpdate({ metadata: event.args.newMetadata, }); });` [Read more](https://ponder.sh/docs/indexing/overview) about writing indexing functions. Name[](https://ponder.sh/docs/config/contracts#name) ------------------------------------------------------- Each contract must have a unique name, provided as a key to the `contracts` object. Names must be unique across contracts, accounts, and block intervals. File ponder.config.ts Copy `import { createConfig } from "ponder"; import { BlitmapAbi } from "./abis/Blitmap"; export default createConfig({ chains: { /* ... */ }, contracts: { Blitmap: { abi: BlitmapAbi, chain: "mainnet", address: "0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63", }, }, });` ABI[](https://ponder.sh/docs/config/contracts#abi) ----------------------------------------------------- Each contract must have an ABI. The indexing engine uses the ABI to validate inputs and encode & decode contract data. File ponder.config.ts Copy `import { createConfig } from "ponder"; import { BlitmapAbi } from "./abis/Blitmap"; export default createConfig({ chains: { mainnet: { id: 1, rpc: process.env.PONDER_RPC_URL_1 }, }, contracts: { Blitmap: { abi: BlitmapAbi, chain: "mainnet", address: "0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63", startBlock: }, }, });` To enable the type system, save all ABIs in `.ts` files and include an `as const` assertion. Read more about these requirements in the [ABIType](https://abitype.dev/guide/getting-started#usage) documentation. File abis/Blitmap.ts Copy `export const BlitmapAbi = [ { inputs: [], stateMutability: "nonpayable", type: "constructor" }, { inputs: [{ internalType: "address", name: "owner", type: "address" }], name: "balanceOf", outputs: [{ internalType: "uint256", name: "", type: "uint256" }], stateMutability: "view", type: "function", }, // ... ] as const;` ### Multiple ABIs[](https://ponder.sh/docs/config/contracts#multiple-abis) Use the [`mergeAbis`](https://ponder.sh/docs/api-reference/ponder-utils#mergeabis) utility function to combine multiple ABIs into one. This function removes duplicate ABI items and maintains strict types. This pattern is often useful for proxy contracts where the implementation ABI has changed over time. File ponder.config.ts Copy `import { createConfig, mergeAbis } from "ponder"; import { ERC1967ProxyAbi } from "./abis/ERC1967Proxy"; import { NameRegistryAbi } from "./abis/NameRegistry"; import { NameRegistry2Abi } from "./abis/NameRegistry2"; export default createConfig({ chains: { /* ... */ }, contracts: { FarcasterNameRegistry: { abi: mergeAbis([ERC1967ProxyAbi, NameRegistryAbi, NameRegistry2Abi]), chain: "goerli", address: "0xe3Be01D99bAa8dB9905b33a3cA391238234B79D1", }, }, });` Chain[](https://ponder.sh/docs/config/contracts#chain) --------------------------------------------------------- ### Single chain[](https://ponder.sh/docs/config/contracts#single-chain) To index a contract on a single chain, pass the chain name as a string to the `chain` field. File ponder.config.ts Copy `import { createConfig } from "ponder"; import { BlitmapAbi } from "./abis/Blitmap"; export default createConfig({ chains: { mainnet: { id: 1, rpc: process.env.PONDER_RPC_URL_1 }, }, contracts: { Blitmap: { abi: BlitmapAbi, chain: "mainnet", address: "0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63", }, }, });` ### Multiple chains[](https://ponder.sh/docs/config/contracts#multiple-chains) To index a contract that exists on multiple chains, pass an object to the `chain` field containing chain-specific overrides. Each contract specified this way _must_ have the same ABI. File ponder.config.ts Copy `import { createConfig } from "ponder"; import { UniswapV3FactoryAbi } from "./abis/UniswapV3Factory"; export default createConfig({ chains: { mainnet: { id: 1, rpc: process.env.PONDER_RPC_URL_1 }, base: { id: 8453, rpc: process.env.PONDER_RPC_URL_8453 }, }, contracts: { UniswapV3Factory: { abi: UniswapV3FactoryAbi, chain: { mainnet: { address: "0x1F98431c8aD98523631AE4a59f267346ea31F984", startBlock: 12369621, }, base: { address: "0x33128a8fC17869897dcE68Ed026d694621f6FDfD", startBlock: 1371680, }, }, }, }, });` With this configuration, the indexing functions you register for the `UniswapV3Factory` contract will handle events from both Ethereum and Base. To determine which chain the current event is from, use the `context.chain` object. File src/index.ts Copy `import { ponder } from "ponder:registry"; ponder.on("UniswapV3Factory:Ownership", async ({ event, context }) => { context.chain; // ^? { name: "mainnet", id: 1 } | { name: "base", id: 8453 } event.log.address; // ^? "0x1F98431c8aD98523631AE4a59f267346ea31F984" | "0x33128a8fC17869897dcE68Ed026d694621f6FDfD" if (context.chain.name === "mainnet") { // Do mainnet-specific stuff! } });` #### Chain override logic[](https://ponder.sh/docs/config/contracts#chain-override-logic) Chain-specific configuration uses an override pattern. Any options defined at the top level are the default, and the chain-specific objects override those defaults. All contract options other than `abi` can be specified per-chain, including `address`, `startBlock`, and `endBlock`. **Example: Uniswap V3** The Uniswap V3 factory contract is deployed to the same address on most chains, but has a different address on Base. This configuration instructs Ponder to use the address defined at the top level (`"0x1F98..."`) for mainnet and Optimism, and the address defined in the `base` object for Base. File ponder.config.ts Copy `import { createConfig } from "ponder"; import { UniswapV3FactoryAbi } from "./abis/EntryPoint"; export default createConfig({ chains: { mainnet: { id: 1, rpc: process.env.PONDER_RPC_URL_1 }, optimism: { id: 10, rpc: process.env.PONDER_RPC_URL_10 }, base: { id: 8453, rpc: process.env.PONDER_RPC_URL_8453 }, }, contracts: { UniswapV3Factory: { abi: UniswapV3FactoryAbi, address: "0x1F98431c8aD98523631AE4a59f267346ea31F984", chain: { mainnet: { startBlock: 12369621 }, optimism: { startBlock: 0 }, base: { address: "0x33128a8fC17869897dcE68Ed026d694621f6FDfD", startBlock: 1371680, }, }, }, }, });` **Example: ERC-4337 EntryPoint** The ERC-4337 EntryPoint contract is deployed to the same address on all chains. Only the `startBlock` needs to be specified per-chain. File ponder.config.ts Copy `import { createConfig } from "ponder"; import { EntryPointAbi } from "./abis/EntryPoint"; export default createConfig({ chains: { mainnet: { id: 1, rpc: process.env.PONDER_RPC_URL_1 }, optimism: { id: 10, rpc: process.env.PONDER_RPC_URL_10 }, }, contracts: { EntryPoint: { abi: EntryPointAbi, address: "0x1F98431c8aD98523631AE4a59f267346ea31F984", chain: { mainnet: { startBlock: 12369621 }, optimism: { startBlock: 88234528 }, }, }, }, });` Address[](https://ponder.sh/docs/config/contracts#address) ------------------------------------------------------------- ### Single address[](https://ponder.sh/docs/config/contracts#single-address) The simplest and most common option is to pass a single static address. File ponder.config.ts Copy `import { createConfig } from "ponder"; import { BlitmapAbi } from "./abis/Blitmap"; export default createConfig({ chains: { /* ... */ }, contracts: { Blitmap: { abi: BlitmapAbi, chain: "mainnet", address: "0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63", }, }, });` ### Multiple addresses[](https://ponder.sh/docs/config/contracts#multiple-addresses) To index multiple contracts that have the same ABI (or share an interface like `ERC20`), pass a list of addresses to the `address` field. File ponder.config.ts Copy `import { createConfig } from "ponder"; import { ERC721Abi } from "./abis/ERC721"; export default createConfig({ chains: { /* ... */ }, contracts: { NiceJpegs: { abi: ERC721Abi, chain: "mainnet", address: [ "0x4E1f41613c9084FdB9E34E11fAE9412427480e56", // Terraforms "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D", // BAYC "0x8a90CAb2b38dba80c64b7734e58Ee1dB38B8992e", // Doodles "0x0000000000664ceffed39244a8312bD895470803", // !fundrop ], }, }, });` ### Factory pattern[](https://ponder.sh/docs/config/contracts#factory-pattern) Use the `factory()` function to specify a dynamic list of addresses collected from a factory contract. Any indexing functions you register for `SudoswapPool` receive events for all contracts matched by the factory configuration (similar to a multiple chain configuration). The `event.log.address` field contains the address of the specific contract that emitted the current event. ponder.config.tssrc/index.ts File ponder.config.ts Copy `import { createConfig, factory } from "ponder"; import { parseAbiItem } from "viem"; export default createConfig({ chains: { /* ... */ }, contracts: { SudoswapPool: { abi: SudoswapPoolAbi, chain: "mainnet", address: factory({ // Address of the factory contract. address: "0xb16c1342E617A5B6E4b631EB114483FDB289c0A4", // Event from the factory contract ABI which contains the child address. event: parseAbiItem("event NewPair(address poolAddress)"), // Name of the event parameter containing the child address. parameter: "poolAddress", }), startBlock: 14645816, }, }, });` ### Proxy & upgradable contracts[](https://ponder.sh/docs/config/contracts#proxy--upgradable-contracts) To index a proxy/upgradable contract, use the proxy contract address in the `address` field. Then, be sure to include the ABIs of all implementation contracts that the proxy has ever had. The implementation ABIs are required to properly identify and decode all event logs throughout the contract history. To add multiple ABIs safely, use the [`mergeAbis`](https://ponder.sh/docs/api-reference/ponder-utils#mergeabis) utility function. ![Etherscan contract proxy address](https://ponder.sh/etherscan-proxy-contract.png) Block range[](https://ponder.sh/docs/config/contracts#block-range) --------------------------------------------------------------------- Use the `startBlock` and `endBlock` options to specify the block range to index. File ponder.config.ts Copy `import { createConfig } from "ponder"; import { BlitmapAbi } from "./abis/Blitmap"; export default createConfig({ chains: { /* ... */ }, contracts: { Blitmap: { abi: BlitmapAbi, chain: "mainnet", address: "0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63", startBlock: 16500000, endBlock: 16501000, }, }, });` ### Start block[](https://ponder.sh/docs/config/contracts#start-block) The `startBlock` option specifies the block number to begin indexing from. The default is `0` – to avoid wasteful RPC requests, set `startBlock` to the contract deployment block number. If you set `startBlock` to `"latest"`, the indexing engine will fetch the latest block on startup and use that value. This is the best way to skip the backfill and only index live blocks. ### End block[](https://ponder.sh/docs/config/contracts#end-block) The `endBlock` option specifies the block number to stop indexing at. The default is `undefined`, which means that indexing will continue indefinitely with live blocks. If you set `endBlock` to `"latest"`, the indexing engine will fetch the latest block on startup and use that value. Filter by indexed parameter value[](https://ponder.sh/docs/config/contracts#filter-by-indexed-parameter-value) ----------------------------------------------------------------------------------------------------------------- Sometimes, it's useful to filter for event logs that match specific [indexed parameter](https://docs.soliditylang.org/en/latest/contracts.html#events) values (topics). This example filters for all `Transfer` events emitted by the USDC contract where the `from` argument matches the Binance 14 exchange address. ponder.config.tssrc/index.ts File ponder.config.ts Copy `import { createConfig } from "ponder"; import { ERC20Abi } from "./abis/ERC20"; export default createConfig({ chains: { /* ... */ }, contracts: { USDC: { abi: ERC20Abi, chain: "mainnet", address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC filter: { event: "Transfer", args: { from: "0x28c6c06298d514db089934071355e5743bf21d60", // Binance 14 }, }, }, }, });` Note that the `filter` option accepts an array of filter configurations and that each field in the `args` object accepts a single value or a list of values to match. Call traces[](https://ponder.sh/docs/config/contracts#call-traces) --------------------------------------------------------------------- Use the `includeCallTraces` option to enable call trace indexing for a contract, which makes it possible to register indexing functions for every _function_ present in the contract ABI. Call traces are **disabled** by default. ponder.config.tssrc/index.ts File ponder.config.ts Copy `import { createConfig } from "ponder"; import { BlitmapAbi } from "./abis/Blitmap"; export default createConfig({ chains: { /* ... */ }, contracts: { Blitmap: { abi: BlitmapAbi, chain: "mainnet", address: "0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63", includeCallTraces: true, }, }, });` Transaction receipts[](https://ponder.sh/docs/config/contracts#transaction-receipts) --------------------------------------------------------------------------------------- Use the `includeTransactionReceipts` option to fetch the transaction receipt for each event. This will make the `event.transactionReceipt` object available in all indexing functions for the contract. Transaction receipts are **disabled** by default. ponder.config.tssrc/index.ts File ponder.config.ts Copy `import { createConfig } from "ponder"; import { BlitmapAbi } from "./abis/Blitmap"; export default createConfig({ chains: { /* ... */ }, contracts: { Blitmap: { abi: BlitmapAbi, chain: "mainnet", address: "0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63", includeTransactionReceipts: true, }, }, });` --- # Block intervals – Ponder [Skip to content](https://ponder.sh/docs/config/block-intervals#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Block intervals On this page Chevron Right To run indexing logic on a **regular schedule**, use the `blocks` field in `ponder.config.ts`. Block intervals are useful for aggregations, time-series logic, and bulk updates using raw SQL. This guide describes each configuration option and suggests patterns for common use cases. Visit the config [API reference](https://ponder.sh/docs/api-reference/ponder/config) for more information. Example[](https://ponder.sh/docs/config/block-intervals#example) ------------------------------------------------------------------- This config instructs the indexing engine to run an indexing function every 10 blocks starting at the start block – `1000`, `1010`, `1020`, and so on. File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ chains: { mainnet: { id: 1, rpc: process.env.PONDER_RPC_URL_1 }, }, blocks: { ChainlinkOracleUpdate: { chain: "mainnet", interval: 10, // Every 10 blocks startBlock: 1000, }, }, });` Now, we can register an indexing function for the `ChainlinkOracleUpdate:block` event. This example reads the latest price from the Chainlink oracle contract and inserts a row into the `priceTimeline` table. File src/index.ts Copy `import { ponder } from "ponder:registry"; import { priceTimeline } from "ponder:schema"; import { ChainlinkOracleAbi } from "../abis/ChainlinkOracle.ts"; ponder.on("ChainlinkOracleUpdate:block", async ({ event, context }) => { // Fetch the price at the current block height (1000, 1010, 1020, etc.) const latestPrice = await context.client.readContract({ abi: ChainlinkOracleAbi, address: "0xD10aBbC76679a20055E167BB80A24ac851b37056", functionName: "latestAnswer", }); // Insert a row into the price timeline table await context.db.insert(priceTimeline).values({ id: event.id, timestamp: event.block.timestamp, price: latestPrice, }); });` Name[](https://ponder.sh/docs/config/block-intervals#name) ------------------------------------------------------------- Every block interval must have a name, provided as a key to the `blocks` object. The name must be unique across `blocks`, `contracts`, and `accounts`. Use a descriptive name to indicate the purpose of the block interval. File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ chains: { /* ... */ }, blocks: { ChainlinkOracleUpdate: { chain: "mainnet", interval: 10, startBlock: 19783636, }, }, });` Interval[](https://ponder.sh/docs/config/block-intervals#interval) --------------------------------------------------------------------- Use the `interval` option to specify how often the indexing function should run. A block interval with a start block of `100` and an interval of `10` will index blocks `100`, `110`, `120`, `130`, and so on. ### Block time[](https://ponder.sh/docs/config/block-intervals#block-time) It's often easier to think about a _time_ interval instead of a _block_ interval. To convert between the two, divide the time interval by the chain's average block time. For example, if the block time is 3 seconds and you want to run an indexing function once per day: Copy `// 24 hours per day, 60 minutes per hour, 60 seconds per minute const secondsInterval = 24 * 60 * 60; // 3 seconds per block const blockTime = 3; // 28800 blocks per day const blockInterval = secondsInterval / blockTime;` To find the block time of a specific chain, check the chain's documentation website or block explorer. Most Etherscan deployments have a [`/chart/blocktime`](https://polygonscan.com/chart/blocktime) page. Chain[](https://ponder.sh/docs/config/block-intervals#chain) --------------------------------------------------------------- The `chain` option for block intervals works the same way as it does for contracts. You can specify a different `interval`, `startBlock` and `endBlock` for each chain. File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ chains: { /* ... */ }, blocks: { PointsAggregation: { chain: { mainnet: { startBlock: 19783636, interval: (60 * 60) / 12, // Every 60 minutes (12s block time) }, optimism: { startBlock: 119534316, interval: (60 * 60) / 2, // Every 60 minutes (2s block time) }, }, }, }, });` [Read more](https://ponder.sh/docs/config/contracts#chain) in the contracts guide. Block range[](https://ponder.sh/docs/config/block-intervals#block-range) --------------------------------------------------------------------------- The `startBlock` and `endBlock` options for block intervals work the same way as they do for contracts. [Read more](https://ponder.sh/docs/config/contracts#block-range) in the contracts guide. --- # Indexing – Ponder [Skip to content](https://ponder.sh/docs/indexing/overview#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Overview On this page Chevron Right An indexing function is a TypeScript function that's triggered by onchain activity (an event log, call trace, transaction, transfer, or block). The purpose of indexing functions is to transform onchain data and insert it into the tables you've defined in `ponder.schema.ts`. Register an indexing function[](https://ponder.sh/docs/indexing/overview#register-an-indexing-function) ---------------------------------------------------------------------------------------------------------- To register an indexing function, use `ponder.on(...)`. The first argument is the event name, and the second argument is the function / callback that the indexing engine runs to process the event. File src/index.ts Copy `import { ponder } from "ponder:registry"; ponder.on("Blitmap:MetadataChanged", async ({ event, context }) => { await context.db .insert(tokens) .values({ id: event.args.tokenId, metadata: event.args.newMetadata, }) .onConflictDoUpdate({ metadata: event.args.newMetadata, }); });` Read more about how to [write to the database](https://ponder.sh/docs/indexing/write) and [read contract data](https://ponder.sh/docs/indexing/read-contracts) within indexing functions. Frequently asked questions[](https://ponder.sh/docs/indexing/overview#frequently-asked-questions) ---------------------------------------------------------------------------------------------------- ### Ordering[](https://ponder.sh/docs/indexing/overview#ordering) For each chain, the indexing engine calls indexing functions according to EVM execution order (block number, transaction index, log index). [Read more](https://ponder.sh/docs/api-reference/ponder/config#ordering) about the ordering guarantee across multiple chains, which is more complex. ### Reorgs[](https://ponder.sh/docs/indexing/overview#reorgs) The indexing engine handles reorgs automatically. You do not need to adjust your indexing function logic to handle them. Here's how it works. * The indexing engine records all changes (insert, update, delete) to each table defined in `ponder.schema.ts` using a trigger-based [transaction log](https://en.wikipedia.org/wiki/Transaction_log) . * When the indexing engine detects a reorg, it follows these steps to reconcile the database state with the new canonical chain. 1. Evict all non-canonical data from the RPC cache. 2. Roll back the database to the common ancestor block height using the transaction log. 3. Fetch the new canonical data from the RPC / remote chain. 4. Run indexing functions to process the new canonical data. * The indexing engine periodically drops finalized data from the transaction log to avoid database bloat. ### Crash recovery[](https://ponder.sh/docs/indexing/overview#crash-recovery) If the process crashes and starts back up again using the same exact configuration, the indexing engine attempts a crash recovery. This process is similar to reorg reconciliation, except all unfinalized changes (the entire transaction log) are rolled back. Then, indexing resumes from the finalized block. ### Backfill vs. live indexing[](https://ponder.sh/docs/indexing/overview#backfill-vs-live-indexing) Internally, the indexing engine has two distinct modes – **backfill** and **live** indexing – which use different approaches for fetching data from the RPC. However, indexing function logic works the same way in both modes. As long as the logic is compatible with the expected onchain activity, indexing will work fine in both modes. --- # Write to the database – Ponder [Skip to content](https://ponder.sh/docs/indexing/write#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Write to the database On this page Chevron Right There are two ways to write to the database in a Ponder app. 1. **Store API**: The recommended way to write to the database. 100-1000x faster than raw SQL. 2. **Raw SQL**: A useful escape hatch for logic that's too complex for the Store API. Store API[](https://ponder.sh/docs/indexing/write#store-api) --------------------------------------------------------------- The Store API is a SQL-like query builder optimized for common indexing workloads. All operations run **in-memory** and rows are flushed to the database periodically using efficient `COPY` statements. The examples below use this `ponder.schema.ts` to demonstrate the core concepts. File ponder.schema.ts Copy `import { onchainTable, primaryKey } from "ponder"; export const accounts = onchainTable("accounts", (t) => ({ address: t.hex().primaryKey(), balance: t.bigint().notNull(), nickname: t.text(), })); export const allowances = onchainTable( "allowances", (t) => ({ owner: t.hex().notNull(), spender: t.hex().notNull(), value: t.bigint().notNull(), }), (table) => ({ pk: primaryKey({ columns: [table.owner, table.spender] }), }) );` ### Insert[](https://ponder.sh/docs/indexing/write#insert) Insert one or many rows into the database. Returns the inserted rows, **including** any default values that were generated. File src/index.ts Copy `import { accounts } from "ponder:schema"; // Insert a single row const row = await db.insert(accounts).values({ address: "0x7Df1", balance: 0n }); // Insert multiple rows const rows = await db.insert(accounts).values([ { address: "0x7Df2", balance: -50n }, { address: "0x7Df3", balance: 100n }, ]);` #### Errors[](https://ponder.sh/docs/indexing/write#errors) If you insert a row that's missing a required column value (not null constraint violation), `insert` will reject with an error. File src/index.ts Copy `import { accounts } from "ponder:schema"; const row = await db.insert(accounts).values({ address: "0x7Df1", }); // Error: Column "balance" is required but not present in the values object.` If you insert a duplicate row (unique constraint violation), `insert` will reject with an error. File src/index.ts Copy `import { accounts } from "ponder:schema"; const row = await db.insert(accounts).values({ address: "0x7Df1", }); // Error: Column "balance" is required but not present in the values object.` Use [conflict resolution](https://ponder.sh/docs/indexing/write#conflict-resolution) to ignore unique constraint violations with `onConflictDoNothing` or achieve upsert behavior with `onConflictDoUpdate`. ### Find[](https://ponder.sh/docs/indexing/write#find) Find a single row by primary key. Returns the row, or `null` if not found. The second argument is an object that specifies the primary key value to search for. File src/index.ts Copy `import { accounts } from "ponder:schema"; const row = await db.find(accounts, { address: "0x7Df1" });` If the table has a composite primary key, the object must include a value for each column in the primary key. File src/index.ts Copy `import { allowances } from "ponder:schema"; const row = await db.find(allowances, { owner: "0x7Df1", spender: "0x7Df2" });` ### Update[](https://ponder.sh/docs/indexing/write#update) Update a row by primary key. Returns the updated row. File src/index.ts Copy `import { accounts } from "ponder:schema"; const row = await db .update(accounts, { address: "0x7Df1" }) .set({ balance: 100n });` You can also pass a function to `set`, which receives the existing row and returns the update object. File src/index.ts Copy `import { accounts } from "ponder:schema"; const row = await db .update(accounts, { address: "0x7Df1" }) .set((row) => ({ balance: row.balance + 100n }));` #### Errors[](https://ponder.sh/docs/indexing/write#errors-1) If the target row is not found, `update` will reject with an error. File src/index.ts Copy `import { tokens } from "ponder:schema"; const row = await db .update(accounts, { address: "0xa4F0" }) .set({ balance: 200n }); // Error: No row found for address "0xa4F0".` If the new row violates a not null constraint, `update` will reject with an error. File src/index.ts Copy `import { tokens } from "ponder:schema"; const row = await db .update(accounts, { address: "0x7Df1" }) .set({ balance: null }); // Error: Column "balance" is required but not present in the object.` ### Delete[](https://ponder.sh/docs/indexing/write#delete) Delete a row by primary key. Returns `true` if the row was deleted, or `false` if not found. File src/index.ts Copy `import { accounts } from "ponder:schema"; const deleted = await db.delete(accounts, { address: "0x7Df1" });` ### Conflict resolution[](https://ponder.sh/docs/indexing/write#conflict-resolution) The `insert` method supports conflict resolution. #### `onConflictDoNothing`[](https://ponder.sh/docs/indexing/write#onconflictdonothing) Use `onConflictDoNothing` to skip the insert if the specified row already exists. This avoids unique constraint violation errors. File src/index.ts Copy `import { accounts } from "ponder:schema"; const row = await db .insert(accounts) .values({ address: "0x7Df1", balance: 0n }) .onConflictDoNothing();` #### `onConflictDoUpdate`[](https://ponder.sh/docs/indexing/write#onconflictdoupdate) Use `onConflictDoUpdate` to achieve "upsert" behavior. If the row does not exist, it will be inserted using the specified `values`. Otherwise, the existing row will be updated with the values passed to `onConflictDoUpdate`. File src/index.ts Copy `import { accounts } from "ponder:schema"; const row = await db .insert(accounts) .values({ address: "0x7Df1", balance: 0n }) .onConflictDoUpdate({ value: 200n });` Just like with `update`, you can pass a function to `onConflictDoUpdate` that receives the existing row and returns the update object. File src/index.ts Copy `import { accounts } from "ponder:schema"; const row = await db .insert(accounts) .values({ address: "0x7Df1", balance: 0n }) .onConflictDoUpdate((row) => ({ balance: row.balance + 50n }));` Raw SQL[](https://ponder.sh/docs/indexing/write#raw-sql) ----------------------------------------------------------- ### Query builder[](https://ponder.sh/docs/indexing/write#query-builder) Use `db.sql` to access the raw Drizzle PostgreSQL query builder. This is useful for complex queries that join multiple tables or use advanced SQL features. File src/index.ts Copy ``import { accounts, tradeEvents } from "ponder:schema"; import { eq, and, gte, inArray, sql } from "drizzle-orm"; // Add 100 points to accounts with recent trades await db.sql .update(accounts) .set({ points: sql`${accounts.points} + 100` }) .where( inArray( accounts.address, db.sql .select({ address: tradeEvents.from }) .from(tradeEvents) .where( gte(tradeEvents.timestamp, event.block.timestamp - 24 * 60 * 60) ) ) );`` ### Relational queries[](https://ponder.sh/docs/indexing/write#relational-queries) Use `db.sql.query` to access Drizzle's relational query builder. This provides a type-safe way to write complex `SELECT` queries that join multiple tables. File src/index.ts Copy `import { accounts, tradeEvents } from "ponder:schema"; // Find recent large trades with account details const trades = await db.sql.query.tradeEvents.findMany({ where: (table, { gt, gte, and }) => and( gt(table.amount, 1_000n), gte(table.timestamp, Date.now() - 1000 * 60 * 60) ), limit: 10, with: { account: true }, });` Visit the [Drizzle documentation](https://orm.drizzle.team/docs/rqb) for more details on writing raw SQL queries. --- # Read contract data – Ponder [Skip to content](https://ponder.sh/docs/indexing/read-contracts#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Read contract data On this page Chevron Right Sometimes, indexing function _triggers_ (event logs, traces, etc.) do not contain all of the onchain data you need to build your application. It's often useful to call read-only contract functions, fetch transaction receipts, or simulate contract interactions. Ponder natively supports this pattern through a custom [Viem Client](https://viem.sh/docs/clients/intro) that includes performance & usability improvements specific to indexing. Basic example[](https://ponder.sh/docs/indexing/read-contracts#basic-example) -------------------------------------------------------------------------------- To read data from a contract, use `context.client.readContract()` and include the contract address and ABI from `context.contracts`. ponder.config.tssrc/index.ts File ponder.config.ts Copy `import { createConfig } from "ponder"; import { BlitmapAbi } from "./abis/Blitmap"; export default createConfig({ chains: { mainnet: { id: 1, rpc: process.env.PONDER_RPC_URL_1 }, }, contracts: { Blitmap: { chain: "mainnet", abi: BlitmapAbi, address: "0x8d04...D3Ff63", startBlock: 12439123, }, }, });` Client[](https://ponder.sh/docs/indexing/read-contracts#client) ------------------------------------------------------------------ The `context.client` object is a custom [Viem Client](https://viem.sh/docs/clients/intro) that caches RPC responses. File src/index.ts Copy `import { ponder } from "ponder:registry"; ponder.on("Blitmap:Mint", async ({ event, context }) => { const tokenUri = await context.client.readContract({ abi: context.contracts.Blitmap.abi, address: context.contracts.Blitmap.address, method: "tokenUri", args: [event.args.tokenId], }); });` ### Supported actions[](https://ponder.sh/docs/indexing/read-contracts#supported-actions) The `context.client` object supports most Viem actions. | name | description | Viem docs | | --- | --- | --- | | readContract | Returns the result of a read-only function on a contract. | [readContract](https://viem.sh/docs/contract/readContract) | | multicall | Similar to readContract, but batches requests. | [multicall](https://viem.sh/docs/contract/multicall) | | simulateContract | Simulates & validates a contract interaction. | [simulateContract](https://viem.sh/docs/contract/simulateContract) | | getBalance | Returns the balance of an address in wei. | [getBalance](https://viem.sh/docs/actions/public/getBalance) | | getBytecode | Returns the bytecode at an address. | [getBytecode](https://viem.sh/docs/contract/getBytecode.html) | | getStorageAt | Returns the value from a storage slot at a given address. | [getStorageAt](https://viem.sh/docs/contract/getStorageAt) | | getBlock | Returns information about a block at a block number, hash or tag. | [getBlock](https://viem.sh/docs/actions/public/getBlock) | | getTransactionCount | Returns the number of transactions an account has broadcast / sent. | [getTransactionCount](https://viem.sh/docs/actions/public/getTransactionCount) | | getBlockTransactionCount | Returns the number of Transactions at a block number, hash or tag. | [getBlockTransactionCount](https://viem.sh/docs/actions/public/getBlockTransactionCount) | | getTransaction | Returns information about a transaction given a hash or block identifier. | [getTransaction](https://viem.sh/docs/actions/public/getTransaction) | | getTransactionReceipt | Returns the transaction receipt given a transaction hash. | [getTransactionReceipt](https://viem.sh/docs/actions/public/getTransactionReceipt) | | getTransactionConfirmations | Returns the number of blocks passed (confirmations) since the transaction was processed on a block. | [getTransactionConfirmations](https://viem.sh/docs/actions/public/getTransactionConfirmations) | | call | An Action for executing a new message call. | [call](https://viem.sh/docs/actions/public/call) | | estimateGas | An Action for estimating gas for a transaction. | [estimateGas](https://viem.sh/docs/actions/public/estimateGas) | | getFeeHistory | Returns a collection of historical gas information. | [getFeeHistory](https://viem.sh/docs/actions/public/getFeeHistory) | | getProof | Returns the account and storage values of the specified account including the Merkle-proof. | [getProof](https://viem.sh/docs/actions/public/getProof) | | getEnsAddress | Gets address for ENS name. | [getEnsAddress](https://viem.sh/docs/ens/actions/getEnsAddress) | | getEnsAvatar | Gets the avatar of an ENS name. | [getEnsAvatar](https://viem.sh/docs/ens/actions/getEnsAvatar) | | getEnsName | Gets primary name for specified address. | [getEnsName](https://viem.sh/docs/ens/actions/getEnsName) | | getEnsResolver | Gets resolver for ENS name. | [getEnsResolver](https://viem.sh/docs/ens/actions/getEnsResolver) | | getEnsText | Gets a text record for specified ENS name. | [getEnsText](https://viem.sh/docs/ens/actions/getEnsText) | ### Direct RPC requests[](https://ponder.sh/docs/indexing/read-contracts#direct-rpc-requests) Use the `context.client.request` method to make direct RPC requests. This low-level approach can be useful for advanced RPC request patterns that are not supported by the actions above. File src/index.ts Copy `import { ponder } from "ponder:registry"; ponder.on("ENS:NewOwner", async ({ event, context }) => { const traces = await context.client.request({ method: 'debug_traceTransaction', params: [event.transaction.hash, { tracer: "callTracer" }] }); // ... });` ### Block number[](https://ponder.sh/docs/indexing/read-contracts#block-number) By default, the `blockNumber` option is set to the block number of the current event (`event.block.number`). File src/index.ts Copy `import { ponder } from "ponder:registry"; ponder.on("Blitmap:Mint", async ({ event, context }) => { const totalSupply = await context.client.readContract({ abi: context.contracts.Blitmap.abi, address: context.contracts.Blitmap.address, functionName: "totalSupply", // This is set automatically, no need to include it yourself. // blockNumber: event.block.number, }); });` You can also specify a `blockNumber` to read data at a specific block height. It will still be cached. File src/index.ts Copy `import { ponder } from "ponder:registry"; ponder.on("Blitmap:Mint", async ({ event, context }) => { const totalSupply = await context.client.readContract({ abi: context.contracts.Blitmap.abi, address: context.contracts.Blitmap.address, functionName: "totalSupply", blockNumber: 15439123n, }); });` ### Caching[](https://ponder.sh/docs/indexing/read-contracts#caching) Most RPC requests made using `context.client` are cached in the database. When an indexing function calls a method with a specific set of arguments for the first time, it will make an RPC request. Any subsequent calls to the same method with the same arguments will be served from the cache. See the [full list](https://github.com/ponder-sh/ponder/blob/main/packages/core/src/indexing/client.ts#L73-L121) of cache-enabled RPC methods in the source code. Contract addresses & ABIs[](https://ponder.sh/docs/indexing/read-contracts#contract-addresses--abis) ------------------------------------------------------------------------------------------------------- The `context.contracts` object contains each contract address and ABI you provide in `ponder.config.ts`. ### Multiple chains[](https://ponder.sh/docs/indexing/read-contracts#multiple-chains) If a contract is configured to run on multiple chains, `context.contracts` contains the contract addresses for whichever chain the current event is from. ponder.config.tssrc/index.ts File ponder.config.ts Copy `import { createConfig } from "ponder"; import { UniswapV3FactoryAbi } from "./abis/UniswapV3Factory"; export default createConfig({ chains: { mainnet: { id: 1, rpc: process.env.PONDER_RPC_URL_1 }, base: { id: 8453, rpc: process.env.PONDER_RPC_URL_8453 }, }, contracts: { UniswapV3Factory: { abi: UniswapV3FactoryAbi, chain: { mainnet: { address: "0x1F98431c8aD98523631AE4a59f267346ea31F984", startBlock: 12369621, }, base: { address: "0x33128a8fC17869897dcE68Ed026d694621f6FDfD", startBlock: 1371680, }, }, }, }, });` ### Factory contracts[](https://ponder.sh/docs/indexing/read-contracts#factory-contracts) The `context.contracts` object does not include an `address` property for contracts that use `factory()`. To read data from the contract that emitted the current event, use `event.log.address`. File src/index.ts Copy `import { ponder } from "ponder:registry"; ponder.on("SudoswapPool:Transfer", async ({ event, context }) => { const { SudoswapPool } = context.contracts; // ^? { abi: [...] } const totalSupply = await context.client.readContract({ abi: SudoswapPool.abi, address: event.log.address, functionName: "totalSupply", }); });` To call a factory contract child from an indexing function for a _different_ contract, use your application logic to determine the correct address. For example, the address might come from `event.args`. File src/index.ts Copy `import { ponder } from "ponder:registry"; ponder.on("LendingProtocol:RegisterPool", async ({ event, context }) => { const totalSupply = await context.client.readContract({ abi: context.contracts.SudoswapPool.abi, address: event.args.pool, functionName: "totalSupply", }); });` ### Read a contract without indexing it[](https://ponder.sh/docs/indexing/read-contracts#read-a-contract-without-indexing-it) The `context.contracts` object only contains addresses & ABIs for the contracts in `ponder.config.ts`. To read an external contract, import the ABI object directly and include the address manually. Ad-hoc requests like this are still cached and the block number will be set automatically. ponder.config.tssrc/index.ts File ponder.config.ts Copy `import { createConfig } from "ponder"; import { AaveTokenAbi } from "./abis/AaveToken"; export default createConfig({ contracts: { AaveToken: { chain: "mainnet", abi: AaveTokenAbi, address: "0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9", startBlock: 10926829, }, }, });` More examples[](https://ponder.sh/docs/indexing/read-contracts#more-examples) -------------------------------------------------------------------------------- ### Zorbs gradient data[](https://ponder.sh/docs/indexing/read-contracts#zorbs-gradient-data) Suppose we're building an application that stores the gradient metadata of each [Zorb NFT](https://etherscan.io/address/0xca21d4228cdcc68d4e23807e5e370c07577dd152#code) . Here's a snippet from the contract. File ZorbNft.sol Copy `contract ZorbNft is ERC721 { function mint() public { // ... } function gradientForAddress(address user) public pure returns (bytes[5] memory) { return ColorLib.gradientForAddress(user); } }` Every Zorb has a gradient, but the contract doesn't emit gradient data in any event logs. To read the gradient data for each new Zorb, we can call the `gradientForAddress` function. File src/index.ts Copy `import { ponder } from "ponder:registry"; import { zorbs } from "ponder:schema"; ponder.on("ZorbNft:Transfer", async ({ event, context }) => { if (event.args.from === ZERO_ADDRESS) { // If this is a mint, read gradient metadata from the contract. const gradientData = await context.client.readContract({ abi: context.contracts.ZorbNft.abi, address: context.contracts.ZorbNft.address, functionName: "gradientForAddress", args: [event.args.to], }); await context.db.insert(zorbs).values({ id: event.args.tokenId, gradient: gradientData, ownerId: event.args.to, }); } else { // If not a mint, just update ownership information. await context.db .update(zorbs, { id: event.args.tokenId }) .set({ ownerId: event.args.to }); } });` --- # SQL over HTTP – Ponder [Skip to content](https://ponder.sh/docs/query/sql-over-http#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu SQL over HTTP On this page Chevron Right Ponder natively supports SQL queries over HTTP using the `@ponder/client` and `@ponder/react` packages. The SQL over HTTP is a more powerful alternative to [GraphQL](https://ponder.sh/docs/query/graphql) that offers zero-codegen type inference, live queries, and the flexibility of SQL directly in your client code. Setup[](https://ponder.sh/docs/query/sql-over-http#setup) ------------------------------------------------------------ ### Enable on the server[](https://ponder.sh/docs/query/sql-over-http#enable-on-the-server) Use the `client` Hono middleware to enable SQL over HTTP queries. File src/api/index.ts Copy `import { db } from "ponder:api"; import schema from "ponder:schema"; import { Hono } from "hono"; import { client } from "ponder"; const app = new Hono(); app.use("/sql/*", client({ db, schema })); export default app;` ### Install `@ponder/client`[](https://ponder.sh/docs/query/sql-over-http#install-ponderclient) Install the `@ponder/client` package in your client project. pnpmyarnnpmbun Terminal pnpm Copy `pnpm add @ponder/client` ### Create a query client[](https://ponder.sh/docs/query/sql-over-http#create-a-query-client) Use `createClient` to connect to the server at the path where you registered the middleware. Client project Copy `import { createClient } from "@ponder/client"; const client = createClient("http://localhost:42069/sql");` ### Import `ponder.schema.ts`[](https://ponder.sh/docs/query/sql-over-http#import-ponderschemats) Import your schema from `ponder.schema.ts` and pass it to `createClient`. [Read more](https://ponder.sh/docs/query/sql-over-http#use-schema-from-a-different-repo) about schema portability. Client project Copy `import { createClient } from "@ponder/client"; import * as schema from "../../ponder/ponder.schema"; const client = createClient("http://localhost:42069/sql", { schema });` Now, the client is ready to send SQL over HTTP queries to the server. Client project Copy ``import { createClient } from "@ponder/client"; import * as schema from "../../ponder/ponder.schema"; const client = createClient("http://localhost:42069/sql", { schema }); const result = await client.db.select().from(schema.account); // ^? { address: `0x${string}`; balance: bigint; }[] //`` Setup React[](https://ponder.sh/docs/query/sql-over-http#setup-react) ------------------------------------------------------------------------ The `@ponder/react` package provides React hook bindings for the SQL over HTTP client. This package wraps [TanStack Query](https://tanstack.com/query) , a popular library for managing async state in React. ### Install dependencies[](https://ponder.sh/docs/query/sql-over-http#install-dependencies) Install `@ponder/react` and peer dependencies in your client project. pnpmyarnnpmbun Terminal pnpm Copy `pnpm add @ponder/react @ponder/client @tanstack/react-query` ### Set up `@ponder/client`[](https://ponder.sh/docs/query/sql-over-http#set-up-ponderclient) Follow the [steps above](https://ponder.sh/docs/query/sql-over-http#guide) to set up a SQL over HTTP query client using `@ponder/client`. File lib/ponder.ts Copy `import { createClient } from "@ponder/client"; import * as schema from "../../ponder/ponder.schema"; const client = createClient("http://localhost:42069/sql", { schema });` ### Set up `PonderProvider`[](https://ponder.sh/docs/query/sql-over-http#set-up-ponderprovider) Wrap your app with `PonderProvider` and include the `client` object you created in the previous step. File app/layout.tsx Copy `import { PonderProvider } from "@ponder/react"; import { client } from "../lib/ponder"; function App() { return ( {/** ... */} ); }` ### Set up TanStack Query[](https://ponder.sh/docs/query/sql-over-http#set-up-tanstack-query) Inside the `PonderProvider`, wrap your app with a TanStack Query Provider. If you're already using TanStack Query, you can skip this step. [Read more](https://tanstack.com/query/latest/docs/framework/react/quick-start) about setting up TanStack Query. File app/layout.tsx Copy `import { PonderProvider } from "@ponder/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { client } from "../lib/ponder"; const queryClient = new QueryClient(); function App() { return ( {/** ... */} ); }` Querying[](https://ponder.sh/docs/query/sql-over-http#querying) ------------------------------------------------------------------ The SQL over HTTP client can be used to read indexed data from your Ponder database. It's different than [direct SQL](https://ponder.sh/docs/query/direct-sql) in two key areas: * Schema names are automatically applied on the server using Postgres's [`search_path`](https://www.postgresql.org/docs/current/ddl-schemas.html#DDL-SCHEMAS-PATH) . Table names should be unqualified. * Only a subset of SQL statements are allowed, with limited resources. Read more about [security](https://ponder.sh/docs/query/sql-over-http#security) . index.ts Node.jsindex.ts React File index.ts Node.js Copy `import { desc } from "@ponder/client"; import * as schema from "../../ponder/ponder.schema"; const result = await client.db .select() .from(schema.account) .orderBy(desc(schema.account.balance));` ### Live queries[](https://ponder.sh/docs/query/sql-over-http#live-queries) Live queries are automatically updated when the underlying data changes. Ponder uses Server-Sent Events to stream updates to the **only when the query result changes**. index.ts Node.jsindex.ts React File index.ts Node.js Copy `import { desc } from "@ponder/client"; import * as schema from "../../ponder/ponder.schema"; await client.live( (db) => db.select().from(schema.account).orderBy(desc(schema.account.balance)), (result) => { // ... handle result }, (error) => { // ... handle error }, );` ### Untyped queries[](https://ponder.sh/docs/query/sql-over-http#untyped-queries) It's also possible to query the Ponder database without relying on importing `ponder.schema.ts` using the [`sql`](https://orm.drizzle.team/docs/sql) operator. index.ts Node.jsindex.ts React File index.ts Node.js Copy ``import { sql } from "@ponder/client"; const result = await client.db.execute(sql`SELECT * FROM account limit 10;`);`` ### Pagination[](https://ponder.sh/docs/query/sql-over-http#pagination) The SQL over HTTP supports both limit/offset and cursor-based pagination patterns to handle large result sets efficiently. index.ts Node.jsindex.ts React File index.ts Node.js Copy `import { desc } from "@ponder/client"; import * as schema from "../../ponder/ponder.schema"; const count = await client.db.$count(schema.account); const result = await client.db .select() .from(schema.account) .orderBy(desc(schema.account.balance)) .limit(100) .offset(500);` ### Relational query builder[](https://ponder.sh/docs/query/sql-over-http#relational-query-builder) The SQL over HTTP also supports the [Drizzle query builder](https://orm.drizzle.team/docs/rqb) with some additional setup required. The `createClient` function accepts a `schema` option to enable the Drizzle query builder. Copy `import { createClient } from "@ponder/client"; import * as schema from "../../ponder/ponder.schema"; const client = createClient("http://localhost:42069/sql", { schema }); const result = await client.db.query.account.findMany({ orderBy: (account, { desc }) => desc(account.balance), });` #### React[](https://ponder.sh/docs/query/sql-over-http#react) In addition to the step above, you need to "register" your `schema` globally with TypeScript using [declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) . Copy `import * as schema from "../../ponder/ponder.schema"; declare module "@ponder/react" { interface Register { schema: typeof schema; } }` `usePonderQuery` is now type-safe without the need to import `schema` in every query. Copy `import { usePonderQuery } from "@ponder/react"; const query = usePonderQuery({ queryFn: (db) => db.query.account.findMany({ orderBy: (account, { desc }) => desc(account.balance), }), });` Examples[](https://ponder.sh/docs/query/sql-over-http#examples) ------------------------------------------------------------------ * [Basic usage](https://github.com/ponder-sh/ponder/blob/main/examples/with-client/client/src/index.ts) (`@ponder/client` only) * [Usage with Next.js](https://github.com/ponder-sh/ponder/blob/main/examples/with-nextjs/frontend/src/pages/index.ts#L11-L18) (`@ponder/client` and `@ponder/react`) * Usage with [`useInfiniteQuery`](https://tanstack.com/query/latest/docs/framework/react/guides/infinite-queries) For this example, we'll use the following schema. File ponder.schema.ts Copy `import { onchainTable } from "ponder"; export const person = onchainTable("person", (t) => ({ id: t.integer().primaryKey(), name: t.text().notNull(), age: t.integer(), }));` Get all `person` records with an `age` greater than `32`. Copy `import { asc, gt } from "@ponder/client"; import { usePonderClient } from "@ponder/react"; import { useInfiniteQuery } from "@tanstack/react-query"; const client = usePonderClient(); const personQuery = useInfiniteQuery({ queryKey: ["persons"], queryFn: ({ pageParam }) => client.db .select() .from(schema.person) .where(gt(schema.person.age, 32)) .orderBy(asc(schema.person.id)) .limit(100) .offset(pageParam), initialPageParam: 0, getNextPageParam: (lastPage, pages) => lastPage.length === 100 ? undefined : pages.length * 100, });` * [Uniswap v4](https://github.com/marktoda/v4-ponder) For this example, we'll use the following schema. File ponder.schema.ts Copy `import { index, relations, primaryKey, onchainTable } from "ponder"; export const token = onchainTable("token", (t) => ({ address: t.hex().notNull(), chainId: t.integer().notNull(), name: t.text().notNull(), symbol: t.text().notNull(), decimals: t.integer().notNull(), creationBlock: t.integer().notNull(), }), (table) => ({ pk: primaryKey({ columns: [table.address, table.chainId] }), addressIndex: index().on(table.address), chainIdIndex: index().on(table.chainId), }) ); export const tokenRelations = relations(token, ({ many }) => ({ pools: many(pool), })); export const pool = onchainTable("pool", (t) => ({ poolId: t.hex().notNull(), currency0: t.hex().notNull(), currency1: t.hex().notNull(), fee: t.integer().notNull(), tickSpacing: t.integer().notNull(), hooks: t.hex().notNull(), chainId: t.integer().notNull(), creationBlock: t.integer().notNull(), }), (table) => ({ pk: primaryKey({ columns: [table.poolId, table.chainId] }), poolIdIndex: index().on(table.poolId), chainIdIndex: index().on(table.chainId), }) ); export const poolRelations = relations(pool, ({ many, one }) => ({ token0: one(token, { fields: [pool.currency0, pool.chainId], references: [token.address, token.chainId] }), token1: one(token, { fields: [pool.currency1, pool.chainId], references: [token.address, token.chainId] }), }));` Get all `pool` records with a `chainId` of `1` and a `fee` of `0` and include the `token0` and `token1` data. Copy `import { usePonderQuery } from "@ponder/react"; const query = usePonderQuery({ queryFn: (db) => qb.query.pool.findMany({ where: (pool, { and, eq }) => and(eq(pool.chainId, 1), eq(pool.fee, 0)), orderBy: (pool, { desc }) => desc(pool.creationBlock), with: { token0: true, token1: true, }, }), });` Frequently asked questions[](https://ponder.sh/docs/query/sql-over-http#frequently-asked-questions) ------------------------------------------------------------------------------------------------------ ### Use schema from a different repo[](https://ponder.sh/docs/query/sql-over-http#use-schema-from-a-different-repo) The `@ponder/client` package needs the `onchainTable` objects exported by `ponder.schema.ts` to properly compile the Drizzle queries client-side. If the client project is in a different repo from the Ponder project, there are a few other options: * Import the `ponder.schema.ts` locally using a monorepo. * Publish the `ponder.schema.ts` to npm. * Copy the `ponder.schema.ts` into the client project and install `ponder` to get access to the `onchainTable` function. * Forfeit type safety and use [untyped queries](https://ponder.sh/docs/query/sql-over-http#untyped-queries) . ### Security[](https://ponder.sh/docs/query/sql-over-http#security) Here are the measures taken by the `client` middleware to prevent malicious queries & denial-of-service attacks. These measures aim to achieve a similar level of risk as the GraphQL API. * **Read-only**: Each query statement runs in a `READ ONLY` transaction using autocommit. * **Query validator**: Each query is parsed using [`libpg_query`](https://github.com/pganalyze/libpg_query) and must pass the following checks. * The query AST root must be a `SELECT` statement. Queries containing multiple statements are rejected. * The query must only contain allowed AST node types and built-in SQL functions. For example, `SELECT`, `WHERE`, and `max()` are allowed, but `DELETE`, `SET`, and `pg_advisory_lock()` are not. [Read more](https://github.com/ponder-sh/ponder/blob/main/packages/core/src/client/validate.ts) . * The query must not reference objects in schemas other than the current schema. [Read more](https://ponder.sh/docs/database#database-schema) . * **Resource limits**: The database session uses the following resource limit settings. Copy `SET work_mem = '512MB'; SET statement_timeout = '500ms'; SET lock_timeout = '500ms';` --- # API endpoints – Ponder [Skip to content](https://ponder.sh/docs/query/api-endpoints#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu API endpoints On this page Chevron Right Ponder supports **custom API endpoints** with direct access to the database and other useful resources. Custom API endpoints offer more flexibility than GraphQL or SQL over HTTP queries, making it possible to serve web requests with complex SQL queries, data from external sources, authentication, and more. Guide[](https://ponder.sh/docs/query/api-endpoints#guide) ------------------------------------------------------------ ### Open `src/api/index.ts`[](https://ponder.sh/docs/query/api-endpoints#open-srcapiindexts) Ponder's API server uses [Hono](https://hono.dev/) , a fast and lightweight HTTP router. The `src/api/index.ts` file **must** default export a Hono instance. File src/api/index.ts (minimal) Copy `import { Hono } from "hono"; const app = new Hono(); export default app;` ### Register a route handler[](https://ponder.sh/docs/query/api-endpoints#register-a-route-handler) To customize the server, register routes and middleware on the Hono instance before exporting it. File src/api/index.ts Copy `import { Hono } from "hono"; const app = new Hono(); app.get("/hello", (c) => { return c.text("Hello, world!"); }); export default app;` ### Test the endpoint[](https://ponder.sh/docs/query/api-endpoints#test-the-endpoint) To test the endpoint, start the development server and visit the route in your browser. Response Copy `Hello, world!` Hono supports all HTTP methods, custom middleware, cookies, JSX, and more. Visit the [Hono documentation](https://hono.dev/docs) for more details. Resources[](https://ponder.sh/docs/query/api-endpoints#resources) -------------------------------------------------------------------- The `ponder:api` virtual module includes useful resources for custom API endpoints. ### Database queries[](https://ponder.sh/docs/query/api-endpoints#database-queries) The `db` object exported from `ponder:api` is a ready-to-use Drizzle database instance. This is the same database instance that powers GraphQL and SQL over HTTP queries. Use the `db` object to run read-only SQL queries within custom API endpoints. File src/api/index.ts Copy `import { db } from "ponder:api"; import { accounts } from "ponder:schema"; import { Hono } from "hono"; import { eq } from "ponder"; const app = new Hono(); app.get("/account/:address", async (c) => { const address = c.req.param("address"); const account = await db .select() .from(accounts) .where(eq(accounts.address, address)); return c.json(account); }); export default app;` ### RPC requests[](https://ponder.sh/docs/query/api-endpoints#rpc-requests) The `publicClients` object exported from `ponder:api` is a collection of Viem [Public Client](https://viem.sh/docs/clients/public) objects — one for each chain defined in `ponder.config.ts`, using the same transports. Use these clients to make RPC requests within custom API endpoints. File src/api/index.ts Copy `import { publicClients } from "ponder:api"; import { Hono } from "hono"; const app = new Hono(); app.get("/balance/:address", async (c) => { const address = c.req.param("address"); const balance = await publicClients["base"].getBalance({ address }); return c.json({ address, balance }); }); export default app;` More examples[](https://ponder.sh/docs/query/api-endpoints#more-examples) ---------------------------------------------------------------------------- * [**Basic usage**](https://github.com/ponder-sh/ponder/tree/main/examples/feature-api-functions/src/api/index.ts) - An app with custom endpoints serving ERC-20 data. * [**Usage with offchain data**](https://github.com/ponder-sh/ponder/blob/main/examples/with-offchain/ponder/src/api/index.ts) - An app that includes data from offchain sources. --- # GraphQL – Ponder [Skip to content](https://ponder.sh/docs/query/graphql#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu GraphQL On this page Chevron Right Ponder automatically generates a [GraphQL](https://graphql.org/learn/) API based on the tables, views, and relations defined in `ponder.schema.ts`. Setup[](https://ponder.sh/docs/query/graphql#setup) ------------------------------------------------------ ### Enable on the server[](https://ponder.sh/docs/query/graphql#enable-on-the-server) To enable the GraphQL API, register the `graphql` Hono middleware. File src/api/index.ts Copy `import { db } from "ponder:api"; import schema from "ponder:schema"; import { Hono } from "hono"; import { graphql } from "ponder"; const app = new Hono(); app.use("/graphql", graphql({ db, schema })); export default app;` The `graphql()` middleware includes a [GraphiQL](https://github.com/graphql/graphiql/tree/main/packages/graphiql#graphiql) interface in response to GET requests (you can visit it in your browser). GraphiQL is a useful tool to explore your schema and test queries. Schema generation[](https://ponder.sh/docs/query/graphql#schema-generation) ------------------------------------------------------------------------------ The GraphQL schema includes **singular** and a **plural** query fields based on the tables and views defined in `ponder.schema.ts`. | Type | Singular field | Plural field | | --- | --- | --- | | `onchainTable` | Yes | Yes (offset **and** cursor pagination) | | `onchainView` | No | Yes (offset pagination **only**) | For example, if your schema contains a `person` table, the GraphQL schema will include a `person` and a `persons` field on the root `Query` type. The singular query field returns a single record (or null) and the plural query field returns a page of records. ponder.schema.tsGraphQL schema File ponder.schema.ts Copy `import { onchainTable } from "ponder"; export const person = onchainTable("person", (t) => ({ id: t.integer().primaryKey(), name: t.text().notNull(), age: t.integer(), }));` Filtering[](https://ponder.sh/docs/query/graphql#filtering) -------------------------------------------------------------- Use the `where` argument to filter for records that match certain criteria. The `where` argument type includes filter options for every column defined on a table. Here are the filter options available for each column type. | Filter option | Available for column types | _Include records where {column}..._ | | --- | --- | --- | | `{column}` | All | **equals** the value | | `{column}_not` | All | **does not equal** the value | | `{column}_in` | All primitives and enums | **is one of** the values | | `{column}_not_in` | All primitives and enums | **is not one of** the values | | `{column}_gt` | Numeric primitives | is **greater than** the value | | `{column}_lt` | Numeric primitives | is **less than** the value | | `{column}_gte` | Numeric primitives | is **greater than or equal to** the value | | `{column}_lte` | Numeric primitives | is **less than or equal to** the value | | `{column}_contains` | String primitives | **contains** the substring | | `{column}_not_contains` | String primitives | **does not contain** the substring | | `{column}_starts_with` | String primitives | **starts with** the substring | | `{column}_not_starts_with` | String primitives | **does not start with** the substring | | `{column}_ends_with` | String primitives | **ends with** the substring | | `{column}_not_ends_with` | String primitives | **does not end with** the substring | | `{column}_has` | Lists of primitives and enums | **has** the value as an element | | `{column}_not_has` | Lists of primitives and enums | **does not have** the value as an element | You can compose filters using the `AND` and `OR` operators. These special fields accept an array of filter objects. ### Examples[](https://ponder.sh/docs/query/graphql#examples) For the following examples, assume these records exist in your database. person data Copy `[ { "id": 1, "name": "Barry", "age": 57 }, { "id": 2, "name": "Lucile", "age": 32 }, { "id": 3, "name": "Sally", "age": 22 }, { "id": 4, "name": "Pablo", "age": 71 }, ]` Get all `person` records with an `age` greater than `32`. QueryResult Query Copy `query { persons(where: { age_gt: 32 }) { name age } }` Get all `person` records with a `name` that does not end with `"y"` _and_ an age greater than `60`. Note that when you include multiple filter conditions, they are combined with a logical `AND`. QueryResult Query Copy `query { persons( where: { AND: [ { name_not_ends_with: "y" }, { age_gte: 60 } ] } ) { name age } }` Get all `person` records with a `name` that contains `"ll"` _or_ an age greater than or equal to `50`. In this case, we use the special `OR` operator to combine multiple filter conditions. QueryResult Query Copy `query { persons( where: { OR: [ { name_contains: "ll" }, { age_gte: 50 } ] } ) { name age } }` Sorting[](https://ponder.sh/docs/query/graphql#sorting) ---------------------------------------------------------- Use the `orderBy` and `orderDirection` arguments to sort the result by a column. By default, the result is sorted by the primary key column(s) in ascending order. | Option | Default | | --- | --- | | `orderBy` | Primary key column(s) | | `orderDirection` | `"asc"` | ### Examples[](https://ponder.sh/docs/query/graphql#examples-1) QueryResult Query Copy `query { persons(orderBy: "age", orderDirection: "desc") { name age } }` Pagination[](https://ponder.sh/docs/query/graphql#pagination) ---------------------------------------------------------------- Ponder supports both **cursor** and **offset** pagination through a unified interface on each plural query field and `many()` relationship field. Here are some trade-offs to consider when choosing between cursor and offset pagination. | | Complexity | Query performance | Jump to any page | | --- | --- | --- | --- | | **Cursor pagination** | More complex | Consistently fast | Not supported | | **Offset pagination** | Simple | Slow for large result sets | Supported | ### Page[](https://ponder.sh/docs/query/graphql#page) Each plural field and `many()` relationship field returns a `Page` type which includes a list of items, a `PageInfo` object, and the total count of records that match the query. ponder.schema.tsGenerated schema File ponder.schema.ts Copy `import { onchainTable } from "ponder"; export const pet = onchainTable("pet", (t) => ({ id: t.text().primaryKey(), name: t.text().notNull(), }));` ### Page info[](https://ponder.sh/docs/query/graphql#page-info) The `PageInfo` object contains information about the position of the current page within the result set. | name | type | | | --- | --- | --- | | **startCursor** | `String` | Cursor of the first record in `items` | | **endCursor** | `String` | Cursor of the last record in `items` | | **hasPreviousPage** | `Boolean!` | Whether there are more records before this page | | **hasNextPage** | `Boolean!` | Whether there are more records after this page | The `PageInfo` type is the same for both pagination strategies, but the `startCursor` and `endCursor` values will always be `null` when using offset pagination. ### Total count[](https://ponder.sh/docs/query/graphql#total-count) The `totalCount` field returns the number of records present in the database that match the specified query. The value is the same regardless of the current pagination position and the `limit` argument. Only the `where` argument, or changes to the underlying dataset, can change the value of `totalCount`. ### Cursor pagination[](https://ponder.sh/docs/query/graphql#cursor-pagination) Cursor pagination works by taking the `startCursor` or `endCursor` value from the previous page's `PageInfo` and passing it as the `before` or `after` argument in the next query. #### Cursor values[](https://ponder.sh/docs/query/graphql#cursor-values) Each cursor value is an opaque string that encodes the position of a record in the result set. * Cursor values should not be decoded or manipulated by the client. The only valid use of a cursor value is an argument, e.g. `after: previousPage.endCursor`. * Cursor pagination works with any filter and sort criteria. However, do not change the filter or sort criteria between paginated requests. This will cause validation errors or incorrect results. #### Examples[](https://ponder.sh/docs/query/graphql#examples-2) For the following examples, assume that these records exist in your database. person data Copy `[ { "id": 1, "name": "Barry", "age": 57 }, { "id": 2, "name": "Lucile", "age": 32 }, { "id": 3, "name": "Sally", "age": 22 }, { "id": 4, "name": "Pablo", "age": 71 }, ]` First, make a request without specifying any pagination options. The `items` list will contain the first `n=limit` records that match the filter and sort criteria. Query 1Result 1 Query 1 Copy `query { persons(orderBy: "age", orderDirection: "asc", limit: 2) { items { name age } pageInfo { startCursor endCursor hasPreviousPage hasNextPage } totalCount } }` To paginate forwards, pass `pageInfo.endCursor` from the previous request as the `after` option in the next request. Query 2Result 2 Query 2 Copy `query { persons( orderBy: "age", orderDirection: "asc", limit: 2, after: "Mxhc3NDb3JlLTA=" ) { items { name age } pageInfo { startCursor endCursor hasPreviousPage hasNextPage } totalCount } }` To paginate backwards, pass `pageInfo.startCursor` from the previous request as the `before` option in the next request. Query 3Result 3 Query 3 Copy `query { persons( orderBy: "age", orderDirection: "asc", limit: 2, before: "MxhcdoP9CVBhY" ) { items { name age } pageInfo { startCursor endCursor hasPreviousPage hasNextPage } totalCount } }` ### Offset pagination[](https://ponder.sh/docs/query/graphql#offset-pagination) Offset pagination works by increasing the `offset` argument with each successive query to "skip" rows that have already been fetched. #### Examples[](https://ponder.sh/docs/query/graphql#examples-3) For the following examples, assume that these records exist in your database. person data Copy `[ { "id": 1, "name": "Barry", "age": 57 }, { "id": 2, "name": "Lucile", "age": 32 }, { "id": 3, "name": "Sally", "age": 22 }, { "id": 4, "name": "Pablo", "age": 71 }, ]` First, make a request without specifying any pagination options. The `items` list will contain the first `n=limit` records that match the filter and sort criteria. Query 1Result 1 Query 1 Copy `query { persons(orderBy: "age", orderDirection: "asc", limit: 2) { items { name age } pageInfo { hasPreviousPage hasNextPage } totalCount } }` To paginate forwards, increase the `offset` argument by the `limit` value from the previous request. Query 2Result 2 Query 2 Copy `query { persons( orderBy: "age", orderDirection: "asc", limit: 2, offset: 2 ) { items { name age } pageInfo { hasPreviousPage hasNextPage } totalCount } }` To jump to a specific page, calculate the `offset` as `(pageNumber - 1) * limit`. For example, to get page 2 with a limit of 2, use `offset: 2`. Query 3Result 3 Query 3 Copy `query { persons( orderBy: "age", orderDirection: "asc", limit: 2, offset: 2 # Page 2: (2 - 1) * 2 = 2 ) { items { name age } pageInfo { hasPreviousPage hasNextPage } totalCount } }` You can also use offset pagination with filtering. The `offset` and `limit` are applied after the `where` filter. Query 4Result 4 Query 4 Copy `query { persons( where: { age_gt: 30 }, orderBy: "age", orderDirection: "asc", limit: 1, offset: 1 ) { items { name age } pageInfo { hasPreviousPage hasNextPage } totalCount } }` Relationship fields[](https://ponder.sh/docs/query/graphql#relationship-fields) ---------------------------------------------------------------------------------- The GraphQL schema includes a relationship field for each `one` or `many` relation defined in your schema. One-to-many fields are very similar to the top-level plural query field, except they are automatically filtered by the parent row ID. ponder.schema.tsGenerated schema File ponder.schema.ts Copy `import { onchainTable, relations } from "ponder"; export const pet = onchainTable("pet", (t) => ({ id: t.text().primaryKey(), name: t.text().notNull(), ownerId: t.integer().notNull(), })); export const petRelations = relations(pet, ({ one }) => ({ owner: one(person, { fields: [pet.ownerId], references: [person.id] }), })); export const person = onchainTable("person", (t) => ({ id: t.integer().primaryKey(), })); export const personRelations = relations(person, ({ many }) => ({ dogs: many(pet), }));` Performance tips[](https://ponder.sh/docs/query/graphql#performance-tips) ---------------------------------------------------------------------------- Here are a few tips for speeding up slow GraphQL queries. 1. **Limit query depth**: Each layer of depth in a GraphQL query introduces at least one additional sequential database query. Avoid queries that are more than 2 layers deep. 2. **Use pagination**: Use cursor-based pagination to fetch records in smaller, more manageable chunks. This can help reduce the load on the database. 3. **Consider database indexes**: Consider creating database indexes to speed up filters, joins, and sort conditions. Keep in mind that relations **do not** automatically create indexes. [Read more](https://ponder.sh/docs/schema/tables#indexes) . 4. **Enable horizontal scaling**: If the GraphQL API is struggling to keep up with request volume, consider spreading the load across multiple instances. [Read more](https://ponder.sh/docs/production/self-hosting#scale-the-http-server) . --- # Direct SQL – Ponder [Skip to content](https://ponder.sh/docs/query/direct-sql#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Direct SQL On this page Chevron Right It's often useful to query Ponder tables directly in Postgres. Limitations[](https://ponder.sh/docs/query/direct-sql#limitations) --------------------------------------------------------------------- As a rule of thumb, the _only_ supported operation is `SELECT` queries against Ponder tables. * **Direct SQL queries are read-only**. Direct SQL queries should not insert, update, or delete rows from Ponder tables. * **Direct SQL with zero-downtime deployments requires additional setup**. [Read more](https://ponder.sh/docs/production/self-hosting) about the views pattern in the production guide. * **Triggers on Ponder tables are not supported**. Database schema[](https://ponder.sh/docs/query/direct-sql#database-schema) ----------------------------------------------------------------------------- Ponder uses **database schemas** to isolate deployments. [Read more](https://ponder.sh/docs/production/self-hosting#database-schema) in the self-hosting guide. Direct SQL queries should target the database schema corresponding to a specific deployment. Or, if your project uses the [views pattern](https://ponder.sh/docs/production/self-hosting#views-pattern) (**recommended**), queries should target the views schema. Here are a few strategies to configure the database schema. ### Manual / hard-coded[](https://ponder.sh/docs/query/direct-sql#manual--hard-coded) The easiest way to target a specific database schema is to specify it manually in each SQL query. This works well for projects using the views pattern, because the schema changes less frequently. Direct SQL query Copy `SELECT * FROM my_ponder_project.accounts -- Database schema specified ORDER BY created_at ASC LIMIT 100;` ### Search path[](https://ponder.sh/docs/query/direct-sql#search-path) Another approach is to write direct SQL queries using unqualified table names (no database schema specified), then update the `search_path` setting to include the target database schema. Set search path Copy `SET search_path TO my_ponder_project, "$user", public;` ### Drizzle (`setDatabaseSchema`)[](https://ponder.sh/docs/query/direct-sql#drizzle-setdatabaseschema) If you're writing direct SQL queries using Drizzle (e.g. from a downstream Node.js application), you can use the `setDatabaseSchema` utility function to target a specific database schema. This function mutates a Drizzle schema such that any queries built using that schema will target the specified database schema. [Read more](https://orm.drizzle.team/docs/schemas) in the Drizzle documentation. DrizzleGenerated SQL Drizzle Copy `import { drizzle, asc } from "drizzle-orm/node-postgres"; import { setDatabaseSchema } from "@ponder/client"; import * as schema from "../../ponder/ponder.schema"; setDatabaseSchema(schema, "target_schema"); const db = drizzle(process.env.DATABASE_URL, { schema, casing: "snake_case", }); const oldAccounts = await db .select() .from(schema.accounts) .orderBy(asc(schema.accounts.createdAt)) .limit(100); console.log(oldAccounts);` --- # Deploy on Marble – Ponder [Skip to content](https://ponder.sh/docs/production/marble#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Marble ✨ On this page Chevron Right [Marble](https://marble.xyz/) is a cloud platform purpose-built for Ponder projects, with automatic support for zero downtime deployments, HTTP autoscaling, observability, and much more. Request access[](https://ponder.sh/docs/production/marble#request-access) ---------------------------------------------------------------------------- Marble is currently in private beta. [Request access](https://marble.xyz/) and we'll get back to you soon. --- # Deploy on Railway – Ponder [Skip to content](https://ponder.sh/docs/production/railway#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Railway On this page Chevron Right [Railway](https://railway.app/) 's general-purpose cloud platform is a great starting point for most Ponder apps. Guide[](https://ponder.sh/docs/production/railway#guide) ----------------------------------------------------------- ### Log in to Railway[](https://ponder.sh/docs/production/railway#log-in-to-railway) Connect your GitHub account, and make sure that your Ponder app has been pushed to remote. ### Create a Ponder app service[](https://ponder.sh/docs/production/railway#create-a-ponder-app-service) From the Railway console: 1. Click **New Project** → **Deploy from GitHub repo** and select your repo from the list. 2. Click **Add variables**, then add RPC URLs (e.g. `PONDER_RPC_URL_1`) and other environment variables. 3. Create a public domain. In **Settings** → **Networking**, click **Generate Domain**. 4. Update the start command. In **Settings** → **Deploy**, set the **Custom Start Command** to include the `--schema` option. This is required to enable zero-downtime deployments. [Read more](https://ponder.sh/docs/database#database-schema) . pnpmyarnnpmbun Terminal pnpm Copy `pnpm start --schema $RAILWAY_DEPLOYMENT_ID` 5. Set the healthcheck path and timeout. In **Settings** → **Deploy**, set the **Healthcheck Path** to `/ready` and the **Healthcheck Timeout** to `3600` seconds (1 hour, the maximum allowed). ### Create a Postgres database[](https://ponder.sh/docs/production/railway#create-a-postgres-database) From the new project dashboard: 1. Click **Create** → **Database** → **Add PostgreSQL** 2. Open the **Variables** tab for the Ponder app service, click **New Variable** → **Add Reference** → select `DATABASE_URL` and click **Add** After a moment, the service running `ponder start` should redeploy successfully. Check the **Build Logs** and **Deploy Logs** tabs to debug any issues. --- # Self-hosting – Ponder [Skip to content](https://ponder.sh/docs/production/self-hosting#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Self-hosting On this page Chevron Right In general, hosting a Ponder app is similar to hosting an ordinary Node.js web server. This section describes the key Ponder-specific quirks to consider when self-hosting in production. Database connection[](https://ponder.sh/docs/production/self-hosting#database-connection) -------------------------------------------------------------------------------------------- Ponder works best with a Postgres database running in the same private network. Set the `DATABASE_URL` environment variable to the connection string of your Postgres database, or manually override the `database.connectionString` option in `ponder.config.ts`. File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ database: { kind: "postgres", connectionString: "postgres://user:password@mycloud.internal:5432/database", }, // ... });` Database schema[](https://ponder.sh/docs/production/self-hosting#database-schema) ------------------------------------------------------------------------------------ Ponder uses **database schemas** to isolate deployments. Each deployment must use a different schema. Use the `DATABASE_SCHEMA` environment variable or the `--schema` CLI argument to specify which database schema a deployment should use. [Read more](https://ponder.sh/docs/database#database-schema) about database schema selection rules. It typically makes sense to automate the database schema for each deployment. Here are a few common options. * Kubernetes pod name * Git branch name or commit hash * Railway deployment ID ### Views pattern[](https://ponder.sh/docs/production/self-hosting#views-pattern) The **views pattern** makes the _latest_ deployment's data available in a _static_ database schema using [database views](https://www.postgresql.org/docs/current/tutorial-views.html) . This makes it possible to write direct SQL queries that always target the latest deployment's tables — without requiring a configuration change after each deployment. Ponder natively supports this pattern in two ways. * **Standalone CLI command**: The `ponder db create-views` CLI command creates (or updates) views in the target schema to point at the specified deployment's tables. Copy `pnpm db create-views --schema=deployment-123 --views-schema=project-name` * **Automated**: The `ponder start` command also accepts the `--views-schema` CLI flag. When specified, the deployment will run the `ponder db create-views` command automatically as soon as it becomes ready (when the backfill is complete). Copy `pnpm start --schema=deployment-123 --views-schema=project-name` Health checks & probes[](https://ponder.sh/docs/production/self-hosting#health-checks--probes) ------------------------------------------------------------------------------------------------- Use the `/health` and `/ready` endpoints to configure health checks or [probes](https://kubernetes.io/docs/concepts/configuration/liveness-readiness-startup-probes/) . * **`/health`**: Returns status code `200` immediately after the process starts. * **`/ready`**: Returns status code `200` once indexing progress has reached realtime across all chains. During the backfill, this endpoint returns status code `503`. Crash recovery[](https://ponder.sh/docs/production/self-hosting#crash-recovery) ---------------------------------------------------------------------------------- If a Ponder app running `ponder start` crashes and restarts using the same database schema, it will attempt to resume indexing where it left off. [Read more](https://ponder.sh/docs/api-reference/ponder/database) about the instance lifecycle and crash recovery mechanism. Advanced[](https://ponder.sh/docs/production/self-hosting#advanced) ---------------------------------------------------------------------- ### Resource Usage[](https://ponder.sh/docs/production/self-hosting#resource-usage) Ponder dynamically adjusts it's memory usage based on availability. To adjust the memory limit, set the `--max-old-space-size` flag for Node.js. [Read more](https://nodejs.org/api/cli.html#--max-old-space-sizesize-in-mib) . When using `ordering: "experimental_isolated"`, Ponder will use up to 4 CPU cores to index blocks in parallel. ### Scale the HTTP server[](https://ponder.sh/docs/production/self-hosting#scale-the-http-server) If a `ponder start` instance receives a large volume of HTTP traffic (e.g. GraphQL requests), the HTTP server will contend with the indexing engine for CPU and memory resources. This can lead to degraded indexing performance and might ultimately crash the instance. To solve this problem, you can use `ponder serve` to run standalone instances of the HTTP server without the indexing engine. Here are a few things to keep in mind. * The `ponder serve` instance should use the same [database schema](https://ponder.sh/docs/production/self-hosting#database-schema) as the `ponder start` instance that you'd like to scale. * If one `ponder serve` instance is not enough, it's safe to run multiple replicas behind a proxy. ### Database maintenance[](https://ponder.sh/docs/production/self-hosting#database-maintenance) The `ponder db` CLI entrypoint offers a set of commands useful for observing and maintaining your database. [Read more](https://ponder.sh/docs/api-reference/ponder/cli) . --- # Factory pattern – Ponder [Skip to content](https://ponder.sh/docs/guides/factory#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Factory pattern On this page Chevron Right The factory pattern makes it possible to index a dynamic list of contracts. You can think of the `factory()` function as returning a list of addresses that updates whenever a log is found that matches the configuration. Basic usage[](https://ponder.sh/docs/guides/factory#basic-usage) ------------------------------------------------------------------- To configure a factory, use the `factory()` function as the `address` value in a contract or account. File ponder.config.ts Copy `import { createConfig, factory } from "ponder"; import { parseAbiItem } from "viem"; import { SudoswapPoolAbi } from "./abis/SudoswapPool"; export default createConfig({ chains: { /* ... */ }, contracts: { SudoswapPool: { abi: SudoswapPoolAbi, chain: "mainnet", address: factory({ // Address of the factory contract. address: "0xb16c1342E617A5B6E4b631EB114483FDB289c0A4", // Event from the factory contract ABI which contains the child address. event: parseAbiItem("event NewPair(address poolAddress)"), // Name of the event parameter containing the child address. parameter: "poolAddress", }), startBlock: 14645816, }, }, });` The indexing functions you register for a contract (or account) that uses `factory()` will process events for _all_ contracts (or accounts) found by the factory configuration. Visit the [contracts](https://ponder.sh/docs/config/contracts) and [accounts](https://ponder.sh/docs/config/accounts) guides to learn more. Index the factory contract itself[](https://ponder.sh/docs/guides/factory#index-the-factory-contract-itself) --------------------------------------------------------------------------------------------------------------- It's often useful to register an indexing function for the factory event itself, e.g. to run setup logic for each child contract. To index the factory contract itself, add a new entry to `contracts`. This entry should be a normal contract with a single address. ponder.config.tssrc/index.ts File ponder.config.ts Copy `import { createConfig, factory } from "ponder"; import { parseAbiItem } from "viem"; import { SudoswapPoolAbi } from "./abis/SudoswapPool"; import { SudoswapFactoryAbi } from "./abis/SudoswapFactory"; export default createConfig({ chains: { /* ... */ }, contracts: { SudoswapFactory: { abi: SudoswapFactoryAbi, chain: "mainnet", address: "0xb16c1342E617A5B6E4b631EB114483FDB289c0A4", startBlock: 14645816, }, SudoswapPool: { abi: SudoswapPoolAbi, chain: "mainnet", address: factory({ address: "0xb16c1342E617A5B6E4b631EB114483FDB289c0A4", event: parseAbiItem("event NewPair(address poolAddress)"), parameter: "poolAddress", }), startBlock: 14645816, }, }, });` Multiple factories, same child[](https://ponder.sh/docs/guides/factory#multiple-factories-same-child) -------------------------------------------------------------------------------------------------------- Sometimes, multiple factory contracts 1) have the same factory event signature and 2) create the same kind of child contract. In these cases, you can pass a list of factory contract addresses to the `factory()` function, and the list of child addresses across all factories will be merged into a single list. File ponder.config.ts Copy `import { createConfig } from "ponder"; import { parseAbiItem } from "viem"; import { SudoswapPoolAbi } from "./abis/SudoswapPool"; export default createConfig({ chains: { /* ... */ }, contracts: { SudoswapPool: { abi: SudoswapPoolAbi, chain: "mainnet", address: factory({ // A list of factory contract addresses that all create SudoswapPool contracts. address: [ "0xb16c1342E617A5B6E4b631EB114483FDB289c0A4", "0xb16c1342E617A5B6E4b631EB114483FDB289c0A4", ], event: parseAbiItem("event NewPair(address poolAddress)"), parameter: "poolAddress", }), }, }, });` Usage with non-factory contracts[](https://ponder.sh/docs/guides/factory#usage-with-non-factory-contracts) ------------------------------------------------------------------------------------------------------------- The factory pattern is simply a primitive that extracts a list of addresses from event logs. The contract does not have to be a "factory" in the EVM/Solidity sense (it doesn't need to create other contracts). For example, the `ENSRegistry` contract emits the `NewResolver` event whenever a name's resolver gets set or changed. We can use this event with the factory pattern to index all resolvers that have ever been used. File ponder.config.ts Copy `import { createConfig, factory } from "ponder"; import { parseAbiItem } from "viem"; import { ENSResolverAbi } from "./abis/ENSResolver"; export default createConfig({ chains: { /* ... */ }, contracts: { ENSResolver: { abi: ENSResolverAbi, chain: "mainnet", address: factory({ // ENS Registry address address: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", event: parseAbiItem("event NewResolver(bytes32 indexed node, address resolver)"), parameter: "resolver", }), }, }, });` Factory block range[](https://ponder.sh/docs/guides/factory#factory-block-range) ----------------------------------------------------------------------------------- In some cases, the indexer needs to collect child addresses from the factory before indexing it. In these cases, to save indexing time you can specify `startBlock` and `endBlock` of the factory separately. The `startBlock` option specifies the block number to begin collecting factory children from. The `endBlock` option specifies the block number to stop collecting factory children at. Both options default to contract's `startBlock` and `endBlock` respectively. This separation allows you to, for example, scan the entire factory history (from its deployment) to collect all existing child contracts, while then indexing those contracts starting from a more recent block. File ponder.config.ts Copy `import { createConfig, factory } from "ponder"; import { parseAbiItem } from "viem"; import { SudoswapPoolAbi } from "./abis/SudoswapPool"; export default createConfig({ chains: { /* ... */ }, contracts: { SudoswapPool: { abi: SudoswapPoolAbi, chain: "mainnet", address: factory({ address: "0xb16c1342E617A5B6E4b631EB114483FDB289c0A4", event: parseAbiItem("event NewPair(address poolAddress)"), parameter: "poolAddress", startBlock: 14645816, }), startBlock: "latest", }, }, });` How it works[](https://ponder.sh/docs/guides/factory#how-it-works) --------------------------------------------------------------------- 1. Fetch logs emitted by the factory contract that match the configuration using `eth_getLogs` in bulk. 2. Decode each log using the provided `event` ABI item and extract the child address from it using the `parameter` name. 3. Fetch the requested data for each child contract – usually, more logs using `eth_getLogs` in bulk. Performance[](https://ponder.sh/docs/guides/factory#performance) ------------------------------------------------------------------- As of version `0.10`, the factory pattern introduces negligible overhead to the indexing process. Limitations[](https://ponder.sh/docs/guides/factory#limitations) ------------------------------------------------------------------- ### Event signature requirements[](https://ponder.sh/docs/guides/factory#event-signature-requirements) The factory contract must emit an event log announcing the creation of each new child contract that contains the new child contract address as a named parameter (with type `"address"`). The parameter can be either indexed or non-indexed. Here are a few factory event signatures with their eligibility explained: Copy `// ✅ Eligible. The parameter "child" has type "address" and is non-indexed. event ChildContractCreated(address child); // ✅ Eligible. The parameter "pool" has type "address" and is indexed. event PoolCreated(address indexed deployer, address indexed pool, uint256 fee); // ❌ Not eligible. The parameter "contracts" is an array type, which is not supported. // Always emit a separate event for each child contract, even if they are created in a batch. event ContractsCreated(address[] contracts); // ❌ Not eligible. The parameter "child" is a struct/tuple, which is not supported. struct ChildContract { address addr; } event ChildCreated(ChildContract child);` ### Nested factory patterns[](https://ponder.sh/docs/guides/factory#nested-factory-patterns) Ponder does not support factory patterns that are nested beyond a single layer. --- # Isolated indexing – Ponder [Skip to content](https://ponder.sh/docs/guides/isolated-indexing#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Isolated indexing On this page Chevron Right The **isolated** ordering mode runs each chain independently across several worker threads. When to use[](https://ponder.sh/docs/guides/isolated-indexing#when-to-use) ----------------------------------------------------------------------------- Isolated indexing is a good choice for projects with the following characteristics. * The project was already using `"multichain"` ordering. * The indexing logic on each chain is **completely independent**. This means that indexing on each chain can logically proceed regardless of indexing progress on other chains. * Backfill duration is a priority, and other performance optimizations are already in place (minimize raw SQL, minimize contract calls). Guide[](https://ponder.sh/docs/guides/isolated-indexing#guide) ----------------------------------------------------------------- ### Enable isolated mode[](https://ponder.sh/docs/guides/isolated-indexing#enable-isolated-mode) To enable isolated mode, use the `ordering` option in `ponder.config.ts`. File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ ordering: "experimental_isolated", // ... });` ### Update primary keys[](https://ponder.sh/docs/guides/isolated-indexing#update-primary-keys) When using isolated mode, each table must have a **composite primary key** that includes a column named `chainId`. If any tables don't, the build step will fail. In this example, the `balance` column value would represent the balance on a specific chain. File ponder.schema.ts Copy `import { onchainTable } from "ponder"; export const account = onchainTable( "account", (t) => ({ chainId: t.integer().notNull(), address: t.hex().notNull(), balance: t.bigint().notNull(), }), (table) => ({ pk: primaryKey({ columns: [table.chainId, table.address] }) }) );` It also generally makes sense to include the `chainId` column in index definitions. File ponder.schema.ts Copy `export const transferEvent = onchainTable( "transfer_event", (t) => ({ chainId: t.integer().notNull(), id: t.text().notNull(), from: t.hex().notNull(), to: t.hex().notNull(), amount: t.bigint().notNull(), }), (table) => ({ pk: primaryKey({ columns: [table.chainId, table.id] }), fromIdx: index().on(table.chainId, table.from), }) );` ### Update indexing logic[](https://ponder.sh/docs/guides/isolated-indexing#update-indexing-logic) Next, update your indexing logic to include `chainId` when inserting data using the `context.chain.id` value. File src/index.ts Copy `import { ponder } from "ponder:registry"; ponder.on("ERC20:Transfer", async ({ event, context }) => { await context.db .insert(account) .values({ chainId: context.chain.id, address: event.args.to, balance: 0, }) .onConflictDoUpdate((row) => ({ balance: row.balance + event.args.value })); // ... })` Frequently asked questions[](https://ponder.sh/docs/guides/isolated-indexing#frequently-asked-questions) ----------------------------------------------------------------------------------------------------------- #### How many threads does it use?[](https://ponder.sh/docs/guides/isolated-indexing#how-many-threads-does-it-use) At the moment, Ponder uses a maximum of 4 threads. If there are more than 4 chains, the chains will be distributed evenly across the 4 threads. The maximum thread count will likely be made configurable as the feature matures. --- # Call traces – Ponder [Skip to content](https://ponder.sh/docs/guides/call-traces#vocs-content) Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Call traces On this page Chevron Right Ponder supports indexing **call traces**, which represent a _function call_ instead of an event log. Guide[](https://ponder.sh/docs/guides/call-traces#guide) ----------------------------------------------------------- ### Enable call traces[](https://ponder.sh/docs/guides/call-traces#enable-call-traces) To enable call traces, use the `includeCallTraces` option on the contract configuration. File ponder.config.ts Copy `import { createConfig } from "ponder"; import { BlitmapAbi } from "./abis/Blitmap"; export default createConfig({ contracts: { Blitmap: { abi: BlitmapAbi, chain: "mainnet", address: "0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63", startBlock: 12439123, includeCallTraces: true, }, }, // ... });` ### Register an indexing function[](https://ponder.sh/docs/guides/call-traces#register-an-indexing-function) Now, each function in the contract ABI will become available as an indexing function event name using the `"ContractName.functionName()"` scheme. File src/index.ts Copy `import { ponder } from "ponder:registry"; ponder.on("Blitmap.mintOriginal()", async ({ event }) => { event.args; // ^? [tokenData: Hex, name: string] event.trace.gasUsed; // ^? bigint });` What is a call trace?[](https://ponder.sh/docs/guides/call-traces#what-is-a-call-trace) ------------------------------------------------------------------------------------------ Let's define call traces from three different perspectives. * **Ponder**: A call trace is similar to a log, but it represents a function call instead of an event log. You can register an indexing function that will run whenever a specific function on one of your contracts gets called. * **Solidity**: A call trace records a function call. For example, whenever someone calls the `transfer(address to, uint256 amount)` function of an ERC20 token contract, it produces a call trace. * **EVM**: A call trace records the execution of the [`CALL`, `STATICCALL`, `DELEGATECALL`, or `CALLCODE`](https://www.evm.codes/#f1) opcode within a transaction. ### Top-level vs. internal calls[](https://ponder.sh/docs/guides/call-traces#top-level-vs-internal-calls) A call trace can be a **top-level call** or an **internal call**. A top-level call is from an externally-owned account, and an internal call is from another contract. To check if a call trace is a top-level call, use `event.trace.traceAddress.length === 0`. File src/index.ts Copy `ponder.on("ERC20.transfer()", async ({ event }) => { const isTopLevelCall = event.trace.traceAddress.length === 0; // ... });` ### `eth_call` and `view` functions[](https://ponder.sh/docs/guides/call-traces#eth_call-and-view-functions) The `eth_call` RPC method **does not** produce a call trace. These calls do not occur during the execution of a transaction, so they are not recorded as call traces. However, calls made to `view` or `pure` functions **do** produce call traces if they are made during the execution of a transaction. These call traces are rarely useful for indexing, but they do happen. --- # Transaction receipts – Ponder [Skip to content](https://ponder.sh/docs/guides/receipts#vocs-content) Introducing [Marble](https://marble.xyz/) , the company behind Ponder Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Transaction receipts On this page Chevron Right A **transaction receipt** is an object containing the _post-execution_ results of a transaction, including the price and amount of gas consumed, the revert status, the logs emitted, and more. Guide[](https://ponder.sh/docs/guides/receipts#guide) -------------------------------------------------------- Ponder supports transaction receipts with the `includeTransactionReceipts` option, **or** dynamic RPC requests through `context.client`. ### Include receipts for every event[](https://ponder.sh/docs/guides/receipts#include-receipts-for-every-event) To fetch the transaction receipt associated with every event produced by a contract, use the `includeTransactionReceipts` option. File ponder.config.ts Copy `import { createConfig } from "ponder"; import { BlitmapAbi } from "./abis/Blitmap"; export default createConfig({ contracts: { Blitmap: { abi: BlitmapAbi, chain: "mainnet", address: "0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63", includeTransactionReceipts: true, startBlock: 12439123, }, }, // ... });` Once enabled, the `event.transactionReceipt` object will become available in your indexing functions. File src/index.ts Copy `import { ponder } from "ponder:registry"; ponder.on("Blitmap:Mint", async ({ event }) => { console.log(event.transactionReceipt); // ... });` ### Fetch receipts ad-hoc[](https://ponder.sh/docs/guides/receipts#fetch-receipts-ad-hoc) If you only need the transaction receipt in special cases, or you need the `logs` array, use the `context.client.getTransactionReceipt` method within your indexing function logic. File src/index.ts Copy `import { ponder } from "ponder:registry"; ponder.on("Blitmap:Mint", async ({ event }) => { const receipt = await context.client.getTransactionReceipt({ hash: event.transaction.hash }); console.log(receipt); // ... });` --- # Offchain data – Ponder [Skip to content](https://ponder.sh/docs/guides/offchain-data#vocs-content) Introducing [Marble](https://marble.xyz/) , the company behind Ponder Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Offchain data On this page Chevron Right Ponder supports querying onchain data (Ponder-managed tables) alongside offchain data (other tables in the same PostgreSQL database) using Drizzle. Setup[](https://ponder.sh/docs/guides/offchain-data#setup) ------------------------------------------------------------- ### Define the Ponder schema[](https://ponder.sh/docs/guides/offchain-data#define-the-ponder-schema) Create a `ponder.schema.ts` file in your Ponder project. No special configuration is required. File ponder.schema.ts Copy `import { onchainTable } from "ponder"; export const account = onchainTable("account", (t) => ({ address: t.hex().primaryKey(), })); export const token = onchainTable("token", (t) => ({ id: t.bigint().primaryKey(), owner: t.hex().notNull(), })); export const transferEvent = onchainTable("transfer_event", (t) => ({ id: t.text().primaryKey(), timestamp: t.integer().notNull(), from: t.hex().notNull(), to: t.hex().notNull(), token: t.bigint().notNull(), }));` ### Define the offchain schema[](https://ponder.sh/docs/guides/offchain-data#define-the-offchain-schema) Next, create a schema definition file for the offchain data using Drizzle ([docs](https://orm.drizzle.team/docs/sql-schema-declaration#example) ). File offchain.ts Copy `import { json, numeric, pgSchema } from "drizzle-orm/pg-core"; export const offchainSchema = pgSchema("offchain"); export const metadataTable = offchainSchema.table("metadata", { tokenId: numeric({ precision: 78, scale: 0 }).primaryKey(), metadata: json(), });` ### Combine the schemas[](https://ponder.sh/docs/guides/offchain-data#combine-the-schemas) In a separate file, combine the onchain and offchain schemas. You can also define Drizzle relations that connect tables across the two schemas. File schema.ts Copy `import { setDatabaseSchema } from "@ponder/client"; import { relations } from "drizzle-orm"; import * as ponderSchema from "./ponder.schema"; import * as offchainSchema from "./offchain"; setDatabaseSchema(ponderSchema, "prod"); export const metadataRelations = relations( offchainSchema.metadataTable, ({ one }) => ({ token: one(ponderSchema.token, { fields: [offchainSchema.metadataTable.tokenId], references: [ponderSchema.token.id], }), }), ); export const schema = { ...offchainSchema, ...ponderSchema, metadataRelations, };` Query[](https://ponder.sh/docs/guides/offchain-data#query) ------------------------------------------------------------- ### From Node.js[](https://ponder.sh/docs/guides/offchain-data#from-nodejs) Once you've defined the combined schema, you can start writing queries from any Node.js code. File index.ts Copy `import { drizzle } from "drizzle-orm/node-postgres"; import { schema } from "./schema"; export const db = drizzle(process.env.DATABASE_URL!, { schema }); const result = await db.query.metadataTable.findMany({ with: { token: true, }, }); console.log(result);` ### From Ponder API endpoints[](https://ponder.sh/docs/guides/offchain-data#from-ponder-api-endpoints) It's also possible to query offchain (or combined) data directly from custom API endpoints. File api/index.ts Copy `import { Hono } from "hono"; import { db } from "ponder:api"; import * as offchainSchema from "../../offchain"; const app = new Hono(); app.post("/new-metadata", async (c) => { const { tokenId, metadata } = await c.req.json(); await db.insert(offchainSchema.metadataTable).values({ tokenId, metadata, }); }); export default app;` --- # Foundry – Ponder [Skip to content](https://ponder.sh/docs/guides/foundry#vocs-content) Introducing [Marble](https://marble.xyz/) , the company behind Ponder Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Foundry On this page Chevron Right Ponder works with Anvil, the local development chain included with Foundry. Foundry projects follow various development workflows (test-driven, deploy to a fresh chain, deploy to a fork, etc). This guide offers patterns that you can adapt to your specific requirements. Configure the `anvil` chain[](https://ponder.sh/docs/guides/foundry#configure-the-anvil-chain) ------------------------------------------------------------------------------------------------- ### Disable caching[](https://ponder.sh/docs/guides/foundry#disable-caching) Ponder's RPC request cache works well for live chains where the chain is generally immutable, but causes issues when indexing a local chain that "resets". Use the `disableCache` option to **disable RPC request caching** for the Anvil chain. With this option set to true, Ponder will clear the cache on start up and between hot reloads. File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ chains: { anvil: { id: 31337, rpc: "http://127.0.0.1:8545", disableCache: true, }, }, // ... });` ### Chain ID[](https://ponder.sh/docs/guides/foundry#chain-id) We recommend using `31337` (the default Anvil chain ID) even when forking a live chain. This avoids common footguns when working with multiple chains. ### Mining mode[](https://ponder.sh/docs/guides/foundry#mining-mode) We recommend using [interval mining](https://book.getfoundry.sh/reference/anvil/#mining-modes) with a block time of ~2 seconds. This better simulates a live chain. Generate ABI files[](https://ponder.sh/docs/guides/foundry#generate-abi-files) --------------------------------------------------------------------------------- To enable end-to-end type safety, the contract ABIs generated by Foundry must be copied into TypeScript (`.ts`) source files. ### Wagmi CLI[](https://ponder.sh/docs/guides/foundry#wagmi-cli) The Wagmi CLI [Foundry plugin](https://wagmi.sh/cli/api/plugins/foundry) is an excellent tool to automate tedious ABI file management. For more information, visit the [Wagmi CLI documentation](https://wagmi.sh/cli/getting-started) . Here is the Wagmi CLI config file used by the Foundry [example project](https://github.com/ponder-sh/ponder/tree/main/examples/with-foundry) . File wagmi.config.ts Copy `import { defineConfig } from "@wagmi/cli"; import { foundry } from "@wagmi/cli/plugins"; export default defineConfig({ out: "abis/CounterAbi.ts", plugins: [ foundry({ project: "foundry", include: ["Counter.sol/**"], }), ], });` Import broadcast files[](https://ponder.sh/docs/guides/foundry#import-broadcast-files) ----------------------------------------------------------------------------------------- Foundry scripts write transaction inputs and receipts to JSON files in the `broadcast` directory. You can import these files directly into `ponder.config.ts` to automate address management and enable hot reloading. ### Automate address management[](https://ponder.sh/docs/guides/foundry#automate-address-management) To read the contract address and deployment block number from a broadcast file, import the file directly into `ponder.config.ts` and access properties from the JSON object. The `ponder.config.ts` file from the Foundry [example project](https://github.com/ponder-sh/ponder/tree/main/examples/with-foundry) demonstrates this pattern. Here, the first transaction in the broadcast file deployed the `Counter.sol` contract. The location of the contract address and start block within the broadcast file depends on the order and number of transactions in your deployment script. File ponder.config.ts Copy `import { createConfig } from "ponder"; import { http, getAddress, hexToNumber } from "viem"; import { counterABI } from "../abis/CounterAbi"; import CounterDeploy from "../foundry/broadcast/Deploy.s.sol/31337/run-latest.json"; const address = getAddress(CounterDeploy.transactions[0]!.contractAddress); const startBlock = hexToNumber(CounterDeploy.receipts[0]!.blockNumber); export default createConfig({ chains: { anvil: { id: 31337, rpc: "http://127.0.0.1:8545", disableCache: true, }, }, contracts: { Counter: { chain: "anvil", abi: counterABI, address, startBlock, }, }, });` ### Enable hot reloading[](https://ponder.sh/docs/guides/foundry#enable-hot-reloading) If you import a JSON broadcast file in `ponder.config.ts`, the dev server will reload each time that file changes. This is a simple way to ensure that Ponder reloads every time you run a Foundry deployment script. File ponder.config.ts Copy `import { createConfig } from "ponder"; import CounterDeploy from "../foundry/broadcast/Deploy.s.sol/31337/run-latest.json"; // // ^ The development server detects changes to this file and triggers a hot reload. export default createConfig({ // ... });` --- # Time-series data – Ponder [Skip to content](https://ponder.sh/docs/guides/time-series#vocs-content) Introducing [Marble](https://marble.xyz/) , the company behind Ponder Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Time-series data On this page Chevron Right This guide describes techniques for working with time-series data in your app. Candlestick charts (bucketing)[](https://ponder.sh/docs/guides/time-series#candlestick-charts-bucketing) ----------------------------------------------------------------------------------------------------------- To power a [candlestick](https://en.wikipedia.org/wiki/Candlestick_chart) or open-high-low-close chart, create a table that stores OHLC data for a specific time interval. File ponder.schema.ts Copy `import { onchainTable } from "ponder"; export const hourBuckets = onchainTable("hour_buckets", (t) => ({ id: t.integer().primaryKey(), open: t.real().notNull(), close: t.real().notNull(), low: t.real().notNull(), high: t.real().notNull(), average: t.real().notNull(), count: t.integer().notNull(), }));` Then, in your indexing function, create or update the bucket record that the current event falls into. File src/index.ts Copy `import { ponder, type Schema } from "ponder:registry"; const secondsInHour = 60 * 60; ponder.on("Token:Swap", async ({ event, context }) => { const { timestamp } = event.block; const { price } = event.args; const hourId = Math.floor(timestamp / secondsInHour) * secondsInHour; await context.db .insert(hourBuckets) .values({ id: hourId, open: price, close: price, low: price, high: price, average: price, count: 1, }) .onConflictDoUpdate((row) => ({ close: price, low: Math.min(row.low, price), high: Math.max(row.high, price), average: (row.average * row.count + price) / (row.count + 1), count: row.count + 1, })); });` Here are GraphQL and SQL queries that return the last 48 hours of OHLC data. These queries could be used to power a chart on your frontend. GraphQL querySQL query GraphQL query Copy `{ hourBuckets(orderBy: { id: "desc" }, limit: 48) { items { id open close low high average count } } }` Include `block.timestamp`[](https://ponder.sh/docs/guides/time-series#include-blocktimestamp) ------------------------------------------------------------------------------------------------ The simplest way to add a time dimension to your data is to include the block number or block timestamp (or both!) as a column. ponder.schema.tssrc/index.ts File ponder.schema.ts Copy `import { onchainTable } from "ponder"; export const swapEvents = onchainTable("swap_events", (t) => ({ id: t.text().primaryKey(), from: t.hex().notNull(), to: t.hex().notNull(), amount: t.bigint().notNull(), timestamp: t.bigint().notNull(), }));` Now, you can use the `timestamp` column to filter and sort data over different time intervals. GraphQL query Copy `{ swapEvents( orderBy: { timestamp: "desc" } where: { timestamp_gt: 1712500000, timestamp_lt: 1713000000 } ) { items { id from to amount timestamp } } }` --- # Custom views – Ponder [Skip to content](https://ponder.sh/docs/schema/views#vocs-content) Introducing [Marble](https://marble.xyz/) , the company behind Ponder Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Views On this page Chevron Right Ponder supports custom [PostgreSQL views](https://www.postgresql.org/docs/current/tutorial-views.html) defined using Drizzle. Usage[](https://ponder.sh/docs/schema/views#usage) ----------------------------------------------------- To define a view, use the `onchainView` function and write a query using Drizzle that references other tables or views in `ponder.schema.ts`. File ponder.schema.ts Copy `import { onchainTable, onchainView, count } from "ponder"; export const pets = onchainTable("pets", (t) => ({ id: t.text().primaryKey(), name: t.text().notNull(), owner: t.text().notNull(), })); export const petLeaderboard = onchainView("pet_leaderboard").as((qb) => qb .select({ ownerName: pets.owner, petCount: count().as("pet_count"), }) .from(pets) .groupBy(pets.owner) );` Each view _must_ be a named export from the `ponder.schema.ts` file. The build step ignores views that are not exported. When to use custom views[](https://ponder.sh/docs/schema/views#when-to-use-custom-views) ------------------------------------------------------------------------------------------- Custom views are particularly useful in two common scenarios. 1. **Customize the GraphQL API**. With views, you can add custom fields to the GraphQL API without adding and populating an entire table. 2. **Move data processing from indexing-time to query-time**. By moving transformation logic to the query layer, views can simplify the project as a whole and help speed up lengthy backfills. Limitations[](https://ponder.sh/docs/schema/views#limitations) ----------------------------------------------------------------- Custom views do not have a primary key constraint, which leads to several important limitations. 1. **Store API disabled**. The indexing function store API cannot access custom views. However, you can query custom views within indexing functions using raw SQL. 2. **No GraphQL singular query fields**. The GraphQL API does not include singular query fields for custom views. 3. **No GraphQL cursor pagination**. The GraphQL API includes plural query fields for custom views that support offset pagination, but do not support cursor pagination. Performance[](https://ponder.sh/docs/schema/views#performance) ----------------------------------------------------------------- Custom views are a useful tool to simplify indexing logic and provide a richer schema, but they are not magic. Each query against a custom view re-executes the stored `SELECT` statement. To avoid performance issues, be sure to check the query plan for each custom view query and add database indexes on the underlying tables as appropriate. Examples[](https://ponder.sh/docs/schema/views#examples) ----------------------------------------------------------- Here's an example of an `hourlyBucket` view from an ERC20 indexer which uses a `GROUP BY` query to aggregate transfer volumes over a time interval. File ponder.schema.ts Copy ``import { onchainTable, onchainView, index, sql, sum, count } from "ponder"; export const transferEvent = onchainTable("transfer_event", (t) => ({ id: t.text().primaryKey(), amount: t.bigint().notNull(), timestamp: t.integer().notNull(), from: t.hex().notNull(), to: t.hex().notNull(), })); export const hourlyBucket = onchainView("hourly_bucket").as((qb) => qb .select({ hour: sql`FLOOR(${transferEvent.timestamp} / 3600) * 3600`.as("hour"), totalVolume: sum(transferEvent.amount).as("total_volume"), transferCount: count().as("transfer_count"), }) .from(transferEvent) .groupBy(sql`FLOOR(${transferEvent.timestamp} / 3600)`) );`` --- # Relations – Ponder [Skip to content](https://ponder.sh/docs/schema/relations#vocs-content) Introducing [Marble](https://marble.xyz/) , the company behind Ponder Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Relations On this page Chevron Right Ponder uses [Drizzle Relations](https://orm.drizzle.team/docs/relations) to define relationships between tables. This guide describes each kind of relationship and how to use them. One-to-one[](https://ponder.sh/docs/schema/relations#one-to-one) ------------------------------------------------------------------- Use the `relations` function exported by `ponder` to define the relationships for a table. To define a one-to-one relationship, use the `one()` operator and specify which columns relate the two tables. In this example, each user has a profile and each profile belongs to one user. File ponder.schema.ts Copy `import { onchainTable, relations } from "ponder"; export const users = onchainTable("users", (t) => ({ id: t.text().primaryKey(), })); export const usersRelations = relations(users, ({ one }) => ({ profile: one(profiles, { fields: [users.id], references: [profiles.userId] }), })); export const profiles = onchainTable("profiles", (t) => ({ id: t.text().primaryKey(), userId: t.text().notNull(), age: t.integer().notNull(), }));` Now that you've defined the relationship, the `profile` field will become available in the Query API (`findMany` and `findFirst`) using the `with` option. File src/index.ts Copy `import { users, profiles } from "ponder:schema"; await db.insert(users).values({ id: "hunter42" }); await db.insert(profiles).values({ userId: "hunter42", age: 29 }); const user = await db.sql.query.users.findFirst({ where: eq(users.id, "hunter42"), with: { profile: true }, }); console.log(user.profile.age); // ^? { id: string; profile: { id: string; userId: string; age: number } }` One-to-many[](https://ponder.sh/docs/schema/relations#one-to-many) --------------------------------------------------------------------- To define a one-to-many relationship, use the `one()` and `many()` operators to define both sides of the relationship. In this example, each dog has one owner and each person can own many dogs. File ponder.schema.ts Copy `import { onchainTable, relations } from "ponder"; export const persons = onchainTable("persons", (t) => ({ name: t.text().primaryKey(), })); export const personsRelations = relations(persons, ({ many }) => ({ dogs: many(dogs), })); export const dogs = onchainTable("dogs", (t) => ({ petId: t.text().primaryKey(), ownerName: t.text().notNull(), })); export const dogsRelations = relations(dogs, ({ one }) => ({ owner: one(persons, { fields: [dogs.ownerName], references: [persons.name] }), }));` Now, any row inserted into the `dogs` table with `ownerName: "Bob"` will become available in Bob's `dogs` field. File src/index.ts Copy `import { persons, dogs } from "ponder:schema"; await db.insert(persons).values({ name: "Bob" }); await db.insert(dogs).values([ { petId: "Chip", ownerName: "Bob" }, { petId: "Spike", ownerName: "Bob" }, ]); const bob = await db.sql.query.persons.findFirst({ where: eq(persons.id, "Bob"), with: { dogs: true }, }); console.log(bob.dogs); // ^? { name: string; dogs: { petId: string; age: number }[] }` Many-to-many[](https://ponder.sh/docs/schema/relations#many-to-many) ----------------------------------------------------------------------- To define a many-to-many relationship, create a "join table" that relates the two tables you want to connect using two one-to-many relationships. File ponder.schema.ts Copy `import { onchainTable, relations, primaryKey } from "ponder"; export const users = onchainTable("users", (t) => ({ id: t.text().primaryKey(), })); export const usersRelations = relations(users, ({ many }) => ({ userTeams: many(userTeams), })); export const teams = onchainTable("teams", (t) => ({ id: t.text().primaryKey(), mascot: t.text().notNull(), })); export const teamsRelations = relations(teams, ({ many }) => ({ userTeams: many(userTeams), })); export const userTeams = onchainTable( "user_teams", (t) => ({ userId: t.text().notNull(), teamId: t.text().notNull(), }), // A composite primary key is often a good choice for a join table. (table) => ({ pk: primaryKey({ columns: [table.userId, table.teamId] }) }) ); export const userTeamsRelations = relations(userTeams, ({ one }) => ({ user: one(users, { fields: [userTeams.userId], references: [users.id] }), team: one(teams, { fields: [userTeams.teamId], references: [teams.id] }), }));` Each row in the `userTeams` table represents a relationship between a `user` and `team` row. You can query for the relationship by nesting the `with` option in the Query API. File src/index.ts Copy `import { users, teams, userTeams } from "ponder:schema"; await db.insert(users).values([ { id: "ron" }, { id: "harry" }, { id: "hermione" } ]); await db.insert(teams).values([ { id: "muggle", mascot: "dudley" }, { id: "wizard", mascot: "hagrid" }, ]); await db.insert(userTeams).values([ { userId: "ron", teamId: "wizard" }, { userId: "harry", teamId: "wizard" }, { userId: "hermione", teamId: "muggle" }, { userId: "hermione", teamId: "wizard" }, ]); const hermione = await db.sql.query.users.findFirst({ where: eq(users.id, "hermione"), with: { userTeams: { with: { team: true } } }, }); console.log(hermione.userTeams); // ^? { // id: string; // userTeams: { // userId: string; // teamId: string; // team: { // id: string; // mascot: string // } // }[] // }` GraphQL API[](https://ponder.sh/docs/schema/relations#graphql-api) --------------------------------------------------------------------- Every relationship you define in `ponder.schema.ts` automatically becomes available in the GraphQL API, with `one` relations creating singular fields and `many` relations creating plural/connection fields. The [one-to-many example](https://ponder.sh/docs/schema/relations#one-to-many) above corresponds to the following GraphQL query and result. Query Copy `query { person(id: "Bob") { id dogs { id } } }` Result Copy `{ "person": { "id": "Bob", "dogs": [ { "id": "Chip" }, { "id": "Spike" } ] } }` --- # create-ponder – Ponder [Skip to content](https://ponder.sh/docs/api-reference/create-ponder#vocs-content) Introducing [Marble](https://marble.xyz/) , the company behind Ponder Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu create-ponder On this page Chevron Right The `create-ponder` CLI tool is the easiest way to get started with Ponder. It asks a few questions, then creates a new Ponder project in the specified directory including all required files (`package.json`, `ponder.config.ts`, ABIs, etc). pnpmyarnnpmbun Terminal pnpm Copy `pnpm create ponder {...options}` Options[](https://ponder.sh/docs/api-reference/create-ponder#options) ------------------------------------------------------------------------ Copy `Usage: $ create-ponder [options] Options: -t, --template [id] Use a template --npm Use npm as your package manager --pnpm Use pnpm as your package manager --yarn Use yarn as your package manager --skip-git Skip initializing a git repository --skip-install Skip installing packages -h, --help Display this message -v, --version Display version number` Templates[](https://ponder.sh/docs/api-reference/create-ponder#templates) ---------------------------------------------------------------------------- Templates help get you started faster by copying contract addresses, ABIs, and deployment blocks from an existing source. ### Example projects[](https://ponder.sh/docs/api-reference/create-ponder#example-projects) The `-t, --template [id]` option creates a new project from one of the [example projects](https://github.com/ponder-sh/ponder/tree/main/examples) . For example, this command creates a project using the `feature-factory` example project. pnpmyarnnpmbun Terminal pnpm Copy `pnpm create ponder --template feature-factory` Here are the available projects: | Name | Template ID | Link | | --- | --- | --- | | Factory contract | feature-factory | [GitHub](https://github.com/ponder-sh/ponder/tree/main/examples/feature-factory) | | Custom event filter | feature-filter | [GitHub](https://github.com/ponder-sh/ponder/tree/main/examples/feature-filter) | | Multiple chains | feature-multichain | [GitHub](https://github.com/ponder-sh/ponder/tree/main/examples/feature-multichain) | | Proxy contract | feature-proxy | [GitHub](https://github.com/ponder-sh/ponder/tree/main/examples/feature-proxy) | | Contract calls | feature-read-contract | [GitHub](https://github.com/ponder-sh/ponder/tree/main/examples/feature-read-contract) | | Friendtech project | project-friendtech | [GitHub](https://github.com/ponder-sh/ponder/tree/main/examples/project-friendtech) | | Uniswap V3 flash loans | project-uniswap-v3-flash | [GitHub](https://github.com/ponder-sh/ponder/tree/main/examples/project-uniswap-v3-flash) | | Basic ERC20 token | reference-erc20 | [GitHub](https://github.com/ponder-sh/ponder/tree/main/examples/reference-erc20) | | Basic ERC721 token | reference-erc721 | [GitHub](https://github.com/ponder-sh/ponder/tree/main/examples/reference-erc721) | --- # Bun – Ponder [Skip to content](https://ponder.sh/docs/guides/bun#vocs-content) Introducing [Marble](https://marble.xyz/) , the company behind Ponder Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Bun On this page Chevron Right [Bun](https://bun.sh/) is a toolkit for modern JavaScript and TypeScript applications. Bun includes a **runtime** (alternative to Node.js), a **package manager** (alternative to `npm`, `pnpm`, and `yarn`), and more. Ponder supports Bun as both a runtime and package manager. New project[](https://ponder.sh/docs/guides/bun#new-project) --------------------------------------------------------------- To create a new project using Bun, just use `bun --bun` to run the `create-ponder` CLI tool. [Read more](https://ponder.sh/docs/api-reference/ponder/cli) . This will create a new project directoty using the Bun package manager and runtime. Terminal shell Copy `bun --bun create ponder {...options}` Existing project[](https://ponder.sh/docs/guides/bun#existing-project) ------------------------------------------------------------------------- ### Migrate to Bun package manager[](https://ponder.sh/docs/guides/bun#migrate-to-bun-package-manager) First, follow [this guide](https://bun.com/docs/guides/install/from-npm-install-to-bun-install) to migrate your repository to Bun's package manager. ### Update `package.json` scripts[](https://ponder.sh/docs/guides/bun#update-packagejson-scripts) Next, update the scripts in `package.json` to use `bun --bun` instead of the bare `ponder` CLI entrypoint to opt-in to Bun's runtime. File package.json Copy `"scripts": { "dev": "ponder dev", "start": "ponder start", "codegen": "ponder codegen", "dev": "bun --bun ponder dev", "start": "bun --bun start", "codegen": "bun --bun ponder codegen", "lint": "eslint .", "typecheck": "tsc" },` ### Run `bun dev` or `bun start`[](https://ponder.sh/docs/guides/bun#run-bun-dev-or-bun-start) That's it! Now, the `dev` and `start` commands will always use the Bun runtime. Terminal shell Copy `bun dev` Known limitations[](https://ponder.sh/docs/guides/bun#known-limitations) --------------------------------------------------------------------------- If you encounter an issue with Ponder that might be caused by Bun, please [open a GitHub issue](https://github.com/ponder-sh/ponder/issues) . * When using Bun, the `/metrics` endpoint omits all default Node.js Prometheus metrics (e.g. `nodejs_heap_space_size_available_bytes` and all other metrics prefixed with `nodejs_` or `process_`). [Read more](https://github.com/siimon/prom-client?tab=readme-ov-file#default-metrics) . --- # ponder.config.ts – Ponder [Skip to content](https://ponder.sh/docs/api-reference/ponder/config#vocs-content) Introducing [Marble](https://marble.xyz/) , the company behind Ponder Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu ponder.config.ts On this page Chevron Right The `ponder.config.ts` file defines chain IDs, RPC URLs, contract addresses & ABIs, and database configuration. File requirements[](https://ponder.sh/docs/api-reference/ponder/config#file-requirements) -------------------------------------------------------------------------------------------- The `ponder.config.ts` file must **default export** the object returned by `createConfig`. File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ chains: { /* ... */ }, contracts: { /* ... */ }, });` By default, `ponder dev` and `start` look for `ponder.config.ts` in the current working directory. Use the `--config-file` CLI option to specify a different path. `createConfig`[](https://ponder.sh/docs/api-reference/ponder/config#createconfig) ------------------------------------------------------------------------------------ ### `database`[](https://ponder.sh/docs/api-reference/ponder/config#database) Here is the logic Ponder uses to determine which database to use: * If the `database.kind` option is specified, use the specified database. * If the `DATABASE_URL` environment variable is defined, use Postgres with that connection string. * If `DATABASE_URL` is not defined, use PGlite. | field | type | | | --- | --- | --- | | **kind** | `"pglite" \| "postgres"` | **Default: See above.** Database to use. | #### PGlite[](https://ponder.sh/docs/api-reference/ponder/config#pglite) | field | type | | | --- | --- | --- | | **kind** | `"pglite"` | | | **directory** | `string` | **Default: `.ponder/pglite`**. Directory path to use for PGlite database files. | File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ database: { kind: "pglite", directory: "./.ponder/pglite", }, // ... });` #### Postgres[](https://ponder.sh/docs/api-reference/ponder/config#postgres) | field | type | | | --- | --- | --- | | **kind** | `"postgres"` | | | **connectionString** | `string` | **Default: `DATABASE_URL` env var**. Postgres database connection string. | | **poolConfig** | [`PoolConfig`](https://node-postgres.com/apis/pool) | **Default: `{ max: 30 }`**. Pool configuration passed to `node-postgres`. | File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ database: { kind: "postgres", connectionString: "postgresql://user:password@localhost:5432/dbname", poolConfig: { max: 100, ssl: true, }, }, // ... });` ### `ordering`[](https://ponder.sh/docs/api-reference/ponder/config#ordering) Specifies how events across multiple chains should be ordered. For single-chain apps, `ordering` has no effect. #### Usage[](https://ponder.sh/docs/api-reference/ponder/config#usage) File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ ordering: "multichain", chains: { /* ... */ }, // ... more config });` #### Parameters[](https://ponder.sh/docs/api-reference/ponder/config#parameters) | field | type | | | --- | --- | --- | | **ordering** | `"omnichain" \| "multichain" \| "experimental_isolated"` | **Default:** `"multichain"` | #### Guarantees[](https://ponder.sh/docs/api-reference/ponder/config#guarantees) The ordering strategies offer different guarantees. Omnichain ordering combines all chains into a single, unified stream of events. Multichain and isolated ordering treat each chain independently, with isolated enforcing additional constraints but taking advantage of multiple CPU cores for better performance. | | Omnichain | Multichain (default) | Isolated | | --- | --- | --- | --- | | Event order for any individual chain | Deterministic, by EVM execution | Deterministic, by EVM execution | Deterministic, by EVM execution | | Event order across chains | Deterministic, by (block timestamp, chain ID, block number) | Non-deterministic, no ordering guarantee | Non-deterministic, no ordering guarantee | | Live indexing latency | Medium-high, must wait for the slowest chain to maintain ordering guarantee | Low, each chain indexes blocks as soon as they arrive | Low, each chain is processed independently | | Indexing logic constraints | None | Must avoid cross-chain writes **or** use commutative logic | Cross-chain reads and write are no possible | | Use cases | Bridges, cross-chain contract calls, global constraints | Same protocol deployed to multiple chains | Same protocol deployed to multiple chains | ### `chains`[](https://ponder.sh/docs/api-reference/ponder/config#chains) An object mapping chain names to chain configuration. #### Usage[](https://ponder.sh/docs/api-reference/ponder/config#usage-1) File ponder.config.ts Copy `import { createConfig } from "ponder"; import { BlitmapAbi } from "./abis/Blitmap"; export default createConfig({ chains: { mainnet: { id: 1, rpc: process.env.PONDER_RPC_URL_1, ws: process.env.PONDER_WS_URL_1, }, }, // ... });` #### Parameters[](https://ponder.sh/docs/api-reference/ponder/config#parameters-1) | field | type | | | --- | --- | --- | | **name** | `string` | **Required**. A unique name for the chain. Must be unique across all chains. _Provided as the object property name._ | | **id** | `number` | **Required**. The [chain ID](https://chainlist.org/)
for the chain. | | **rpc** | `string \| string[] \| Transport` | **Required**. One or more RPC endpoints or a Viem [Transport](https://viem.sh/docs/clients/transports/http)
e.g. `http` or `fallback`. | | **ws** | `string` | **Default: `undefined`**. A webSocket endpoint for realtime indexing on this chain. | | **pollingInterval** | `number` | **Default: `1_000`**. Frequency (in ms) used when polling for new events on this chain. | | **disableCache** | `boolean` | **Default: `false`**. Disables the RPC request cache. Use when indexing a [local node](https://ponder.sh/docs/guides/foundry)
like Anvil. | | **ethGetLogsBlockRange** | `number` | **Default: `undefined`**. Maximum block range for `eth_getLogs` requests. If undefined, Ponder will attempt to determine the block range automatically based on error messages. | ### `contracts`[](https://ponder.sh/docs/api-reference/ponder/config#contracts) An object mapping contract names to contract configuration. Ponder will fetch RPC data and run indexing functions according to the options you provide. #### Usage[](https://ponder.sh/docs/api-reference/ponder/config#usage-2) File ponder.config.ts Copy `import { createConfig } from "ponder"; import { BlitmapAbi } from "./abis/Blitmap"; export default createConfig({ contracts: { Blitmap: { abi: BlitmapAbi, chain: "mainnet", address: "0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63", startBlock: 12439123, }, }, // ... });` #### Parameters[](https://ponder.sh/docs/api-reference/ponder/config#parameters-2) | field | type | | | --- | --- | --- | | **name** | `string` | **Required**. A unique name for the smart contract. Must be unique across all contracts. _Provided as the object property name._ | | **abi** | `abitype.Abi` | **Required**. The contract [ABI](https://docs.soliditylang.org/en/v0.8.17/abi-spec.html)
as an array as const. Must be asserted as constant, see [ABIType documentation](https://abitype.dev/guide/getting-started#usage)
for details. | | **chain** | `string \| ChainConfig` | **Required**. The name of the chain this contract is deployed to. References the `chains` field. Also supports [multiple chains](https://ponder.sh/docs/config/contracts#multiple-chains)
. | | **address** | `0x{string} \| 0x{string}[] \| Factory` | **Default: `undefined`**. One or more contract addresses or factory configuration. | | **startBlock** | `number \| "latest"` | **Default: `0`**. Block number or tag to start indexing. Usually set to the contract deployment block number. | | **endBlock** | `number \| "latest"` | **Default: `undefined`**. Block number or tag to stop indexing. If this field is specified, the contract will not be indexed in realtime. This field can be used alongside `startBlock` to index a specific block range. | | **filter** | [`Filter`](https://ponder.sh/docs/api-reference/ponder/config#filter) | **Default: `undefined`**. Event filter criteria. [Read more](https://ponder.sh/docs/config/contracts#filter-by-indexed-parameter-value)
. | | **includeTransactionReceipts** | `boolean` | **Default: `false`**. If this field is `true`, `transactionReceipt` will be included in `event`. | | **includeCallTraces** | `boolean` | **Default: `false`**. If this field is `true`, each function in the abi will be available as an indexing function event name. [Read more](https://ponder.sh/docs/guides/call-traces)
. | #### `filter`[](https://ponder.sh/docs/api-reference/ponder/config#filter) The `filter` option is used to filter event logs by argument value. [Read more](https://ponder.sh/docs/config/contracts#filter-by-indexed-parameter-value) about log filters. | field | type | | | --- | --- | --- | | **event** | `string \| string[]` | **Required**. One or more event names present in the provided ABI. | | **args** | `object` | **Required**. An object containing indexed argument values to filter for. Only allowed if **one** event name was provided in `event`. | ### `accounts`[](https://ponder.sh/docs/api-reference/ponder/config#accounts) An object mapping account names to account configuration. Accounts are used to index transactions or native transfers. #### Usage[](https://ponder.sh/docs/api-reference/ponder/config#usage-3) File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ accounts: { coinbasePrime: { chain: "mainnet", address: "0xCD531Ae9EFCCE479654c4926dec5F6209531Ca7b", startBlock: 12111233, }, }, // ... });` #### Parameters[](https://ponder.sh/docs/api-reference/ponder/config#parameters-3) | field | type | | | --- | --- | --- | | **name** | `string` | **Required**. A unique name for the smart contract. Must be unique across all contracts. _Provided as the object property name._ | | **chain** | `string` | **Required**. The name of the chain this contract is deployed to. References the `chains` field. Also supports [multiple chains](https://ponder.sh/docs/config/contracts#chain)
. | | **address** | `0x{string} \| 0x{string}[] \| Factory` | **Default: `undefined`**. One or more contract addresses or factory configuration. | | **startBlock** | `number` | **Default: `0`**. Block number to start syncing events. | | **endBlock** | `number` | **Default: `undefined`**. Block number to stop syncing events. If this field is specified, the contract will not be indexed in realtime. This field can be used alongside `startBlock` to index a specific block range. | | **includeTransactionReceipts** | `boolean` | **Default: `false`**. If this field is `true`, `transactionReceipt` will be included in `event`. | ### `blocks`[](https://ponder.sh/docs/api-reference/ponder/config#blocks) An object mapping block interval names to block interval configuration. #### Usage[](https://ponder.sh/docs/api-reference/ponder/config#usage-4) File ponder.config.ts Copy `import { createConfig } from "ponder"; export default createConfig({ blocks: { ChainlinkPriceOracle: { chain: "mainnet", startBlock: 19_750_000, interval: 5, // every minute }, }, // ... });` #### Parameters[](https://ponder.sh/docs/api-reference/ponder/config#parameters-4) | field | type | | | --- | --- | --- | | **name** | `string` | **Required**. A unique name for the block interval. Must be unique across all block intervals. _Provided as the object property name._ | | **chain** | `string` | **Required**. The name of the chain this block interval is deployed to. References the `chains` field. Also supports [multiple chains](https://ponder.sh/docs/config/contracts#chain)
. | | **startBlock** | `number` | **Default: `0`**. Block number to start syncing events. | | **endBlock** | `number` | **Default: `undefined`**. Block number to stop syncing events. If this field is specified, the contract will not be indexed in realtime. This field can be used alongside `startBlock` to index a specific block range. | | **interval** | `number` | **Default: `0`**. The interval between blocks to index. | `factory`[](https://ponder.sh/docs/api-reference/ponder/config#factory) -------------------------------------------------------------------------- Specifies a list of addresses collected from decoded event logs. Both [`contracts`](https://ponder.sh/docs/api-reference/ponder/config#contracts) and [`accounts`](https://ponder.sh/docs/api-reference/ponder/config#accounts) support `factory()` in their `address` field. [Read more](https://ponder.sh/docs/guides/factory) in the factory pattern guide. | field | type | | | --- | --- | --- | | **address** | `0x{string} \| 0x{string}[] \| undefined` | **Optional**. Address of the factory contract that creates instances of this contract. | | **event** | [`AbiEvent`](https://abitype.dev/api/types#abievent) | **Required**. ABI item of the event that announces the creation of a new child contract. | | **parameter** | `string` | **Required**. Name of the parameter within `event` that contains child contract addresses. | File ponder.config.ts Copy `import { createConfig, factory } from "ponder"; export default createConfig({ contracts: { uniswapV2: { // ... address: factory({ address: "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", event: parseAbiItem( "event PairCreated(address indexed token0, address indexed token1, address pair, uint256)" ), parameter: "pair", }), }, }, // ... });` Types[](https://ponder.sh/docs/api-reference/ponder/config#types) -------------------------------------------------------------------- The `ponder` package exports several utility types. Use these types to maintain type safety when generating config options dynamically. ### DatabaseConfig[](https://ponder.sh/docs/api-reference/ponder/config#databaseconfig) File ponder.config.ts Copy `import { createConfig, type DatabaseConfig } from "ponder"; const database = { kind: "postgres", connectionString: process.env.DATABASE_URL, } as const satisfies DatabaseConfig; export default createConfig({ database, // ... });` ### ChainConfig[](https://ponder.sh/docs/api-reference/ponder/config#chainconfig) File ponder.config.ts Copy `import { createConfig, type ChainConfig } from "ponder"; const mainnet = { id: 1, rpc: process.env.PONDER_RPC_URL_1, ws: process.env.PONDER_WS_URL_1, } as const satisfies ChainConfig; export default createConfig({ chains: { mainnet, } // ... });` ### ContractConfig[](https://ponder.sh/docs/api-reference/ponder/config#contractconfig) File ponder.config.ts Copy `import { createConfig, type ContractConfig } from "ponder"; import { Erc20Abi } from "./abis/Erc20Abi.ts"; const Erc20 = { chain: "mainnet" abi: Erc20Abi, address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", } as const satisfies ContractConfig; export default createConfig({ contracts: { Erc20, }, // ... });` ### BlockConfig[](https://ponder.sh/docs/api-reference/ponder/config#blockconfig) File ponder.config.ts Copy `import { createConfig, type BlockConfig } from "ponder"; const ChainlinkPriceOracle = { chain: "mainnet", startBlock: 19_750_000, interval: 5, } as const satisfies BlockConfig; export default createConfig({ blocks: { ChainlinkPriceOracle, }, // ... });` --- # ponder – Ponder [Skip to content](https://ponder.sh/docs/api-reference/ponder/cli#vocs-content) Introducing [Marble](https://marble.xyz/) , the company behind Ponder Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu CLI (dev, start, serve) On this page Chevron Right The CLI (provided by the `ponder` package) is the entrypoint for your project. Copy `Usage: ponder [OPTIONS] Options: --root Path to the project root directory (default: working directory) --config Path to the project config file (default: "ponder.config.ts") -v, --debug Enable debug logs, e.g. realtime blocks, internal events -vv, --trace Enable trace logs, e.g. db queries, indexing checkpoints --log-level Minimum log level ("error", "warn", "info", "debug", or "trace", default: "info") --log-format The log format ("pretty" or "json") (default: "pretty") -V, --version Show the version number -h, --help Show this help message Commands: dev [options] Start the development server with hot reloading start [options] Start the production server serve [options] Start the production HTTP server without the indexer db Database management commands codegen Generate the ponder-env.d.ts file, then exit` dev[](https://ponder.sh/docs/api-reference/ponder/cli#dev) ------------------------------------------------------------- Start the app in development mode. * The app automatically restarts when changes are detected in any project file. * An auto-updating terminal UI displays useful information. Copy `Usage: ponder dev [options] Start the development server with hot reloading Options: --schema Database schema (max: 45 characters) --disable-ui Disable the terminal UI -p, --port Port for the web server (default: 42069) -H, --hostname Hostname for the web server (default: "0.0.0.0" or "::") -h, --help display help for command` start[](https://ponder.sh/docs/api-reference/ponder/cli#start) ----------------------------------------------------------------- Start the app in production mode. * Project files are built once on startup, and file changes are ignored. * The terminal UI is disabled. Copy `Usage: ponder start [options] Start the production server Options: --schema Database schema (max: 45 characters) --views-schema Views database schema (max: 45 characters) -p, --port Port for the web server (default: 42069) -H, --hostname Hostname for the web server (default: "0.0.0.0" or "::") -h, --help display help for command` serve[](https://ponder.sh/docs/api-reference/ponder/cli#serve) ----------------------------------------------------------------- Start the app in server-only mode. This option can be used to horizontally scale the HTTP server in production. * Only works with Postgres. * Project files are built once on startup, and file changes are ignored. * Indexing is disabled. * The HTTP server runs as normal, serving data from the connected database. Copy `Usage: ponder serve [options] Start the production HTTP server without the indexer Options: --schema Database schema (max: 45 characters) -p, --port Port for the web server (default: 42069) -H, --hostname Hostname for the web server (default: "0.0.0.0" or "::") -h, --help display help for command` codegen[](https://ponder.sh/docs/api-reference/ponder/cli#codegen) --------------------------------------------------------------------- Write or update the `ponder-env.d.ts` file. Copy `Usage: ponder codegen [options] Generate the ponder-env.d.ts file, then exit Options: -h, --help display help for command` db[](https://ponder.sh/docs/api-reference/ponder/cli#db) ----------------------------------------------------------- Database management commands. ### create-views[](https://ponder.sh/docs/api-reference/ponder/cli#create-views) Creates views for the views pattern. [Read more](https://ponder.sh/docs/production/self-hosting#views-pattern) . Copy `Usage: ponder create-views [options] Create database views for the views pattern Options: --schema Database schema (max: 45 characters) --views-schema Views database schema (max: 45 characters) -h, --help display help for command` ### list[](https://ponder.sh/docs/api-reference/ponder/cli#list) List all `ponder start` instances that have ever ran in the connected database. Copy `Usage: ponder db list [options] List all Ponder deployments Options: -h, --help display help for command` Terminal Result Copy `│ Schema │ Active │ Last active │ Table count │ ├───────────────┼──────────┼────────────────┼─────────────┤ │ indexer_prod │ yes │ --- │ 10 │ │ test │ no │ 26m 58s ago │ 10 │ │ demo │ no │ 1 day ago │ 5 │` ### prune[](https://ponder.sh/docs/api-reference/ponder/cli#prune) Drop all database tables, functions, and schemas created by Ponder deployments that are not currently active. Copy `Usage: ponder db prune [options] Drop all database tables, functions, and schemas created by Ponder deployments that are not active Options: -h, --help display help for command` --- # Database reference – Ponder [Skip to content](https://ponder.sh/docs/api-reference/ponder/database#vocs-content) Introducing [Marble](https://marble.xyz/) , the company behind Ponder Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Database reference On this page Chevron Right This page describes internal details of the Ponder instance lifecycle, which may be useful for advanced workflows and database administration. Instance lifecycle[](https://ponder.sh/docs/api-reference/ponder/database#instance-lifecycle) ------------------------------------------------------------------------------------------------ Here's an overview of the instance lifecycle. Here, _instance_ refers to a Node.js process running `ponder dev` or `ponder start`. 1. Start returning `200` from the `/health` endpoint. Return `503` from the `/ready` endpoint. 2. Build all user code (config, schema, indexing functions), then generate a `build_id` by hashing the build artifacts. 3. Follow the database schema flow chart below to validate and prepare the target database schema. 4. Start the backfill, either from scratch or from where the previous instance left off (crash recovery). 5. When the backfill finishes, start returning `200` from the `/ready` endpoint. 6. On shutdown (e.g. due to a process exit signal), release the lock on the target schema. Do not drop any tables. ### Database schema rules[](https://ponder.sh/docs/api-reference/ponder/database#database-schema-rules) * No two instances can use the same database schema at the same time. This prevents data corruption. * Once an instance running `ponder start` uses a schema, no other instance can use that schema – even after the instance stops. This is a safety mechanism to prevent data loss in production. * If the target schema was previously used by `ponder dev`, the new instance will drop the previous tables and start successfully. Here is the detailed flow chart. ### Lock / metadata table[](https://ponder.sh/docs/api-reference/ponder/database#lock--metadata-table) The `_ponder_meta` table tracks the state of an instance running in a specific database schema. It stores the `build_id`, indexing checkpoint, and table names of the current instance, and uses a heartbeat locking mechanism to support the lifecycle logic described above. ### Checkpoint Table[](https://ponder.sh/docs/api-reference/ponder/database#checkpoint-table) The `_ponder_checkpoint` table tracks the indexing progress of each chain. ### Build ID and crash recovery[](https://ponder.sh/docs/api-reference/ponder/database#build-id-and-crash-recovery) During the build step, each instance generates a content hash of the entire app (config, schema, and indexing function file contents). This is called the `build_id`. Note that crash recovery is disabled when running `ponder dev`. RPC request cache[](https://ponder.sh/docs/api-reference/ponder/database#rpc-request-cache) ---------------------------------------------------------------------------------------------- Ponder caches RPC request data to improve reindexing performance on hot reloads and redeployments. This includes logs, blocks, transactions, traces, and any RPC requests made with `context.client`. * The RPC request cache is located in the `ponder_sync` schema. * The cache is durable and persists across instance restarts. * The cache query patterns are lock-free. It's safe for multiple instances to run in the same Postgres database at the same time (reading and writing to the same `ponder_sync` schema). * The cache does not store unfinalized data. --- # Tables – Ponder [Skip to content](https://ponder.sh/docs/schema/tables#vocs-content) Introducing [Marble](https://marble.xyz/) , the company behind Ponder Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Tables On this page Chevron Right Ponder's schema definition API is built on [Drizzle](https://orm.drizzle.team/) , a modern TypeScript ORM. To define a table, use the `onchainTable` function and include column definitions. File ponder.schema.ts Copy `import { onchainTable } from "ponder"; export const pets = onchainTable("pets", (t) => ({ name: t.text().primaryKey(), age: t.integer().notNull(), }));` Each table _must_ be a named export from the `ponder.schema.ts` file. The build step ignores tables that are not exported. Column types[](https://ponder.sh/docs/schema/tables#column-types) -------------------------------------------------------------------- The schema definition API supports most PostgreSQL data types – here's a quick reference for the most common options. Read the [Drizzle documentation](https://orm.drizzle.team/docs/column-types/pg) for a complete list. | name | description | TypeScript type | SQL data type | | --- | --- | --- | --- | | `text` | UTF‐8 character sequence | `string` | `TEXT` | | `integer` | Signed 4‐byte integer | `number` | `INTEGER` | | `real` | Signed 4-byte floating‐point value | `number` | `REAL` | | `boolean` | `true` or `false` | `boolean` | `BOOLEAN` | | `timestamp` | Date and time value (no time zone) | `Date` | `TIMESTAMP` | | `json` | JSON object | `any` or [custom](https://ponder.sh/docs/schema/tables#json) | `JSON` | Ponder also includes a few extra column types built specifically for EVM indexing. | name | description | TypeScript type | SQL data type | | --- | --- | --- | --- | | `bigint` | Large integer (holds `uint256` and `int256`) | `bigint` | `NUMERIC(78,0)` | | `hex` | UTF‐8 character sequence with `0x` prefix | `0x${string}` | `TEXT` | ### `bigint`[](https://ponder.sh/docs/schema/tables#bigint) Use the `bigint` column type to store EVM `uint256` or `int256` values. File ponder.schema.ts Copy `import { onchainTable } from "ponder"; export const accounts = onchainTable("accounts", (t) => ({ address: t.hex().primaryKey(), balance: t.bigint().notNull(), }));` ### `hex`[](https://ponder.sh/docs/schema/tables#hex) Use the `hex` column type to store EVM `address`, `bytes`, or any other hex-encoded value. File ponder.schema.ts Copy `import { onchainTable } from "ponder"; export const accounts = onchainTable("accounts", (t) => ({ address: t.hex().primaryKey(), balance: t.bigint().notNull(), }));` Enums[](https://ponder.sh/docs/schema/tables#enums) ------------------------------------------------------ To define an enum, use the `onchainEnum` function. Then, use the value returned by `onchainEnum` as a column type. Under the hood, `onchainEnum` creates a PostgreSQL [enumerated type](https://www.postgresql.org/docs/current/datatype-enum.html) . File ponder.schema.ts Copy `import { onchainEnum, onchainTable } from "ponder"; export const color = onchainEnum("color", ["ORANGE", "BLACK"]); export const cats = onchainTable("cats", (t) => ({ name: t.text().primaryKey(), color: color("color"), }));` Arrays[](https://ponder.sh/docs/schema/tables#arrays) -------------------------------------------------------- To define an array column, use the `.array()` modifier. Arrays are a good fit for small one-dimensional collections. Don't use arrays for [relationships](https://ponder.sh/docs/schema/relations) between records. File ponder.schema.ts Copy `import { onchainTable } from "ponder"; export const cats = onchainTable("cats", (t) => ({ name: t.text().primaryKey(), vaccinations: t.text().array(), // ["rabies", "distemper", "parvo"] // }));` Not null[](https://ponder.sh/docs/schema/tables#not-null) ------------------------------------------------------------ To mark a column as not null, use the `.notNull()` modifier. If you attempt to insert a row that does not include a value for a `NOT NULL` column, the database will throw an error. File ponder.schema.ts Copy `import { onchainTable } from "ponder"; export const cats = onchainTable("cats", (t) => ({ name: t.text().primaryKey(), age: t.integer().notNull(), }));` Default value[](https://ponder.sh/docs/schema/tables#default-value) ---------------------------------------------------------------------- To set a default value for a column, use the `.default()` modifier and pass a string, number, boolean, or `null`. File ponder.schema.ts Copy `import { onchainTable } from "ponder"; export const cats = onchainTable("cats", (t) => ({ name: t.text().primaryKey(), livesRemaining: t.integer().default(9), }));` Alternatively, use the `.$default()` modifier to specify a JavaScript function that returns the default value. With this approach, the database driver calls the function to generate a default value for each row dynamically. File ponder.schema.ts Copy `import { onchainTable } from "ponder"; import { generateId } from "../utils"; export const cats = onchainTable("cats", (t) => ({ name: t.text().primaryKey(), age: t.integer().$default(() => generateId()), }));` Primary key[](https://ponder.sh/docs/schema/tables#primary-key) ------------------------------------------------------------------ Every table **must** have a primary key. To define a primary key on a single column, use the `.primaryKey()` modifier. File ponder.schema.ts Copy `import { onchainTable } from "ponder"; export const tokens = onchainTable("tokens", (t) => ({ id: t.bigint().primaryKey(), }));` ### Composite primary key[](https://ponder.sh/docs/schema/tables#composite-primary-key) To create a composite primary key, use the `primaryKey()` function exported by `ponder`. Each column that forms the primary key must be not null. [Read more](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-PRIMARY-KEYS) about composite primary keys. File ponder.schema.ts Copy `import { onchainTable, primaryKey } from "ponder"; export const poolStates = onchainTable( "pool_states", (t) => ({ poolId: t.bigint().notNull(), address: t.hex().notNull(), balance: t.bigint().notNull(), }), (table) => ({ pk: primaryKey({ columns: [table.poolId, table.address] }), }) );` Indexes[](https://ponder.sh/docs/schema/tables#indexes) ---------------------------------------------------------- To create a database index, use the `index()` function. This example defines B-tree indexes on the `persons.name` column to support search queries, and on the `dogs.ownerId` column to support the `persons.dogs` relational query. File ponder.schema.ts Copy `import { onchainTable, relations, index } from "ponder"; export const persons = onchainTable( "persons", (t) => ({ id: t.text().primaryKey(), name: t.text(), }), (table) => ({ nameIdx: index().on(table.name), }) ); export const personsRelations = relations(persons, ({ many }) => ({ dogs: many(dogs), })); export const dogs = onchainTable( "dogs", (t) => ({ id: t.text().primaryKey(), ownerId: t.text().notNull(), }), (table) => ({ ownerIdx: index().on(table.ownerId), }) ); export const dogsRelations = relations(dogs, ({ one }) => ({ owner: one(persons, { fields: [dogs.ownerId], references: [persons.id] }), }));` The `index()` function supports specifying multiple columns, ordering, and custom index types like GIN and GIST. Read more in the [Drizzle](https://orm.drizzle.team/docs/indexes-constraints#indexes) and [PostgreSQL](https://www.postgresql.org/docs/current/indexes.html) documentation. Best practices[](https://ponder.sh/docs/schema/tables#best-practices) ------------------------------------------------------------------------ ### Primary keys[](https://ponder.sh/docs/schema/tables#primary-keys) Select a primary key that matches the access pattern of your indexing logic. If a table has two or more columns that together form a unique identifier for a row, use a composite primary key – don't use a concatenated string. File ponder.schema.ts Copy ``import { onchainTable, primaryKey } from "ponder"; // ❌ Don't concatenate strings to form a primary key export const allowances = onchainTable("allowances", (t) => ({ id: t.string().primaryKey(), // `${owner}_${spender}` owner: t.hex(), spender: t.hex(), amount: t.bigint(), })); // ✅ Use a composite primary key export const allowances = onchainTable( "allowances", (t) => ({ owner: t.hex(), spender: t.hex(), amount: t.bigint(), }), (table) => ({ pk: primaryKey({ columns: [table.owner, table.spender] }) }) );`` ### Timestamps[](https://ponder.sh/docs/schema/tables#timestamps) Use the `bigint` column type to store block timestamps using their EVM-native Unix timestamp representation. This maintains consistency with Viem's approach, and avoids error-prone timezone manipulation code. File ponder.schema.ts Copy `import { onchainTable } from "ponder"; export const events = onchainTable("events", (t) => ({ id: t.text().primaryKey(), timestamp: t.bigint(), // Unix timestamp in seconds }));` If you strongly prefer working with JavaScript `Date` objects, you can also use the `timestamp` column type, but we recommend doing this conversion in the view layer. File ponder.schema.ts Copy `import { onchainTable } from "ponder"; export const events = onchainTable("events", (t) => ({ id: t.text().primaryKey(), timestamp: t.timestamp(), // JavaScript Date object }));` ### Custom types[](https://ponder.sh/docs/schema/tables#custom-types) Use the `.$type()` modifier to customize the TypeScript type for a column. Note that the `.$type()` modifier does not validate data at runtime or in the database, it only enforces a TypeScript type. File ponder.schema.ts Copy `import { onchainTable } from "ponder"; export const tokens = onchainTable("tokens", (t) => ({ id: t.bigint().primaryKey(), metadata: t.json().$type<{ name: string; symbol: string; decimals: number }>(), }));` ### `camelCase` vs `snake_case`[](https://ponder.sh/docs/schema/tables#camelcase-vs-snake_case) Use `camelCase` for TypeScript names and `snake_case` for SQL names. This guideline applies to all database objects and properties, including tables, columns, relations, and indexes. File ponder.schema.ts Copy ``import { onchainTable } from "ponder"; export const registrationEvents = onchainTable( "registration_events", // Use snake_case for the SQL table name (t) => ({ createdAt: t.bigint(), // Drizzle automatically converts this to `created_at` invitedBy: t.text("invited_by"), // Avoid manual case conversion for columns // ... }) );`` Examples[](https://ponder.sh/docs/schema/tables#examples) ------------------------------------------------------------ ### ERC20[](https://ponder.sh/docs/schema/tables#erc20) Here's a schema for a simple ERC20 app. File ponder.schema.ts Copy `import { index, onchainTable, primaryKey } from "ponder"; export const account = onchainTable("account", (t) => ({ address: t.hex().primaryKey(), balance: t.bigint().notNull(), isOwner: t.boolean().notNull(), })); export const allowance = onchainTable( "allowance", (t) => ({ owner: t.hex(), spender: t.hex(), amount: t.bigint().notNull(), }), (table) => ({ pk: primaryKey({ columns: [table.owner, table.spender] }), }) ); export const transferEvent = onchainTable( "transfer_event", (t) => ({ id: t.text().primaryKey(), amount: t.bigint().notNull(), timestamp: t.integer().notNull(), from: t.hex().notNull(), to: t.hex().notNull(), }), (table) => ({ fromIdx: index().on(table.from), }) ); export const approvalEvent = onchainTable("approval_event", (t) => ({ id: t.text().primaryKey(), amount: t.bigint().notNull(), timestamp: t.integer().notNull(), owner: t.hex().notNull(), spender: t.hex().notNull(), }));` --- # ponder.schema.ts – Ponder [Skip to content](https://ponder.sh/docs/api-reference/ponder/schema#vocs-content) Introducing [Marble](https://marble.xyz/) , the company behind Ponder Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu ponder.schema.ts On this page Chevron Right The `ponder.schema.ts` file defines your database tables and their relationships. Each table you include in `ponder.schema.ts` will be created as an SQL table, populated during indexing, and exposed via the GraphQL API. File requirements[](https://ponder.sh/docs/api-reference/ponder/schema#file-requirements) -------------------------------------------------------------------------------------------- The `ponder.schema.ts` must use **named exports** for tables, enums, and relations. These objects must be created using the correct functions exported from `"ponder"`. File ponder.schema.ts Copy `import { onchainTable } from "ponder"; export const pets = onchainTable("pets", (t) => ({ name: t.text().primaryKey(), age: t.integer().notNull(), }));` `onchainTable`[](https://ponder.sh/docs/api-reference/ponder/schema#onchaintable) ------------------------------------------------------------------------------------ The `onchainTable` function accepts three positional arguments. | field | type | description | | --- | --- | --- | | **name** | `string` | The SQL table name. Use `snake_case`. Maximum of 45 characters. | | **columns** | `(t: TableBuilder) => Record` | A function that returns column definitions. | | **constraints** | `(table: Table) => Record` | Optional function that returns table constraints like composite primary keys and indexes. | File ponder.schema.ts Copy `import { onchainTable } from "ponder"; export const transferEvents = onchainTable( "transfer_event", // SQL table name (t) => ({ // Column definitions id: t.text().primaryKey(), from: t.hex().notNull(), to: t.hex().notNull(), value: t.bigint().notNull(), }), (table) => ({ // Constraints & indexes fromIdx: index().on(table.from), }) );` ### Column types[](https://ponder.sh/docs/api-reference/ponder/schema#column-types) The schema definition API supports most PostgreSQL data types. Here's a quick reference for the most commonly used data types. For a complete list, visit the [Drizzle documentation](https://orm.drizzle.team/docs/column-types/pg) . | name | description | TypeScript type | SQL data type | | --- | --- | --- | --- | | `text` | UTF‐8 character sequence | `string` | `TEXT` | | `integer` | Signed 4‐byte integer | `number` | `INTEGER` | | `real` | Signed 4-byte floating‐point value | `number` | `REAL` | | `boolean` | `true` or `false` | `boolean` | `BOOLEAN` | | `timestamp` | Date and time value (no time zone) | `Date` | `TIMESTAMP` | | `json` | JSON object | `any` or [custom](https://ponder.sh/docs/api-reference/ponder/schema#json) | `JSON` | | `bigint` | Large integer (holds `uint256` and `int256`) | `bigint` | `NUMERIC(78,0)` | | `hex` | UTF‐8 character sequence with `0x` prefix | `0x${string}` | `TEXT` | ### Column modifiers[](https://ponder.sh/docs/api-reference/ponder/schema#column-modifiers) Column modifiers can be chained after column type definitions. | modifier | description | | --- | --- | | `.primaryKey()` | Marks column as the table's primary key | | `.notNull()` | Marks column as NOT NULL | | `.array()` | Marks column as an array type | | `.default(value)` | Sets a default value for column | | `.$default(() => value)` | Sets a dynamic default via function | | `.$type()` | Annotates column with a custom TypeScript type | ### Constraints[](https://ponder.sh/docs/api-reference/ponder/schema#constraints) #### Primary key[](https://ponder.sh/docs/api-reference/ponder/schema#primary-key) Every table must have exactly one primary key defined using either the `.primaryKey()` column modifier or the `primaryKey()` function in the table constraints argument. File ponder.schema.ts Copy `import { onchainTable, primaryKey } from "ponder"; // Single column primary key export const tokens = onchainTable("tokens", (t) => ({ id: t.bigint().primaryKey(), })); // Composite primary key export const poolStates = onchainTable( "pool_states", (t) => ({ poolId: t.bigint().notNull(), address: t.hex().notNull(), }), (table) => ({ pk: primaryKey({ columns: [table.poolId, table.address] }), }) );` ### Indexes[](https://ponder.sh/docs/api-reference/ponder/schema#indexes) Create indexes using the `index()` function in the constraints & indexes argument. Ponder creates database indexes after the backfill completes, just before the app becomes ready. File ponder.schema.ts Copy `import { onchainTable, index } from "ponder"; export const persons = onchainTable( "persons", (t) => ({ id: t.text().primaryKey(), name: t.text(), }), (table) => ({ nameIdx: index().on(table.name), }) );` `onchainEnum`[](https://ponder.sh/docs/api-reference/ponder/schema#onchainenum) ---------------------------------------------------------------------------------- The `onchainEnum` function accepts two positional arguments. It returns a function that can be used as a column type. | field | type | description | | --- | --- | --- | | **name** | `string` | The SQL enum name. Use `snake_case`. | | **values** | `string[]` | An array of strings representing the allowed values for the enum. | File ponder.schema.ts Copy `import { onchainEnum, onchainTable } from "ponder"; export const color = onchainEnum("color", ["ORANGE", "BLACK"]); export const cats = onchainTable("cats", (t) => ({ name: t.text().primaryKey(), color: color().notNull(), }));` Like any other column types, you can use modifiers like `.notNull()`, `.default()`, and `.array()` with enum columns. File ponder.schema.ts Copy `// ... export const dogs = onchainTable("cats", (t) => ({ name: t.text().primaryKey(), color: color().array().default([]), }));` `onchainView`[](https://ponder.sh/docs/api-reference/ponder/schema#onchainview) ---------------------------------------------------------------------------------- The `onchainView` function uses the Drizzle query builder API to define a custom query against other tables and views in `ponder.schema.ts`. File ponder.schema.ts Copy ``import { onchainView, sql, sum, count } from "ponder"; // ... `transferEvent` and other table definitions export const hourlyBucket = onchainView("hourly_bucket").as((qb) => qb .select({ hour: sql`FLOOR(${transferEvent.timestamp} / 3600) * 3600`.as("hour"), totalVolume: sum(transferEvent.amount).as("total_volume"), transferCount: count().as("transfer_count"), }) .from(transferEvent) .groupBy(sql`FLOOR(${transferEvent.timestamp} / 3600)`), );`` [Read more](https://orm.drizzle.team/docs/select) about the Drizzle query builder API. `relations`[](https://ponder.sh/docs/api-reference/ponder/schema#relations) ------------------------------------------------------------------------------ Use the `relations` function to define relationships between tables. File ponder.schema.ts Copy `import { onchainTable, relations } from "ponder"; export const users = onchainTable("users", (t) => ({ id: t.text().primaryKey(), })); export const usersRelations = relations(users, ({ one }) => ({ profile: one(profiles, { fields: [users.id], references: [profiles.userId], }), }));` ### Relationship types[](https://ponder.sh/docs/api-reference/ponder/schema#relationship-types) | type | method | description | | --- | --- | --- | | **One-to-one** | `one()` | References single related record | | **One-to-many** | `many()` | References array of related records | | **Many-to-many** | Combination | Uses join table with two one-to-many relations | Read more in the [relationships guide](https://ponder.sh/docs/schema/relations) and the Drizzle [relations documentation](https://orm.drizzle.team/docs/relations) . --- # Indexing functions – Ponder [Skip to content](https://ponder.sh/docs/api-reference/ponder/indexing-functions#vocs-content) Introducing [Marble](https://marble.xyz/) , the company behind Ponder Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Indexing functions On this page Chevron Right Indexing functions are user-defined functions that receive blockchain data (a log, block, transaction, trace, or transfer) and insert data into the database. You can register indexing functions within any `.ts` file inside the `src/` directory. Registration[](https://ponder.sh/docs/api-reference/ponder/indexing-functions#registration) ---------------------------------------------------------------------------------------------- To register an indexing function, use the `.on()` method of the `ponder` object exported from `"ponder:registry"`. File src/index.ts Copy `import { ponder } from "ponder:registry"; ponder.on("ContractName:EventName", async ({ event, context }) => { const { db, chain, client, contracts } = context; // ... });` ### Log event[](https://ponder.sh/docs/api-reference/ponder/indexing-functions#log-event) Log events are specified with `"ContractName:EventName"`. File src/index.ts Copy `import { ponder } from "ponder:registry"; ponder.on("ContractName:EventName", async ({ event, context }) => { // ... });` The `event` object contains the decoded log arguments and the raw log, block, and transaction. Copy ``type LogEvent = { name: string; args: Args; log: Log; block: Block; transaction: Transaction; // Enabled using `includeTransactionReceipts` in contract config transactionReceipt?: TransactionReceipt; };`` #### Decoded arguments[](https://ponder.sh/docs/api-reference/ponder/indexing-functions#decoded-arguments) The `event.args` object contains log argument data (`log.topics` and `log.data`) decoded using Viem's [decodeEventLog](https://viem.sh/docs/contract/decodeEventLog.html) function. Copy ``/** Sample `args` type for an ERC20 Transfer event. */ type Args = { from: `0x${string}`; to: `0x${string}`; value: bigint; };`` ### Call trace event[](https://ponder.sh/docs/api-reference/ponder/indexing-functions#call-trace-event) Call trace events are specified using `"ContractName.functionName()"`. File src/index.ts Copy `import { ponder } from "ponder:registry"; ponder.on("ContractName.functionName()", async ({ event, context }) => { // ... });` The `event` object contains the decoded trace arguments and the raw trace, block, and transaction. Copy ``type TraceEvent = { name: string; args: Args; result: Result; trace: Trace; block: Block; transaction: Transaction; // Enabled using `includeTransactionReceipts` in contract config transactionReceipt?: TransactionReceipt; };`` #### Decoded arguments[](https://ponder.sh/docs/api-reference/ponder/indexing-functions#decoded-arguments-1) The `event.args` and `event.result` objects contain `trace.input` and `trace.output` decoded using Viem's [decodeFunctionData](https://viem.sh/docs/contract/decodeFunctionData.html) and [decodeFunctionResult](https://viem.sh/docs/contract/decodeFunctionResult.html) functions, respectively. ### Transaction event[](https://ponder.sh/docs/api-reference/ponder/indexing-functions#transaction-event) Transaction events are specified using `"AccountName:transaction:from"` or `"AccountName:transaction:to"`. File src/index.ts Copy `import { ponder } from "ponder:registry"; ponder.on("AccountName:transaction:from", async ({ event, context }) => { // ... });` The `event` object contains the raw transaction, transaction receipt, and block. Copy `type TransactionEvent = { block: Block; transaction: Transaction; transactionReceipt: TransactionReceipt; };` ### Transfer event[](https://ponder.sh/docs/api-reference/ponder/indexing-functions#transfer-event) Native transfer events are specified using `"AccountName:transfer:from"` or `"AccountName:transfer:to"`. File src/index.ts Copy `import { ponder } from "ponder:registry"; ponder.on("AccountName:transfer:from", async ({ event, context }) => { // ... });` The `event` object contains the transfer and raw block, transaction, and trace. Copy ``type TransferEvent = { transfer: { from: `0x${string}`; to: `0x${string}`; value: bigint; }; block: Block; transaction: Transaction; trace: Trace; // Enabled using `includeTransactionReceipts` in account config transactionReceipt?: TransactionReceipt; };`` ### Block event[](https://ponder.sh/docs/api-reference/ponder/indexing-functions#block-event) Block events are created by [block intervals](https://ponder.sh/docs/config/block-intervals) . File src/index.ts Copy `import { ponder } from "ponder:registry"; ponder.on("SourceName:block", async ({ event, context }) => { // ... });` The `event` object contains only the block. Copy `type BlockEvent = { block: Block; };` ### `"setup"` event[](https://ponder.sh/docs/api-reference/ponder/indexing-functions#setup-event) You can also define a setup function for each contract that runs before indexing begins. * The indexing function does not receive an `event` argument, only `context`. * If you read from contracts in a `"setup"` indexing function, the `blockNumber` for the request is set to the contract's `startBlock`. For example, you might have a singleton `World` record that occasionally gets updated in indexing functions. File src/index.ts Copy `import { ponder } from "ponder:registry"; import { world } from "ponder:schema"; ponder.on("FunGame:NewPlayer", async ({ context }) => { await context.db .insert(world) .values({ id: 1, playerCount: 0 }) .onConflictDoUpdate((row) => ({ playerCount: row.playerCount + 1, })); });` Without the `"setup"` event, you need to upsert the record in each indexing function that attempts to use it, which is clunky and bad for performance. Instead, use the `"setup"` event to create the singleton record once at the beginning of indexing. File src/index.ts Copy `import { ponder } from "ponder:registry"; import { world } from "ponder:schema"; ponder.on("FunGame:setup", async ({ context }) => { await context.db.insert(world).values({ id: 1, playerCount: 0, }); }); ponder.on("FunGame:NewPlayer", async ({ context }) => { await context.db .update(world, { id: 1 }) .set((row) => ({ playerCount: row.playerCount + 1, })); });` Context[](https://ponder.sh/docs/api-reference/ponder/indexing-functions#context) ------------------------------------------------------------------------------------ The `context` argument passed to each indexing function contains database model objects and helper objects based on your config. At runtime, the indexing engine uses a different `context` object depending on the chain the current event was emitted on. The TypeScript types for the `context` object reflect this by creating a union of possible types for `context.chain` and `context.contracts`. Copy ``type Context = { db: Database; chain: { name: string; id: number }; client: ReadOnlyClient; contracts: Record< string, { abi: Abi; address?: `0x${string}`; startBlock?: number; endBlock?: number; } >; };`` ### Database[](https://ponder.sh/docs/api-reference/ponder/indexing-functions#database) The `context.db` object is a live database connection. [Read more](https://ponder.sh/docs/indexing/write) about writing to the database. File src/index.ts Copy `import { ponder } from "ponder:registry"; import { persons, dogs } from "ponder:schema"; ponder.on("Neighborhood:NewNeighbor", async ({ event, context }) => { await context.db.insert(persons).values({ name: "bob", age: 30 }); await context.db.insert(dogs).values({ name: "jake", ownerName: "bob" }); const jake = await context.db.find(dogs, { name: "jake" }); });` ### Chain[](https://ponder.sh/docs/api-reference/ponder/indexing-functions#chain) The `context.chain` object includes information about the chain that the current event is from. The object is strictly typed according to the chains defined in `ponder.config.ts`. File src/index.ts Copy `ponder.on("UniswapV3Factory:Ownership", async ({ event, context }) => { context.chain; // ^? { name: "mainnet", id: 1 } | { name: "base", id: 8453 } if (context.chain.name === "mainnet") { // Do mainnet-specific stuff! } });` ### Client[](https://ponder.sh/docs/api-reference/ponder/indexing-functions#client) ### Contracts[](https://ponder.sh/docs/api-reference/ponder/indexing-functions#contracts) Types[](https://ponder.sh/docs/api-reference/ponder/indexing-functions#types) -------------------------------------------------------------------------------- The `"ponder:registry"` module exports utility types that are useful for creating reusable helper functions in your indexing files. ### EventNames[](https://ponder.sh/docs/api-reference/ponder/indexing-functions#eventnames) A union of all event names that are available from the contracts defined in `ponder.config.ts`. File src/helpers.ts Copy `import { ponder, type EventNames } from "ponder:registry"; function helper(eventName: EventNames) { eventName; // ^? "Weth:Deposit" | "Weth:Withdraw" | "Weth:Approval | "Weth:Transfer" }` ### Event[](https://ponder.sh/docs/api-reference/ponder/indexing-functions#event) A generic type that optionally accepts an event name and returns the `event` object type for that event. File src/helpers.ts Copy ``import { ponder, type Event } from "ponder:registry"; function helper(event: Event<"Weth:Deposit">) { event; // ^? { // args: { dst: `0x${string}`; wad: bigint }; // block: Block; // event: "Deposit"; // transaction: Transaction; // log: Log; // } }`` If no event name is provided, `Event` is the union of all event types. This can be useful if all you need is the `block`, `transaction`, and `log` types which are the same for all events. File src/helpers.ts Copy ``import { ponder, type Event } from "ponder:registry"; function helper(event: Event) { event; // ^? { args: { dst: `0x${string}`; wad: bigint }; block: Block; event: "Deposit"; transaction: Transaction; log: Log; } // | { args: { src: `0x${string}`; wad: bigint }; block: Block; event: "Withdraw"; transaction: Transaction; log: Log; } // ... }`` ### Context[](https://ponder.sh/docs/api-reference/ponder/indexing-functions#context-1) A generic type that optionally accepts an event name and returns the `context` object type. File src/helpers.ts Copy ``import { ponder, type Context } from "ponder:registry"; function helper(context: Context<"Weth:Deposit">) { event; // ^? { // chain: { name: "mainnet"; id: 1; }; // client: ReadonlyClient; // db: { Account: DatabaseModel<{ id: `0x${string}`; balance: bigint; }> }; // contracts: { weth9: { abi: ...; address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" } }; // } }`` If no event name is provided, `Context` returns the union of all context types. This can be useful if all you need is the `db` or `contracts` types which are the same for all events. ### IndexingFunctionArgs[](https://ponder.sh/docs/api-reference/ponder/indexing-functions#indexingfunctionargs) A generic type that optionally accepts an event name and returns the indexing function argument type. File src/helpers.ts Copy `import { ponder, type IndexingFunctionArgs } from "ponder:registry"; function helper(args: IndexingFunctionArgs<"Weth:Deposit">) { args; // ^? { // event: { ... }; // context: { ... }; // } }` Like `Event` and `Context`, `IndexingFunctionArgs` returns the union of all indexing function argument types if no event name is provided. ### Schema[](https://ponder.sh/docs/api-reference/ponder/indexing-functions#schema) Use the [Drizzle type helpers](https://orm.drizzle.team/docs/goodies#type-api) to create custom types for database records. File src/helpers.ts Copy `import { accounts } from "ponder:schema"; function helper(account: typeof accounts.$inferSelect) { account; // ^? { // id: bigint; // balance: bigint; // nickname: string; // createdAt: number; // } }` ### EVM objects[](https://ponder.sh/docs/api-reference/ponder/indexing-functions#evm-objects) Copy ``/** The block containing the transaction that emitted the log being processed. */ type Block = { /** Base fee per gas */ baseFeePerGas: bigint | null; /** "Extra data" field of this block */ extraData: `0x${string}`; /** Maximum gas allowed in this block */ gasLimit: bigint; /** Total used gas by all transactions in this block */ gasUsed: bigint; /** Block hash */ hash: `0x${string}`; /** Logs bloom filter */ logsBloom: `0x${string}`; /** Address that received this block's mining rewards */ miner: `0x${string}`; /** Block number */ number: bigint; /** Parent block hash */ parentHash: `0x${string}`; /** Root of the this block's receipts trie */ receiptsRoot: `0x${string}`; /** Size of this block in bytes */ size: bigint; /** Root of this block's final state trie */ stateRoot: `0x${string}`; /** Unix timestamp of when this block was collated */ timestamp: bigint; /** Total difficulty of the chain until this block */ totalDifficulty: bigint | null; /** Root of this block's transaction trie */ transactionsRoot: `0x${string}`; }; /** The transaction that emitted the log being processed. */ type Transaction = { /** Transaction sender */ from: `0x${string}`; /** Gas provided for transaction execution */ gas: bigint; /** Base fee per gas. */ gasPrice?: bigint | undefined; /** Hash of this transaction */ hash: `0x${string}`; /** Contract code or a hashed method call */ input: `0x${string}`; /** Total fee per gas in wei (gasPrice/baseFeePerGas + maxPriorityFeePerGas). */ maxFeePerGas?: bigint | undefined; /** Max priority fee per gas (in wei). */ maxPriorityFeePerGas?: bigint | undefined; /** Unique number identifying this transaction */ nonce: number; /** Transaction recipient or `null` if deploying a contract */ to: `0x${string}` | null; /** Index of this transaction in the block */ transactionIndex: number; /** Value in wei sent with this transaction */ value: bigint; }; /** A confirmed Ethereum transaction receipt. */ type TransactionReceipt = { /** Address of new contract or `null` if no contract was created */ contractAddress: Address | null; /** Gas used by this and all preceding transactions in this block */ cumulativeGasUsed: bigint; /** Pre-London, it is equal to the transaction's gasPrice. Post-London, it is equal to the actual gas price paid for inclusion. */ effectiveGasPrice: bigint; /** Transaction sender */ from: Address; /** Gas used by this transaction */ gasUsed: bigint; /** * NOTE: The `logs` property is NOT included for performance reasons. * List of log objects generated by this transaction * logs: Log[]; */ /** Logs bloom filter */ logsBloom: Hex; /** `success` if this transaction was successful or `reverted` if it failed */ status: "success" | "reverted"; /** Transaction recipient or `null` if deploying a contract */ to: Address | null; /** Transaction type */ type: TransactionType; }; /** The log being processed. */ type Log = { /** Globally unique identifier for this log (`${blockHash}-${logIndex}`). */ id: string; /** The address from which this log originated */ address: `0x${string}`; /** Contains the non-indexed arguments of the log */ data: `0x${string}`; /** Index of this log within its block */ logIndex: number; /** * Indicates if this log was removed in a chain reorganization. * * Ponder automatically handles reorgs, so this will always be `false`. */ removed: boolean; /** List of order-dependent topics */ topics: [`0x${string}`, ...`0x${string}`[]] | []; }; type Trace = { /** Globally unique identifier for this trace (`${transactionHash}-${tracePosition}`) */ id: string; /** The type of the call. */ type: | "CALL" | "CALLCODE" | "DELEGATECALL" | "STATICCALL" | "CREATE" | "CREATE2" | "SELFDESTRUCT"; /** The address of that initiated the call. */ from: Address; /** The address of the contract that was called. */ to: Address | null; /** How much gas was left before the call. */ gas: bigint; /** How much gas was used by the call. */ gasUsed: bigint; /** Calldata input. */ input: Hex; /** Output of the call, if any. */ output?: Hex; /** Error message, if any. */ error?: string; /** Why this call reverted, if it reverted. */ revertReason?: string; /** Value transferred. */ value: bigint | null; /** Index of this trace in the transaction. */ traceIndex: number; /** Number of subcalls. */ subcalls: number; };`` --- # @ponder/client – Ponder [Skip to content](https://ponder.sh/docs/api-reference/ponder-client#vocs-content) Introducing [Marble](https://marble.xyz/) , the company behind Ponder Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu @ponder/client On this page Chevron Right The `@ponder/client` package provides a typed SQL over HTTP client for querying your Ponder database from client applications. Installation[](https://ponder.sh/docs/api-reference/ponder-client#installation) ---------------------------------------------------------------------------------- pnpmyarnnpmbun Terminal pnpm Copy `pnpm add @ponder/client` `createClient`[](https://ponder.sh/docs/api-reference/ponder-client#createclient) ------------------------------------------------------------------------------------ Create a client object connected to a Ponder server. #### Usage[](https://ponder.sh/docs/api-reference/ponder-client#usage) Client project Copy `import { createClient } from "@ponder/client"; import * as schema from "../../ponder/ponder.schema"; const client = createClient("https://.../sql", { schema }); export { client, schema };` #### Parameters[](https://ponder.sh/docs/api-reference/ponder-client#parameters) | Parameter | Type | Description | | --- | --- | --- | | `baseUrl` | `string` | Ponder server URL where the `client` middleware is running | | `options.schema` | `Schema \| undefined` | The schema exported by `ponder.schema.ts` | #### Returns[](https://ponder.sh/docs/api-reference/ponder-client#returns) Returns a `Client` object with methods for querying the database. `client.db`[](https://ponder.sh/docs/api-reference/ponder-client#clientdb) ----------------------------------------------------------------------------- Build a SQL query using Drizzle and execute it over HTTP. #### Usage[](https://ponder.sh/docs/api-reference/ponder-client#usage-1) Client project Copy `import { client, schema } from "../lib/ponder"; import { eq } from "@ponder/client"; const result = await client.db.select().from(schema.account).limit(10); const filteredResults = await client.db .select() .from(schema.account) .where(eq(schema.account.id, "0x123..."));` #### Returns[](https://ponder.sh/docs/api-reference/ponder-client#returns-1) Returns an array of objects according to the query. `client.live`[](https://ponder.sh/docs/api-reference/ponder-client#clientlive) --------------------------------------------------------------------------------- Subscribe to live updates from the database using server-sent events (SSE). #### Usage[](https://ponder.sh/docs/api-reference/ponder-client#usage-2) Client project Copy `import { client, schema } from "../lib/ponder"; const { unsubscribe } = client.live( (db) => db.select().from(schema.account), (result) => { console.log("Updated accounts:", result); }, (error) => { console.error("Subscription error:", error); } ); // Later, to stop receiving updates: unsubscribe();` #### Parameters[](https://ponder.sh/docs/api-reference/ponder-client#parameters-1) | Parameter | Type | Description | | --- | --- | --- | | `queryFn` | `(db: ClientDb) => Promise` | A query builder callback using the `db` argument | | `onData` | `(result: Result) => void` | Callback that receives each new query result | | `onError` | `(error: Error) => void` | Optional callback that handles any errors that occur | #### Returns[](https://ponder.sh/docs/api-reference/ponder-client#returns-2) Returns an object with an `unsubscribe` method that can be called to stop receiving updates. #### Implementation notes[](https://ponder.sh/docs/api-reference/ponder-client#implementation-notes) * Each `createClient` instance multiplexes all live queries over a single SSE connection. * The server notifies the client whenever a new block gets indexed. If a query result is no longer valid, the client immediately refetches it to receive the latest result. `client.getStatus`[](https://ponder.sh/docs/api-reference/ponder-client#clientgetstatus) ------------------------------------------------------------------------------------------- Fetch the indexing progress of each chain. #### Usage[](https://ponder.sh/docs/api-reference/ponder-client#usage-3) Client project Copy `import { client, schema } from "../lib/ponder"; const status = await client.getStatus(); console.log("Mainnet indexing status:", status.mainnet);` #### Returns[](https://ponder.sh/docs/api-reference/ponder-client#returns-3) Returns a Promise that resolves to an object containing the indexing status of each chain. Copy `type Status = { [chain: string]: { id: number; block: { number: number; timestamp: number } | null; }; };` Drizzle utility functions[](https://ponder.sh/docs/api-reference/ponder-client#drizzle-utility-functions) ------------------------------------------------------------------------------------------------------------ The `@ponder/client` package exports all relevant Drizzle utility functions ([full list](https://github.com/ponder-sh/ponder/blob/main/packages/client/src/index.ts#L176) ). You shouldn't need to install `drizzle-orm` separately. #### Usage[](https://ponder.sh/docs/api-reference/ponder-client#usage-4) Client project Copy `import { client, schema } from "../lib/ponder"; import { eq, gte, and, desc } from "@ponder/client"; const result = await client.db .select() .from(schema.transfers) .where( and( gte(schema.transfers.value, 1000000n), eq(schema.transfers.from, "0x123...") ) ) .orderBy(desc(schema.transfers.blockNumber));` --- # API endpoints – Ponder [Skip to content](https://ponder.sh/docs/api-reference/ponder/api-endpoints#vocs-content) Introducing [Marble](https://marble.xyz/) , the company behind Ponder Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu API endpoints On this page Chevron Right Ponder's HTTP server is built with [Hono](https://hono.dev/) , a fast and lightweight routing framework. Hono[](https://ponder.sh/docs/api-reference/ponder/api-endpoints#hono) ------------------------------------------------------------------------- Hono supports all standard HTTP methods and an intuitive middleware API. | method | description | | --- | --- | | `get` | Register a handler for GET requests | | `post` | Register a handler for POST requests | | `use` | Register middleware for a path | | ...and many more | | File requirements[](https://ponder.sh/docs/api-reference/ponder/api-endpoints#file-requirements) --------------------------------------------------------------------------------------------------- The `src/api/index.ts` file _must_ **default export** a Hono app instance. You can use other files in `src/api/` to organize your code, just be sure to export the Hono app instance correctly. File src/api/index.ts Copy `import { Hono } from "hono"; const app = new Hono(); app.get("/hello", (c) => { return c.text("Hello, world!"); }); export default app;` `ponder:schema`[](https://ponder.sh/docs/api-reference/ponder/api-endpoints#ponderschema) -------------------------------------------------------------------------------------------- The `ponder:schema` virtual module conveniently re-exports the `ponder.schema.ts` file. File src/api/index.ts Copy `import { db } from "ponder:api"; import { accounts } from "ponder:schema"; import { Hono } from "hono"; const app = new Hono(); app.get("/account/top-10", async (c) => { const account = await db .select() .from(accounts) .limit(10) return c.json(account); }); export default app;` `ponder:api`[](https://ponder.sh/docs/api-reference/ponder/api-endpoints#ponderapi) -------------------------------------------------------------------------------------- The `ponder:api` virtual module exports a ready-to-use Drizzle database client and Viem clients configured according to `ponder.config.ts`. ### `db`[](https://ponder.sh/docs/api-reference/ponder/api-endpoints#db) A read-only Drizzle database client backed by a client-side connection pool. #### Usage[](https://ponder.sh/docs/api-reference/ponder/api-endpoints#usage) File src/api/index.ts Copy `import { db } from "ponder:api"; import { accounts } from "ponder:schema"; import { Hono } from "hono"; import { eq } from "ponder"; const app = new Hono(); app.get("/account/:address", async (c) => { const address = c.req.param("address"); const account = await db .select() .from(accounts) .where(eq(accounts.address, address)); return c.json(account); }); export default app;` ### `publicClients`[](https://ponder.sh/docs/api-reference/ponder/api-endpoints#publicclients) A object containing a Viem [Public Client](https://viem.sh/docs/clients/public) for each chain defined in `ponder.config.ts`. #### Usage[](https://ponder.sh/docs/api-reference/ponder/api-endpoints#usage-1) File src/api/index.ts Copy `import { publicClients } from "ponder:api"; import { Hono } from "hono"; const app = new Hono(); app.get("/balance/:address", async (c) => { const address = c.req.param("address"); const balance = await publicClients["base"].getBalance({ address }); return c.json({ address, balance }); }); export default app;` Middlewares[](https://ponder.sh/docs/api-reference/ponder/api-endpoints#middlewares) --------------------------------------------------------------------------------------- ### `graphql`[](https://ponder.sh/docs/api-reference/ponder/api-endpoints#graphql) The `graphql` function is a Hono middleware that accepts the `ponder.schema.ts` schema object and automatically generates a fast & secure GraphQL API. | field | type | description | | --- | --- | --- | | **db** | `Database` | **Required**. Drizzle database object exported from `"ponder:api"`. | | **schema** | `Schema` | **Required**. Drizzle schema exported from `"ponder:schema"`. | #### Usage[](https://ponder.sh/docs/api-reference/ponder/api-endpoints#usage-2) File src/api/index.ts Copy `import { db } from "ponder:api"; import schema from "ponder:schema"; import { graphql } from "ponder"; import { Hono } from "hono"; const app = new Hono(); app.use("/", graphql({ db, schema })); app.use("/graphql", graphql({ db, schema })); export default app;` ### `client`[](https://ponder.sh/docs/api-reference/ponder/api-endpoints#client) The `client` function is a Hono middleware that serves SQL over HTTP queries submitted by `@ponder/client`. | field | type | description | | --- | --- | --- | | **db** | `Database` | **Required**. Drizzle database object exported from `"ponder:api"`. | | **schema** | `Schema` | **Required**. Drizzle schema exported from `"ponder:schema"`. | #### Usage[](https://ponder.sh/docs/api-reference/ponder/api-endpoints#usage-3) File src/api/index.ts Copy `import { db } from "ponder:api"; import schema from "ponder:schema"; import { Hono } from "hono"; import { client } from "ponder"; const app = new Hono(); app.use("/sql/*", client({ db, schema })); export default app;` Reserved routes[](https://ponder.sh/docs/api-reference/ponder/api-endpoints#reserved-routes) ----------------------------------------------------------------------------------------------- Ponder reserves a few routes for standard use. If you register custom endpoints that conflict with these routes, the build will fail. | route | description | | --- | --- | | `/health` | Returns status code `200` immediately after the process starts. | | `/ready` | Returns status code `503` during the backfill, and status code `200` after the backfill is complete. | | `/status` | Returns the current indexing status. [Read more](https://ponder.sh/docs/advanced/observability#indexing-status)
. | | `/metrics` | Prometheus metrics endpoint. [Read more](https://ponder.sh/docs/advanced/observability#metrics)
. | --- # @ponder/react – Ponder [Skip to content](https://ponder.sh/docs/api-reference/ponder-react#vocs-content) Introducing [Marble](https://marble.xyz/) , the company behind Ponder Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu @ponder/react On this page Chevron Right The `@ponder/react` package provides React hooks for subscribing to live updates from your database. This package uses [`@ponder/client`](https://ponder.sh/docs/api-reference/ponder-client) to execute SQL queries over HTTP, and integrates with [TanStack Query](https://tanstack.com/query) for async state management. Installation[](https://ponder.sh/docs/api-reference/ponder-react#installation) --------------------------------------------------------------------------------- `@ponder/react` has peer dependencies on `@ponder/client` and `@tanstack/react-query`. pnpmyarnnpmbun Terminal pnpm Copy `pnpm add @ponder/react @ponder/client @tanstack/react-query` `PonderProvider`[](https://ponder.sh/docs/api-reference/ponder-react#ponderprovider) --------------------------------------------------------------------------------------- React Context Provider that makes the SQL over HTTP client instance available to all child components. #### Usage[](https://ponder.sh/docs/api-reference/ponder-react#usage) Client project Copy `import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { PonderProvider } from "@ponder/react"; import { client } from "../lib/ponder"; // Client instance from @ponder/client const queryClient = new QueryClient(); function App() { return ( {/* Your application components */} ); }` #### Parameters[](https://ponder.sh/docs/api-reference/ponder-react#parameters) | Parameter | Type | Description | | --- | --- | --- | | `client` | `Client` | A client instance returned by `createClient` from `@ponder/client` | `usePonderQuery`[](https://ponder.sh/docs/api-reference/ponder-react#useponderquery) --------------------------------------------------------------------------------------- Hook to run a custom SQL query over HTTP with live updates. #### Usage[](https://ponder.sh/docs/api-reference/ponder-react#usage-1) Client project Copy `import { usePonderQuery } from "@ponder/react"; import { schema } from "../lib/ponder"; function AccountList() { const { data, isLoading, error } = usePonderQuery({ queryFn: (db) => db .select() .from(schema.account) .orderBy(schema.account.createdAt) .limit(10), }); if (isLoading) return
Loading...
; if (error) return
Error: {error.message}
; return (
    {data?.map((account) =>
  • {account.address}
  • )}
); }` #### Parameters[](https://ponder.sh/docs/api-reference/ponder-react#parameters-1) | Parameter | Type | Description | | --- | --- | --- | | `params.queryFn` | `(db: Client["db"]) => Promise` | Required query builder callback using the `db` argument | | `params.live` | `boolean` | Whether to subscribe to live updates (default: `true`) | | `...params` | `Omit, "queryFn" \| "queryKey">` | All `useQuery` options except `queryFn` and `queryKey` | #### Returns[](https://ponder.sh/docs/api-reference/ponder-react#returns) Returns a normal TanStack `useQuery` result object. [Read more](https://tanstack.com/query/latest/docs/framework/react/reference/useQuery) in the TanStack documentation. #### Implementation notes[](https://ponder.sh/docs/api-reference/ponder-react#implementation-notes) * Uses [`client.live`](https://ponder.sh/docs/api-reference/ponder-client#clientlive) to automatically refetch data when new blocks are indexed * Subscribes to updates on mount and unsubscribes on unmount * Requires `PonderProvider` in the component tree `usePonderStatus`[](https://ponder.sh/docs/api-reference/ponder-react#useponderstatus) ----------------------------------------------------------------------------------------- Hook to query the indexing status of the Ponder server over HTTP with live updates. #### Usage[](https://ponder.sh/docs/api-reference/ponder-react#usage-2) Client project Copy `import { usePonderStatus } from "@ponder/react"; function IndexingStatus() { const { data, isLoading } = usePonderStatus(); if (isLoading) return
Loading status...
; return (
{Object.entries(data).map(([chain, status]) => (

{chain}

{status.block && (

Block: {status.block.number} ( {new Date(status.block.timestamp * 1000).toLocaleString()})

)}
))}
); }` #### Parameters[](https://ponder.sh/docs/api-reference/ponder-react#parameters-2) | Parameter | Type | Description | | --- | --- | --- | | `params` | `Omit, "queryFn" \| "queryKey">` | All `useQuery` options except `queryFn` and `queryKey` | #### Returns[](https://ponder.sh/docs/api-reference/ponder-react#returns-1) Returns a normal TanStack `useQuery` result containing the indexing [status object](https://ponder.sh/docs/advanced/observability#indexing-status) . [Read more](https://tanstack.com/query/latest/docs/framework/react/reference/useQuery) in the TanStack documentation. `getPonderQueryOptions`[](https://ponder.sh/docs/api-reference/ponder-react#getponderqueryoptions) ----------------------------------------------------------------------------------------------------- Helper function to build the TanStack Query `queryFn` and `queryKey` for a SQL over HTTP query. File index.ts Copy `import { getPonderQueryOptions } from "@ponder/react"; import { client, schema } from "../lib/ponder"; const accountsQueryOptions = getPonderQueryOptions(client, (db) => db.select().from(schema.account).limit(10) ); const query = usePonderQuery(accountsQueryOptions);` #### Parameters[](https://ponder.sh/docs/api-reference/ponder-react#parameters-3) | Parameter | Type | Description | | --- | --- | --- | | `client` | `Client` | A client instance created by `createClient` | | `queryFn` | `(db: Client["db"]) => Promise` | Function that receives the Drizzle query builder and returns a query result promise | #### Returns[](https://ponder.sh/docs/api-reference/ponder-react#returns-2) | Property | Type | Description | | --- | --- | --- | | `queryKey` | `QueryKey` | A TanStack Query [Key](https://tanstack.com/query/latest/docs/framework/react/guides/query-keys)
that encodes the SQL statement | | `queryFn` | `() => Promise` | A TanStack Query [Function](https://tanstack.com/query/latest/docs/framework/react/guides/query-functions)
that executes the SQL query over HTTP | `usePonderQueryOptions`[](https://ponder.sh/docs/api-reference/ponder-react#useponderqueryoptions) ----------------------------------------------------------------------------------------------------- Same as [`getPonderQueryOptions`](https://ponder.sh/docs/api-reference/ponder-react#getponderqueryoptions) , but uses the `PonderProvider` context to get the client instance. File index.ts Copy `import { usePonderQueryOptions } from "@ponder/react"; import { schema } from "../lib/ponder"; const accountsQueryOptions = usePonderQueryOptions((db) => db.select().from(schema.account).limit(10) ); const query = usePonderQuery(accountsQueryOptions);` #### Parameters[](https://ponder.sh/docs/api-reference/ponder-react#parameters-4) | Parameter | Type | Description | | --- | --- | --- | | `queryFn` | `(db: Client["db"]) => Promise` | Function that receives the Drizzle query builder and returns a query result promise | #### Returns[](https://ponder.sh/docs/api-reference/ponder-react#returns-3) | Property | Type | Description | | --- | --- | --- | | `queryKey` | `QueryKey` | A TanStack Query [Key](https://tanstack.com/query/latest/docs/framework/react/guides/query-keys)
that encodes the SQL statement | | `queryFn` | `() => Promise` | A TanStack Query [Function](https://tanstack.com/query/latest/docs/framework/react/guides/query-functions)
that executes the SQL query over HTTP | `usePonderClient`[](https://ponder.sh/docs/api-reference/ponder-react#useponderclient) ----------------------------------------------------------------------------------------- Hook to get the `client` instance from the `PonderProvider` context. File index.ts Copy `import { usePonderClient } from "@ponder/react"; const client = usePonderClient();` #### Returns[](https://ponder.sh/docs/api-reference/ponder-react#returns-4) Returns the `client` instance from the `PonderProvider` context. --- # @ponder/utils – Ponder [Skip to content](https://ponder.sh/docs/api-reference/ponder-utils#vocs-content) Introducing [Marble](https://marble.xyz/) , the company behind Ponder Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu @ponder/utils On this page Chevron Right The `@ponder/utils` package provides utility functions for common tasks in Ponder apps. `mergeAbis`[](https://ponder.sh/docs/api-reference/ponder-utils#mergeabis) ----------------------------------------------------------------------------- Combines many ABIs into one. Removes duplicate items if necessary. #### Usage[](https://ponder.sh/docs/api-reference/ponder-utils#usage) File index.ts Copy `import { mergeAbis } from "@ponder/utils"; import { erc20Abi, erc4626Abi } from "viem"; const tokenAbi = mergeAbis([erc20Abi, erc4626Abi]);` #### Parameters[](https://ponder.sh/docs/api-reference/ponder-utils#parameters) | Parameter | Type | Description | | --- | --- | --- | | `ABIs` | `Abi[]` | A list of ABIs to merge. | #### Returns[](https://ponder.sh/docs/api-reference/ponder-utils#returns) A new ABI including all items from the input list, with duplicates removed. #### Usage in Ponder[](https://ponder.sh/docs/api-reference/ponder-utils#usage-in-ponder) Use `mergeAbis` to include multiple ABIs for a single contract. This is especially useful for proxy contracts that have had several different implementation ABIs. For convenience, `ponder` re-exports `mergeAbis` from `@ponder/utils`. File ponder.config.ts Copy `import { createConfig, mergeAbis } from "ponder"; import { ERC1967ProxyAbi } from "./abis/ERC1967Proxy"; import { NameRegistryAbi } from "./abis/NameRegistry"; import { NameRegistry2Abi } from "./abis/NameRegistry2"; export default createConfig({ contracts: { FarcasterNameRegistry: { abi: mergeAbis([ERC1967ProxyAbi, NameRegistryAbi, NameRegistry2Abi]), // ... }, }, // ... });` `replaceBigInts`[](https://ponder.sh/docs/api-reference/ponder-utils#replacebigints) --------------------------------------------------------------------------------------- Replaces all [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) values in an object (deep traversal) with a new value, specified by a replacer function. #### Usage[](https://ponder.sh/docs/api-reference/ponder-utils#usage-1) This example simply converts `BigInt` values to a string. File index.ts Copy `import { replaceBigInts } from "@ponder/utils"; const obj = { a: 100n, b: [-12n, 3_000_000_000n] }; const result = replaceBigInts(obj, (v) => String(v)); // ?^ { a: '100', b: [ '-12', '3000000000' ] }` #### Parameters[](https://ponder.sh/docs/api-reference/ponder-utils#parameters-1) | Parameter | Type | Description | | --- | --- | --- | | `value` | `any` | The scalar, array, or object containing `BigInt` values to be replaced. | | `replacer` | `(value: bigint) => JSONSerializable` | A custom replacer function that will be called for each `BigInt` value. | #### Returns[](https://ponder.sh/docs/api-reference/ponder-utils#returns-1) The scalar, array, or object with all `BigInt` values replaced. #### Replacer functions[](https://ponder.sh/docs/api-reference/ponder-utils#replacer-functions) Here are three common ways to replace `BigInt` values. | Encoding | Replacer type | Replacer function | | --- | --- | --- | | **Hex** | `0x${string}` | [`numberToHex`](https://viem.sh/docs/utilities/toHex#numbertohex) | | **String** | `string` | `String` | | **Lossless string** | `#bigint.${string}` | `(x) => #bigint.${String(x)}` | See the [Wagmi FAQ](https://wagmi.sh/react/guides/faq#bigint-serialization) for more information on `BigInt` serialization. #### Usage in Ponder: `json` columns[](https://ponder.sh/docs/api-reference/ponder-utils#usage-in-ponder-json-columns) The [`json` column type](https://ponder.sh/docs/schema/tables#column-types) does not support `BigInt` values. Use `replaceBigInts` to prepare objects containing `BigInt` values for insertion. File src/index.ts Copy `import { ponder } from "ponder:registry"; import { userOperations } from "ponder:schema"; import { replaceBigInts } from "@ponder/utils"; import { toHex } from "viem"; ponder.on("EntryPoint:UserOp", async ({ event, context }) => { await context.db.insert(userOperations).values({ id: event.log.id, receipt: replaceBigInts(event.transactionReceipt, toHex), }); });` To maintain type safety for column values, use the `ReplaceBigInts` helper type in the column `$type` annotation. File ponder.schema.ts Copy `import { onchainTable } from "ponder"; import type { ReplaceBigInts } from "@ponder/utils"; import type { TransactionReceipt, Hex } from "viem"; export const userOperations = onchainTable("user_operations", (t) => ({ id: t.text().primaryKey(), receipt: t.json>(), }));` #### Usage in Ponder: API endpoints[](https://ponder.sh/docs/api-reference/ponder-utils#usage-in-ponder-api-endpoints) The GraphQL API automatically serializes `BigInt` values to strings before returning them in HTTP responses. In custom API endpoints, you need to handle this serialization process manually. File src/api/index.ts Copy `import { ponder } from "ponder:registry"; import { accounts } from "ponder:schema"; import { replaceBigInts } from "@ponder/utils"; import { numberToHex } from "viem"; ponder.get("/whale-balances", async (c) => { const rows = await c.db .select({ address: accounts.address, ethBalance: accounts.ethBalance, dogeBalance: accounts.dogeBalance, }) .from(accounts) .where(eq(accounts.address, address)); const result = replaceBigInts(rows, (v) => numberToHex(v)); return c.json(result); });` Transports[](https://ponder.sh/docs/api-reference/ponder-utils#transports) ----------------------------------------------------------------------------- The `@ponder/utils` package exports two new [Viem transports](https://viem.sh/docs/clients/intro#transports) , `loadBalance` and `rateLimit`. These transports are useful for managing the RPC request workloads that Ponder apps commonly encounter. ### `loadBalance`[](https://ponder.sh/docs/api-reference/ponder-utils#loadbalance) The `loadBalance` Transport distributes requests across a list of inner Transports in a simple round-robin scheme. #### Usage[](https://ponder.sh/docs/api-reference/ponder-utils#usage-2) Copy `import { loadBalance } from "@ponder/utils"; import { createPublicClient, fallback, http, webSocket } from "viem"; import { mainnet } from "viem/chains"; const transport = loadBalance([ http("https://cloudflare-eth.com"), webSocket("wss://ethereum-rpc.publicnode.com"), rateLimit(http("https://rpc.ankr.com/eth"), { requestsPerSecond: 5 }), ]), const client = createPublicClient({ chain: mainnet, transport });` #### Parameters[](https://ponder.sh/docs/api-reference/ponder-utils#parameters-2) | Parameter | Type | Description | | --- | --- | --- | | `Transports` | `Transport[]` | A list of Transports to load balance requests across. | #### Usage in Ponder[](https://ponder.sh/docs/api-reference/ponder-utils#usage-in-ponder-1) For convenience, `ponder` re-exports `loadBalance` from `@ponder/utils`. File ponder.config.ts Copy `import { createConfig, loadBalance } from "ponder"; import { http, webSocket, rateLimit } from "viem"; export default createConfig({ chains: { mainnet: { id: 1, rpc: loadBalance([ http("https://cloudflare-eth.com"), http("https://eth-mainnet.public.blastapi.io"), webSocket("wss://ethereum-rpc.publicnode.com"), rateLimit(http("https://rpc.ankr.com/eth"), { requestsPerSecond: 5 }), ]), }, }, // ... });` ### `rateLimit`[](https://ponder.sh/docs/api-reference/ponder-utils#ratelimit) The `rateLimit` Transport limits the number of requests per second submitted to an inner Transport using a first-in-first-out queue. #### Usage[](https://ponder.sh/docs/api-reference/ponder-utils#usage-3) Copy `import { rateLimit } from "@ponder/utils"; import { createPublicClient, fallback, http } from "viem"; import { mainnet } from "viem/chains"; const client = createPublicClient({ chain: mainnet, transport: rateLimit(http("https://eth-mainnet.g.alchemy.com/v2/..."), { requestsPerSecond: 25, }), });` #### Parameters[](https://ponder.sh/docs/api-reference/ponder-utils#parameters-3) | Parameter | Type | Description | | --- | --- | --- | | `Transport` | `Transport` | An inner transport to rate limit. | | `requestsPerSecond` | `number` | The maximum number of requests per second to allow. | | `browser` | `boolean` (default: `true`) | If `false`, the internal queue will use the Node.js-specific `process.nextTick()` API to schedule requests. This leads to more predictable behavior in Node.js, but is not available in the browser. | #### Usage in Ponder[](https://ponder.sh/docs/api-reference/ponder-utils#usage-in-ponder-2) For convenience, `ponder` re-exports `rateLimit` from `@ponder/utils`. File ponder.config.ts Copy `import { createConfig, rateLimit } from "ponder"; import { http } from "viem"; export default createConfig({ chains: { mainnet: { id: 1, rpc: rateLimit(http(process.env.PONDER_RPC_URL_1), { requestsPerSecond: 25, }), }, }, contracts: { // ... }, });` --- # Observability – Ponder [Skip to content](https://ponder.sh/docs/advanced/observability#vocs-content) Introducing [Marble](https://marble.xyz/) , the company behind Ponder Search... [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) [![Logo](https://ponder.sh/ponder-dark.svg)![Logo](https://ponder.sh/ponder-light.svg)](https://ponder.sh/) Docs Chevron Down Menu Observability On this page Chevron Right Logs[](https://ponder.sh/docs/advanced/observability#logs) ------------------------------------------------------------- Ponder produces logs to help you understand and debug your application. ![Terminal logs gif](https://ponder.sh/logs-014.gif) ### Log level[](https://ponder.sh/docs/advanced/observability#log-level) There are two ways to configure the minimum log level. If specified, the environment variable takes precedence over the CLI flag. * Set the `PONDER_LOG_LEVEL` environment variable * Use the `--log-level `, `-v` (debug) or `-vv` (trace) CLI option File .env.local Copy `PONDER_LOG_LEVEL=trace` Terminal Terminal Copy `ponder dev --log-level warn # or, use the shortcut flag for debug ponder dev -v` #### Levels[](https://ponder.sh/docs/advanced/observability#levels) | Log level | Example | | --- | --- | | `silent` | | | `error` | Errors thrown in user code and other errors that will likely cause a crash | | `warn` | Malformed RPC data, reorgs, and other errors that will be retried | | `info` (default) | Indexing progress and key lifecycle events | | `debug` | Internal updates | | `trace` | Database query logs, RPC request logs | #### User logs[](https://ponder.sh/docs/advanced/observability#user-logs) Logs produced by your code (e.g. `console.log` statements in `ponder.config.ts` or indexing functions) will always be written to the console. Note that Ponder _does_ catch **errors** thrown by your code and emits an `error` log including the original error message and stack trace. ### Log format[](https://ponder.sh/docs/advanced/observability#log-format) Use the `--log-format ` CLI option to set the log format. #### Pretty (default)[](https://ponder.sh/docs/advanced/observability#pretty-default) Terminal Terminal Copy `ponder start --log-format pretty` Terminal Output Copy `12:12:15.391 INFO Indexed block chain=mainnet number=23569900 event_count=14 (23ms) 12:12:16.159 INFO Indexed block chain=polygon number=77633630 event_count=0 (1ms) 12:12:16.174 INFO Indexed block chain=optimism number=142386579 event_count=1 (4ms) 12:12:16.226 INFO Indexed block chain=base number=36791294 event_count=9 (14ms) 12:12:18.068 INFO Indexed block chain=optimism number=142386580 event_count=2 (8ms) 12:12:18.125 INFO Indexed block chain=polygon number=77633631 event_count=0 (1ms) 12:12:18.188 INFO Indexed block chain=base number=36791295 event_count=10 (16ms) 12:12:20.021 INFO Indexed block chain=optimism number=142386581 event_count=0 (4ms)` #### JSON[](https://ponder.sh/docs/advanced/observability#json) Terminal Terminal Copy `ponder start --log-format json` The JSON log format emits newline-delimited JSON objects with required properties `level`, `time`, and `msg`. Most logs also include a `duration` property and other properties depending on the context. Output Copy `{"level":30,"time":1760372079306,"msg":"Indexed block","chain":"mainnet","chain_id":1,"number":23569912,"event_count":17,"duration":27.752416999996058} {"level":30,"time":1760372080106,"msg":"Indexed block","chain":"polygon","chain_id":137,"number":77633702,"event_count":0,"duration":3.4684160000033444} {"level":30,"time":1760372080122,"msg":"Indexed block","chain":"optimism","chain_id":10,"number":142386651,"event_count":0,"duration":2.3179999999993015} {"level":30,"time":1760372080314,"msg":"Indexed block","chain":"base","chain_id":8453,"number":36791366,"event_count":10,"duration":18.320999999996275} {"level":30,"time":1760372082131,"msg":"Indexed block","chain":"optimism","chain_id":10,"number":142386652,"event_count":0,"duration":3.074124999999185} {"level":30,"time":1760372082258,"msg":"Indexed block","chain":"polygon","chain_id":137,"number":77633703,"event_count":0,"duration":1.7850829999952111} {"level":30,"time":1760372082328,"msg":"Indexed block","chain":"base","chain_id":8453,"number":36791367,"event_count":4,"duration":9.394625000000815} {"level":30,"time":1760372084153,"msg":"Indexed block","chain":"optimism","chain_id":10,"number":142386653,"event_count":0,"duration":2.679999999993015}` ### Terminal UI[](https://ponder.sh/docs/advanced/observability#terminal-ui) The dynamic terminal UI displays a useful summary of chain connection status, indexing function duration, and backfill progress. Terminal Output Copy `Chains │ Chain │ Status │ Block │ RPC (req/s) │ ├──────────┼────────┼───────────┼─────────────┤ │ optimism │ live │ 142388578 │ 1.6 │ │ polygon │ live │ 77635629 │ 17.2 │ │ base │ live │ 36793293 │ 4.2 │ │ mainnet │ live │ 23570232 │ 7.5 │ Indexing (live) │ Event │ Count │ Duration (ms) │ ├───────────────┼───────┼───────────────┤ │ WETH:Deposit │ 107 │ 1.554 │ API endpoints Live at http://localhost:42069` The terminal UI is disabled by default for `ponder start`. Use the `--disable-ui` CLI option to disable the UI for `ponder dev`. Terminal Terminal Copy `ponder dev --disable-ui` Metrics[](https://ponder.sh/docs/advanced/observability#metrics) ------------------------------------------------------------------- Ponder apps publish Prometheus metrics at the `/metrics` path. | name | description | type | | --- | --- | --- | | ponder\_indexing\_total\_seconds | Total number of seconds required for indexing | gauge | | ponder\_indexing\_completed\_seconds | Number of seconds that have been completed | gauge | | ponder\_indexing\_completed\_events | Number of events that have been processed | gauge | | ponder\_indexing\_completed\_timestamp | Timestamp through which all events have been completed | gauge | | ponder\_indexing\_function\_duration | Duration of indexing function execution | histogram | | ponder\_indexing\_function\_error\_total | Total number of errors encountered during indexing function execution | counter | | ponder\_historical\_start\_timestamp | Unix timestamp (ms) when the historical sync service started | gauge | | ponder\_historical\_total\_blocks | Number of blocks required for the historical sync | gauge | | ponder\_historical\_cached\_blocks | Number of blocks that were found in the cache for the historical sync | gauge | | ponder\_historical\_completed\_blocks | Number of blocks that have been processed for the historical sync | gauge | | ponder\_realtime\_is\_connected | Boolean (0 or 1) indicating if the realtime sync service is connected | gauge | | ponder\_realtime\_latest\_block\_number | Block number of the latest synced block | gauge | | ponder\_realtime\_latest\_block\_timestamp | Block timestamp of the latest synced block | gauge | | ponder\_realtime\_reorg\_total | Count of how many re-orgs have occurred | counter | | ponder\_database\_method\_duration | Duration of database operations | histogram | | ponder\_database\_method\_error\_total | Total number of errors encountered during database operations | counter | | ponder\_http\_server\_active\_requests | Number of active HTTP server requests | gauge | | ponder\_http\_server\_request\_duration\_ms | Duration of HTTP responses served by the server | histogram | | ponder\_http\_server\_request\_size\_bytes | Size of HTTP requests received by the server | histogram | | ponder\_http\_server\_response\_size\_bytes | Size of HTTP responses served by the server | histogram | | ponder\_rpc\_request\_duration | Duration of RPC requests | histogram | | ponder\_rpc\_request\_error\_total | Total number of failed RPC requests | counter | | ponder\_postgres\_pool\_connections | Gauge of current connections for PostgreSQL pools | gauge | | ponder\_postgres\_query\_queue\_size | Current size of the query queue for PostgreSQL | gauge | | ponder\_postgres\_query\_total | Total number of queries processed by PostgreSQL | counter | Indexing status[](https://ponder.sh/docs/advanced/observability#indexing-status) ----------------------------------------------------------------------------------- To check the indexing status of your app, use the `/status` endpoint or the `_meta` field in the GraphQL API. ### Usage[](https://ponder.sh/docs/advanced/observability#usage) Use the indexing status to quickly confirm that Ponder is working as expected. You can also poll the status to confirm that a specific block number has been ingested by Ponder before refetching a query client-side (for example, in a form submit handler). #### HTTP[](https://ponder.sh/docs/advanced/observability#http) Terminal Request Copy `curl http://localhost:42069/status` Response Copy `{ "mainnet": { "id": 1, "block": { "number": 20293450, "timestamp": 1720823759 } }, "base": { "id": 8453, "block": { "number": 17017206, "timestamp": 1720823759 } } }` #### GraphQL[](https://ponder.sh/docs/advanced/observability#graphql) Query Copy `query { _meta { status } }` Result Copy `{ "_meta": { "status": { "mainnet": { "id": 1, "block": { "number": 20293464, "timestamp": 1720823939 } }, "base": { "id": 8453, "block": null } } } }` ### API[](https://ponder.sh/docs/advanced/observability#api) The response object contains a property for each chain in your app with the following fields. | field | type | description | | --- | --- | --- | | **id** | `number` | The chain ID. | | **block** | `{ number: number; timestamp: number; } \| null` | The most recently indexed block, or `null` if the backfill is not complete. | ---