Skip to main content

Build an Indexer from Scratch

This guide demonstrates how SDK packages can be combined into a working indexer (called a squid). This page goes through all the technical details to make the squid architecture easier to understand. If you want to get to a working indexer ASAP, bootstrap from a template.

Prerequisites

Before you begin, ensure you have:

Project: Polkadot Balance Transfers API

In this tutorial, you’ll build an indexer that tracks balance transfers on Polkadot, saves the data to PostgreSQL, and serves it as a GraphQL API.

Required packages

From the requirements above, you’ll need these packages:
  • @subsquid/substrate-processor - for retrieving Substrate chain data
  • @subsquid/typeorm-store, @subsquid/typeorm-codegen, and @subsquid/typeorm-migration - for saving data to PostgreSQL

Optional packages

  • @subsquid/substrate-typegen - for generating type-safe wrappers for Substrate events and calls
  • @subsquid/graphql-server / OpenReader - for serving GraphQL API

Build the Indexer

1

Initialize the project

Create a new folder and initialize a Node.js project:
npm init
Create a .gitignore file:
.gitignore
node_modules
lib
2

Install dependencies

Install the required packages:
npm i dotenv typeorm @subsquid/substrate-processor @subsquid/typeorm-store @subsquid/typeorm-migration @subsquid/graphql-server
Install development dependencies:
npm i typescript @subsquid/typeorm-codegen @subsquid/substrate-typegen --save-dev
3

Configure TypeScript

Create a tsconfig.json file with minimal configuration:
tsconfig.json
{
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "lib",
    "module": "commonjs",
    "target": "es2020",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}
4

Define the data schema

Create a schema.graphql file to define your data model:
schema.graphql
type Transfer @entity {
  id: ID!
  from: String! @index
  to: String! @index
  amount: BigInt!
  blockNumber: Int!
  timestamp: DateTime!
}
Generate TypeORM classes from the schema:
npx squid-typeorm-codegen
The TypeORM classes are now available at src/model/index.ts.
5

Set up the database

Create environment variables in a .env file:
.env
DB_NAME=squid
DB_PORT=23798
RPC_POLKADOT_HTTP=https://rpc.polkadot.io
Create a docker-compose.yaml file for PostgreSQL:
docker-compose.yaml
services:
  db:
    image: postgres:15
    environment:
      POSTGRES_DB: "${DB_NAME}"
      POSTGRES_PASSWORD: postgres
    ports:
      - "${DB_PORT}:5432"
Start the database:
docker compose up -d
Compile the TypeORM classes:
npx tsc
Generate and apply the database migration:
npx squid-typeorm-migration generate
npx squid-typeorm-migration apply
6

Generate type-safe wrappers

Create a typegen.json file to specify which Substrate types to generate:
typegen.json
{
  "outDir": "src/types",
  "specVersions": "https://v2.archive.subsquid.io/metadata/polkadot",
  "pallets": {
    "Balances": {
      "events": ["Transfer"]
    }
  }
}
Generate TypeScript type-safe wrappers:
npx squid-substrate-typegen typegen.json
The type-safe wrappers are now available in src/types.
7

Create the processor

Create src/main.ts with the following code:
main.ts
import { SubstrateBatchProcessor } from "@subsquid/substrate-processor";
import { TypeormDatabase } from "@subsquid/typeorm-store";
import { events } from "./types";
import { Transfer } from "./model";

const processor = new SubstrateBatchProcessor()
  .setGateway("https://v2.archive.subsquid.io/network/polkadot")
  .setRpcEndpoint({
    url: process.env.RPC_POLKADOT_HTTP,
    rateLimit: 10,
  })
  .setBlockRange({ from: 1000000 })
  .addEvent({
    name: [events.balances.transfer.name],
  })
  .setFields({
    event: {
      args: true,
    },
    block: {
      timestamp: true,
    },
  });

const db = new TypeormDatabase();

processor.run(db, async (ctx) => {
  const transfers: Transfer[] = [];
  
  for (let block of ctx.blocks) {
    for (let event of block.events) {
      if (event.name === events.balances.transfer.name) {
        let decoded = events.balances.transfer.v1050.decode(event);
        
        transfers.push(
          new Transfer({
            id: event.id,
            from: decoded.from,
            to: decoded.to,
            amount: decoded.amount,
            blockNumber: block.header.height,
            timestamp: new Date(block.header.timestamp!),
          })
        );
      }
    }
  }
  
  await ctx.store.insert(transfers);
});
The typegen tool automatically detects which spec versions are needed and generates versioned decoders. The example uses v1050 - your actual version may differ.
8

Run the processor

Compile the project:
npx tsc
Start the processor:
node -r dotenv/config lib/main.js
The processor is now indexing balance transfers from the Polkadot blockchain.
9

Start the GraphQL server

Update your .env file to include the GraphQL server port:
.env
DB_NAME=squid
DB_PORT=23798
RPC_POLKADOT_HTTP=https://rpc.polkadot.io
GRAPHQL_SERVER_PORT=4350
In a separate terminal, start the GraphQL server:
npx squid-graphql-server
The GraphQL API with GraphiQL is available at localhost:4350/graphql.

Next Steps

Congratulations! You’ve built a complete Substrate blockchain indexer from scratch.
The commands in this tutorial are often abbreviated as custom sqd commands in production squids. Use the commands.json file from the Substrate template as a starter.