Setting up an Indexer

SubQuery is a fast, flexible, and reliable open-source data decentralised infrastructure network, providing both RPC and indexed data to consumers around the world.

In this guide, we will walk through the steps required to set up an indexer using SubQuery on AssetChain.

Prerequisites

Before starting, ensure the following tools are installed on your machine:

  • Node.js: Install Node.js (LTS version recommended) from the official site.

  • Docker: Required for running SubQuery locally. Install from the Docker website.

  • SubQuery CLI: Install using the command

# NPM
npm install -g @subql/cli

# Test that it was installed correctly
subql --help

Getting Started

Once you're done installing the Subquery CLI, Initialize a new Subquery project. Run the following command inside the directory that you want to create a SubQuery project in:

subql init

You'll be asked certain questions as you proceed ahead:

  • Project Name: Choose a name for your SubQuery project.

  • Network Family: Select the EVM . Use the arrow keys to browse the available options (there may be multiple pages to scroll through).

  • Network: Pick the specific network(i.e Asset Chain Testnet) to be indexed by this SubQuery project. Use the arrow keys to scroll through the available networks.

  • Template Project: Select a template project to kickstart the development process(asset-chain-testnet-starter ).

  • RPC Endpoint: Provide the HTTP or websocket URL for a running RPC endpoint. This can be a public endpoint, a private node, or the default endpoint. Ensure the node has the full state of the data you wish to index—archive nodes are recommended.

  • Git Repository: Enter the Git URL for the repository where your SubQuery project will be hosted.

  • Authors: Specify the owner or author of this project (e.g., your name) or use the default provided.

  • Description: Write a brief description explaining the data your project indexes and what users can do with it, or use the provided default.

  • Version: Enter a custom version number or keep the default.

  • License: Provide a software license for the project or leave it as the default (MIT).

Let's look at an example

subql init
? Project name indexer-tutorial
? Select a network family EVM
? Select a network Asset Chain Testnet
? Select a template project asset-chain-testnet-starter     This SubQuery project indexes all transfers and approval events for the WRWA Token on Asset Chain Test Network
? RPC endpoint: https://enugu-rpc.assetchain.org
? Author KodeSage
? Description This is my subquery tutorial
indexer-tutorial is ready
? Do you want to generate scaffolding from an existing contract abi? no

Finally, run the following command to install the new project’s dependencies from within the new project's directory

cd indexer-tutorial // Replace with you project name
yarn install

You have now initialised your first SubQuery project with just a few simple steps. Let’s now customise the asset-chain-testnet template for our project.

Building the Project

For this Project, we are going to be indexing this MultitokenLauncher Contract. Specifically, we will be indexing the TokenCreatedEvent.

Inside your Project, you abis directory and rename the erc20.abi.json file to multitokenlauncher.abi.json and replace the file content with this content below

 [
      {
        anonymous: false,
        inputs: [
          {
            indexed: true,
            internalType: "address",
            name: "previousOwner",
            type: "address",
          },
          {
            indexed: true,
            internalType: "address",
            name: "newOwner",
            type: "address",
          },
        ],
        name: "OwnershipTransferred",
        type: "event",
      },
      {
        anonymous: false,
        inputs: [
          {
            indexed: true,
            internalType: "address",
            name: "tokenAddress",
            type: "address",
          },
          {
            indexed: true,
            internalType: "address",
            name: "deployer",
            type: "address",
          },
          {
            indexed: false,
            internalType: "string",
            name: "tokenname",
            type: "string",
          },
          {
            indexed: false,
            internalType: "string",
            name: "tokensymbol",
            type: "string",
          },
        ],
        name: "TokenCreated",
        type: "event",
      },
      {
        inputs: [
          {
            internalType: "address",
            name: "tokenAddress",
            type: "address",
          },
          {
            internalType: "uint256",
            name: "amount",
            type: "uint256",
          },
        ],
        name: "burnTokens",
        outputs: [],
        stateMutability: "nonpayable",
        type: "function",
      },
      {
        inputs: [
          {
            internalType: "string",
            name: "name",
            type: "string",
          },
          {
            internalType: "string",
            name: "symbol",
            type: "string",
          },
        ],
        name: "createToken",
        outputs: [],
        stateMutability: "nonpayable",
        type: "function",
      },
      {
        inputs: [],
        name: "getTokens",
        outputs: [
          {
            internalType: "address[]",
            name: "",
            type: "address[]",
          },
        ],
        stateMutability: "view",
        type: "function",
      },
      {
        inputs: [],
        name: "owner",
        outputs: [
          {
            internalType: "address",
            name: "",
            type: "address",
          },
        ],
        stateMutability: "view",
        type: "function",
      },
      {
        inputs: [],
        name: "renounceOwnership",
        outputs: [],
        stateMutability: "nonpayable",
        type: "function",
      },
      {
        inputs: [
          {
            internalType: "uint256",
            name: "",
            type: "uint256",
          },
        ],
        name: "tokens",
        outputs: [
          {
            internalType: "address",
            name: "",
            type: "address",
          },
        ],
        stateMutability: "view",
        type: "function",
      },
      {
        inputs: [
          {
            internalType: "address",
            name: "newOwner",
            type: "address",
          },
        ],
        name: "transferOwnership",
        outputs: [],
        stateMutability: "nonpayable",
        type: "function",
      },
    ]

We will edit the SubQuery project by changing the following files:

  • The project manifest in project.ts defines the key project configuration and mapping handler filters

  • The GraphQL Schema (schema.graphql) defines the shape of the resulting data that you are using SubQuery to index

  • The Mapping functions in src/mappings/ directory are typescript functions that handle transformation logic

In the schema.graphql file, replace the content with this:

type TokenCreated @entity {
  id: ID!
  tokenAddress: String!
  owner: String!
  name: String!
  symbol: String!
}

In the mappings/mappingHandlers.tsfile, replace the content with this:

export async function handleTokenCreated(): Promise<void> {
}

In the project.tsfile, replace the content with this:


import {
  EthereumProject,
  EthereumDatasourceKind,
  EthereumHandlerKind,
} from "@subql/types-ethereum";

import * as dotenv from 'dotenv';
import path from 'path';

const mode = process.env.NODE_ENV || 'production';

// Load the appropriate .env file
const dotenvPath = path.resolve(__dirname, `.env${mode !== 'production' ? `.${mode}` : ''}`);
dotenv.config({ path: dotenvPath });

// Can expand the Datasource processor types via the generic param
const project: EthereumProject = {
  specVersion: "1.0.0",
  version: "0.0.1",
  name: "indexer tutorials",
  description: "This is a sample implementation of the indexer tutorials",
  runner: {
    node: {
      name: "@subql/node-ethereum",
      version: ">=3.0.0",
    },
    query: {
      name: "@subql/query",
      version: "*",
    },
  },
  schema: {
    file: "./schema.graphql",
  },
  network: {
    /**
     * chainId is the EVM Chain ID, for Asset Chain Testnet this is 42421
     * https://chainlist.org/chain/42421
     */
    chainId: process.env.CHAIN_ID!,
    /**
     * These endpoint(s) should be public non-pruned archive node
     * We recommend providing more than one endpoint for improved reliability, performance, and uptime
     * Public nodes may be rate limited, which can affect indexing speed
     * When developing your project we suggest getting a private API key
     * If you use a rate limited endpoint, adjust the --batch-size and --workers parameters
     * These settings can be found in your docker-compose.yaml, they will slow indexing but prevent your project being rate limited
     */
    endpoint: process.env.ENDPOINT!?.split(",") as string[] | string,
  },
  dataSources: [
    {
      kind: EthereumDatasourceKind.Runtime,
      startBlock: 141381, //The Block Number where your contract was created
      options: {
        abi: "multitokenlauncher",
        // This is the contract address for MultiTokenlauncher
        address: "0x6fCc46E73E5B96479F6fA731893534b6Ec6bf5E1",
      },
      assets: new Map([
        ["multitokenlauncher", { file: "./abis/multitokenlauncher.abi.json" }],
      ]),
      mapping: {
        file: "./dist/index.js",
        handlers: [
          {
            kind: EthereumHandlerKind.Event,
            handler: "handleTokenCreated",
            filter: {
              /**
               * Follows standard log filters https://docs.ethers.io/v5/concepts/events/
               * address: "0x6fCc46E73E5B96479F6fA731893534b6Ec6bf5E1"
               */
              topics: [
                "TokenCreated(address indexed tokenAddress, address indexed deployer, string tokenname, string tokensymbol)",
              ],
            },
          },
        ],
      },
    },
  ],
  repository: "https://github.com/subquery/ethereum-subql-starter", // Replace with your own repository
};

// Must set default to the project instance
export default project;

Afterwards , Then Open your terminal and run the following code below

yarn codegen

This Generates types from the GraphQL schema definition and contract ABIs and saves them in the /src/types directory. This must be done after each change to the schema.graphql file or the contract ABIs and Updating the project.tsfile.

Update mappings/mappingHandlers.ts file with this content below:


import { TokenCreated } from "../types";
import { TokenCreatedLog } from "../types/abi-interfaces/MultitokenlauncherAbi";
import assert from "assert";

export async function handleTokenCreated(log: TokenCreatedLog): Promise<void> {
  logger.info(`New TokenCreated event log at block ${log.blockNumber}`);
  assert(log.args, "No log.args");

  const token = TokenCreated.create({
    id: log.transactionHash,
    tokenAddress: log.args.tokenAddress,
    owner: log.args.deployer,
    name: log.args.tokenname,
    symbol: log.args.tokensymbol,
  });

  await token.save();
}

// This handles the Transformation Logic and make the data's indexed. 

Now that our Project is Completed, we need to build out the dist file and test it.

in your terminal, run the following command:

yarn build

Once the Build Passes, Kudos your Subgraph Project is Ready.

Testing

You need to test the project locally and make sure it is working before deploying.

To Test, make sure you have your Docker Environment is running.

in your terminal, run the following commands:

docker-compose pull && docker-compose up

Open your Browser and Navigate to this URL http://localhost:3000/

query the graphql using the code below to get all Tokens created

query GetTokenCreated {
  tokenCreateds{
    edges {
      node {
        id
        tokenAddress
        owner
        name
        symbol
      }
    }
  }
}

to get a a Single Token created,

query GetSingleToken {
  tokenCreated(id: "0xbb5d708468aefcbca97711c99d7c7db270c7c521059367c64703b7edf785472c") {
    id
    tokenAddress
    owner
    name
    symbol
  }
}

You should see the different output of this project in Browser.

Deploying our Subquery Project

Before you deploy your subquery project, you need to publish it first on the network.

Prepare your SUBQL_ACCESS_TOKEN

  • Step 1: Go to SubQuery Managed Service and log in.

  • Step 2: Click on your profile at the top right of the navigation menu, then click on Refresh Token.

  • Step 3: Copy the generated token.

  • Step 4: To use this token:

    • Add SUBQL_ACCESS_TOKEN in your environment variables. EXPORT SUBQL_ACCESS_TOKEN=<token> (Windows) or export SUBQL_ACCESS_TOKEN=<token> (Mac/Linux)

Publish your project

In your project directory, run the following command

subql publish

you should then see a similar output below

SubQuery Project project.yaml uploaded to IPFS: QmPWDhbqBqN4peb5BQKuFZPFt1qfAP6b6aQNmzEYzaWg9M

Deploying Our Project to The Network

To create your first project, head to SubQuery Managed Service. You'll need to authenticate with your GitHub account to login.

SubQuery Projects is where you manage all your hosted projects uploaded to the SubQuery platform. You can create, delete, and even upgrade projects all from this application.

Click on the Create Project Buttton to Create your Project, A Modal will pop up, then you Click on the Subquery Project and click the Next Button.

and then follow the steps and enter the following:

The information you are filling should tally with the same information you used while setting up the project using the CLI

  • Project Name: Name your project.

  • Description: Provide a description of your project.

  • Database: Premium customers can access dedicated databases to host production SubQuery projects from. If this interests you, you can contact sales@subquery.network to have this setting enabled.

  • Visible in Explorer: If selected, this will show the project from the public SubQuery explorer to share with the community.\

Below is the final code for our indexer project

Last updated