Substrate WASM Support

We provide a custom data source processor for Substrate WASM contractopen in new window. This offers a simple way to filter and index both WASM and Substrate activity on many Polkadot networks within a single SubQuery project.

Tested and Supported networks

Network NameWebsocket EndpointDictionary Endpoint
AstarComing soonComing soon
Shidenwss://shiden.api.onfinality.io/public-wshttps://api.subquery.network/sq/subquery/shiden-dictionary
Shibuyahttps://api.subquery.network/sq/subquery/shibuya-dictionary
Edgewarewss://edgeware.api.onfinality.io/public-wshttps://api.subquery.network/sq/subquery/edgeware-dictionary

You can also refer to the basic Substrate WASMopen 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 WASM starter template though subql init OR for existing projects, yarn add @subql/substrate-wasm-processor.
  2. Import processor file to your project.yaml like below
  ...
  dataSources:
    - kind: substrate/Wasm
      startBlock: 970733
      processor:
        file: ./node_modules/@subql/substrate-wasm-processor/dist/bundle.js
  1. Add a custom data source as described below.
  2. Add handlers for the custom data source to your code.

Data Source Spec

FieldTypeRequiredDescription
kindsubstrate/WasmYesType of the datasource
processor.file'./node_modules/@subql/substrate-wasm-processor/dist/bundle.jsYesFile reference to the data processor code
processor.optionsProcessorOptionsNoOptions specific to the WASM Processor
assets{ [key: String]: { file: String }}NoAn object of external asset files

Processor Options

FieldTypeRequiredDescription
abiStringNoThe ABI/Metadata that is used by the processor to parse arguments. MUST be a key of assets
contractString 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.

FieldTypeRequiredDescription
kindsubstrate/WasmCallYesSpecifies that this is an Call type handler
filterCall FilterNoFilter the data source to execute

Call Filters

FieldTypeExample(s)Description
selectorString0x681266a0Selectoropen in new window identifies a method
methodStringapproveThe label for the message, same as the function called on the contract
fromString0x6bd193ee6d2104f14f94e2ca6efefae561a4334bThe sender, a Substrate Account that submitted the transaction

Handler Functions

Unlike a normal handler you will not get a SubstrateExtrinsic as the parameter, instead you will use substrate/WasmCall and receive WasmCall as the parameter.

The WasmCall includes following data:

  from: Address; // An Substrate Account that submitted the transaction
  dest: Address; // The contract address been called
  gasLimit: Weight; // Gas limit for this contract
  storageDepositLimit: Option<Compact<u128>>;
  data: {args: T; message: AbiMessage} | string; // The data passed from this contract, with its arguments and message body
  selector: string; // Contract selector, identifies the method been called
  success: boolean; // Success state
  value: BalanceOf;
  // ... and more
import { Approval } from "../types";
import { WasmCall } from "@subql/substrate-wasm-processor";
import { Balance, AccountId } from "@polkadot/types/interfaces/runtime";

// Setup types from ABI
type ApproveCallArgs = [AccountId, Balance];

export async function handleWasmCall(
  call: WasmCall<ApproveCallArgs>
): Promise<void> {
  const approval = new Approval(`${call.blockNumber}-${call.idx}`);
  approval.hash = call.hash;
  approval.owner = call.from.toString();
  if (typeof call.data !== "string") {
    const [spender, value] = call.data.args;
    approval.spender = spender.toString();
    approval.value = value.toBigInt();
  } else {
    logger.info(`Decode call failed ${call.hash}`);
  }
  approval.contractAddress = call.dest.toString();
  await approval.save();
}

Event Handlers

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

FieldTypeRequiredDescription
kindsubstrate/WasmEventYesSpecifies that this is an Event type handler
filterEvent FilterNoFilter the data source to execute

Event Filters

FieldTypeExample(s)Description
fromStringYeuN6quBhpFnd5DyWNCrwGvpyBgq51Q3nbVMSsQJ6toPXSfThe sender of the contract call which trigger this contract event
contractStringa6Yrf6jAPUwjoi5YvvoTE4ES5vYAMpV55ZCsFHtwMFPDx7HA contract address where the event is from
identifierStringTransferThe label of the event, it identifies the event type in the contract

Handler Functions

Unlike a normal handler you will not get a SubstrateEvent as the parameter, instead you will get a substrate/WasmEvent with WasmEvent as its parameter. The WasmEvent includes following data:

  from: string; // An Substrate Account that triggered this event
  contract: AccountId; // A contract address where the event is from
  eventIndex: number; // The event index in its Abi
  identifier?: string | undefined; // The label of the event
  args?: T | undefined; // The argument of the events, contains data to extract in mapping
  transactionHash: string; // Transaction hash of this event
  // ... and more
import { Transaction } from "../types";
import { WasmEvent } from "@subql/substrate-wasm-processor";
import { Balance, AccountId } from "@polkadot/types/interfaces/runtime";
import { Option } from "@polkadot/types-codec";

// Setup types from ABI
type TransferEventArgs = [Option<AccountId>, Option<AccountId>, Balance];

export async function handleSubstrateWasmEvent(
  event: WasmEvent<TransferEventArgs>
): Promise<void> {
  const [from, to, value] = event.args;
  const transaction = new Transaction(
    `${event.blockNumber}-${event.eventIndex}`
  );
  transaction.transactionHash = event.transactionHash;
  transaction.value = value.toBigInt();
  transaction.from = from.toString();
  transaction.to = to.toString();
  transaction.contractAddress = event.contract.toString();
  await transaction.save();
}

Data Source Example

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

dataSources:
  - kind: substrate/Wasm
    startBlock: 970733
    processor:
      file: ./node_modules/@subql/substrate-wasm-processor/dist/bundle.js
      options:
        abi: erc20
        contract: "a6Yrf6jAPUwjoi5YvvoTE4ES5vYAMpV55ZCsFHtwMFPDx7H"
    assets:
      erc20:
        file: ./erc20Metadata.json
    mapping:
      file: ./dist/index.js
      handlers:
        - handler: handleSubstrateWasmEvent
          kind: substrate/WasmEvent
          filter:
            # from: 'xxxx'
            contract: "a6Yrf6jAPUwjoi5YvvoTE4ES5vYAMpV55ZCsFHtwMFPDx7H"
            identifier: "Transfer"
        - handler: handleSubstrateCall
          kind: substrate/WasmCall
          filter:
            selector: "0x681266a0"
            method: "approve"

Querying contracts

SubstrateWasmProvideropen in new window provides contract query method like getStorage and call.

Known Limitations

  • Does not support dynamic datasources at the moment, which means we are unable to import and handle contracts dynamically.