Skip to main content
Version: Firesquid

Squid EVM typegen

EVM logs

The Ethereum Virtual Machine smart contract is a bytecode deployed on an EVM-capable blockchain. There could be several functions in a contract. An Application Binary Interface is the interface between two program modules, one of which is often at the level of machine code. The interface is the de facto method for encoding/decoding data into/out of the machine code.

An ABI is necessary so that you can specify which function in the contract to invoke, as well as get a guarantee that the function will return data in the format you are expecting.

Subsquid has developed a CLI tool that is able to inspect the ABI in JSON format, parse it and create TypeScript interfaces and mappings to decode functions and data, as specified in the ABI itself.

Similarly to Substrate entities, having Interfaces for data and mappings for function decoding, speeds up the development of EVM log handler functions, creating standards for passing data around.

ABI interface and decoding

Solidity developers, and more generally, those who have dealt with EVM contracts should already be familiar with the concept of ABI, but as a refresher, and specifically for Substrate specialist who are taking a look at this for the first time, ABI stands for Application Binary Interface and it is described as:

The Contract Application Binary Interface (ABI) is the standard way to interact with contracts in the Ethereum ecosystem, both from outside the blockchain and for contract-to-contract interaction. Data is encoded according to its type, as described in this specification. The encoding is not self describing and thus requires a schema in order to decode.

In many ways, it is a very similar concept to the Types Bundle of Substrate. It collects types, and names, inputs output and properties of functions expressed in contracts. And the resulting interface is noted in a JSON file.

As an example, here is the ABI for the ERC-721 standard

ERC-721 ABI

Defining an ABI (or more) is crucial for being able to process EVM logs, but there a few more steps to take in order to do so.

Installation

The Squid evm typegen tool is part of Subsquid SDK and is used for generating TypeScript interface classes for EVM Application Binary Interfaces.

info

Note: in the context of this guide, we assume the Development Environment has been already set up and that npm is used, although other options are available.

To install EVM typegen tool, simply run this in a console.

npm install @subsquid/substrate-evm-typegen

Once installed, check available commands by running

npx squid-evm-typegen --help

Which will print out a help.

Options for squid-substrate-typegen command

ArgumentDescriptionRequired
-h or --helpdisplay help for command
--abipath to a JSON abi fileyes
--outputpath for output typescript fileyes

EVM Typegen Example

npx squid-evm-typegen --abi=src/abi/ERC721.json --output=src/abi/erc721.ts

Subsquid provides a tool called squid-evm-typegen that accepts a JSON file, with an ABI definition as an input, and will generate a TypeScript file, containing Interfaces and decoding mappings as an output.

In the squid-evm-template repository you'll find a JSON file containing the ERC721 ABI and right next to it, the TypeScript file generated by such tool. Let's dissect and explain what it contains:

erc721.ts
import * as ethers from "ethers";

export const abi = new ethers.utils.Interface(getJsonAbi());

These first two lines import and instantiate a programmatic interface for the ABI.

Then, a series of data interfaces are declared. These are the inputs and outputs of the functions declared in the ABI.

erc721.ts
export interface ApprovalAddressAddressUint256Event {
owner: string;
approved: string;
tokenId: ethers.BigNumber;
}

export interface ApprovalForAllAddressAddressBoolEvent {
owner: string;
operator: string;
approved: boolean;
}

export interface TransferAddressAddressUint256Event {
from: string;
to: string;
tokenId: ethers.BigNumber;
}

export interface EvmEvent {
data: string;
topics: string[];
}

Below them, you'll find a dictionary that maps the signature of a function to its topic and a method to decode it.

erc721.ts
export const events = {
"Approval(address,address,uint256)": {
topic: abi.getEventTopic("Approval(address,address,uint256)"),
decode(data: EvmEvent): ApprovalAddressAddressUint256Event {
const result = abi.decodeEventLog(
abi.getEvent("Approval(address,address,uint256)"),
data.data || "",
data.topics
);
return {
owner: result[0],
approved: result[1],
tokenId: result[2],
}
}
}
,
"ApprovalForAll(address,address,bool)": {
topic: abi.getEventTopic("ApprovalForAll(address,address,bool)"),
decode(data: EvmEvent): ApprovalForAllAddressAddressBoolEvent {
const result = abi.decodeEventLog(
abi.getEvent("ApprovalForAll(address,address,bool)"),
data.data || "",
data.topics
);
return {
owner: result[0],
operator: result[1],
approved: result[2],
}
}
}
,
"Transfer(address,address,uint256)": {
topic: abi.getEventTopic("Transfer(address,address,uint256)"),
decode(data: EvmEvent): TransferAddressAddressUint256Event {
const result = abi.decodeEventLog(
abi.getEvent("Transfer(address,address,uint256)"),
data.data || "",
data.topics
);
return {
from: result[0],
to: result[1],
tokenId: result[2],
}
}
}
,
}

At the bottom of the file, there will always be an auxiliary function that returns the ABI in a raw JSON format (not reported here, for brevity).