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 stalls the sync

It does not error. The server returns an empty page with next_slot equal to the from_slot you sent, so the cursor never advances and the indexer sits there retrying the same empty range while reporting itself healthy. Both https://solana.hypersync.xyz and https://solana-mainnet-history.hypersync.xyz 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