Getting started
Create a new Ponder project →
Migrate a Graph Protocol subgraph →
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
âś… Â 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
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.
import { createConfig } from "@ponder/core";
import { http } from "viem";
import { BaseRegistrarAbi } from "./abis/BaseRegistrar";
export default createConfig({
networks: {
mainnet: {
chainId: 1,
transport: http("https://eth-mainnet.g.alchemy.com/v2/..."),
},
},
contracts: {
BaseRegistrar: {
network: "mainnet",
abi: BaseRegistrarAbi,
address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85",
startBlock: 9380410,
},
},
});
Create your schema
The ponder.schema.ts
file contains the database schema, and defines the shape data that the GraphQL API serves. Let's add an EnsName
table that stores the name, the owner's address, and a timestamp.
import { createSchema } from "@ponder/core";
export default createSchema((p) => ({
EnsName: p.createTable({
id: p.string(),
name: p.string(),
owner: p.string(),
registeredAt: p.int(),
}),
}));
Write indexing code
Indexing functions are TypeScript functions that process a blockchain event and insert data into the store. Here's an indexing function to process the NameRegistered
event. It inserts an EnsName
record into the store using the event parameters and the block timestamp.
import { ponder } from "@/generated";
ponder.on("BaseRegistrar:NameRegistered", async ({ event, context }) => {
const { EnsName } = context.db;
await EnsName.create({
id: `${name}-${owner}`,
data: {
name: event.args.name,
owner: event.args.owner,
registeredAt: event.block.timestamp
}
});
});
Query the API
Now that we've inserted some data into the store, we can query that data from the autogenerated GraphQL API.
query GetEnsNames {
ensNames(first: 2) {
name
owner
registeredAt
}
}
{
"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 table defined in ponder.schema.ts
.