Once you internalize one idea, the entire Solana RPC API stops looking like a grab-bag of method names and starts looking like a map. The idea: everything on Solana lives in an account — a flat blob of bytes with an owner — and every API method is just a different lens for reading those accounts. Balances, tokens, NFTs, validator stake, the block that wrapped your transaction: all of it is account data, sometimes raw, sometimes indexed, sometimes parsed for you off-chain. This is the layer-by-layer mapping, from the data structure on the chain to the call that reads it.
The Account Layer
An account is the atom. It holds lamports (the smallest unit of SOL), a data blob, and an owner (the program allowed to write to it). Reading state, at bottom, is reading an account.
| On-chain structure | API method | What comes back |
|---|---|---|
| Account { lamports, data, owner } | getAccountInfo | lamports, data (base64), owner, executable |
| Many accounts at once | getMultipleAccounts | same fields, as an array |
| Every account a program owns | getProgramAccounts | account list, with filters |
| Token mint account | getAccountInfo → parse MintLayout | supply, decimals, mintAuthority |
| Token account (ATA) | getTokenAccountsByOwner | amount, mint, owner |
| Balance only (lamports) | getBalance | lamports (u64) |
Notice that the mint account and the token account both come back through plain account reads — you just have to know the byte layout to decode them. An ATA, by the way, is an associated token account: the one deterministic address that holds a given wallet's balance of a given token.
The Transaction Layer
A transaction is a bundle of signatures plus a message. Once it lands, the chain also attaches meta — the receipt, with balance snapshots, logs, and any nested calls. That meta is where most of the useful information actually lives.
| On-chain structure | API method | Notes |
|---|---|---|
| Transaction { signatures, message } | getTransaction | the full transaction, meta included |
| Many transactions | getTransactions | up to 1,000 at a time |
| An address's history | getSignaturesForAddress | signatures + slot only, nothing parsed |
| meta.pre/postBalances | getTransaction → meta | SOL balance change, aligned by accountKeys index |
| meta.pre/postTokenBalances | getTransaction → meta | SPL token balance change |
| meta.innerInstructions | getTransaction → meta | the nested CPI calls (one program invoking another) |
| meta.logMessages | getTransaction → meta | program log output — where you parse events from |
The catch with the raw API: it hands you bytes and logs, not meaning. To know that a transaction was a swap, you have to read the logs and the inner instructions yourself. That's the gap Helius — an RPC provider that indexes and parses on its own servers — fills with an enhanced layer that does the interpretation for you.
| Raw structure | Helius enhanced API | What it adds |
|---|---|---|
| raw instructions + logs | getTransactions (Enhanced) | auto-parsed type (SWAP, NFT_SALE…), tokenTransfers, nativeTransfers |
| signature list | getTransactionsForAddress | pagination, type filter, source filter |
The Block and Slot Layer
A slot is a 400ms time bucket (the target); a block is what fills one when the scheduled leader produces it. This layer is how you move between "what time is it on the chain" and "what happened in that window."
| On-chain structure | API method | Notes |
|---|---|---|
| Block { parentSlot, transactions[], rewards[] } | getBlock | the full block, every transaction in it |
| Current slot | getSlot | latest confirmed slot |
| Slot → timestamp | getBlockTime | Unix timestamp |
| Block height | getBlockHeight | current height |
| Recent blockhash (replay guard) | getLatestBlockhash | blockhash + lastValidBlockHeight |
The Token Layer
SPL tokens (Solana's token standard) are themselves just accounts, but they're common enough that the API gives them shortcuts so you don't have to decode layouts by hand.
| On-chain structure | API method | Notes |
|---|---|---|
| Mint account | getTokenSupply | uiAmount, decimals, amount |
| All token accounts a wallet holds | getTokenAccountsByOwner | filter by programId or mint |
| Every holder of a mint | getProgramAccounts + filter | scans all Token Program accounts — slow |
| Token balance (one account) | getTokenAccountBalance | uiAmount, decimals |
| Token balance (many) | getTokenAccountsByOwner | returned in one batch |
NFTs and compressed assets are the exception. A compressed NFT (a cNFT) doesn't get its own token account at all — it lives as a leaf in a Merkle tree to save rent (the SOL deposit that keeps an account alive) — so the normal token calls can't see it. For those you need the DAS API (Digital Asset Standard), a separate indexed layer built for exactly this.
| Asset type | DAS method | Notes |
|---|---|---|
| One NFT / asset | getAsset | metadata, ownership, compression proof |
| All NFTs a wallet holds | getAssetsByOwner | paginated, with sortBy |
| All NFTs in a collection | getAssetsByGroup | groupKey = collection |
| Compressed-NFT Merkle proof | getAssetProof | verify a leaf on-chain |
| Search assets | searchAssets | filter by owner / creator / mint and more |
The Validator and Stake Layer
Who's producing blocks, who's delegated to them, and when the next handoff happens — all of it is readable, because validators and stake are (you guessed it) just accounts owned by the vote and stake programs.
| On-chain structure | API method | Notes |
|---|---|---|
| All validator vote accounts | getVoteAccounts | current (active) + delinquent (offline) |
| Epoch info | getEpochInfo | epoch, slotIndex, slotsInEpoch, absoluteSlot |
| Epoch schedule | getEpochSchedule | slotsPerEpoch, warmup config (how fast newly activated stake ramps up) |
| Stake account | getProgramAccounts → Stake Program | delegation, activationEpoch |
| Leader schedule | getLeaderSchedule | slot → validator mapping |
| Block production stats | getBlockProduction | produced / skipped per validator |
The Network State Layer
The last layer is the chain's vital signs — throughput, fees, supply, who's online. This is what you read when you're tuning a transaction or building a dashboard, not chasing a specific account.
| On-chain structure | API method | Notes |
|---|---|---|
| Network TPS / performance | getRecentPerformanceSamples | tx count + duration per sample window |
| Network fee | getFeeForMessage | cost for a specific message |
| Priority fee estimate | getPriorityFeeEstimate (Helius) | suggested microLamports, by percentile |
| Supply | getSupply | total, circulating, nonCirculating |
| Cluster node list | getClusterNodes | IP / version of every gossip node |
The Whole Map
Stacked up, the layers and where the off-chain indexers (Helius, DAS) bolt on look like this:
Solana on-chain state │ ├── Account layer getAccountInfo / getProgramAccounts ├── Transaction layer getTransaction / getSignaturesForAddress ──► Helius enhanced │ (type, tokenTransfers, nativeTransfers) ├── Block layer getBlock / getSlot / getBlockTime ├── Token layer getTokenAccountsByOwner / getTokenSupply ───► DAS layer │ (getAsset / getAssetsByOwner / searchAssets) ├── Validator layer getVoteAccounts / getEpochInfo └── Network layer getRecentPerformanceSamples / getFeeForMessage
The Rules That Explain the Shape
Six patterns fall out of the map. Learn these and you can usually guess the right method before you look it up.
| Rule | What it means |
|---|---|
| Account = data container | All state lives in accounts. Every read is, underneath, reading an account. |
| Program = stateless logic | getAccountInfo(programId) returns bytecode, never state. |
| Index vs real-time | getSignaturesForAddress reads history from an index; getAccountInfo reads current state from a live RPC. |
| Balance changes need a diff | pre/postBalances are snapshots — the change is post − pre, and you compute it yourself. |
| Compressed assets go through DAS | A cNFT has no normal token account, so only the DAS API can find it. |
| Enhanced parsing is off-chain | Helius's type / tokenTransfers are indexed off-chain — not raw on-chain fields. |
That last rule is the one people trip on most. The chain stores bytes and emits logs; it never tells you "this was a swap." Any time a field reads like a human sentence — a transaction type, a clean list of token transfers — something off-chain parsed it for you. Useful, but it's an interpretation layer, not ground truth. When you need ground truth, drop back down to the raw account and decode it yourself.