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

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
Solana is TypeScript-only

The --language flag is ignored for Solana; projects are always scaffolded in TypeScript. There is no contract-import flow for Solana — unlike EVM/Fuel you wire up programs in config.yaml by hand (see Configuration).

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 a start slot

start_block in config.yaml is a slot number, not a block number, and HyperSync for Solana keeps a rolling window of recent history — so don't hard-code an old slot. Query the current head and start somewhere sensible below it:

curl -s https://solana.hypersync.xyz/height
# => {"height": 421234567}

Set start_block in config.yaml to, say, a few thousand slots below the head for a quick backfill, or to the slot your program was deployed at for a full history (within the retention window).

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 programs_experimental 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 per instruction so HyperIndex knows which instruction to match (discriminators).
  4. Register a handler with indexer.onInstruction.
config.yaml
ecosystem: svm
chains:
- rpc: https://api.mainnet-beta.solana.com
start_block: 417995000
programs_experimental:
- name: TokenMetadata
program_id: metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s
instructions:
- name: CreateMetadataAccountV3
discriminator: "0x21"
field_selection:
transaction_fields: true
src/handlers/TokenMetadataHandlers.ts
import { indexer, type TokenMetadataAccount } from "envio";

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

context.TokenMetadataAccount.set({
id: decoded.accounts.metadata,
mint: decoded.accounts.mint ?? "",
createdAtSlot: event.slot,
lastTxSignature: event.transaction?.signatures[0],
});
},
);

Next steps