In the previous article, we learnt how blockchain indexers work, their purpose, and their importance in building fast decentralized applications.

As per my last article

In this article, we will build a program that indexes a smart contract, stores it in an SQL database, and creates a graphQL interface to query the data you need.

This article will also be focused on EVM-supported chains. However, the idea of indexing and querying blockchain data is the same across any chain.

An Autograph Indexer

You're a Web3 enthusiast, and you have many mentors, idols, and people you like to meet and interact with in the Web3 space. You create a contract where each person you meet can sign a message to you like an autograph. You can collect as many autographs as possible, including multiples from the same person.

For this autograph, you want maximum flexibility to fetch information however you want. E.g. Select autographs from a specific person, select autographs from a certain period, get all autographs taken from a specific location, etc.

Autograph Contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract Autograph {
    event Signature(
        address signer,
        string message,
        string location,
        uint256 timestamp
    );

    function sign(string memory message, string memory location) public {
        emit Signature(msg.sender, message, location, block.timestamp);
    }
}

This is a simple contract written in solidity where any user can sign a message and the events would be emitted.

We're going to write an indexer that would take information from all signatures to this contract and store it in a database.

Deployed Contract Instance On Base Sepolia Testnet: https://sepolia.basescan.org/address/0x1852a7e0b37d8a08762053ea93bc140a5c58509f#code

Autograph Indexer

For this, we would use Ponder. Ponder is the best way especially to build a Proactive or An Active Indexer for smart contracts.

Prerequisites:

  • [Node.js/Npm]
  • Postgres Database URL (Optional)

Initialize Ponder

npm create ponder@latest ponder-new -- --template empty

Folder Structure

πŸ—‚οΈ abis
    πŸ“ ExampleContractAbi.ts
πŸ—‚οΈ node_modules
πŸ—‚οΈ src
    πŸ—‚οΈ api
        πŸ“ index.ts
    πŸ“ index.ts
πŸ“ .env.local
πŸ“ .eslintrc.json
πŸ“ .gitignore
πŸ“ package.lock.json
πŸ“ package.json
πŸ“ ponder.env.d.ts
πŸ“ ponder.config.ts
πŸ“ ponder.schema.ts
πŸ“ tsconfig.json

Add The Autograph Abi

Create a new file AutographAbi.ts in the /abis folder and add the following code: AutographAbi.ts

Update Config With Contract Details

Replace the content of ponder.config.ts with the following code:

// ponder.config.ts
import { createConfig } from "ponder";
import { http } from "viem";
import { baseSepolia } from "viem/chains";
import { AutographAbi } from "./abis/AutographAbi";
export default createConfig({
  networks: {
    baseSepolia: {
      chainId: baseSepolia.id,
      transport: http(),
    },
  },
  contracts: {
    Autograph: {
      network: "baseSepolia",
      abi: AutographAbi,
      address: "0x1852a7e0b37d8a08762053ea93bc140a5c58509f",
      //explorer: https://sepolia.basescan.org/address/0x1852a7e0b37d8a08762053ea93bc140a5c58509f#code

     //Block just before the contract was deployed. By default, it starts from 0
      startBlock: 24946615
    }
  }
});

This adds a deployed instance (on base testnet) of the Autograph Contract.

Create DB Schema

Replace the content of ponder.schema.ts with the following code:

// ponder.schema.ts

import { onchainTable } from "ponder";

export const autograph = onchainTable("autograph", (t) => ({
  hash: t.hex().primaryKey(),
  message: t.text().notNull(),
  location: t.text().notNull(),
  signer: t.text().notNull(),
  timestamp: t.bigint().notNull(),
}));

This will generate a simple table with five columns you can use to query the Autograph signature data. It also generates a graphical schema as well.

Update Indexer File

Put the content below into ./src/index.ts

// src/index.ts
import { ponder } from 'ponder:registry'
import schema from 'ponder:schema'

ponder.on("Autograph:Signature", async ({ event, context }) => {
    const { signer, message, location, timestamp } = event.args
    const hash = event.transaction.hash
    const res = await context.db.insert(schema.autograph).values({
        hash,
        signer,
        message,
        location,
        timestamp
    })
}
)

This will watch the autograph contract for each time the Signature event is fired and insert the data into our database.

Start Your Server

Once you ensure all the files are set up correctly, you can simply start your server to start indexing the contents.

npm run dev

Ponder Terminal Output

Go to http://localhost:42069/ and you get a graphQl interface where you can query the data.

Testing Your Indexer

Enter the query into the query box.

query MyQuery {
  autographs(orderBy: "timestamp", orderDirection: "desc", limit: 100) {
    items {
      hash
      message
      location
      signer
      timestamp
    }
    totalCount
  }
}

You should get similar results.

{
  "data": {
    "autographs": {
      "items": [
        {
          "hash": "0xa164030291bcb934f3488dc73a9f93a57882173f3d5a13f061a4f66e5c3d6335",
          "message": "Happy Learning about Indexers",
          "location": "Oslo",
          "signer": "0x4aFE825e848b16475797F28cF892D084e2756eD2",
          "timestamp": "1745780094"
        },
        {
          "hash": "0xdf7ff3e88870a6130c292269c28a019fa1e19946fc20eeae260fc024420cfb22",
          "message": "You are awesome",
          "location": "ReykjavΓ­k",
          "signer": "0x4aFE825e848b16475797F28cF892D084e2756eD2",
          "timestamp": "1745780028"
        }
      ],
      "totalCount": 2
    }
  }
}

The returned data above represents some signature events that had occurred on the Autograph contract. Going forward, all other signatures will be automatically indexed, and stored in your database and you can query as you desire.

GraphQL Query and Result

Deployment

The indexer can be deployed on any server that supports Node.js. But you need to set up your custom database for it. By default, it uses PGlite.

Get the URL from a database instance. This URL will be used as a DATABASE_URL environment variable.

You also need to set the database schema location where the tables defined in ponder.schema.ts are created when you deploy to production.

DATABASE_URL=<Your_Postgres_Or_Sqlite_Database_Url>
DATABASE_SCHEMA=myschema

Use the links below as a guide to deploy your database and application:

How to Deploy a Postgres Database

How To Deploy Nodejs Applications

Test Deployment And Source Code

I've deployed the Autograph indexer for you to test.

Autograph Indexer Live Deployment

Also, Get the full source code on Github

Autograph Indexer Github

Conclusion

If you got to this point, Congratulations!! πŸŽ‰πŸŽ‰. You can now get data from any contract on any EVM-supported chain if you have the following information:

  • The contract address
  • The contract ABI

Info

There are other alternatives to using ponder including manual indexing and using services that offer smart contract indexing as their model offering. Here are some of the alternatives:

  1. Viem: Viem exposes a Watch Contract Events function that gets the blockchain data as they happen. Under the hood, it polls the contract every few seconds or milliseconds (customizable) and returns the event data if found.

However, this will get complex for Proactive indexers since you have to fetch and sync the data before starting to watch the contract. You also have to figure out the choice of database, an easy way to query the data (like the ponder graphQl interface), etc. You own the entire process from start to finish. Ponder had that all figured out from the start.

  1. Moralis: They run indexer as a service and provide an sdk you can query you data. However, for the service they provide, at some point, you have to pay for it. Also, since they hold the entire data, your business has to depend almost entirely on them.

  2. The Graph Protocol: The graph is another that offers smart contract indexing as a service. However, there are some differences in how they approach it.

  • They by default return a graphQl interface that makes it more flexible to query.
  • The data is stored in a decentralized way. Meaning you can trust that your data will be available even if primary nodes go down.
  1. Dune: Built on SQL syntax, you can also query dune to get blockchain data directly. Similar to the other two services mentioned, it comes at some cost. These services are also limited in the chains they offer to index.