Orca Crypto
Menu
Start Here
Learn
Chains
Exchanges
Markets
Tools
Safety
Glossary
More
Our tokens Book a session
Expert · Scaling architecture

Inside the Solana runtime: accounts, parallelism and fees

The EVM discovers what a transaction touches by running it. Solana makes you say so first, and builds everything on that promise.

Loading live prices
By the Orca Crypto teamUpdated 2026-09-28How we check this17 min readExpert
The short answer

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:

FieldWhat it holds
lamportsThe balance, in lamports (109 per SOL)
dataA byte array of up to 10 MiB
ownerThe program allowed to modify this account
executableWhether the account is a program
rent_epochA 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:

minimum balance = ( 128 + data length in bytes ) × lamports_per_byte128 bytes is the fixed overhead each account carries.

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.

FormatMaximum sizeAccountsResource limits set by
Legacy1,232 bytesInline, about 32 fitComputeBudget instructions
v01,232 bytesUp to 64, via address lookup tablesComputeBudget instructions
v1, since September 15, 20264,096 bytesUp to 64, inline, no lookup tablesA 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.

rust
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:

LimitValue
Default per instruction200,000 CUs (3,000 for builtin programs)
Maximum per transaction1,400,000 CUs
Maximum per block62,500,000 CUs at 250 ms slots (100,000,000 on the original 400 ms basis)
Maximum per writable account per block40% of the block: 25,000,000 CUs at 250 ms
Loaded account data per transaction64 MiB
Heap per program32 KiB by default, up to 256 KiB on request
Cross program invocation depth4 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.

62,500,000 CU ÷ 0.25 s = 250,000,000 CU per secondThe same as 100,000,000 CU per 400 ms slot. Shorter slots buy faster confirmation, not more compute.

Fees: base, priority and how the scheduler ranks you

fee = 5,000 × signatures + ⌈ CU price × CU limit ÷ 1,000,000 ⌉In lamports. CU price is in micro lamports per CU. Applies to legacy and v0 transactions; v1 states the priority fee directly.

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.
priority = reward × 1,000,000 ÷ ( cost + 1 )reward = priority fee + the unburned half of the base fee. cost = signature, write lock, data, loaded data and requested compute costs.
Illustrative transactionCU limitCU pricePriority feeScheduler priority
A: default sized limit1,400,0001,000 micro lamports1,400 lamportsabout 2,782
B: measured limit140,00010,000 micro lamports1,400 lamportsabout 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:

typescript
// 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:

PDA = sha256( seeds ‖ bump ‖ program_id ‖ "ProgramDerivedAddress" ), rejected if on the curvefind_program_address tries bump 255 first and counts down until the result is off the curve. At most 16 seeds of up to 32 bytes each.

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.

KindExtensions
Mint: economicsTransfer fees, interest bearing, scaled UI amount
Mint: controlPermanent delegate, pausable, permissioned burn, non transferable, default account state, mint close authority
Mint: logic and metadataTransfer hook, metadata pointer and metadata, group and group member (with pointers)
Mint: privacyConfidential transfers, confidential mint and burn
AccountMemo 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.

Read the extensions first
A Token 2022 mint can carry authorities that an ordinary token does not. Before you hold or integrate one, read its extensions on a block explorer: a permanent delegate or a pause authority changes who is really in control of the balance.

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

SolanaEVM (Ethereum)
StateSeparate accounts owned by programs; programs hold no stateEach contract owns its storage slots
ParallelismScheduled from declared read and write setsSequential; access lists only pre-warm slots
Storage costRefundable deposit: (128 + bytes) × 5,080 lamports at the end of September 202620,000 gas per new slot, only partly refunded if cleared
Compute cap per transaction1,400,000 CUs16,777,216 gas (EIP-7825)
Transaction size1,232 bytes, or 4,096 with v1No 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 marketFixed base fee per signature, local priority feesGlobal EIP-1559 base fee, burned, plus a tip
ReentrancyOnly direct self recursionAllowed; guarding it is the developer’s job
UpgradesPrograms upgradeable by an upgrade authority unless it is removedCode 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.

Kept on this device only. Nothing is sent anywhere.

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.

Share this page

Was this page useful?