Indexing events with SubQuery

This guide outlines the process of indexing all transfers and approval events from the Wrapped ETH on ZKsync Era Network.

This guide outlines the process of indexing all transfers and approval events from the Wrapped ETH on ZKsync Era Network.

What you'll learn:

  • How to use Subquery
  • How to create an indexer
  • How to index smart contract events


  • - SubQuery
  • - GraphQL
Last Updated: May 09, 2024

The SubQuery Network indexes and services data to the global community in an incentivized and verifiable way. After publishing your project to the SubQuery Network, anyone can index and host it — providing data to users around the world faster and reliably.


  • Node.js: If not installed, download it here.
  • Docker: If not installed, download it here.
  • Tooling: This guide utilizes @subql/cli. Ensure you have it accessible or installed in your environment.

Understanding SubQuery

A SubQuery extracts data from a blockchain, processing it and storing it so that it can be easily queried via GraphQL. Make advanced, flexible, but simple queries over GraphQL from any website or app. SubQuery supports advanced features like aggregate functions and subscriptions. dApps often need to fetch data from chain. Instead of querying the chain directly, which can be slow and expensive, dApps can use SubQuery to quickly retrieve the data they need.

Key components:

  • project.yaml: a YAML file containing the subgraph manifest.
  • schema.graphql: a GraphQL schema that defines what data is stored for your subgraph, and how to query it via GraphQL.
  • Mappings: Translates from the event data to the entities defined in your schema.

Environment setup

Install the SubQuery CLI using NPM (avoid using yarn global due to dependency issues):

npm install -g @subql/cli
subql --help  # Verify installation

Run subql init in the desired directory, follow the prompts to set up your project:

subql init
# Follow the interactive prompts

After initialization, navigate to your project directory and install dependencies:

npm install

Configure Your Project Manifest File

The project.yaml file serves as the entry point to your ZKsync project, defining how SubQuery will index and transform the chain data. For ZKsync Era, three types of mapping handlers are available:

  • BlockHandlers: Executes a mapping function on every block.
  • TransactionHandlers: Executes a mapping function on each transaction matching optional filter criteria.
  • LogHandlers: Executes a mapping function on each log matching optional filter criteria.

Start by:

Import the contract ABI definition from any standard ERC-20 contract, save it as erc20.abi.json in the /abis directory.

Update the dataSources section in project.yaml as shown below:

  - kind: ethereum/Runtime # We use ethereum runtime since Zksync is a layer-2 that is compatible
    startBlock: 10456259 # This is the block that the contract was deployed on
      # Must be a key of assets
      abi: erc20
      address: "0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4" # This is the contract address for wrapped ether
        file: "./abis/erc20.abi.json"
      file: "./dist/index.js"
        - handler: handleTransaction
          kind: ethereum/TransactionHandler # We use ethereum handlers since Zksync is a layer-2 that is compatible
            ## The function can either be the function fragment or signature
            # function: '0x095ea7b3'
            # function: '0x7ff36ab500000000000000000000000000000000000000000000000000000000'
            function: approve(address to, uint256 value)
        - handler: handleLog
          kind: ethereum/LogHandler # We use ethereum handlers since Zksync is a layer-2 that is compatible
              ## Follows standard log filters
              - Transfer(address indexed from, address indexed to, uint256 amount)
              # address: "0x60781C2586D68229fde47564546784ab3fACA982"

Update Your GraphQL Schema File

Remove existing entities and update schema.graphql file to index block information, transfers, and approvals as shown below:

type Transfer @entity {
  id: ID!
  blockHeight: BigInt
  to: String!
  from: String!
  value: BigInt!
  contractAddress: String!

type Approval @entity {
  id: ID!
  blockHeight: BigInt
  owner: String!
  spender: String!
  value: BigInt!
  contractAddress: String!

Generate entity classes and ABI types:

npm run-script codegen

Import the generated types in your project:

import { Approval, Transfer } from "../types";
import { ApproveTransaction, TransferLog } from "../types/abi-interfaces/Erc20Abi";

Add Mapping Functions

Navigate to src/mappings directory, you'll find handleLog and handleTransaction functions.

Update the functions to process and store the desired data as shown below:

export async function handleLog(log: TransferLog): Promise<void> {`New transfer transaction log at block ${log.blockNumber}`);
  assert(log.args, "No log.args");

  const transaction = Transfer.create({
    id: log.transactionHash,
    blockHeight: BigInt(log.blockNumber),
    from: log.args.from,
    value: log.args.value.toBigInt(),
    contractAddress: log.address,


export async function handleTransaction(tx: ApproveTransaction): Promise<void> {`New Approval transaction at block ${tx.blockNumber}`);
  assert(tx.args, "No tx.args");

  const approval = Approval.create({
    id: tx.hash,
    owner: tx.from,
    spender: await tx.args[0],
    value: BigInt(await tx.args[1].toString()),


These functions process transaction and log data, extracting necessary information to save in the database. Ensure to check the Mappings documentation for more insights.

Step 6 — Build, Run, and Query Your Project

Build your project:

npm run build

Run your project locally with Docker:

npm run start:docker

Open your browser and head to http://localhost:3000. Explore the GraphQL playground and use the Docs tab to understand the available queries.

Try the following query to understand how it works for your new SubQuery starter project:

  query {
    transfers(first: 5, orderBy: VALUE_DESC) {
      nodes {
  approvals(first: 5, orderBy: BLOCK_HEIGHT_DESC) {
    nodes {

You should now see the results displayed below in the GraphQL playground, and you're ready to continue developing your SubQuery project.


Congratulations! You have successfully set up a SubQuery project running locally, ready to handle GraphQL API requests pertaining to data transfers.

To ensure your SubQuery project operates efficiently and to sidestep common pitfalls, delve into the Project Optimization section. Happy querying!

Made with ❤️ by the ZKsync Community