Indexers provide an easy solution to query on-chain data. They process smart contract events in the background and store them in a database, which you can query using GraphQL. This enables efficient access to all smart contract events and allows for filtering the data as needed.

Prerequisites

  • Node.js 20.x or later is installed.
  • Docker is installed, and you have basic knowledge of using it.

Instructions

Install Subquid

Create a Squid: Follow the instructions for creating a Squid.

  • During setup, create a new Squid with a name of your choice. This will be used later.

Create the Indexer

Initialize the JavaScript environment

npm init

Install required packages

npm i dotenv typeorm @subsquid/evm-processor @subsquid/typeorm-store @subsquid/typeorm-migration @subsquid/graphql-server @subsquid/evm-abi

npm i typescript @subsquid/typeorm-codegen @subsquid/evm-typegen --save-dev

Add tsconfig.json

Create a tsconfig.json file in your project root with the following configuration:

{
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "lib",
    "module": "commonjs",
    "target": "es2020",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Define the schema

Create a schema.graphql file and define the database schema. For example:

type Transfer @entity {
  id: ID!
  from: String! @index
  to: String! @index
  value: BigInt!
}

This schema represents the structure of the events you want to store in your database.

Generate TypeORM entities

Run the following command to generate TypeORM entities based on the schema:

npx squid-typeorm-codegen

Create a .env file

Define the database credentials in a .env file:

DB_NAME=peaq
DB_PORT=23798

Create docker-compose.yaml

Create a docker-compose.yaml file to set up a PostgreSQL database:

version: "3"
services:
  db:
    image: postgres:15
    environment:
      POSTGRES_DB: "${DB_NAME}"
      POSTGRES_PASSWORD: postgres
    ports:
      - "${DB_PORT}:5432"

Start the database container:

docker compose up -d

Compile TypeORM classes

Compile the generated TypeORM classes:

npx tsc

Generate and apply migrations

  • Generate the migration file:
npx squid-typeorm-migration generate
  • Apply the migration:
npx squid-typeorm-migration apply

Tie the code together

Create a src/main.ts file and add the following code:

import { EvmBatchProcessor } from '@subsquid/evm-processor';
import { TypeormDatabase } from '@subsquid/typeorm-store';
import { DataStoredEvent } from './model';

const DataStoredEventTopic = '0x9455957c3b77d1d4ed071e2b469dd77e37fc5dfd3b4d44dc8a997cc97c7b3d49';
const CONTRACT_ADDRESS = '0xdbdac2ef52681230f996624a5fa2624b06972671';

const processor = new EvmBatchProcessor()
  .setRpcEndpoint({
    url: 'https://rpcpc1-qa.agung.peaq.network',
  })
  .setFinalityConfirmation(5)
  .setBlockRange({ from: 3564900 })
  .addLog({
    address: [CONTRACT_ADDRESS]
  })
  .setFields({
    log: {
      transactionHash: true,
    },
  });

const db = new TypeormDatabase();

processor.run(db, async (ctx) => {
  const events: DataStoredEvent[] = [];
  for (let block of ctx.blocks) {
    for (let log of block.logs) {
      if (log.topics[0] === DataStoredEventTopic) {
        const data = BigInt(log.data);
        console.log("ID: ", log.id);
        console.log("DATA: ", data);
        console.log("BLOCK_NUMBER: ", block.header.height);
        console.log("TX_HASH: ", log.block.hash);

        events.push(new DataStoredEvent({
          id: log.id,
          data: Number(data),
          blockNumber: block.header.height,
          transactionHash: log.block.hash,
        }));
      }
    }
  }
  await ctx.store.insert(events);
});

Compile and start the processor

  • Compile the code:
npx tsc
  • Start the processor:
node -r dotenv/config lib/main.js

Start the GraphQL server

In a separate terminal, start the GraphQL server:

npx squid-graphql-server