checkpoint
  • Introduction
  • Guides
    • Quickstart
    • Step-by-step setup
    • Indexing Ethereum contracts
    • Templates
    • One-to-one relations
    • BigInt and BigDecimal
    • ApolloServer usage
    • Time-travel queries
    • Advanced options
    • Media
  • Core Concepts
    • Models
    • Checkpoint configuration
    • Entity schema
    • Data writers (Starknet)
    • Data writers (EVM)
    • Internal data query
  • Use Cases
    • Snapshot X API
  • Community
    • Discord
    • Telegram
    • GitHub
Powered by GitBook
On this page
  • Creating a Checkpoint Configuration
  • Defining GraphQL entity schemas
  • Creating Data Writers
  • Starting Checkpoint
  • Querying data
  • Conclusion

Was this helpful?

Export as PDF
  1. Guides

Step-by-step setup

PreviousQuickstartNextIndexing Ethereum contracts

Last updated 3 months ago

Was this helpful?

Checkpoint is a Node.js library and running the following NPM commands will install it:

npm install @snapshot-labs/checkpoint@beta

# or if using yarn
yarn add @snapshot-labs/checkpoint@beta

Two arguments can be used to initialize a Checkpoint instance. These are:

  • GraphQL schema (required)

  • (optional)

Creating a Checkpoint Configuration

A Checkpoint configuration is an object that defines the smart contract addresses and their respective events. For this guide, we will be indexing the list of posts and authors for this . A copy of this contract is deployed on following Starknet networks:

  • Mainnet: 0x0654e9232d5f402829755029901f69c32b423ded0f8c081e416e3b24f5a7a46e

  • Sepolia: 0x03aa7630a4f9c5108bf3cd1910c7d45404cba865fc0fc0756bf9eedc073a98a9

With new versions of Checkpoint contracts on different networks can be indexed and queried from single Checkpoint instance.

To successfully track the addresses of authors, you'll need to listen to events from the contract. Therefore, a valid checkpoint configuration for the above requirements will be:

import { CheckpointConfig } from "@snapshot-labs/checkpoint";

export const mainnetConfig: CheckpointConfig = {
  network_node_url: "https://starknet-mainnet.infura.io/v3/46a5dd9727bf48d4a132672d3f376146",
  sources: [
    {
      contract: "0x0654e9232d5f402829755029901f69c32b423ded0f8c081e416e3b24f5a7a46e",
      start: 639485,
      events: [
        {
          name: "new_post",
          fn: "handleNewPost",
        }
      ],
    }
  ]
};

export const sepoliaConfig: CheckpointConfig = {
  network_node_url: "https://starknet-sepolia.infura.io/v3/46a5dd9727bf48d4a132672d3f376146",
  sources: [
    {
      contract: "0x03aa7630a4f9c5108bf3cd1910c7d45404cba865fc0fc0756bf9eedc073a98a9",
      start: 65137,
      events: [
        {
          name: "new_post",
          fn: "handleNewPost",
        }
      ],
    }
  ]
};

In this case we want to index Poster contract on both Starknet mainnet and Starknet sepolia so we define two configurations.

The start block number is set to 639485 because our mainnet contract was deployed at that block. This will mean Checkpoint starts scanning from that block as opposed to starting at block 0.

Defining GraphQL entity schemas

Checkpoint requires a set of defined GraphQL Schema Objects. These schema objects will be used to create the database tables for indexing records and also generate graphql queries for accessing the indexed data.

For this guide, we will want to track a Post entity and have it exposed via the graphql API. This entity can be defined as the following schema file:

src/schema.gql
""" Entity named Post """
type Post {
  id: String!
  author: String!
  created_at_block: Int!
}

When updating your schema you should run following script to generate ORM models:

yarn checkpoint generate

Creating Data Writers

Data writers are JavaScript functions that get invoked by Checkpoint when it discovers a block containing this event. A Data writer is responsible for writing records to the database. These records will eventually be exposed via Checkpoint's GraphQL endpoint.

We have defined data writer function in our Checkpoint configuration for new_post event called handleNewPost.

Let's create these data writer functions:

src/writers.ts
import { starknet } from "@snapshot-labs/checkpoint";
import { getAddress } from '@ethersproject/address';
import { Post } from '../.checkpoint/models';

// We create createWriters function that accepts indexerName.
// This means we can reuse those for different networks (for example
// "mainnet" and "sepolia".
export const createWriters(indexerName: string) {
    // handleNewPost will get invoked when a `new_post` event
    // is found at a block
    const handleNewPost: starknet.Writer = async ({ event, block }) => {
        if (!event) return;
    
        // extract posters address from events data
        const author = getAddress(BigNumber.from(event.data[0]).toHexString());
        
        // store Post in database (note we pass indexerName here to be able
        // to tell apart Posts on each network)
        const post = new Post(`${author}/${tx.transaction_hash}`, indexerName);
        post.author = author;
        post.created_at_block = block.blockNumber;
        await post.save();
    };
    
    return { handleNewPost };
}

With the above code snippet, we have a data writer that writes new posts to the PostgreSQL database.

Starting Checkpoint

Finally, we can initialize a checkpoint instance with our arguments like these:

src/index.ts
import fs from 'fs/promises';
import Checkpoint from "@snapshot-labs/checkpoint";
import { createWriters } from './writers.ts';
import { mainnetConfig, sepoliaConfig } from './config.ts';

...

const schemaFile = path.join(__dirname, `${dir}../src/schema.gql`);
const schema = fs.readFileSync(schemaFile, 'utf8');

...

const checkpoint = new Checkpoint(schema);

const mainnetIndexer = new starknet.StarknetIndexer(createWriters('mainnet'));
checkpoint.addIndexer('mainnet', mainnetConfig, mainnetIndexer);

const sepoliaIndexer = new starknet.StarknetIndexer(createWriters('sepolia'));
checkpoint.addIndexer('sepolia', sepoliaConfig, sepoliaIndexer);

Next, we start up checkpoint's indexer like this:

checkpoint.start();

The above code will start the checkpoint indexer, and it will begin processing each Starknet block. When a relevant contract event is found, it gets passed to the data writer for that event.

Querying data

Once Checkpoint has run for a while and has written some data to its database, you can query this data using the generated GraphQL API.

You can mount the query endpoint on any port you like using the graphql handler exported by Checkpoint's object. Like this:

import express from 'express';

const checkpoint = new Checkpoint(...);

const app = express();
app.use('/graphql', checkpoint.graphql);

const PORT = 3000;
app.listen(PORT, () => console.log(`Listening at http://localhost:${PORT}`));

This will mount a GraphQL endpoint that can be accessed at http://localhost:3000/graphql (and a GraphiQL interface at http://localhost:3000/graphql when visited from the browser).

Checkpoint exposes two types of queries:

These enables users to fetch information about indexer.

For starters, you can visit http://localhost:3000/graphql in your browser and try running the sample query generated in the graphiql UI.

Conclusion

At this juncture, you should have Checkpoint running, indexing your contracts data (on multiple networks) and serving this indexed data via graphql.

fn value is the name of the data writer function to be invoked when new_post events are encountered. Read more about Checkpoint configuration .

Checkpoint will use the above entity (Post) to generate a PostgreSQL database table named posts with columns matching the defined fields. It will also generate a list of GraphQL queries to enable querying indexed data. Read more about how queries are generated .

You can view a more comprehensive data writer example in our checkpoint-template codebase .

Next up, you can explore our template repository to get up and running quickly.

You can also explore some Checkpoint or move on to the next guide.

CheckpointOptions
Poster Contract
new_post
here
here
Internal data Queries
here
core concepts
here
Entity Queries