Core concepts: accounts, programs, transactions — the pillars of Solana
The mental model of Solana: accounts, programs, transactions — and why the whole network fits into one sentence.
In the first two parts we figured out how the network looks from the outside, installed the tooling, and created a wallet. Before we start firing RPC calls at the network, you need Solana’s mental model — otherwise the responses will look like magic. The good news: the model is genuinely simple and fits into one sentence.
Everything in Solana is an account.
A wallet is an account. A program is an account. A program’s data is an account. Your tokens are accounts. You can picture the entire blockchain as one giant key → value database, where the key is an address (32 bytes — a public key) and the value is an account. Everything else follows from there.
Accounts
Every account has the same five fields:
- lamports — the balance in lamports, Solana’s smallest unit of money (1 SOL = 10⁹ lamports — think cents to a dollar, just with a lot more zeros);
- data — arbitrary bytes, up to 10 MiB;
- owner — the address of the owning program. The key rule: only the owner program can change an account’s data or deduct lamports from it. Depositing lamports, anyone can do;
- executable — a flag that says “this is a program”;
- rent_epoch — a leftover from the days when the network actually collected rent: it recorded when this account was next due. Rent hasn’t worked that way for years (more on that just below), so today the field sits there for compatibility and you can safely ignore it.
Your wallet from the previous article is an account whose owner is the System Program, whose data is empty, and whose lamports hold your SOL. That’s the entire “wallet”.
About rent: storing data on the blockchain has to cost something — every validator keeps a copy of every account — but in practice no “rent” has been charged for years. Instead, every account must put down a deposit at creation (the rent-exempt minimum), proportional to its data size. An empty account is ~0.00089 SOL; a 165-byte token account is exactly the ~0.00204 SOL that “vanished” when you created your ATA in part 2 (your associated token account — the standard little account that holds a token balance for your wallet; more on why it’s built the way it is below). The deposit is refunded in full if you close the account. So it’s not an expense — it’s collateral.
Programs
Programs (other chains call them smart contracts — code that lives on the blockchain and runs identically for everyone) are the same accounts, just with executable = true and compiled bytecode (sBPF, Solana’s flavor of the BPF virtual machine format) sitting in data.
The most important difference from Ethereum: Solana programs are stateless. “State” here just means the data that survives between calls — balances, counters, who owns what. A contract on Ethereum stores its state inside itself; a Solana program stores nothing — all state lives in separate accounts that the program owns. Code in one place, data in another. That separation is exactly what makes parallel execution possible (hello, Sealevel): if two transactions don’t touch the same accounts for writing, they run at the same time.
By the way, the System Program, the Token Program, the Vote Program — also just programs. Some of them are baked into the validator itself (native programs), but the interface is the same for all.
And how does bytecode end up inside a program account in the first place, if blocks only contain transactions? Via ordinary transactions, that’s how: solana program deploy slices the compiled bytecode into chunks and sends dozens, sometimes hundreds, of transactions, each one appending its fragment to the account’s data (blame the transaction size limit — more on that in a moment). A deploy isn’t some special operation — it’s just a lot of writes into one account. Which is also why it costs real money: an account holding hundreds of kilobytes of bytecode requires a correspondingly large rent-exempt deposit.
Instructions and Transactions
So how do you change anything in this big database of accounts? You send a transaction.
A transaction consists of:
- signatures (64 bytes each) — cryptographic proof, produced with a private key, that the relevant account holders actually authorized this. Whoever signs is a signer, and the first signer pays the fee;
- a list of every account it will touch, each marked writable or readonly. Yes, all accounts have to be declared up front: that’s precisely how the runtime knows which transactions it can run in parallel;
- a recent blockhash — the hash of a recent block;
- one or more instructions.
An instruction is the smallest unit of work: “call program X, hand it accounts Y and data Z”. A transaction is a batch of instructions, and it is atomic: either every instruction succeeds, or everything rolls back. “Buy a token and immediately send it to a friend” is one transaction, and “bought but didn’t send” simply cannot happen.
A couple of rakes every newcomer steps on:
Fees
Fees are fairly simple:
- Base fee — 5000 lamports per signature (usually there’s just one). 50% is burned — destroyed forever, shrinking the total supply — and 50% goes to the leader, the validator whose turn it is to produce blocks.
- Compute units (CU) — the “fuel” of execution: every operation a program performs costs some units. A transaction has a CU limit (200k per instruction by default, 1.4M max per transaction); you can — and should — set the limit explicitly.
- Priority fee — a voluntary tip: you set a price per CU (in micro-lamports), and the total = price × CU limit. Since SIMD-96 it goes 100% to the leader — the very economics we talked about in the introduction in the context of bots and MEV (the money extractable from deciding whose transactions land first).
The practical takeaway: if you want your transaction to land during busy moments, set a sensible priority fee and a precise CU limit (the scheduler prefers compact transactions with a high price per CU).
A lesser-known knob from the same family: SetLoadedAccountsDataSizeLimit. Before executing a transaction, the runtime has to load every account it touches into memory — and by default it reserves a generous 64 MiB for that. The reservation isn’t free: loaded bytes are paid for in CU, and the scheduler weighs them when packing a block. If your transaction only touches a few small accounts, saying so explicitly makes it both cheaper and easier to land — Anza’s write-up above walks through the numbers.
PDA — Program Derived Addresses
Remember in part two I asked you to file away that bit about on-curve and off-curve? Chekhov’s gun goes off. (Quick recap: a normal address is a point on the Ed25519 elliptic curve — the math behind Solana’s keypairs — and every such address has a matching private key.)
A PDA is an address deterministically computed from a set of seeds (arbitrary bytes) + a program’s address + a bump (a number that “pushes” the address off the Ed25519 curve). Since the address is off the curve, a private key for it does not exist, as a matter of principle. Nobody can sign for a PDA… except the program it was derived from: the runtime allows a program to “sign” for its own PDAs (via invoke_signed).
Why this matters:
- Deterministic addresses: both the client and the program can compute the same address from the same seeds, without storing anything anywhere. Your ATA is exactly this: a PDA derived from the seeds
[your wallet, Token Program, the token's mint]. (A mint, by the way, is the account that defines a token — its total supply, its decimals, who’s allowed to issue more. Every token on Solana is one mint account plus many token accounts holding balances of it.) - A program as the owner of funds: escrows, liquidity pools, vaults — all of these are PDAs controlled only by program code, not by anyone’s private key.
Half of Solana’s architecture rests on PDAs — no exaggeration.
CPI — Cross-Program Invocation
CPI is one program calling an instruction of another. The nesting depth is limited (4 levels), but that goes a long way: a single swap on Jupiter (Solana’s most popular trade router) is a cascade of CPIs, where the router calls the DEX programs — the decentralized exchanges — which in turn call the Token Program to move the tokens, and all of it happens inside one atomic transaction.
The PDA + CPI combo is what Solana’s “composability” actually is: programs freely call each other and manage their own PDA vaults.
Address Lookup Tables
And the last one — the cure for the 1232-byte limit. An ALT (Address Lookup Table) is a directory account you pre-load with up to 256 addresses. A transaction in the newer v0 format then passes 1-byte indexes into that table instead of full 32-byte addresses. That fits several times more accounts into a transaction — without ALTs, complex DeFi routes simply wouldn’t assemble.
For you and me the practical consequence is a single one: transactions come in legacy and v0 flavors, and you’ll have to account for that when reading transactions over RPC (spoiler: the maxSupportedTransactionVersion parameter).
What’s next
This was the most “reference-manual” article of the series — you’ll come back to it more than once. But now RPC responses won’t read like a cipher: in the next part we finally start poking the network over HTTP — we’ll look at your wallet, your ATA, and your token purchase through RPC’s eyes, and assemble a transaction by hand.