Get started – Ponder
Skip to content

Get started

An introduction to Ponder

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

Run create-ponder

The quickest way to create a new Ponder project is create-ponder, which sets up everything automatically for you.

pnpm
pnpm create ponder

On installation, you'll see a few prompts.

Default
✔ 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

After installation, start the local development server.

pnpm
pnpm dev

Ponder will connect to the database, start the HTTP server, and begin indexing.

Logs
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

Visit 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.

Query
query {
  accounts(orderBy: "balance", orderDirection: "desc", limit: 2) {
    items {
      address
      balance
    }
    totalCount
  }
}

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.

ponder.schema.ts
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
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

Update the indexing logic to include isOwner when inserting new rows into the account table.

src/index.ts
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.

Logs
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

This quickstart only scratches the surface of what Ponder can do. Take a look at the examples directory for more complex projects, or the GitHub dependents for a list of real-world repositories using Ponder.

Or, continue reading the guides and API reference here on the documentation site.