Developer guide / Data Indexing and Querying
Indexing Somnia with Envio
Envio HyperIndex is a multichain indexing framework that turns smart contract events into a GraphQL API. You describe what to index in a config file, write the indexing logic in TypeScript, and HyperIndex fetches the data through HyperSync, Envio's high-performance data source.
Somnia Mainnet and Somnia Testnet are both supported by HyperSync, so there is no RPC endpoint to set up.
This tutorial indexes every Transfer event of USDC.e on Somnia Mainnet, runs the indexer on your machine and queries the data with GraphQL. The last section shows how to move an existing subgraph to HyperIndex.
Using a coding agent? Copy the agent prompt and let it do the steps for you.
Create the Indexer
You need Node.js 22.15 or newer, pnpm, and Docker running.
Run the following command to scaffold a new indexer from the ERC20 template. Replace YOUR_ENVIO_API_TOKEN with your Envio API token, which you can create for free at envio.dev/app/api-tokens.
pnpx envio init template -t erc20 -n somnia-indexer -l typescript -d ./somnia-indexer --api-token "YOUR_ENVIO_API_TOKEN"
cd somnia-indexer
This creates the project in the somnia-indexer folder, installs its dependencies and saves your token in .env. The template indexes a token on Ethereum, so the next steps point it at Somnia.
Remove the template's example handler and test.
rm src/handlers/ERC20.ts src/indexer.test.ts
Configure the Indexer for Somnia
Replace the contents of config.yaml with the following.
# yaml-language-server: $schema=./node_modules/envio/evm.schema.json
name: somnia-indexer
description: USDC.e transfers on Somnia
field_selection:
transaction_fields:
- hash
contracts:
- name: USDCe
events:
- event: "Transfer(address indexed from, address indexed to, uint256 value)"
chains:
- id: 5031 # Somnia Mainnet
start_block: 430000000
contracts:
- name: USDCe
address: "0x28BEc7E30E6faee657a03e19Bf1128AaD7632A00"
id: 5031points the indexer at Somnia Mainnet. HyperIndex uses the Somnia HyperSync endpoint automatically.start_blockis the block the indexer starts from.430000000is a recent block, so the first sync finishes quickly. To index USDC.e's full history, set it to83645688, the block of the first USDC.e transfer on Somnia. A full sync takes longer.field_selectionadds the transaction hash to every event, which the schema below stores.
To index your own contract, change the name, the event signatures and the address.
Define the Schema
Replace the contents of schema.graphql with the following.
type Transfer {
id: ID!
from: Bytes!
to: Bytes!
value: BigInt!
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
Write the Event Handler
Create src/handlers/USDCe.ts with the following code. HyperIndex loads every file in src/handlers automatically.
import { indexer } from "envio";
indexer.onEvent(
{ contract: "USDCe", event: "Transfer" },
async ({ event, context }) => {
context.Transfer.set({
id: `${event.transaction.hash}-${event.logIndex}`,
from: event.params.from,
to: event.params.to,
value: event.params.value,
blockNumber: BigInt(event.block.number),
blockTimestamp: BigInt(event.block.timestamp),
transactionHash: event.transaction.hash,
});
},
);
Every Transfer event emitted by the USDC.e contract runs this handler, which saves one Transfer row.
Run the Indexer
Generate the types for your config and schema, then start the indexer.
pnpm codegen
pnpm dev
pnpm dev starts Postgres and Hasura in Docker and begins indexing. The terminal shows the sync progress. You can query the data as soon as the first events are indexed, and once the indexer reaches the latest block it follows new Somnia blocks in real time.

The indexer backfilling USDC.e transfers on Somnia Mainnet and reaching the latest block in seconds.
Open the Hasura console at http://localhost:8080 and log in with the admin secret testing. Your GraphQL endpoint is http://localhost:8080/v1/graphql.
To stop the indexer, press Ctrl+C. To also remove the local Docker containers and data, run pnpm envio stop.
Query the Data
Run these queries in the API tab of the Hasura console, or send them to http://localhost:8080/v1/graphql from your app.
Fetch Latest Transfers
query {
Transfer(limit: 10, order_by: { blockNumber: desc }) {
id
from
to
value
blockTimestamp
transactionHash
}
}
Get Transfers by Address
Addresses are stored in checksum format, so _ilike is the easiest way to match any letter case.
query {
Transfer(
where: { from: { _ilike: "0xYourWalletAddress" } }
limit: 10
order_by: { blockNumber: desc }
) {
from
to
value
transactionHash
}
}
Get Large Transfers
value is stored in the token's smallest unit. USDC.e has 6 decimals, so 1000000000 is 1,000 USDC.e.
query {
Transfer(
where: { value: { _gt: "1000000000" } }
limit: 10
order_by: { blockNumber: desc }
) {
from
to
value
transactionHash
}
}
Check Indexing Progress
query {
_meta {
chainId
progressBlock
sourceBlock
eventsProcessed
isReady
}
}
isReady turns true once the indexer has caught up with the latest Somnia block.
Query From Your App
curl -s -X POST http://localhost:8080/v1/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{ Transfer(limit: 3, order_by: { blockNumber: desc }) { from to value transactionHash } }"}'
Index Somnia Testnet
To index a contract on Somnia Testnet, replace the chains section of config.yaml with the following. Set address to your testnet contract and start_block to the block it was deployed in.
chains:
- id: 50312 # Somnia Testnet
hypersync_config:
url: https://50312.hypersync.xyz
start_block: 0 # replace with your contract's deployment block
contracts:
- name: USDCe
address: "0xYourTestnetContractAddress"
HyperIndex finds the Mainnet endpoint on its own, but for Testnet you set the HyperSync URL yourself with hypersync_config. If your contract emits a different event, update the event signature in contracts and the handler to match.
After changing config.yaml, run pnpm codegen and restart with pnpm dev -r, which clears the data from the previous run.
Track Balances and Volume
The Somnia Token Analytics indexer builds on this tutorial. It tracks balances, holder counts, total supply and daily volume for WSOMI, USDC.e, USDT, WETH, WBTC and USDso.
Its handlers load an entity, update it and save it again as each event arrives. This is how it keeps a running volume per token per day.
async function addDailyVolume(context: Ctx, tokenId: string, value: bigint, timestamp: number) {
const day = Math.floor(timestamp / SECONDS_PER_DAY);
const id = `${tokenId}-${day}`;
const existing = await context.DailyVolume.get(id);
context.DailyVolume.set({
id,
token_id: tokenId,
dayStartTimestamp: day * SECONDS_PER_DAY,
transferCount: (existing?.transferCount ?? 0) + 1,
volume: (existing?.volume ?? 0n) + value,
});
}
Once it has synced, one query returns the top holders of any token.
query {
AccountBalance(
where: { token: { symbol: { _eq: "WSOMI" } } }
order_by: { balance: desc }
limit: 10
) {
account
balance
}
}
Balances need every transfer, so this indexer starts at each token's deployment block and the first sync takes longer than this tutorial's.
Deploy the Indexer
When your indexer works locally, push it to GitHub and deploy it on Envio Cloud. Envio Cloud runs it for you and gives you a production GraphQL endpoint, with a free plan for development. Follow the deployment guide for the steps. You can also self-host the indexer on your own infrastructure.
Build It With an AI Agent
The prompt below hands this tutorial to a coding agent such as Claude Code or Cursor. Give the agent the current Envio docs first, so it works from the latest syntax instead of what it remembers.
claude mcp add --transport http envio-docs https://docs.envio.dev/mcp
Agents with shell access can use envio tools search-docs instead. Both are covered on the MCP server page.
Show the full prompt
Build me an Envio HyperIndex indexer for a contract on Somnia.
The contract is: <CONTRACT ADDRESS>
The network is: <Somnia Mainnet or Somnia Testnet>
The events to index are: <EVENT NAMES, or "all events">
(Optional) My existing subgraph is in: <PATH>. If set, convert it instead.
Before writing any code, read the current Envio docs rather than working from
memory. If you have the envio-docs MCP server, use docs_search and docs_fetch.
Otherwise run `envio tools search-docs <query>` and `envio tools fetch-docs <url>`.
Read the quickstart prerequisites, configuration file, event handlers and
testing pages first. For a subgraph, also read the migration guide.
1. Check the prerequisites from the docs are installed and Docker is running.
An Envio API token must be set as ENVIO_API_TOKEN. After scaffolding, check
the "engines" field in package.json for the exact Node.js version needed.
2. Scaffold a project. Block explorer import does not cover Somnia, so start
from the ERC20 template and replace its files:
pnpx envio init template -t erc20 -n <project-name> -l typescript \
-d ./<project-name> --api-token "$ENVIO_API_TOKEN"
Delete src/handlers/ERC20.ts and src/indexer.test.ts, and replace
config.yaml and schema.graphql in full (the template indexes other chains).
3. Write config.yaml. HyperIndex builds the ABI from the event signatures, so
no ABI file is needed.
- Take the event signatures, including parameter names, from the contract's
ABI in my project (for example Hardhat's artifacts/ or Foundry's out/
folder). If there is no ABI in the project, ask me for it.
- Somnia Mainnet is chain ID 5031. HyperIndex finds its HyperSync endpoint
on its own.
- Somnia Testnet is chain ID 50312. Set hypersync_config.url to
https://50312.hypersync.xyz, it is not found automatically.
- Use the contract's deployment block as start_block. Find it with
eth_getCode only: binary search from block 0 to the latest block for the
first block where eth_getCode returns code. Use the Somnia RPC
https://api.infra.mainnet.somnia.network for Mainnet or
https://api.infra.testnet.somnia.network for Testnet.
4. Write schema.graphql and the handlers in src/handlers. If you track
balances or supply, handle every event that changes them. For example, a
wrapped token's Deposit and Withdrawal events do not emit a Transfer.
Check your indexed total supply against totalSupply() on the Somnia RPC.
5. Run `pnpm codegen`, then add a test for one real event (the testing docs
show how to find one) and make `pnpm test` pass. A test on a real block can
need a longer timeout than the template's default.
6. Run `pnpm dev` and query a few rows. A busy contract's full history can take
a long time to sync. Report progress from the _meta query rather than
waiting for isReady: true. For a quick first run, use a recent start_block,
then set it back to the deployment block and restart with `pnpm dev -r`.
Report back:
- the events you indexed and the entities you created
- the start block you used and how you confirmed it
- how many events the indexer processed, with one example row
- anything that did not match these instructions
Give the real numbers you got.
Migrate an Existing Subgraph
If you already have a subgraph on Somnia, you can move it to HyperIndex in one of two ways.
Option 1. Run Your Subgraph As It Is (Preview)
The Envio Subgraph Engine runs an unmodified subgraph on HyperIndex. It reads your subgraph.yaml, schema and AssemblyScript mappings as they are, and works with network: somnia and network: somnia-testnet.
From your subgraph folder, install its dependencies, add your Envio API token and start it.
cd mytoken
npm install
export ENVIO_API_TOKEN="YOUR_ENVIO_API_TOKEN"
pnpx envio@subgraph dev
The data is served from the same local Hasura endpoint, http://localhost:8080/v1/graphql. Bytes fields come back in Postgres hex format, with a \x prefix instead of 0x.
The Subgraph Engine is a preview. If your mappings make contract calls (for example MyToken.bind(...)), also set ENVIO_SUBGRAPH_RPC to a Somnia RPC endpoint that serves archive state. Features it does not support yet, such as call handlers and grafting, are named when it starts.
Option 2. Convert Your Subgraph to HyperIndex
Converting your subgraph gives you TypeScript handlers, multichain indexing and the rest of HyperIndex's features. The indexer you built above is a converted ERC20 subgraph. Here is how each file maps across, using a MyToken subgraph as the example.
| Subgraph | HyperIndex |
|---|---|
subgraph.yaml |
config.yaml |
schema.graphql |
schema.graphql, almost unchanged |
src/mytoken.ts (AssemblyScript) |
src/handlers/*.ts (TypeScript) |
graph codegen && graph build |
pnpm codegen |
graph deploy |
pnpm dev locally, or a push to Envio Cloud |
1. Convert subgraph.yaml to config.yaml
Copy the network, contract address, start block and event signatures across. In config.yaml, event signatures include the parameter names, and the network is the chain ID.
subgraph.yaml
dataSources:
- kind: ethereum
name: MyToken
network: somnia
source:
address: "0x28BEc7E30E6faee657a03e19Bf1128AaD7632A00"
abi: MyToken
startBlock: 83645688
mapping:
kind: ethereum/events
apiVersion: 0.0.9
language: wasm/assemblyscript
entities:
- Transfer
abis:
- name: MyToken
file: ./abis/MyToken.json
eventHandlers:
- event: Transfer(indexed address,indexed address,uint256)
handler: handleTransfer
file: ./src/mytoken.ts
config.yaml
# yaml-language-server: $schema=./node_modules/envio/evm.schema.json
name: mytoken
field_selection:
transaction_fields:
- hash
contracts:
- name: MyToken
events:
- event: "Transfer(address indexed from, address indexed to, uint256 value)"
chains:
- id: 5031 # network: somnia
start_block: 83645688
contracts:
- name: MyToken
address: "0x28BEc7E30E6faee657a03e19Bf1128AaD7632A00"
Use id: 50312 for a subgraph on somnia-testnet. HyperIndex generates the ABI from the event signatures, so you do not need the ABI file.
2. Copy the Schema
Copy schema.graphql across, remove the @entity directive and change any id: Bytes! to id: ID!.
# Subgraph
type Transfer @entity(immutable: true) {
id: Bytes!
...
}
# HyperIndex
type Transfer {
id: ID!
...
}
3. Convert the Handlers
AssemblyScript is close to TypeScript, so most of the logic copies across. The main changes are as follows.
- Register handlers with
indexer.onEvent(...)instead of exported functions. - Replace
entity.save()withcontext.Entity.set({...}). - Load entities with
await context.Entity.get(id)instead ofEntity.load(id).
Subgraph handler
export function handleTransfer(event: TransferEvent): void {
let entity = new Transfer(
event.transaction.hash.concatI32(event.logIndex.toI32())
)
entity.from = event.params.from
entity.to = event.params.to
entity.value = event.params.value
entity.blockNumber = event.block.number
entity.blockTimestamp = event.block.timestamp
entity.transactionHash = event.transaction.hash
entity.save()
}
HyperIndex handler
import { indexer } from "envio";
indexer.onEvent(
{ contract: "MyToken", event: "Transfer" },
async ({ event, context }) => {
context.Transfer.set({
id: `${event.transaction.hash}-${event.logIndex}`,
from: event.params.from,
to: event.params.to,
value: event.params.value,
blockNumber: BigInt(event.block.number),
blockTimestamp: BigInt(event.block.timestamp),
transactionHash: event.transaction.hash,
});
},
);
4. Update Your Queries
HyperIndex serves a standard Hasura GraphQL API. You query an entity by its schema name (Transfer instead of transfers), first becomes limit, and orderBy with orderDirection becomes order_by.
# Subgraph
{
transfers(first: 10, orderBy: blockTimestamp, orderDirection: desc) {
id
from
to
value
}
}
# HyperIndex
{
Transfer(limit: 10, order_by: { blockTimestamp: desc }) {
id
from
to
value
}
}
The query conversion guide covers filters and the other differences. For larger subgraphs, see the full migration guide, migrating with AI, and the Indexer Migration Validator to compare your subgraph and HyperIndex results.
Example Repositories
- somnia-indexer-example is the complete indexer from this tutorial.
- somnia-token-analytics tracks balances, holders, supply and daily volume for Somnia's main tokens.
Conclusion
You have indexed USDC.e transfers on Somnia with Envio HyperIndex and queried them with GraphQL. Some next steps to try.
- Index more events or contracts by adding them to
config.yaml. - Index Somnia Mainnet and Testnet in the same indexer by listing both chains.
- Connect your GraphQL endpoint to a frontend, as in the Building Subgraph UIs tutorials.
For more information, visit the Envio docs or ask in the Envio Discord.
Copied from docs.somnia.network (somnia-main-docs), until it finds a home in a product's docs.Edit this page