Introduction

Ponder

Ponder is an open-source framework for blockchain application backends. With Ponder, you can build a GraphQL API for any set of smart contracts on Ethereum-based blockchains while benefiting from hot reloading, type safety, and easy deployment to production.

Features

✅  Local development server with hot reloading
✅  create-ponder CLI tool to bootstrap a new project from Etherscan or a Graph Protocol subgraph
✅  End-to-end type safety using ABIType (opens in a new tab)
✅  Autogenerated GraphQL API (compatible with Graph Protocol subgraph schemas)
✅  Quickly deploy to Railway, or anywhere using Node.js/Docker
✅  Native support for cross-chain apps

Architecture overview

Ponder's architecture is very similar to Graph Protocol subgraphs. If you're familiar with building subgraphs, you can skip to Getting started.

TODO Figure 1: Blockchain events → Event handler functions → Entity database → GraphQL API → Client app

Example: ENS

Let's use Ponder to build a GraphQL API that tracks ownership of Ethereum Name Service registrations.

The ENS BaseRegistrar contract emits a NameRegistered event every time a user registers a new ENS name. Here's the relevant snippet from that contract:

contract BaseRegistrar {
 
    event NameRegistered(string name, address owner);
 
    function registerName(string calldata name, address owner) external {
        // ...
        emit NameRegistered(name, owner);
    }
}

Ponder config

First we'll add BaseRegistrar as a contract in our Ponder config file. Ponder's indexing engine will now fetch all the NameRegistered events that have been emitted by the BaseRegistrar contract.

ponder.config.ts
export const config = {
  networks: [
    {
      name: "mainnet",
      chainId: 1,
      rpcUrl: "https://eth-mainnet.g.alchemy.com/v2/...",
    },
  ],
  contracts: [
    {
      name: "BaseRegistrar",
      network: "mainnet",
      abi: "./abis/BaseRegistrar.json",
      address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85",
      startBlock: 9380410,
    },
  ],
};

GraphQL schema

Next we'll add an entity to our schema. The schema.graphql file defines the data that our GraphQL API will serve. Let's add an EnsName entity that stores the ENS name, the owner address, and a timestamp.

schema.graphql
type EnsName @entity {
  id: ID!
  name: String!
  owner: String!
  registeredAt: Int!
}

Event handler functions

Event handlers are JavaScript functions that accept a contract event and insert data into the entity store.

Here's an event handler to process the NameRegistered event. It inserts an EnsName entity into the store using the event parameters and the block timestamp.

src/EthereumNameService.ts
import { ponder } from "../generated";
 
ponder.on("BaseRegistrar:NameRegistered", async ({ event, context }) => {
  const { EnsName } = context.entities
 
  await EnsName.insert(`${owner}-${name}`, {
    name: event.params.name,
    owner: event.params.owner,
    registeredAt: event.block.timestamp,
  })
})

GraphQL API

Now that we've inserted some data into the entity store, we can query that data from the autogenerated GraphQL API.

TODO Figure 2: GraphiQL interface or figure with query and data

Get started

Migrate a Graph Protocol subgraph →

Create a new Ponder project →

Last updated on February 8, 2023