Indexing on Berachain Artio Testnet with Envio SDK
How to deploy a local Indexer on Berachain Artio ?
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:
- Query Optimization: They restructure blockchain data into query-friendly formats, significantly enhancing query performance beyond the blockchain’s inherent sequential data storage.
- Data Structuring: Indexers transform raw blockchain data into a more digestible and interpretable format, facilitating easier access and analysis.
- Scalability Solutions: By offloading data queries from the main blockchain infrastructure, indexers mitigate scalability constraints, handling large data volumes efficiently.
- Load Alleviation: They reduce direct blockchain query loads, preserving network performance and responsiveness.
- Advanced Query Support: Indexers enable complex data queries, accommodating sophisticated operational demands beyond simple blockchain transactions.
- Real-time Data Provision: They support applications requiring immediate data feeds, ensuring timely data delivery without burdening the primary blockchain network.
- 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.
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:
- Node.js (use v18 or newer)
- pnpm (use v8 or newer)
- 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.
- Open your terminal in a preferred directory and run the command
envio init
. - Name your indexer anything you’d like (e.g.
weth-berachain-indexer
), and specify a folder name (e.g.weth-berachain-indexer
.) - 3. Choose a preferred language, select
Template
, and then selectErc20
.
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).
- 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.
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:
Exploring Indexed Data
- 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 .
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 —
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.
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! 💪🏼 🐻