Docs

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: 5031 points the indexer at Somnia Mainnet. HyperIndex uses the Somnia HyperSync endpoint automatically.
  • start_block is the block the indexer starts from. 430000000 is a recent block, so the first sync finishes quickly. To index USDC.e's full history, set it to 83645688, the block of the first USDC.e transfer on Somnia. A full sync takes longer.
  • field_selection adds 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 syncing Somnia Mainnet and reaching the latest block

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() with context.Entity.set({...}).
  • Load entities with await context.Entity.get(id) instead of Entity.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

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

Send feedback

Agents: POST the same to /api/feedback.