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

Solana Configuration File

A Solana indexer is defined by a config.yaml with ecosystem: svm. It tells HyperIndex which chain to read, which programs and instructions to match, and how to decode them. This page is the field-by-field reference; for the meaning of discriminators, IDLs and argument types see Decoding Instructions.

Add this line at the top of the file for editor autocompletion and validation:

# yaml-language-server: $schema=./node_modules/envio/svm.schema.json

A complete example

config.yaml
# yaml-language-server: $schema=./node_modules/envio/svm.schema.json
name: my-solana-indexer
description: Index Jupiter swaps and SPL Token transfers
ecosystem: svm
chains:
- id: solana
start_slot: 437000000 # a SLOT number, not a block number
programs:
# --- decoded from an IDL ---
- name: Jupiter
program_id: JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4
idl: ./idls/jupiter.json # every usable instruction of the IDL is indexable
# --- decoded from an inline layout (no IDL) ---
- name: SplToken
program_id: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
instructions:
- name: transfer
discriminator: "0x03"
accounts: [source, destination, authority]
args:
- name: amount
type: u64

Handlers then select what they index and which fields they read - see Instruction Handlers.

Top-level fields

FieldRequiredDefaultNotes
name-Project name.
ecosystem-Must be svm.
chains-One or more Solana clusters to index (see below).
programs--Programs to index. Declared once for the whole project, not per chain.
description--Free-text description.
schema-schema.graphqlPath to your GraphQL schema.
handlers-src/handlersDirectory that handler files are auto-loaded from.
full_batch_size-5000Target number of instructions processed per batch.
storage-postgres: trueStorage backends (postgres, clickhouse).
disable_default_cross_chain-falseMake entities and effect caches per-chain instead of shared.
No EVM-style global fields

For Solana, several EVM top-level fields don't apply: contracts, rollback_on_reorg, save_full_history, raw_events, field_selection, address_format and bytes_type. Reorgs are handled automatically on the HyperSync source (it rolls back on reorg), field selection is per handler (the fields option), addresses are base58, and Bytes is always a Uint8Array.

chains

Each entry is one Solana cluster.

FieldRequiredDefaultNotes
id-The cluster: the label solana (7565164), solana-devnet (7565165), or an explicit number for another cluster. SVM has no native numeric chain id, so Envio assigns the label ids.
start_slot-The slot to start indexing from, or latest to start from the current head (see below).
end_slot--Stop at this slot. Useful for finite backfills.
block_lag--Stay this many slots behind the head.
hypersync_config-the chain id's public endpointBlock containing url. Optional for solana and solana-devnet; required for any other chain id.
rpc--Accepted but unused: instruction sync is served by HyperSync.
skip-falseExclude the chain from indexing and migrations.
chains:
- id: solana
start_slot: 437000000
- id: solana-devnet
start_slot: latest
EVM difference: the id is a label

EVM chains use the public numeric chain ID. Solana clusters have no such id, so you write solana or solana-devnet and HyperIndex maps them to 7565164 / 7565165 - the values you see as context.chain.id in handlers and as the key in test indexer chain overrides. Before v3.8 the Solana chain id was 0.

Starting from the head

start_slot: latest resolves the current head once, when the indexer is first deployed, and persists the concrete slot. A normal resume (a crash or a process restart) picks up from the stored slot, so downtime is backfilled rather than skipped; envio dev/envio start with -r re-resolves it against the head at that time.

Choosing an endpoint and a start slot

The endpoint follows the chain id, so the only real decision is the start slot:

ChainChain idEndpoint
Mainnetsolana (7565164)https://solana.hypersync.xyz
Devnetsolana-devnet (7565165)https://solana-devnet.hypersync.xyz

Each endpoint serves history back to its earliest indexed slot, not to genesis - mainnet is around slot 403,000,000 as of September 2026, and we keep extending it backwards. GET <endpoint>/height returns the current head.

A start_slot before the earliest indexed slot doesn't error

The indexer starts from the earliest indexed slot instead, so a backfill can look healthy while silently skipping every slot before that. To test a candidate slot, send a one-slot bounded query and compare next_slot to from_slot: equal means the slot isn't indexed yet, from_slot + 1 means served (see the HyperSync curl examples). Need history further back? Tell us on Discord.

programs

Programs are defined once for the whole project; program_id says where each one lives on every chain.

FieldRequiredDefaultNotes
name-A unique name you choose. Used in handlers (onInstruction({ program: "<name>" })) and in generated types.
program_id-Base58 program address. A single value is allowed only when the config defines one chain; with several, give a mapping keyed by chain id, naming every one of them and writing _ for a chain the program isn't deployed on.
idl--Path to an IDL JSON (Anchor 0.30+, legacy Anchor, Shank or Codama), relative to config.yaml. Every usable instruction becomes indexable, and onInstruction selects by name.
instructions--Instructions to index. Required when there's no idl; with an idl, use it only to override or add instructions (see below).
programs:
- name: SplToken
program_id:
solana: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
solana-devnet: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
- name: MyProgram
program_id:
solana: MyPr0gram11111111111111111111111111111111111
solana-devnet: _ # not deployed there

instructions

Each entry declares one instruction of the program.

FieldRequiredDefaultNotes
name-The instruction name, unique per program. It's the key in onInstruction({ instruction: "<name>" }) and in the generated types.
discriminator-0x-prefixed hex prefix of the instruction data to dispatch on, of any whole number of bytes; an 8-byte value is the standard Anchor discriminator. The empty prefix "0x" matches every instruction of the program. See discriminators.
accounts--Positional account slots, in the order the program expects them. The Nth entry names slot N and surfaces it as instruction.accounts.<name>.
args--Borsh argument layout. Setting it attaches a decoder that also filters - see args is also a filter.

With an idl, both accounts and args are required on a row whose name the IDL declares (that row replaces the IDL's version of it); a row with a new name adds an instruction the IDL didn't declare.

instructions:
- name: transferChecked
discriminator: "0x0c"
accounts: [source, mint, destination, authority]
args:
- name: amount
type: u64
- name: decimals
type: u8

Account slots

Entries in accounts are positional, and three forms are accepted:

FormMeaning
payerA slot the call always carries. Surfaces as instruction.accounts.payer.
?authorityAn optional slot. The key is absent from instruction.accounts when the call omits the slot or fills it with the program id.
_Holds a position without naming it, so the slots after it keep theirs. Never surfaced, never filterable, and the list may not end with one.

Naming a slot is what makes it filterable from a handler's where.accounts; the raw positional addresses are available either way through instruction.accountArguments.

Choosing what to index

Solana's highest-frequency programs (SPL Token, System) produce enormous volumes of instructions, and matching them unfiltered can swamp a backfill. Narrow a registration with a where filter on account keys, or read value flow from account activity on a protocol instruction instead of indexing every transfer.