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 & IDLs.

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 Metaplex NFT mints
ecosystem: svm
chains:
- start_block: 437000000 # a SLOT number, not a block
experimental:
hypersync_config:
url: https://solana.hypersync.xyz # required: the HyperSync endpoint serving instructions
programs:
# --- decoded from an Anchor IDL ---
- name: Jupiter
program_id: JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4
idl: idls/jupiter.json
instructions:
- name: sharedAccountsRoute
discriminator: "0xc1209b3341d69c81"
field_selection:
transaction_fields: [signature, feePayer, success]
token_balance_fields: true
# --- decoded from an inline schema (no IDL) ---
- name: Raydium
program_id: 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8
instructions:
- name: swap
discriminator: "0x09"
args:
- { name: amountIn, type: u64 }
- { name: minAmountOut, type: u64 }
accounts:
- tokenProgram
- amm
- userSourceTokenAccount
- userDestTokenAccount
field_selection:
token_balance_fields: true
transaction_fields: [signature]

Top-level fields

FieldRequiredDefaultNotes
nameProject name.
ecosystemMust be svm.
chainsOne or more chains to index (see below).
descriptionFree-text description.
schemaschema.graphqlPath to your GraphQL schema.
handlers-src/handlersDirectory that handler files are auto-loaded from.
full_batch_size5000Batch size for processing.
storagepostgres: trueStorage backends (postgres, clickhouse).
disable_default_cross_chain-falseMake entities and effect caches per-chain instead of shared across chains.
No EVM-style global fields

For Solana, several EVM top-level fields don't apply: contracts, rollback_on_reorg, save_full_history, raw_events, a global field_selection, and address_format. Reorgs are handled automatically on the HyperSync source (it rolls back on reorg); the RPC source indexes finalized data only. Field selection is per-instruction only (see field selection).

chains

Each entry is one Solana cluster.

FieldRequiredDefaultNotes
start_block-The slot to start indexing from.
experimental--HyperSync-backed instruction indexing: hypersync_config + programs (see below). The key is named experimental to signal that this shape is still evolving.
rpc--RPC URL. Required only when experimental is not set; ignored in favour of the HyperSync source when it is.
end_block--Stop at this slot (inclusive of the range processed). Useful for finite backfills and tests.
block_lag--Stay this many slots behind the head.
skip-falseSkip this chain.
EVM difference: no chain id

EVM chains require an id (the public chain ID). Solana chains have no id - the cluster is identified by the endpoints you point at. Inside handlers the Solana chain id is 0.

experimental

Everything HyperSync-backed lives under the chain's experimental key:

FieldRequiredDefaultNotes
hypersync_config-Block containing url. Both the block and url are required: a chain with an experimental key but no hypersync_config fails config validation rather than falling back to a default.
programs-Solana programs to index on this chain (see below).

Choosing an endpoint and a start slot

Two public Solana HyperSync endpoints exist:

EndpointOldest slot servedHead
https://solana.hypersync.xyz403,000,000440,067,639
https://solana-mainnet-history.hypersync.xyz403,000,000440,067,639

Measured 2026-08-18 by bisecting from_slot against each endpoint. On that date the two were indistinguishable: same floor to the slot, same head. They have not always been, so prefer whichever one your project already pins rather than switching on the assumption that they are interchangeable.

Treat both numbers as moving. Query GET <endpoint>/height for the head, and re-probe the floor rather than copying one into a config as if it were permanent. 403,000,000 is a suspiciously round number, which is what a deliberate backfill target looks like; it is not a guarantee.

A start_block below the floor stalls the sync silently

If start_block is earlier than the endpoint's history floor, the server does not error. It returns an empty page with next_slot equal to the from_slot you sent, so the cursor never advances and the indexer retries the same empty range forever while reporting itself healthy.

To probe the floor directly, send a one-slot query and compare next_slot against what you asked for. Below the floor they are equal; at or above it, next_slot is from_slot + 1:

curl -sS -X POST https://solana.hypersync.xyz/query \
-H "Authorization: Bearer $ENVIO_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"from_slot": 403000000, "to_slot": 403000001,
"include_all_blocks": true,
"field_selection": {"block": ["slot"]}}' | jq .next_slot
# 403000001 => served
# 403000000 => below the floor

Two consequences:

  • Pick an endpoint that covers the range you want, then set start_block at or above its floor.
  • If a backfill is not progressing, check next_slot against from_slot before assuming your discriminators or handlers are wrong.

programs

FieldRequiredDefaultNotes
name-A unique name you choose. Used in handlers (onInstruction({ program: "<name>" })) and generated types.
program_id-The base58 program address.
instructions-Instructions to match within this program (see below).
idl--Path to an Anchor IDL JSON, relative to config.yaml. If set, HyperIndex derives each instruction's args + accounts from the IDL entry matching that instruction's configured discriminator. Mutually exclusive with per-instruction inline args/accounts.
handler-autoPath to the file that registers this program's handlers. By default handler files are auto-loaded.

instructions

Each entry selects one instruction of the program to index. Only name is required by the schema, but in practice discriminator is required too: it is both how HyperIndex tells your instruction apart from the program's others and how it resolves the decode layout.

FieldRequiredDefaultNotes
name-The instruction name, unique per program. It is the key in onInstruction({ instruction: "<name>" }) and in the generated types. It is a label only: matching is done by discriminator, so the name need not equal the IDL's.
discriminator--Hex bytes that identify the instruction (e.g. "0xc1209b3341d69c81"). Always set it, including for modern Anchor IDLs. HyperIndex reads the discriminator only from this key, never from an IDL's embedded discriminator array, and it is also the key it looks the IDL layout up by. Omit it and the instruction matches every instruction of the program and decodes nothing (params is undefined). See discriminators.
is_innerunsettrue = inner (CPI) only, false = top-level only, omitted = matches both.
argsInline argument schema (Borsh), { name, type } per arg. Requires accounts too. Mutually exclusive with the program's idl. See supported types.
accountsInline ordered list of account names. The Nth name labels the Nth account. Requires args too.
account_filtersRestrict matches by the pubkey in specific account positions (see below).
field_selectionOpt into extra data on the event (see below).

Field selection

By default a handler receives only the instruction itself, plus its block's slot/time/hash. Opt into more data per instruction:

KeyValueAdds to the handler's instruction
transaction_fieldslist of field namesinstruction.transaction.<field> for each selected field: signature, allSignatures, feePayer, success, err, fee, computeUnitsConsumed, accountKeys, recentBlockhash, version, transactionIndex.
block_fieldslist of field namesinstruction.block.<field> on top of the always-present slot/time/hash: height, parentSlot, parentHash.
token_balance_fieldstrueinstruction.transaction.tokenBalances: pre/post SPL Token balances. Independent of transaction_fields.
log_fieldstrueinstruction.logs: program logs scoped to this instruction.
field_selection:
transaction_fields: [signature, feePayer, success]
block_fields: [height]
token_balance_fields: true
log_fields: true
signature vs allSignatures

signature is the scalar transaction id (a string) and is what nearly every handler wants. allSignatures is the full readonly string[] of signatures from every signer, which only matters for multi-signer analysis. They are selected independently: selecting signature does not give you allSignatures.

Unselected fields are typed as compile errors in the handler (FieldNotSelected), so reading a field you forgot to select fails at tsc time, not silently at runtime.

Account filters

Match an instruction only when specific account positions hold specific pubkeys — useful to index, say, only swaps that touch a particular pool or mint. Positions are 05; within a position values are OR-ed, and across positions they are AND-ed.

instructions:
- name: swap
discriminator: "0x09"
account_filters:
- position: 1
values:
- 58oQChx4yWmvKdwLLZzBi4ChoCc2fqCUWBkwMihLYQo2 # only this pool

Use any_of to OR several AND-groups together:

    account_filters:
any_of:
- [ { position: 0, values: [So11111111111111111111111111111111111111112] } ]
- [ { position: 1, values: [EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v] } ]

Choosing what to index

Solana's highest-frequency programs (SPL Token, System) produce enormous volumes of instructions. Matching them directly can swamp a backfill. Two practical patterns:

  • Index DeFi/protocol instructions and read value flow from token balances. Enabling token_balance_fields on a protocol instruction gives you the transaction's net token movements without indexing every Transfer.
  • Scope high-volume instructions with account_filters or a tight slot window (start_block/end_block) when you do need them.