For AI agents: the documentation index is at /llms.txt. Markdown versions of pages are available by appending .md to the URL.
Skip to main content

Getting Started on Solana

This guide takes you from nothing to a running Solana indexer with a live GraphQL API. If you've used HyperIndex on EVM the workflow is identical — only the config and handlers differ.

Prerequisites

  • Node.js v20+ and pnpm. The commands below use pnpm/pnpx; npm, Yarn, and Bun work too if you swap the equivalents
  • Docker Desktop (for the local Postgres + GraphQL stack)
  • A HyperSync API token — the CLI's login flow sets this up for you, or generate one in the Envio Cloud portal. See API tokens.

1. Scaffold a project

pnpx envio init

Choose Solana at the ecosystem prompt, then choose a template:

  • Metaplex Token Metadata (instructions) — indexes the Metaplex Token Metadata program's CreateMetadataAccountV3 / UpdateMetadataAccountV2 instructions. A realistic instruction-indexing starting point.
  • Feature: Block Handler (onSlot) — a minimal slot handler that fetches each block over RPC.

Non-interactive equivalents:

pnpx envio init svm template --template metaplex-token-metadata --name my-indexer
pnpx envio init svm template --template feature-block-handler --name my-indexer

The template scaffolds:

my-indexer/
├── config.yaml # chain + program/instruction selection
├── schema.graphql # the entities you index into
├── src/
│ └── handlers/…ts # your onInstruction / onSlot handlers
├── .env # ENVIO_API_TOKEN, RPC URL
└── package.json

envio init also runs codegen, installs dependencies, and initializes git.

2. Pick an endpoint and a start slot

start_block in config.yaml is a slot number, not a block number. Each HyperSync endpoint serves history back to its own floor slot, and that floor rolls forward over time, so don't hard-code an old slot. Query the current head first:

curl -s https://solana.hypersync.xyz/height
# => 440067639

Set start_block to a few tens of thousands of slots below the head for a quick backfill, or to the slot your program was deployed at for a fuller history - provided that slot sits above the endpoint's floor.

A start_block below the endpoint's floor is silently wrong

It never errors. An indexer running toward head skips straight to the floor and syncs happily, writing nothing at all for the slots before it. An indexer whose end_block is also below the floor stalls instead: the server returns an empty page with next_slot equal to the from_slot it was sent, so the cursor never advances. Both endpoints served from slot 403,000,000 when measured on 2026-08-18, but treat that as a moving number. See choosing an endpoint for how to probe it.

3. Run it

pnpm install            # if you didn't let init do it
pnpm envio codegen # regenerate types from config.yaml + schema.graphql
pnpm envio dev # start Postgres + the indexer + GraphQL (Docker)

envio dev brings up the local stack and runs the indexer with hot reload. The GraphQL playground (Hasura) is at http://localhost:8080 (default admin secret testing). See Navigating Hasura.

To run the pieces separately:

pnpm envio local docker up   # start Postgres + Hasura
pnpm envio codegen
pnpm envio start # run the indexer against the running stack
Re-run codegen after config/schema changes

Editing config.yaml or schema.graphql — including adding a program, instruction, or IDL — requires pnpm envio codegen to regenerate the typed envio module and the entity types in .envio/.

4. Add your own program

Open config.yaml and add a program under experimental.programs with the instructions you want, then write a handler. The shortest path:

  1. Point at an Anchor IDL if you have one — HyperIndex derives the argument and account layout for you (IDL decoding).
  2. Or declare an inline schema (args + accounts) for programs without an IDL (inline schema).
  3. Add a discriminator to every instruction. It is how HyperIndex matches the instruction and how it looks up the layout, including when an IDL is set (discriminators).
  4. Register a handler with indexer.onInstruction.
config.yaml
ecosystem: svm
chains:
- start_block: 437000000 # example only; query /height (step 2) and pick a recent slot above the endpoint's floor
experimental:
hypersync_config:
url: https://solana.hypersync.xyz
programs:
- name: TokenMetadata
program_id: metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s
instructions:
- name: CreateMetadataAccountV3
discriminator: "0x21"
field_selection:
transaction_fields: [signature]
src/handlers/TokenMetadataHandlers.ts
import { indexer } from "envio";

indexer.onInstruction(
{ program: "TokenMetadata", instruction: "CreateMetadataAccountV3" },
async ({ instruction, context }) => {
const params = instruction.params;
if (!params) return; // discriminator matched but decode failed - skip

context.TokenMetadataAccount.set({
id: params.accounts.metadata,
mint: params.accounts.mint ?? "",
createdAtSlot: instruction.block.slot,
lastTxSignature: instruction.transaction.signature,
});
},
);

Next steps