Expert level
Solana keeps all state in accounts and makes every transaction declare up front which accounts it reads and writes. That declaration lets the runtime execute non overlapping transactions in parallel, lets congestion stay local to hot accounts, and shapes everything a developer does, from compute budgets and priority fees to program derived addresses and the way tokens work.
Programs are stateless, accounts hold everything
Solana separates code from state completely. A program is an executable account; its sBPF bytecode sits in it or, for upgradeable programs, in a linked program data account, and it keeps no mutable state of its own. All state, every token balance and every pool, lives in separate data accounts that programs own. Every account has the same five fields:
| Field | What it holds |
|---|---|
lamports | The balance, in lamports (109 per SOL) |
data | A byte array of up to 10 MiB |
owner | The program allowed to modify this account |
executable | Whether the account is a program |
rent_epoch | A legacy field from when rent was collected periodically |
The address is not a field but the key the account is stored under, usually an Ed25519 public key or a program derived address. The runtime enforces ownership after every instruction: only the owner program can change an account’s data or debit its lamports, any program can credit lamports to a writable account, and lamports must balance across each instruction. Only the current owner can reassign an account, and only while its data is zeroed.
The consequence for developers: a program never reaches into global storage the way an EVM contract reads its own slots. It receives the accounts it needs as inputs, checks that they are the ones it expects (owner, address, signer and writable flags), and operates on them. Skip the owner check and the program will happily operate on a forged account, the classic Solana bug (see vulnerability classes).
Rent: a refundable deposit, now shrinking
Storage is paid with a deposit, not a recurring fee. Every account must hold a minimum balance proportional to its size, returned in full when the account is closed:
For years lamports_per_byte was effectively 6,960: a rate of 3,480 lamports per byte year times a two
year exemption threshold. SIMD-0437 is cutting it to 696 in five feature gated steps. The first two activated on
mainnet on September 3 and September 11, 2026, taking it to 5,080, and the remaining three were expected with Agave
4.4. A 165 byte token account therefore needed 1,488,440 lamports (about 0.0015 SOL) at the end of September 2026,
down from 2,039,280, and would need 203,928 after the final step. Accounts can hold up to 10 MiB and grow by at most
10 KiB per instruction, which is why large accounts are allocated up front or grown over several instructions.
Transactions declare everything in advance
A Solana transaction is a list of signatures and a message. The message lists every account the transaction will touch, marks each as signer or not and as writable or read only, names a recent blockhash (usable for 150 blocks, which doubles as replay protection) and carries instructions. Each instruction names a program and the indexes of the accounts passed to it. Nothing outside the list can be touched, and a transaction can lock at most 64 accounts.
| Format | Maximum size | Accounts | Resource limits set by |
|---|---|---|---|
| Legacy | 1,232 bytes | Inline, about 32 fit | ComputeBudget instructions |
| v0 | 1,232 bytes | Up to 64, via address lookup tables | ComputeBudget instructions |
| v1, since September 15, 2026 | 4,096 bytes | Up to 64, inline, no lookup tables | A config in the message; unset limits default to zero |
The v1 format (SIMD-0385, carrying the size increase from SIMD-0296) activated on mainnet at the start of epoch 1035. It moves the compute limit, loaded data limit, heap size and priority fee into the message and states the priority fee as an absolute number of lamports. Reading v1 transactions is a breaking change for indexers and fee sponsors, and senders must set the compute and data limits explicitly, because they default to zero.
Sealevel: parallelism from declared access
Because every transaction declares its reads and writes, the runtime knows before executing anything which transactions conflict. Two transactions can run at the same time on different cores if neither writes an account the other touches. Readers of the same account never conflict with each other; a writer conflicts with everyone else touching that account. This is Sealevel. Parallelism is not discovered speculatively; it is scheduled from metadata.
use solana_program::{
instruction::{AccountMeta, Instruction},
program::invoke_signed,
pubkey::Pubkey,
};
// The vault authority is a PDA: an address with no private key,
// derived from seeds plus this program's id.
let (vault_authority, bump) =
Pubkey::find_program_address(&[b"vault", user.as_ref()], program_id);
// Every account the instruction touches is declared up front, with its
// access mode. These flags are what let the runtime run transactions
// that do not overlap in parallel.
let ix = Instruction {
program_id: token_program_id,
accounts: vec![
AccountMeta::new(vault_token_account, false), // writable
AccountMeta::new(user_token_account, false), // writable
AccountMeta::new_readonly(vault_authority, true), // signer (the PDA)
],
data: transfer_data,
};
// Only this program can sign for the PDA, by presenting the same seeds.
invoke_signed(&ix, &account_infos, &[&[b"vault", user.as_ref(), &[bump]]])?;The leader’s scheduler pulls transactions from its buffer in priority order, takes their read and write locks and hands non conflicting work to parallel threads, and validators replaying the block apply the same rules. Since April 2026 (SIMD-0083) conflicting transactions may also share an entry, the unit blocks are built from, and are applied in order, which gives the scheduler more freedom. The EVM, by contrast, executes a block strictly in order. Ethereum’s planned block level access lists (EIP-7928) aim to recover some parallelism by having the builder publish what every transaction touched; see the EVM under the hood.
Compute units and the limits that bound them
Execution is metered in compute units (CUs). The virtual machine charges per instruction executed and fixed amounts for syscalls: a cross program invocation costs 946 CUs before the callee runs (1,000 until SIMD-0339), and deriving a program address 1,500. The limits at the end of September 2026:
| Limit | Value |
|---|---|
| Default per instruction | 200,000 CUs (3,000 for builtin programs) |
| Maximum per transaction | 1,400,000 CUs |
| Maximum per block | 62,500,000 CUs at 250 ms slots (100,000,000 on the original 400 ms basis) |
| Maximum per writable account per block | 40% of the block: 25,000,000 CUs at 250 ms |
| Loaded account data per transaction | 64 MiB |
| Heap per program | 32 KiB by default, up to 256 KiB on request |
| Cross program invocation depth | 4 nested calls (instruction stack depth 5) |
Two changes in 2026 explain the block figures. SIMD-0286 raised the block limit from 60 million to 100 million CUs on July 29, 2026. Then SIMD-0525 began cutting the slot time from 400 ms toward 200 ms in 50 ms steps, scaling every per block limit in proportion so the budget per second stays constant. The 350 ms and 300 ms steps activated in August 2026 and the 250 ms step was scheduled on September 16 and took effect at the next epoch boundary; the 200 ms step had not been activated at the end of September. The per account cap has been 40% of the block since SIMD-0306 activated in November 2025. Sources: SIMD-0525 and the compute budget documentation.
Fees: base, priority and how the scheduler ranks you
The base fee is 5,000 lamports per signature, counting signatures verified by the Ed25519, secp256k1 and secp256r1 precompiles. Half is burned and half goes to the leader. The priority fee goes entirely to the leader, a change made by SIMD-0096 in February 2025. Three details matter in practice:
- You pay for the limit you request, not the CUs you use. Without SetComputeUnitLimit, a transaction with two program instructions reserves 400,000 CUs and pays priority on all of them.
- Fees are charged even when the transaction fails, because the leader did the work.
- A tight limit also buys priority. The scheduler ranks transactions by reward per unit of estimated cost, and the requested limit is part of that cost.
| Illustrative transaction | CU limit | CU price | Priority fee | Scheduler priority |
|---|---|---|---|---|
| A: default sized limit | 1,400,000 | 1,000 micro lamports | 1,400 lamports | about 2,782 |
| B: measured limit | 140,000 | 10,000 micro lamports | 1,400 lamports | about 27,465 |
Both pay 6,400 lamports in total and hand the leader the same 3,900 lamports, yet B ranks about ten times higher (assuming about 2,000 CUs of signature, write lock and data cost each, which also requires setting a loaded accounts data size limit with SetLoadedAccountsDataSizeLimit: left at the 64 MiB default, that term alone adds 16,384 CUs and B’s lead falls to about nine times). Simulate, then set the limit to the measured value plus a margin:
// Legacy and v0 transactions: set both, or pay for compute you never use.
// Measure with a simulation first, then add a margin.
const limitIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 140_000 });
const priceIx = ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 10_000 });
// priority fee = ceil(10,000 * 140,000 / 1,000,000) = 1,400 lamports
// v1 transactions ignore these instructions and carry the limits in the message.Local fee markets
Ethereum prices congestion with one base fee for the whole block. Solana has no base fee that rises with demand at all; it is fixed per signature. Congestion is priced through priority fees, and it tends to be local: when a token launch or a pool is hot, the transactions that write to its accounts compete with each other while unrelated transactions do not.
The per account limit is what makes this work. Because no single writable account can absorb more than 40% of a
block’s compute, a rush on one pool fills that pool’s share and spills no further. Transactions for that pool
bid up their priority fees; a payment or a swap elsewhere can still land cheaply. Fee estimators reflect this: the
getRecentPrioritizationFees RPC method takes a list of accounts and reports recent fees paid by
transactions that locked them.
Locality is imperfect, since network ingress and the leader’s scheduler are shared. And much of the competition for ordering happens outside the protocol, in Jito bundles and tips; see the MEV supply chain guide.
Program derived addresses and cross program invocation
Programs need to own assets and authorize their movement, but a program has no private key. Program derived addresses solve this. A PDA is a hash of seeds and the program id that is checked to lie off the Ed25519 curve, so no private key for it can exist:
The program signs for the PDA during a cross program invocation by passing the same seeds to
invoke_signed; the runtime recomputes the address and grants the signer flag. Store and check the first
bump found, the canonical bump, because accepting an arbitrary bump lets a caller substitute a different valid
address.
Cross program invocation rules worth knowing: signer and writable privileges extend from caller to callee but can never be escalated; direct self recursion is allowed, but indirect reentrancy (A calls B calls A) fails with ReentrancyNotAllowed; and calls nest at most 4 deep. SIMD-0268 would raise that to 8, but it was not active on mainnet at the end of September 2026.
The token programs and Token 2022
Solana has no per token contracts. Every standard fungible token is data held in accounts owned by one shared program: a mint account (82 bytes) holds supply, decimals and authorities, and each holder has a token account (165 bytes) per mint, usually at a deterministic associated token account address. Moving a token means invoking the token program, never code written by the token’s issuer.
The original Token program was replaced in place on May 13, 2026 by p-token (SIMD-0266), a reimplementation with the same instructions. In the Solana Foundation’s measurements Approve fell from 2,904 CUs to 124 and Burn from 4,753 to 126, freeing an estimated 10% of total block space.
Token 2022, also called Token Extensions, is a separate program at a different address and a strict superset of Token: the same instruction layouts byte for byte, the same first 165 bytes of an account and 82 bytes of a mint, with extensions stored after those (a mint with extensions is padded to 165 bytes, then an account type byte, then the extensions). Extensions are chosen when a mint or account is created; the full list is in the extension guide.
| Kind | Extensions |
|---|---|
| Mint: economics | Transfer fees, interest bearing, scaled UI amount |
| Mint: control | Permanent delegate, pausable, permissioned burn, non transferable, default account state, mint close authority |
| Mint: logic and metadata | Transfer hook, metadata pointer and metadata, group and group member (with pointers) |
| Mint: privacy | Confidential transfers, confidential mint and burn |
| Account | Memo required on incoming transfers, immutable ownership, CPI guard |
Several extensions are powers someone holds over your balance or your transfers. A permanent delegate can move or burn tokens from any account of that mint. A pausable mint can halt transfers. A transfer hook makes every transfer call a program the issuer chose, which can fail or change behavior, and integrators must pass its extra accounts. Confidential transfers depend on the ZK ElGamal proof program, which was disabled by feature gate in June 2025 and re-enabled in June 2026.
Leaders, Gulf Stream and Turbine
Solana has no public mempool. Each epoch of 432,000 slots has a leader schedule computed in advance from stake, and each leader gets 4 consecutive slots. Clients and RPC nodes send transactions straight to the current and upcoming leaders over QUIC (Gulf Stream), with stake weighted quality of service reserving bandwidth for staked senders. At 250 ms slots a leader window lasts 1 second and an epoch about 30 hours.
The leader streams its block while building it, cut into shreds grouped in erasure coded sets of 32 data and 32 coding shreds, so any 32 of the 64 recover the set. Turbine spreads shreds through a stake weighted tree with a fanout of 200: the leader sends each shred to a root node, which retransmits it to up to 200 others, each of which forward again. Proof of History, a sequential SHA-256 hash chain, paces the stream so validators can check that the leader spent its slot’s time. Validators vote with Tower BFT, and a block counts as finalized once votes have stacked up over 32 slots: 12.8 seconds at the original 400 ms slot time, proportionally less now.
That consensus layer is scheduled to change. Alpenglow, approved by validator governance in 2025, replaces Tower BFT with Votor, in which validators exchange votes directly and aggregate them into certificates, targeting finality around 150 ms. It is planned for Agave 4.3 and was not active on mainnet at the end of September 2026. A second phase, Rotor, would later replace Turbine. The consensus guide covers the protocol itself.
Validator clients: Agave, Frankendancer and Firedancer
Agave
The Rust client maintained by Anza, descended from the original Solana Labs validator. Most stake runs Agave or a fork of it, notably Jito’s, which adds a block engine for bundles and tips.
Frankendancer
A hybrid from Jump: Firedancer’s networking and block production running beside Agave’s runtime and consensus. It speeds up the leader path while sharing Agave’s execution code.
Firedancer
An independent reimplementation in C by Jump, with its own runtime and consensus code. Full Firedancer builds were running on mainnet with real stake by September 2026.
A snapshot of the client IDs that validators report in mainnet gossip, taken on September 29, 2026 and weighted by stake, showed Agave and its forks securing about 84% of stake, Frankendancer about 5%, and full Firedancer builds, including forks, about 11%. The figures shift every epoch; the Firedancer documentation tracks releases.
Diversity matters because of the fault thresholds. A crash bug in a client with more than a third of stake halts the chain; a correctness bug in one with more than two thirds could finalize a wrong state. With about 84% of stake on the Agave lineage, its bugs were the ones the network could least absorb, which is the risk Firedancer exists to reduce.
Solana and the EVM, side by side
| Solana | EVM (Ethereum) | |
|---|---|---|
| State | Separate accounts owned by programs; programs hold no state | Each contract owns its storage slots |
| Parallelism | Scheduled from declared read and write sets | Sequential; access lists only pre-warm slots |
| Storage cost | Refundable deposit: (128 + bytes) × 5,080 lamports at the end of September 2026 | 20,000 gas per new slot, only partly refunded if cleared |
| Compute cap per transaction | 1,400,000 CUs | 16,777,216 gas (EIP-7825) |
| Transaction size | 1,232 bytes, or 4,096 with v1 | No consensus cap beyond gas; blocks are capped at about 8 MiB of RLP (EIP-7934, Fusaka) and clients relay transactions of at most 128 KiB by default |
| Fee market | Fixed base fee per signature, local priority fees | Global EIP-1559 base fee, burned, plus a tip |
| Reentrancy | Only direct self recursion | Allowed; guarding it is the developer’s job |
| Upgrades | Programs upgradeable by an upgrade authority unless it is removed | Code immutable; upgrades through proxies |
Neither design is simply faster. Solana pays for parallelism with the burden of declaring every account, and for low latency with demanding validator hardware. The EVM keeps a simpler mental model at the cost of sequential execution. The two are converging from opposite sides: Ethereum toward access lists and parallel validation, Solana toward larger transactions and cheaper state. For the user’s view, see Ethereum vs Solana and the Solana network page.
Common questions
Why does a Solana transaction have to list every account it uses?
Because the runtime schedules execution from that list. Knowing every read and write in advance lets it run non conflicting transactions in parallel, lock only what is needed, and cap how much compute any single writable account can consume in a block. A transaction that tries to touch an unlisted account fails.
How are Solana priority fees calculated?
For legacy and v0 transactions the priority fee is the compute unit price in micro lamports multiplied by the requested compute unit limit, divided by one million and rounded up. It is based on the limit you request, not the compute you use, and it goes entirely to the leader. The v1 format introduced in September 2026 states the priority fee directly in lamports instead.
What is the compute unit limit per block on Solana?
At the end of September 2026 it was 62,500,000 CUs per block with 250 ms slots, equivalent to the 100 million CU limit per 400 ms slot set by SIMD-0286 in July 2026. Any single writable account can use at most 40% of that, and a single transaction at most 1,400,000 CUs.
What is the difference between Token and Token 2022 on Solana?
Token 2022 is a separate program that accepts the same instructions and account layouts as the original Token program and adds optional extensions: transfer fees, transfer hooks, metadata, confidential transfers, permanent delegates, pausing and more. The extensions add features but also give issuers powers an ordinary token does not have, so they are worth reading before you hold one.
Is Firedancer live on Solana mainnet?
Yes, in two forms. Frankendancer, which pairs Firedancer’s networking and block production with Agave’s runtime, and full Firedancer builds were both running on mainnet in September 2026, at about 5% and 11% of stake respectively in a gossip snapshot on September 29, 2026.
Where to go next
Next: Proposer builder separation and the MEV supply chain
Guide 9 of 16 in the Expert level. Your place is kept on this device, so you can pick up exactly here next time.