ink! contract indexing
Objective
This tutorial starts with thesubstrate squid template and goes through all the necessary changes to index the events of a WASM contract developed with ink!. This approach is taken to illustrate the development process. If you want to start indexing ASAP, consider starting with the ink template that contains the final code of this tutorial:
XnrLUQucQvzp5kaaWLG9Q3LbZw5DPwpGn69B5YcywSWVr5w. Our squid will track all the token holders and account balances, together with the historical token transfers.
Squid SDK only supports WASM contracts executed by the Contracts pallet natively. The pallet is enabled by the following network runtimes:
Astar(aPolkadotparachain)Shibuya(Astartestnet)Shiden(Kusama-cousin ofAstar)AlephZero(a standalone Substrate-based chain)
Pre-requisites
- Familiarity with Git
- A properly set up development environment consisting of Node.js, Git and Docker
- Squid CLI
Run the template
Retrieve thesubstrate template with sqd init:
Define the data schema
The next step is to define the target data entities and their relations atschema.graphql, then use that file to autogenerate TypeORM entity classes.
We track:
- Wallet balances
- Token transfers
- a one-to-many relation between
OwnerandTransfer; @indexdecorators for properties that we want to be able to filter the data by.
TypeORM entity classes from the schema with the squid-typeorm-codegen tool:
src/model/generated.
Finally, we create database migrations to match the changed schema. We restore the database to a clean state, then replace any existing migrations with the new one:
WASM ABI Tools
TheContracts pallet stores the contract execution logs (calls and events) in a binary format. The decoding of this data is contract-specific and is done with the help of an ABI file typically published by the contract developer. ABI for ERC20 contracts like the one we’re indexing can be found here.
Download that file to the abi folder and install the following two tools from Squid SDK:
@subsquid/ink-abi— A performant library for decoding binary ink! contract data.@subsquid/ink-typegen— A tool for making TypeScript modules for handling contract event and call data based on ABIs of contracts.
@subsquid/ink-typegen is only used to generate source files, we install it as a dev dependency.
Generate the contract data handling module by running
src/abi/erc20.ts module defines interfaces to represent WASM data defined in the ABI, as well as functions necessary to decode this data (e.g. the decodeEvent function).
Define the processor object
Squid SDK provides users with theSubstrateBatchProcessor 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 exposes methods to “subscribe” them to specific data such as Substrate events, extrinsics, storage items etc. The Contracts pallet emits ContractEmitted events wrapping the logs emitted by the WASM contracts. Processor allows one to subscribe to such events emitted by a specific contract.
The processor is instantiated and configured at the src/processor.ts. Here are the changes we need to make there:
- Change the gateway used to
https://v2.archive.subsquid.io/network/shibuya-substrate. - Remove the
addEventfunction call, and addaddContractsContractEmittedinstead, specifying the address of the contract we are interested in. The address should be represented as a hex string, so we need to decode our ss58 address of interest,XnrLUQucQvzp5kaaWLG9Q3LbZw5DPwpGn69B5YcywSWVr5w.
src/processor.ts
Define the batch handler
Once requested, the events can be processed by calling the.run() function that starts generating requests to SQD Network for batches of data.
Every time a batch is returned by the Network, it will trigger the callback function, or batch handler (passed to .run() as second argument). 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.
Batch handler is typically defined at the squid processor entry point, src/main.ts. Here is one that works for our task:
src/main.ts
getTransferRecords function generates a list of TransferRecord objects that contain the data we need to fill the models we have defined with our schema. This data is extracted from the events found in the batch context, ctx. We use the in the main body of the batch handler, the arrow function used as the second argument of the .run() function call, to fetch or create Owner instance and create a Transfer instance for every event found in the context.
Finally, these TypeORM entity instances are saved to the database, all in one go. This is done to reduce the number of database queries.
In the
getTransferRecords function we loop over the blocks and over the events contained in them, then filter the events with an if. The filtering is redundant when there’s only one event type to process but will be needed when the processor is subscribed to multiple ones.Launch the Project
Build, then launch the processor withlocalhost:4350/graphql to access the GraphiQl console. There you can perform queries such as this one, to find out the account owners with the biggest balances:

