Skip to main content
V2 gateway requests require an API key. Set SQD_API_KEY in .env before running gateway-based examples, or pass apiKey: process.env.SQD_API_KEY in GatewaySettings.

Frontier EVM-indexing Squid

Objective

The goal of this tutorial is to guide you through creating a simple blockchain indexer (“squid”) using Squid SDK. The squid will be indexing the data from two contracts (AstarDegens and AstarCats) deployed on the Astar network. The objective will be to track ownership and transfers of all NFTs issued by these contracts. A somewhat outdated version of the final result can be browsed here.

Pre-requisites

Scaffold using sqd init

We will start with the frontier-evm squid template available through sqd init. It is built to index EVM smart contracts deployed on Astar/Shiden, but it is also capable of indexing Substrate events. To retrieve the template and install the dependencies, run

Define Entity Schema

Next, we ensure that the data schema of the squid defines entities that we would like to track. We are interested in:
  • Token transfers
  • Ownership of tokens
  • Contracts and their minted tokens
Here is a schema that defines the exact entities we need:
schema.graphql
It’s worth noting a couple of things in this schema definition:
  • @entity: Signals that this type will be translated into an ORM model that is going to be persisted in the database.
  • @derivedFrom: Signals that the field will not be persisted in the database. Instead, it will be derived from the entity relations.
  • type references (e.g. from: Owner): When used on entity types, they establish a relation between two entities.
TypeScript entity classes have to be regenerated whenever the schema is changed, and to do that we use the squid-typeorm-codegen tool:
The (re)generated entity classes can then be browsed at src/model/generated.

ABI Definition and Wrapper

SQD maintains tools for automated generation of TypeScript classes for handling EVM logs and transactions based on a JSON ABI of the contract. For our squid we will need such a module for the ERC-721-compliant part of the contracts’ interfaces. Once again, the template repository already includes it, but it is still important to explain what needs to be done in case one wants to index a different type of contract. Place any ABIs you requre for interfacing your contracts at ./abi and run
The results will be stored at src/abi. One module will be generated for each ABI file, and it will include constants useful for filtering and functions for decoding EVM events and functions defined in the ABI.

Processor object and the batch handler

Squid SDK provides users with the SubstrateBatchProcessor class. Its instances connect to SQD Network gateways at chain-specific URLs, to get chain data and apply custom transformations. The indexing begins at the starting block and keeps up with new blocks after reaching the tip. SubstrateBatchProcessors expose methods that “subscribe” them to specific data such as Substrate events and calls. There are also specialized methods for subscribing to EVM logs and transactions by address. The actual data processing is then started by calling the .run() function. This will start generating requests to the SQD Network gateway for batches of data specified in the configuration, and will trigger the callback function, or batch handler (passed to .run() as second argument) every time a batch is returned by the gateway. It is in this callback function that all the mapping logic is expressed. This is where chain data decoding should be implemented, and where the code to save processed data on the database should be defined.

Managing the EVM contract

Before we begin defining the mapping logic of the squid, we are going to write a src/contracts.ts utility module for managing the involved EVM contracts. It will export: Here are the full file contents:
src/contracts.ts

Create the processor object

The src/processor.ts file is where squids instantiate and configure their processor objects. We will use an instance of SubstrateBatchProcessor. We adapt the template code to handle two contracts instead of one and point the processor data source setting to the astar SQD Network gateway URL. Here is the end result:
src/processor.ts
This code expects to find an URL of a working Astar RPC endpoint in the RPC_ENDPOINT environment variable. Set it in the .env file and in SQD Cloud secrets if and when you deploy your squid there. We tested the code using a public endpoint available at wss://astar.public.blastapi.io; for production, we recommend using private endpoints or our RPC addon.

Define the batch handler

We change the batch handler logic taking care to avoid token ID clashing:
src/main.ts
The contract.tokenURI call is accessing the state of the contract via a chain RPC endpoint. This is slowing down the indexing a little bit, but this data is only available this way. You’ll find more information on accessing state in the dedicated section of our docs.

Database and the migration

Before giving your squid processor a local test, launch a PostgreSQL container with
Squid projects automatically manage the database connection and schema via an ORM abstraction. In this approach the schema is managed through migration files. Since we’ve made changes to the schema, we need to remove the existing migration(s) and create a new one. This involves the following steps:
  1. Build the code:
  2. Make sure you start with a clean Postgres database. The following commands drop-create the Postgres instance in Docker:
    Skip this step if you haven’t used your database since the last docker compose up -d.
  3. Regenerate the DB migration:
  4. Apply the migration:

Launch the Project

To launch the processor run the following command (this will block the current terminal):
Finally, in a separate terminal window, launch the GraphQL server:
Visit localhost:4350/graphql to access the GraphiQL console. From this window, you can perform queries such as this one, to find out the account owners with the biggest balances:
Or this other one, looking up the tokens owned by a given owner:
Have fun playing around with queries, after all, it’s a playground!