EVM Processor
Subsquid API framework was initially built with Substrate blockchains in mind. It is fully and natively compatible with all network built with such scheme.
But EVM-compatible projects, such as Moonbeam or Acala, created the demand to add support for EVM logs to our Processors. And Subsquid responded by developing the @subsquid/substrate-evm-processor.
The inner workings are similar in all aspects to the base Substrate Processor. As a matter of facts, the Substrate EVM Processor is an extension of the aforementioned Substrate Processor.
This page will go over the most important customizations a developer would want to make, when building their API.

Prerequisite

It is important, before even starting, to verify that the Archive our Processor will be connecting to is EVM-compatible.
To know exactly what this means, please check the related section in the Archive guide.

Importing and instantiating

The Substrate EVM Processor is defined in an npm package that needs to be installed before being able to use it:
Note: the subsquid-template does not have this package in its dependencies.
1
npm install @subsquid/substrate-evm-processor
Copied!
Then the SubstrateEvmProcessor class can be imported from the package
1
import {SubstrateEvmProcessor} from "@subsquid/substrate-evm-processor"
Copied!
Then, it's finally possible to declare an instance of it:
1
const processor = new SubstrateEvmProcessor('moonbeam')
Copied!
Note: all of the code snippets in this page can be found in the processor.ts file in the test subfolder of our main project's repository.

Handlers and interfaces

To know more about Handler functions, Handler Interfaces and Context Interface linked to this processor, take a look at the dedicated pages:

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.
The Typegen automated tool, takes care of generating TypeScript class wrappers for abstract types. Unfortunately, there is no equivalent to this for the EVM Events and Topics, so this has to be done manually. Let's look at an example:
erc721.ts
1
import {Interface} from "@ethersproject/abi"
2
import erc721Json from "./erc721.json"
3
4
const event_name = 'Transfer(address,address,uint256)'
5
6
const abi = new Interface(erc721Json)
7
8
interface EvmLogData {
9
data: string
10
topics: string[]
11
}
12
13
export interface TransferEvent {
14
from: string
15
to: string
16
tokenId: bigint
17
}
18
19
const transfer_fragment = abi.getEvent(event_name)
20
21
export const events = {
22
event_name : {
23
topic: abi.getEventTopic(event_name),
24
decode(data: EvmLogData): TransferEvent {
25
let result = abi.decodeEventLog(transfer_fragment, data.data, data.topics)
26
return {
27
from: result[0],
28
to: result[1],
29
tokenId: result[2].toBigInt()
30
}
31
}
32
}
33
}
Copied!
This file uses the official @ethersproject/abi to wrap the ABI JSON in an Interface class, and then exports an events object mapping an event name with its related topic and a function to decode the EVM data.
This function can then be used in the body of an EvmLogHandler function, like this:
processor.ts
1
async fuction evmTransfer (ctx: EvmLogHandlerContext ): Promise<void> {
2
let transfer = erc721.events['Transfer(address,address,uint256)'].decode(ctx)
3
4
// ...
5
6
}
Copied!
Where transfer will be an object with from, to, tokenId fields, as defined above.