Introduction

Introduction

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 with hot reloading, type safety, and easy deployment to production.

Features

✅  Local development server with hot reloading
✅  create-ponder CLI tool to get started from an Etherscan link or 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
✅  Gracefully handles chain reorganizations
🏗️  Process transactions calls (in addition to logs)
🏗️  Run effects (e.g. send an API request) in indexing code

Architecture

PonderIndexing functionsBlockchainRawblockchaindataDatabaseApplicationApplicationdataWebsiteGraphQL APIMobile appSmart contractSmart contractApplication data

Data flows from smart contracts → indexing functions → application data store → GraphQL API → client applications.

Example: Ethereum Name Service

Ponder is similar to Graph Protocol subgraphs. If you've built a subgraph before, skip to getting started.

Let's use Ponder to build a GraphQL API that tracks ownership of ENS registrations. The ENS BaseRegistrar contract emits a NameRegistered event every time a user registers a new ENS name.

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

Add a contract to ponder.config.ts

First, we add BaseRegistrar as a contract in our config file. Ponder's sync engine fetches all the events logs that have been emitted by the BaseRegistrar contract.

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

Create your schema

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

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

Write indexing code

Indexing functions are TypeScript functions that process a blockchain event and insert data into the entity store. Here's an indexing function 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;
  const { name, owner } = event.params;
 
  await EnsName.create({
    id: `${name}-${owner}`,
    data: {
      name: name,
      owner: owner,
      registeredAt: event.block.timestamp
    }
  });
});

Query the API

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

http://localhost:42069/graphql
query GetEnsNames {
  ensNames(first: 2) {
    name
    owner
    registeredAt
  }
}
Result
{
  "ensNames": [
    {
      "name": "vitalik.eth",
      "owner": "0x0904Dac3347eA47d208F3Fd67402D039a3b99859",
      "registeredAt": 1580345271
    },
    {
      "name": "joe.eth",
      "owner": "0x6109DD117AA5486605FC85e040ab00163a75c662",
      "registeredAt": 1580754710
    },
  ]
}

The GraphQL API automatically includes features like pagination, sorting, and complex filters for each entity defined in schema.graphql.

Get started

Create a new Ponder project →

Migrate a Graph Protocol subgraph →