Indexing Etherlink EVM contracts with Envio
Envio HyperIndex is an indexing framework that turns smart contract events into a GraphQL API. You describe the contracts and events to index, write handlers that store the data you need, and query the results with GraphQL. HyperIndex can generate a working indexer from a contract that is verified on the block explorer.
Etherlink Mainnet (chain ID 42793) is on the list of HyperSync supported networks, so HyperIndex reads Etherlink Mainnet data through HyperSync by default.
Shadownet Testnet is not on that list.
To index a network that HyperSync does not support, see RPC as a data source in the Envio documentation.
This guide builds an indexer for the Wrapped XTZ (WXTZ) token on Etherlink Mainnet at address 0xc9B53AB2679f573e480d01e0f49e2B5CFB7a3EAb.
You can follow the same steps with any verified contract.
Quick start
To get started, follow these three steps:
- Initialize your indexer project
- Run the indexer locally
- Query the indexed data
Prerequisites
- Node.js version 22 or later
- pnpm
- Docker Desktop, which HyperIndex uses to run its local database and GraphQL server
- An Envio HyperSync API token, which you can create for free on the HyperSync tokens page at https://envio.dev/app/api-tokens. HyperRPC tokens are a separate product and are not needed for this guide. For the steps, see API tokens for HyperSync in the Envio documentation.
Initialize your indexer project
-
Run the Envio CLI
In an empty folder, run this command:
pnpx envio initThe CLI asks a series of questions to set up the project.
-
Answer the prompts
Give the project a folder name, then choose these options:
- For the blockchain ecosystem, select
Evm. - For the initialization option, select
From Address - Lookup ABI from block explorer. - For the blockchain, type
etherlinkto filter the list and selectetherlink. - For the contract address, enter the address of your contract, such as
0xc9B53AB2679f573e480d01e0f49e2B5CFB7a3EAbfor WXTZ.
The CLI fetches the contract ABI from the Etherlink EVM block explorer and lists the events in the contract. All events are selected by default. Press the left arrow key to clear the selection, then use the arrow keys and space bar to select the events you want. For this guide, select
Approval,Deposit,Transfer, andWithdrawal, then press Enter:> Which events would you like to index? Approval(address indexed owner, address indexed spender, uint256 value), Deposit(address indexed dst, uint256 wad), Transfer(address indexed from, address indexed to, uint256 value), Withdrawal(address indexed src, uint256 wad)When the CLI asks whether you want to add another contract, select
I'm finished. If theENVIO_API_TOKENenvironment variable is not set, the CLI asks you to create a new API token or add an existing one to the project's.envfile.The CLI generates the project, runs code generation and installs the dependencies:
Your indexer is ready! Pick how you'd like to run it:1. cd etherlink-wxtz-indexer && pnpm test # run the tests (recommended for AI)2. cd etherlink-wxtz-indexer && pnpm dev # run locally3. cd etherlink-wxtz-indexer && pnpm start # run in production - For the blockchain ecosystem, select
-
Review the generated files
The project contains these main files:
config.yaml: The chain, contract address and events to indexschema.graphql: The entities that the indexer stores, one for each selected eventsrc/handlers/WXTZ.ts: The handler functions that turn each event into an entity.env: Your Envio API token
The generated
config.yamlfile looks like this:# yaml-language-server: $schema=./node_modules/envio/evm.schema.jsonname: etherlink-wxtz-indexerdisable_default_cross_chain: truechains:- id: 42793start_block: 0contracts:- name: WXTZaddress:- "0xc9B53AB2679f573e480d01e0f49e2B5CFB7a3EAb"events:- event: Approval(address indexed owner, address indexed spender, uint256 value)- event: Deposit(address indexed dst, uint256 wad)- event: Transfer(address indexed from, address indexed to, uint256 value)- event: Withdrawal(address indexed src, uint256 wad)The
idfield is the Etherlink Mainnet chain ID. Each handler stores the event parameters in an entity with an ID in the format<chain ID>_<block number>_<log index>, as in this generated handler for theDepositevent (within filesrc/handlers/WXTZ.tsin our example):indexer.onEvent({ contract: "WXTZ", event: "Deposit" }, async ({ event, context }) => {const entity: WXTZ_Deposit = {id: `${event.chainId}_${event.block.number}_${event.logIndex}`,dst: event.params.dst,wad: event.params.wad,};context.WXTZ_Deposit.set(entity);});You can edit the schema and handlers to store the data your application needs. For more information, see Configuration file, Schema and Event handlers in the Envio documentation.
Run the indexer locally
-
Run the tests
The generated project includes tests for the handlers. Run them from the project folder:
cd etherlink-wxtz-indexerpnpm testThe output shows the result of each test:
Test Files 1 passed (1)Tests 2 passed (2) -
Start the indexer
Make sure that Docker is running, then run this command:
pnpm devThe indexer starts a Postgres database and a Hasura GraphQL server in Docker, then starts indexing:
Connected to Docker via default socketStarted envio-postgresStarted envio-hasuraWaiting for Postgres...Waiting for Hasura... ready (0.0s)... ready (3.0s)[17:30:03.036] INFO: Initializing the indexer storage...[17:30:03.075] INFO: No cache found to upload.[17:30:03.094] INFO: Tracking tables in Hasura[17:30:03.421] INFO: Hasura configuration completed[17:30:03.421] INFO: The indexer storage is ready. Starting indexing!The indexer will take a long time to complete, but you don't have to wait for it to go further with this tutorial.
-
Check the indexing status
The Hasura console is available at http://localhost:8080. When it asks for the admin secret, enter
testing.To see how far the indexer has synced, run the
_metaquery, either in the API tab of the Hasura console or from the command line in another terminal:curl -s http://localhost:8080/v1/graphql \-H "Content-Type: application/json" \-d '{"query": "{ _meta { chainId progressBlock sourceBlock isReady } }"}'The response shows the last block that the indexer processed (
progressBlock) and the latest block on the chain (sourceBlock). TheisReadyfield isfalsewhile the indexer is still catching up with the chain:{"data":{"_meta":[{"chainId":42793,"progressBlock":8573209,"sourceBlock":54162031,"isReady":false}]}}
Query the indexed data
You can query the indexed data from the GraphQL endpoint at http://localhost:8080/v1/graphql while the indexer is running.
Each entity in schema.graphql has a query with the same name, such as WXTZ_Deposit, and a _by_pk query that returns one entity by its chain ID and ID.
Alternatively, you can run this query for example in a terminal, which returns the WXTZ deposit with log index 1 in block 659864:
curl -s http://localhost:8080/v1/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ WXTZ_Deposit_by_pk(chainId: 42793, id: \"42793_659864_1\") { id dst wad } }"}'
The response contains the account that received the WXTZ and the amount, in the token's smallest unit:
{"data":{"WXTZ_Deposit_by_pk":{"id":"42793_659864_1","dst":"0x21c79736B62A0C9a1c843D9F99049Bac391B9A05","wad":"100000000000000"}}}
You can compare this result with the transaction on the block explorer.
To stop the local environment and delete the local database, stop the indexer with Ctrl+C if it is still running, then run pnpm envio stop in the project folder.
Deploy your indexer
The local setup is for development. To run the indexer in production, you can deploy it to Envio Cloud, which hosts the indexer and gives it a GraphQL endpoint, or host it on your own infrastructure. For more information, see Envio Cloud, Deploying your indexer and Self-hosting in the Envio documentation.
Appendix
Sample query
This query returns the three largest WXTZ deposits that the indexer has processed:
{
WXTZ_Deposit(limit: 3, order_by: {wad: desc}) {
id
dst
wad
}
}
The results depend on how far the indexer has synced. This is an example of the result:
{
"WXTZ_Deposit": [
{
"id": "42793_7883592_3",
"dst": "0x0D1C515CCb1B723eCBA2Cbb14c458168078529DA",
"wad": "500000000000000000000000"
},
{
"id": "42793_3199737_1",
"dst": "0x34f957BCD46A9B0f8fCCdcc945004cE03ac6bDb5",
"wad": "478216070729682252095318"
},
{
"id": "42793_8077778_1",
"dst": "0xA237E96Abc3180AF377EcF22aE590C02991f9b1F",
"wad": "370000000000000000000000"
}
]
}
Sample code
This Node.js script sends the sample query to the local GraphQL endpoint and prints the result.
Save it as query.mjs and run it with node query.mjs:
const query = `{
WXTZ_Deposit(limit: 3, order_by: {wad: desc}) {
id
dst
wad
}
}`;
const response = await fetch("http://localhost:8080/v1/graphql", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query }),
});
const { data } = await response.json();
console.log(JSON.stringify(data, null, 2));
Additional resources
- For more ways to set up an indexer, see the HyperIndex quickstart.
- For the commands that the Envio CLI provides, see CLI commands.
- For more information about the Hasura console, see Navigating Hasura.