Indexing on Berachain Artio Testnet with Envio SDK
De The Bera
De The Bera
9 min read
Indexing on Berachain Artio Testnet with Envio SDK

Indexing on Berachain Artio Testnet with Envio SDK

Bm dev bears! šŸ»

In this short walkthrough we will be learning to deploy a Indexer that supports Berachain Artio!

Special mention to Denham and Sven from Evio for preparing the initial resource on Github!

Why are Indexers Needed?

Indexers are critical components in blockchain ecosystems, serving several pivotal roles:

  1. Query Optimization: They restructure blockchain data into query-friendly formats, significantly enhancing query performance beyond the blockchainā€™s inherent sequential data storage.
  2. Data Structuring: Indexers transform raw blockchain data into a more digestible and interpretable format, facilitating easier access and analysis.
  3. Scalability Solutions: By offloading data queries from the main blockchain infrastructure, indexers mitigate scalability constraints, handling large data volumes efficiently.
  4. Load Alleviation: They reduce direct blockchain query loads, preserving network performance and responsiveness.
  5. Advanced Query Support: Indexers enable complex data queries, accommodating sophisticated operational demands beyond simple blockchain transactions.
  6. Real-time Data Provision: They support applications requiring immediate data feeds, ensuring timely data delivery without burdening the primary blockchain network.
  7. Historical Data Analysis: Indexers organize and maintain accessible historical records, simplifying retrospective data examinations.

In essence, indexers are indispensable for maintaining operational efficiency, facilitating complex data interactions, and enhancing the overall utility of blockchain platforms in sophisticated development environments.

šŸ› ļø What are we building?

This developer guide will walk you through setting up an indexer, to query any ERC20 contract on the Berachain network using a GraphQL API, all with Envio.

This guide analyzes all WETH token ā€œapprovalā€ and ā€œtransferā€ event logs emitted by the WETH contract to gain real-time insights into metrics such as token holders, balances, and transfers of the WETH token.

For this specific guide, letā€™s analyze the top ten accounts with the largest WETH holdings, by querying the balance of token holders.

The full github code repository can be found in the guides section of this repository under Index ERC20 Contract Using Envio.

guides/apps/envio-indexer-erc20 at main Ā· berachain/guides
A demonstration of different contracts, languages, and libraries that work with Berachain EVM. ā€¦

Pre-requisites

Before beginning, make sure you have the following installed or setup on your computer before hand.ā€‹

The following are the prerequisite packages required for Envio:

  1. Node.js (use v18 or newer)
  2. pnpm (use v8 or newer)
  3. Docker Desktop (Docker is required specifically for running the Envio indexer locally).

Berachain-Envio Setup

Install And Setup Envioā€‹

You can install Envio by running the command below:

sudo npm i -g envio 
# Password: 
 
# added 2 packages in 3sLetā€™s start by initializing the indexer and generating a boilerplate to index all events emitted by the WETH ERC20 token contract on Berachain.

This is the WETH contract address: 0x8239FBb3e3D0C2cDFd7888D8aF7701240Ac4DcA4.

  1. Open your terminal in a preferred directory and run the command envio init.
  2. Name your indexer anything youā€™d like (e.g. weth-berachain-indexer), and specify a folder name (e.g.weth-berachain-indexer.)
  3. 3. Choose a preferred language, select Template, and then select Erc20.
envio init 
# > Name your indexer: weth-berachain-envio-test 
# > Specify a folder name (ENTER to skip):  weth-berachain-envio-test 
# > Which language would you like to use? Typescript 
# > Choose an initialization option Template 
# > Which template would you like to use? Erc20 
# Project template ready 
# Running codegen 
# installing packages... 
# Checking for pnpm package... 
# 8.14.1 
# Package pnpm is already installed. Continuing... 
# Scope: all 2 workspace projects
Note: Indexers on Envio can be written in JavaScript, TypeScript, or ReScript. For this demonstration, weā€™ve chosen to use TypeScript as the preferred language.

A project template is generated with all the required files to run your indexer.

Changes to Generated Code

Open the existing code via IDE of yout choice, weā€™re using VS Code (Visual Code Studio).

  1. File: ./config.yaml

This file defines the network, start block, contract address, and events we want to index on Berachain.

Replace the placeholder values for network, start block and contract address with the correct values, i.e.

  • Network Id = 80085
  • Start Block = 0
  • Contract Address = 0x8239FBb3e3D0C2cDFd7888D8aF7701240Ac4DcA4

Post changes the file should look exactly like below ā€”

File: ./config.yaml

name: weth-berachain-indexer 
description: ERC-20 indexer 
networks: 
  - id: 80085 # Berachain Artio Testnet 
    start_block: 0 
    contracts: 
      - name: ERC20 
        address: "0x8239FBb3e3D0C2cDFd7888D8aF7701240Ac4DcA4" #WETH 
        handler: src/EventHandlers.ts 
        events: 
          - event: "Approval(address indexed owner, address indexed spender, uint256 value)" 
            requiredEntities: 
              - name: "Account" 
              - name: "Approval" 
          - event: "Transfer(address indexed from, address indexed to, uint256 value)" 
            requiredEntities: 
              - name: "Account" 
              - name: "Approval"

The contract addresse used above is the contract address for WETH on Berachain Artio Testnet. Feel free to check it out on Berachain Explorer https://artio.beratrail.io/token/0x8239FBb3e3D0C2cDFd7888D8aF7701240Ac4DcA4.

2. File: ./Schema.graphql

This file saves and defines the data structures for selected events, such as the Approval event.

## Pls note - there are no changes required below for this guide, but obv changes can be made based on requirements 
 
type Account { 
  # id is the address of the account 
  id: ID! 
  # approvals are a list of approvals that this account has given 
  approvals: [Approval!]! @derivedFrom(field: "owner") 
  # account balance of tokens 
  balance: BigInt! 
} 
 
type Approval { 
  # id is the owner address and spender address [owner-spender] 
  id: ID! 
  # amount is the amount of tokens approved 
  amount: BigInt! 
  # owner is the account that approved the tokens 
  owner: Account! 
  # spender is the account that is approved to spend the tokens 
  spender: Account! 
}

3. File: ./src/EventHandlers.ts

This file defines what happens when an event is emitted and saves what code is going to run, allowing customization in data handling.

import { 
  ERC20Contract_Approval_loader, 
  ERC20Contract_Approval_handler, 
  ERC20Contract_Transfer_loader, 
  ERC20Contract_Transfer_handler, 
} from "../generated/src/Handlers.gen"; 
 
import { AccountEntity, ApprovalEntity } from "../generated/src/Types.gen"; 
 
ERC20Contract_Approval_loader(({ event, context }) => { 
  // loading the required Account entity 
  context.Account.load(event.params.owner.toString()); 
}); 
 
ERC20Contract_Approval_handler(({ event, context }) => { 
  //  getting the owner Account entity 
  let ownerAccount = context.Account.get(event.params.owner.toString()); 
 
  if (ownerAccount === undefined) { 
    // Usually an accoun that is being approved alreay has/has had a balance, but it is possible they havent. 
 
    // create the account 
    let accountObject: AccountEntity = { 
      id: event.params.owner.toString(), 
      balance: 0n, 
    }; 
    context.Account.set(accountObject); 
  } 
 
  let approvalId = 
    event.params.owner.toString() + "-" + event.params.spender.toString(); 
 
  let approvalObject: ApprovalEntity = { 
    id: approvalId, 
    amount: event.params.value, 
    owner_id: event.params.owner.toString(), 
    spender_id: event.params.spender.toString(), 
  }; 
 
  // this is the same for create or update as the amount is overwritten 
  context.Approval.set(approvalObject); 
}); 
 
ERC20Contract_Transfer_loader(({ event, context }) => { 
  context.Account.load(event.params.from.toString()); 
  context.Account.load(event.params.to.toString()); 
}); 
 
ERC20Contract_Transfer_handler(({ event, context }) => { 
  let senderAccount = context.Account.get(event.params.from.toString()); 
 
  if (senderAccount === undefined || senderAccount === null) { 
    // create the account 
    // This is likely only ever going to be the zero address in the case of the first mint 
    let accountObject: AccountEntity = { 
      id: event.params.from.toString(), 
      balance: 0n - event.params.value, 
    }; 
 
    context.Account.set(accountObject); 
  } else { 
    // subtract the balance from the existing users balance 
    let accountObject: AccountEntity = { 
      id: senderAccount.id, 
      balance: senderAccount.balance - event.params.value, 
    }; 
    context.Account.set(accountObject); 
  } 
 
  let receiverAccount = context.Account.get(event.params.to.toString()); 
 
  if (receiverAccount === undefined || receiverAccount === null) { 
    // create new account 
    let accountObject: AccountEntity = { 
      id: event.params.to.toString(), 
      balance: event.params.value, 
    }; 
    context.Account.set(accountObject); 
  } else { 
    // update existing account 
    let accountObject: AccountEntity = { 
      id: receiverAccount.id, 
      balance: receiverAccount.balance + event.params.value, 
    }; 
 
    context.Account.set(accountObject); 
  } 
});

Deploying Local Indexer

Starting the Indexer & Exploring Indexed Data.

Now, letā€™s run our indexer locally by running envio dev command.

Localhost Login Screen

Your browser would have opened a local Hasura console at http://localhost:8080/console.

Head over to the Hasura console, type in the admin-secret password = testing .

Your localhost should look something like below:

Localhost Homepage: Hasura

Exploring Indexed Data

  1. Head over to the Hasura console, type in the admin-secret password testing, and navigate to ā€œDataā€ in the above column to explore the data. For example, you can:
  • View ā€œevents_sync_stateā€ table to see which block number you are on to monitor the indexing progress.
  • View the ā€œchain_metadataā€ table to see the block height of the chain.
  • View the ā€œraw_eventsā€ table to see all events being indexed.

If you view the ā€œAccountā€ table, you will see a column called ā€œbalanceā€.

2. Letā€™s analyze our data, by clicking ā€œAPIā€ in the above column to access the GraphQL endpoint to query real-time data.

3. From there you can run a query to explore details such as holders and their respective balances of the WETH ERC-20 token.

Thatā€™s it! Weā€™ve indexed the required data from existing WETH contract. By now you would have learned to deploy a local indexer on Berachain and then query the data from desired contracts on Berachain Artio!

Full Code and Repository

File: ./config.yaml

name: weth-berachain-indexer 
description: ERC-20 indexer 
networks: 
  - id: 80085 # Berachain Artio Testnet 
    start_block: 0 
    contracts: 
      - name: ERC20 
        address: "0x8239FBb3e3D0C2cDFd7888D8aF7701240Ac4DcA4" #WETH 
        handler: src/EventHandlers.ts 
        events: 
          - event: "Approval(address indexed owner, address indexed spender, uint256 value)" 
            requiredEntities: 
              - name: "Account" 
              - name: "Approval" 
          - event: "Transfer(address indexed from, address indexed to, uint256 value)" 
            requiredEntities: 
              - name: "Account" 
              - name: "Approval"

2. File: ./Schema.graphql

## Pls note - there are no changes required below for this guide, but obv changes can be made based on requirements 
 
type Account { 
  # id is the address of the account 
  id: ID! 
  # approvals are a list of approvals that this account has given 
  approvals: [Approval!]! @derivedFrom(field: "owner") 
  # account balance of tokens 
  balance: BigInt! 
} 
type Approval { 
  # id is the owner address and spender address [owner-spender] 
  id: ID! 
  # amount is the amount of tokens approved 
  amount: BigInt! 
  # owner is the account that approved the tokens 
  owner: Account! 
  # spender is the account that is approved to spend the tokens 
  spender: Account! 
}

3. File: ./src/EventHandlers.ts

import { 
  ERC20Contract_Approval_loader, 
  ERC20Contract_Approval_handler, 
  ERC20Contract_Transfer_loader, 
  ERC20Contract_Transfer_handler, 
} from "../generated/src/Handlers.gen"; 
 
import { AccountEntity, ApprovalEntity } from "../generated/src/Types.gen"; 
ERC20Contract_Approval_loader(({ event, context }) => { 
  // loading the required Account entity 
  context.Account.load(event.params.owner.toString()); 
}); 
ERC20Contract_Approval_handler(({ event, context }) => { 
  //  getting the owner Account entity 
  let ownerAccount = context.Account.get(event.params.owner.toString()); 
  if (ownerAccount === undefined) { 
    // Usually an accoun that is being approved alreay has/has had a balance, but it is possible they havent. 
    // create the account 
    let accountObject: AccountEntity = { 
      id: event.params.owner.toString(), 
      balance: 0n, 
    }; 
    context.Account.set(accountObject); 
  } 
  let approvalId = 
    event.params.owner.toString() + "-" + event.params.spender.toString(); 
  let approvalObject: ApprovalEntity = { 
    id: approvalId, 
    amount: event.params.value, 
    owner_id: event.params.owner.toString(), 
    spender_id: event.params.spender.toString(), 
  }; 
  // this is the same for create or update as the amount is overwritten 
  context.Approval.set(approvalObject); 
}); 
ERC20Contract_Transfer_loader(({ event, context }) => { 
  context.Account.load(event.params.from.toString()); 
  context.Account.load(event.params.to.toString()); 
}); 
ERC20Contract_Transfer_handler(({ event, context }) => { 
  let senderAccount = context.Account.get(event.params.from.toString()); 
  if (senderAccount === undefined || senderAccount === null) { 
    // create the account 
    // This is likely only ever going to be the zero address in the case of the first mint 
    let accountObject: AccountEntity = { 
      id: event.params.from.toString(), 
      balance: 0n - event.params.value, 
    }; 
    context.Account.set(accountObject); 
  } else { 
    // subtract the balance from the existing users balance 
    let accountObject: AccountEntity = { 
      id: senderAccount.id, 
      balance: senderAccount.balance - event.params.value, 
    }; 
    context.Account.set(accountObject); 
  } 
  let receiverAccount = context.Account.get(event.params.to.toString()); 
  if (receiverAccount === undefined || receiverAccount === null) { 
    // create new account 
    let accountObject: AccountEntity = { 
      id: event.params.to.toString(), 
      balance: event.params.value, 
    }; 
    context.Account.set(accountObject); 
  } else { 
    // update existing account 
    let accountObject: AccountEntity = { 
      id: receiverAccount.id, 
      balance: receiverAccount.balance + event.params.value, 
    }; 
    context.Account.set(accountObject); 
  } 
});

Github Repository

The complete template of the above guide is available at https://github.com/berachain/guides/tree/main/apps/envio-indexer-erc20 .

Envio Template on Berachain Repo

More dApps and How To Get Support ?

If youā€™re interested in understanding and developing more on Berachain, try various example repos provided in the official guides repo ā€”

guides/apps at main Ā· berachain/guides
A demonstration of different contracts, languages, and libraries that work with Berachain EVM. - guides/apps at main Ā·ā€¦

Berachainā€™s developer documentation also has multiple starter guides with goto tools such as Hardhat, Foundry and more! Head over to developer section on the docs using the link below.

Connecting to Berachain
Berachain API Node Requests

How to get Developer Support?

āš ļø Still facing issues or have doubts? Head over to Official Berachain Discord and raise a ticket as shown in the gif below!

Our DevRel team will be happy to assist! šŸ¤

AdiĆ³s dev bears! Catch ya in then next one! šŸ’ŖšŸ¼ šŸ»