Skip to main content

Substrate EVM Support

SubQuery TeamAbout 5 min

Substrate EVM Support

We provide a custom data source processor for Parity's Frontier EVMopen in new window and Acala's EVM+open in new window. This offers a simple way to filter and index both EVM and Substrate activity on many Polkadot networks within a single SubQuery project.

Tested and Supported networks

Network NameWebsocket EndpointDictionary Endpoint
Moonbase Alphawss://

Theoretically the following networks should also be supported since they implement Parity's Frontier EVM. Please let us know if you verify this and we can add them to the known support:

  • Automata
  • Bitcountry
  • Clover
  • Darwinia
  • Edgeware
  • Gamepower
  • Human
  • InVarch
  • T3rn
  • PAID
  • Manta
  • Parastate
  • Polkafoundry
  • ChainX
  • Gaia
  • Thales
  • Unique

You can also refer to the basic Moonriver EVMopen in new window or Acala EVM+open in new window example projects with an event and call handler. This project is also hosted live in the SubQuery Explorer hereopen in new window.

Getting started

  1. Add the custom datasource as a dependency:
  • Create a new project from an EVM template though subql init OR
  • For existing projects, yarn add @subql/frontier-evm-processor or npm i @subql/acala-evm-processor.
  1. Add exports to your package.json like below in order for IPFS deployments to work
  "exports": {
    "frontierEvm": "./node_modules/@subql/frontier-evm-processor/dist/index.js"
    //"acalaEvm": "./node_modules/@subql/acala-evm-processor/dist/index.js",
    "chaintypes": "./src/chaintypes.ts" // chain types if required
  1. Add a custom data source as described below.
  2. Add handlers for the custom data source to your code.

Data Source Spec

kindsubstrate/FrontierEvm of substrate/AcalaEVMYesType of the datasource
processor.file"./node_modules/@subql/frontier-evm-processor/dist/bundle.js" or "./node_modules/@subql/acala-evm-processor/dist/bundle.js"YesFile reference to the data processor code
processor.optionsProcessorOptionsNoOptions specific to the Frontier Processor
assets{ [key: String]: { file: String }}NoAn object of external asset files

Processor Options

abiStringNoThe ABI that is used by the processor to parse arguments. MUST be a key of assets
addressString or nullNoA contract address where the event is from or call is made to. null will capture contract creation calls

Call Handlers

Works in the same way as substrate/CallHandler except with a different handler argument and minor filtering changes.

kindsubstrate/FrontierEvmCall or substrate/AcalaEvmCallYesSpecifies that this is an Call type handler
filterCall FilterNoFilter the data source to execute

Call Filters

functionString0x095ea7b3, approve(address to,uint256 value)Either Function Signatureopen in new window strings or the function sighash to filter the function called on the contract
fromString0x6bd193ee6d2104f14f94e2ca6efefae561a4334bAn Ethereum address that sent the transaction

Handler Functions

Unlike a normal handler you will not get a SubstrateExtrinsic as the parameter, instead you will get a FrontierEvmCall or AcalaEvmCall which is based on Ethers TransactionResponseopen in new window type.

Changes from the TransactionResponse type:

  • It doesn't have wait and confirmations properties.
  • A success property is added to know if the transaction was a success.
  • args is added if the abi field is provided and the arguments can be successfully parsed. You can add a generic parameter like so to type args: FrontierEvmCall<{ from: string, to: string, value: BigNumber }>.

Event Handlers

Works in the same way as substrate/EventHandler except with a different handler argument and minor filtering changes.

kindsubstrate/FrontierEvmEvent or substrate/AcalaEvmEventYesSpecifies that this is an Event type handler
filterEvent FilterNoFilter the data source to execute

Event Filters

topicsString arrayTransfer(address indexed from,address indexed to,uint256 value)The topics filter follows the Ethereum JSON-PRC log filters, more documentation can be found hereopen in new window.

Note on topics:

There are a couple of improvements from basic log filters:


If you're creating a new Substrate Frontier EVM or Acala EVM+ based project, the normal codegen command will also generate ABI types and save them into src/types using the npx typechain --target=ethers-v5 command, allowing you to bind these contracts to specific addresses in the mappings and call read-only contract methods against the block being processed. It will also generate a class for every contract event to provide easy access to event parameters, as well as the block and transaction the event originated from. All of these types are written to src/typs/**.ts. In the example Moonriver EVM Starter SubQuery projectopen in new window, you would import these types like so.

import { GraphQLEntity1, GraphQLEntity2 } from "../types";

Handler Functions

Unlike a normal handler you will not get a SubstrateEvent as the parameter, instead you will get a FrontierEvmEvent or AcalaEvmEvent which is based on Ethers Logopen in new window type.

Changes from the Log type:

  • args is added if the abi field is provided and the arguments can be successfully parsed. You can add a generic parameter like so to type args: FrontierEvmEvent<{ from: string, to: string, value: BigNumber }>.
import { Approval, Transaction } from "../types";
import {
} from "@subql/frontier-evm-processor";
import { BigNumber } from "ethers";

// Setup types from ABI
type TransferEventArgs = [string, string, BigNumber] & {
  from: string;
  to: string;
  value: BigNumber;
type ApproveCallArgs = [string, BigNumber] & {
  _spender: string;
  _value: BigNumber;

export async function handleFrontierEvmEvent(
  event: FrontierEvmEvent<TransferEventArgs>
): Promise<void> {
  const transaction = new Transaction(event.transactionHash);

  transaction.value = event.args.value.toBigInt();
  transaction.from = event.args.from; =;
  transaction.contractAddress = event.address;


export async function handleFrontierEvmCall(
  event: FrontierEvmCall<ApproveCallArgs>
): Promise<void> {
  const approval = new Approval(event.hash);

  approval.owner = event.from;
  approval.value = event.args._value.toBigInt();
  approval.spender = event.args._spender;
  approval.contractAddress =;


Data Source Example

This is an extract from the project.yaml manifest file.

  - kind: substrate/FrontierEvm
    startBlock: 752073
      file: "./node_modules/@subql/frontier-evm-processor/dist/bundle.js"
        # Must be a key of assets
        abi: erc20
        # Contract address (or recipient if transfer) to filter, if `null` should be for contract creation
        address: "0x6bd193ee6d2104f14f94e2ca6efefae561a4334b"
        file: ./erc20.abi.json
      file: ./dist/index.js
        - handler: handleFrontierEvmEvent
          kind: substrate/FrontierEvmEvent
              - "Transfer(address indexed from,address indexed to,uint256 value)"
              - null
              - null
              - null
        - handler: handleFrontierEvmCall
          kind: substrate/FrontierEvmCall
            ## The function can either be the function fragment or signature
            # function: '0x095ea7b3'
            # function: '0x7ff36ab500000000000000000000000000000000000000000000000000000000'
            # function: approve(address,uint256)
            function: "approve(address to,uint256 value)"

Querying contracts

@subql/frontier-evm-processor is the only package that currently allows this. It provides FrontierEthProvideropen in new window which implements an Ethers Provideropen in new window, this implementation is restricted to only support methods for the current height. You can pass it to a contract instance in order to query contract state at the hight currently being indexed.

Known Limitations

  • There is no way to get the transaction receipts with call handlers.
  • blockHash properties are currently left undefined, the blockNumber property can be used instead.

Extra info

  • There is also a @subql/moonbeam-evm-processor which is an alias for @subql/frontier-evm-processor.
  • The source code for these processors can be found in our datasource-processors repoopen in new window.