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 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
🏗️ Handle transactions calls (in addition to logs)
🏗️ Run effects (e.g. send an API request) in event handler code
Architecture
Data flows from smart contracts → event handler functions → application data store → GraphQL API → client applications
Example: ENS
Ponder is conceptually similar to Graph Protocol subgraphs. If you've built a subgraph before, you can skip to Getting started.
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 events logs that have been emitted by the BaseRegistrar
contract.
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 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.
type EnsName @entity {
id: ID!
name: String!
owner: String!
registeredAt: Int!
}
Event handler functions
Event handlers are TypeScript functions that process a blockchain event and insert data into the Entity store matching the GraphQL schema.
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.
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
}
});
});
GraphQL API
Now that we've inserted some data into the Entity 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 includes useful features like pagination, sorting, and complex filters for each entity defined in schema.graphql
.