What is Solana Accounts?
Learn what Solana accounts are, how ownership and PDAs work, why token balances live in separate accounts, and how Solana stores on-chain state.

Introduction
Solana accounts are the basic storage objects that hold the chain’s state. That sounds simple, but it hides the design choice that makes Solana feel different from many other blockchains: on Solana, everything important lives in an account; your SOL balance, a token balance, a program’s code, a mint’s metadata, and an application’s persistent state.
That matters because people often bring the wrong mental model with them. If you come from Ethereum, you may expect an “account” to mean either a user-controlled address or a smart contract address. On Solana, that distinction is not the center of the model. The center is this: the chain is a large key-value store, and each value is an account. programs do not keep hidden storage inside themselves the way an Ethereum contract does. Instead, code and state are separated, and programs are given accounts to read and modify under runtime-enforced rules.
Once that clicks, many things that first seem strange about Solana start to make sense. Why does a token balance sit in its own account? Why do transactions have to list all the accounts they touch? Why are there so many derived addresses? Why does a wallet sometimes need SOL just to create a token account? The common answer is that Solana treats state as explicit, addressable account data rather than as implicit storage behind a contract.
What does it mean that Solana treats state as explicit on‑chain objects?
| Model | State location | Owner | Addressing | Tx listing |
|---|---|---|---|---|
| Solana | Explicit account data | Program ID owns data | Many small addresses | Must list all accounts |
| Ethereum | Implicit contract storage | Contract manages storage | Contract + account addresses | Transaction targets contract |
At the most basic level, Solana stores network state in a key-value store where the key is a unique 32-byte address and the value is an account. That address is usually shown as a base58 string, but the important part is not the display format. The important part is that an address names a single state object.
This is the compression point for understanding the model: an account is not just an identity; it is a chunk of on-chain state with a standard header and optional data. Some accounts mostly act like wallets because they hold lamports, the native balance unit of Solana. Some accounts hold program code. Many accounts hold serialized application data. Some are controlled by ordinary keypairs, and some are controlled by programs through deterministic derivation.
That is why the word account is broader on Solana than in many other ecosystems. A person’s wallet address is an account, but so is a token mint. So is an individual token-holding account. So is a staking record. So is a piece of app state derived from a user ID and a program ID. The term names the storage primitive, not a social category like “user” or “contract.”
An analogy can help here. You can think of Solana as a city where every important thing has its own mailbox, and the network knows how to route messages to those mailboxes. That explains why data is easy to point at and why transactions can declare exactly which mailboxes they will touch. But the analogy fails in one important way: mailboxes are passive containers, while Solana accounts participate in a runtime that enforces ownership, mutability, and funding rules.
What are the five fields of a Solana account and what does each do?
Every Solana account has the same five fields: lamports, data, owner, executable, and rent_epoch.
The field that most users encounter first is lamports, which is the account’s balance in the smallest unit of SOL. 1 SOL = 1,000,000,000 lamports. This field is why a wallet account can hold spendable SOL, but it is also why many non-wallet accounts carry SOL too: accounts often need a minimum balance to remain stored on-chain.
The data field is where the account’s actual state lives. It is an arbitrary byte array, and programs interpret those bytes according to their own layout rules. A token account stores one kind of structure. A mint stores another. A custom application might serialize user profiles, order books, positions, votes, or game state into this byte array. Solana itself does not impose meaning on these bytes beyond size and ownership constraints.
The owner field is one of the most important security boundaries in the system. It stores the program ID of the program that owns the account. Here, owner does not mean “the human who feels like this is theirs.” It means “the program that has authority to modify the account’s data or deduct lamports from it.” That distinction is easy to miss. A user may control a token account in the application sense, while the account is still owned by the SPL Token program in the runtime sense.
The executable field tells the runtime whether the account is a program account containing executable code or a regular data account. If executable is true, the account is treated as program code. If false, it is just state. This is the formal expression of Solana’s code-and-state separation.
The last field, rent_epoch, still exists in the account structure, but current Solana documentation treats rent collection as deprecated, and new rent-exempt accounts typically set this field to the maximum u64 value. The practical point for most readers is not to build mental models around recurring rent deductions. What still matters is the minimum balance requirement tied to account size.
How does account ownership enforce permissions on Solana?
If there is one runtime rule to remember, it is this: only the owner program can modify an account’s data or deduct lamports from it. By contrast, any program can credit lamports to any writable account.
This single rule explains much of Solana’s architecture. It means the chain does not ask, in a vague way, “who wants to change this state?” It asks a more mechanical question: “which program owns this account, and is that program the one trying to write?” That gives the runtime a crisp invariant to enforce.
For wallet users, this creates a subtle but important separation between signing authority and runtime ownership. Your keypair may sign a transaction that asks the SPL Token program to move tokens from a token account you control. But the token account’s owner field, in Solana’s low-level sense, is still the SPL Token program. Your signature authorizes the program to act according to its own rules; it does not make your wallet the direct runtime owner of the account data.
This is also why Solana applications often feel account-heavy. Instead of a single contract storing all its own tables internally, a Solana program typically operates over many separate accounts, each with an address, a funding requirement, and an owner field. The runtime’s permission checks are account-by-account.
A smart reader might ask: if only the owner program can change data, how can programs cooperate? The answer is that they do so by passing accounts into instructions and, when needed, making cross-program invocations. One program can call another, but the callee still only gets to mutate the accounts it is allowed to own or otherwise act on. Cooperation happens through explicit account passing, not through implicit shared storage.
Why does Solana separate program code from on‑chain data?
This separation is not just stylistic. It changes how developers model applications and how wallets present assets.
On some chains, a contract address and the contract’s persistent storage are conceptually fused. On Solana, a program account contains executable code, while persistent state usually sits in separate data accounts. That means an application is not “the contract and its storage” as one object. It is better understood as a program plus a set of state accounts the program owns.
The consequence is powerful but demanding. It becomes easier to reason about specific pieces of state as independent objects with their own addresses. It also becomes possible to derive predictable addresses for application state, pass only the needed accounts into a transaction, and make account access explicit. But it pushes complexity to developers, who must design account layouts, sizes, initialization flows, and authority checks carefully.
This is one reason frameworks like Anchor became popular: not because they change the underlying model, but because they help organize it. The underlying runtime still cares about the same things; owner checks, signer checks, writable accounts, program-derived addresses, and serialized data in byte arrays.
How do public‑key accounts differ from program‑derived addresses (PDAs)?
| Type | Private key? | Who signs? | Derivation | Best for |
|---|---|---|---|---|
| Ed25519 pubkey | Yes | Keypair holder | Key generation | Wallet-owned accounts |
| PDA | No (off-curve) | Owning program via invokesigned | Seeds + bump | Program-owned predictable state |
Every Solana account address is a 32-byte value, and there are two important ways an address can arise.
The first is the straightforward case: an Ed25519 public key backed by a private key. This is what most people think of as a wallet address. If you hold the private key, you can produce signatures for that address.
The second is a program derived address, or PDA. A PDA is deterministically derived from a program ID and one or more seeds. Critically, a PDA is guaranteed to be off the Ed25519 curve, which means there is no corresponding private key. Nobody can simply “own” a PDA by possessing a secret key.
That sounds restrictive until you see the mechanism. A PDA exists so that a program can have predictable, program-controlled addresses without any private key management. The runtime allows only the program whose ID was used in the derivation to act as the PDA’s signer, and it does so through invoke_signed during cross-program invocation.
Here is the intuition. A normal address proves authority with cryptographic possession of a private key. A PDA proves authority by construction: if the runtime can verify that the calling program is the one that could have derived this PDA from the given seeds, it lets that program act on behalf of the PDA. This turns deterministic naming into an authorization tool.
That is why PDAs show up everywhere in Solana applications. A program can derive a user-specific state account from seeds like a user public key plus a label. The same inputs will always produce the same address, so clients do not need a discovery step. They can compute where the state should live and look there directly.
There are practical limits. PDA derivation allows at most 16 seeds, each up to 32 bytes. A bump seed in the range 0..255 may be required because some candidate derivations land on the Ed25519 curve and must be rejected until an off-curve address is found. There are also compute costs to derivation, especially when repeated attempts are needed to find a valid bump. Those are implementation realities, not just theory, and they influence how programs choose seed designs.
Why are token balances like USDC stored in separate token accounts on Solana?
Suppose Alice has a Solana wallet and wants to receive USDC. Someone new to Solana might expect the token balance to appear “inside” her wallet address. That is not how the system is modeled.
Instead, the balance usually lives in a token account governed by the SPL Token program. Alice’s wallet public key identifies her, but the token balance is stored in a separate account whose data layout the token program understands. That account records facts like which mint it belongs to and who the controlling authority is.
If Alice does not yet have the standard token account for USDC, a client can derive her associated token account address from her wallet address and the USDC mint. This address is deterministic: the same wallet-plus-mint pair always maps to the same canonical token account address under the Associated Token Account convention. Anyone can create that associated token account by sending the right instruction and funding its creation. But once created, the account belongs to Alice in the application sense, even though the runtime owner of the account data is still the token program.
Now the token transfer makes sense mechanically. The sender is not “sending USDC to Alice’s wallet address” in the Ethereum sense. The sender is instructing the SPL Token program to debit one token account and credit another token account. Alice’s wallet key matters because it controls the destination token account’s authority, and because wallets use her public key to locate the canonical associated token account. But the balance itself lives in a dedicated account object.
This single example explains a lot of wallet behavior on Solana. Wallets often need to create token accounts before receiving assets. Senders can sometimes fund that creation on a recipient’s behalf. A single wallet can control many token accounts for the same mint, even though there is a standard associated one. And when a wallet interface shows “your USDC balance,” it is really summarizing the state of one or more token accounts, not reading a single native balance field from the wallet address.
How do storage costs and the rent‑exempt minimum affect account design on Solana?
Because Solana stores account data on-chain, accounts must carry enough lamports to justify that storage. The official docs describe this as a minimum lamport balance proportional to account size, commonly called the rent-exempt minimum.
The important idea is economic, not terminological. Storing bytes on-chain is scarce, so an account cannot persist with arbitrary data while holding no balance. In practice, this behaves like a refundable deposit more than like an ongoing subscription fee: the docs note that the full balance can be recovered on account close.
This directly shapes application design. If your program wants to store a large struct for every user, each account needs enough lamports to satisfy the minimum balance for that size. If the state grows, the funding requirement grows too. So account design is not only a question of schema elegance; it is also a question of capital efficiency.
The official docs also give concrete limits developers need to respect. Account data has a hard maximum size of 10 MiB. Data growth per instruction is limited, and there are transaction-level growth limits as well. The account model therefore encourages careful preallocation, sharding state across multiple accounts when necessary, and avoiding designs that assume a single object can grow without bound.
These limits are not arbitrary annoyances. They are consequences of making state explicit and addressable. Once each state object is a real on-chain account, the runtime has to bound how large one object can become and how aggressively it can expand during execution.
How should wallets and clients read account data and choose commitment levels?
| Level | Finality | Latency | Use case |
|---|---|---|---|
| Processed | Lowest | Fastest | UI freshness |
| Confirmed | Medium | Balanced | Dependent txs |
| Finalized | Highest | Slowest | Irreversible reads |
A wallet or application never sees “the state of Solana” all at once. It queries accounts through RPC methods, and those reads come with a commitment level such as processed, confirmed, or finalized.
This matters because account data is only meaningful relative to a slot. The same account may have different contents at different points in the confirmation process. Solana’s RPC responses often include both a value and a context containing the slot at which the data was evaluated. For a wallet, that slot is part of the meaning of the answer.
There is a speed-versus-safety tradeoff here. processed is fresher but less stable. confirmed is often the practical balance for transaction flows that depend on recent state. finalized is the safest but can lag more. So even “read the account balance” is not a purely static operation; it is a read against a moving consensus state.
For common programs, nodes can sometimes return parsed JSON when clients request jsonParsed. That is convenient for token accounts and similar standard structures. But fundamentally, the chain still stores arbitrary bytes. Parsed output is a client convenience layered on top of the account model, not a property of the model itself.
What are common confusions about Solana accounts and ownership?
The most common confusion is thinking that a Solana address always directly is a wallet in the everyday sense. Sometimes it is, but very often it is just the address of some state object. A mint has an address. A token account has an address. A PDA has an address. A program has an address. The address names an account; what kind of thing that account is depends on its fields and how programs interpret its data.
A second confusion is around the word owner. In Solana runtime terms, owner means the program allowed to write the account’s data or debit its lamports. In application terms, people also talk about a wallet “owning” a token account or NFT. Both are common usages, but they refer to different mechanisms. If you mix them up, many permission rules seem contradictory when they are not.
A third confusion is assuming successful execution implies the intended state change happened. The broader Solana ecosystem has seen cases where naive account validation or instruction introspection led to security failures. The Wormhole exploit is a vivid reminder that programs must verify the exact accounts they expect, including sysvars and program IDs, rather than trusting lookalikes supplied by a caller. In an account-based runtime, which account was passed is often as important as which instruction was called.
What failures happen when programs assume incorrect account invariants?
The account model works well because the runtime enforces strong invariants: owner programs control writes, account addresses are explicit, and programs must operate over the accounts supplied to them.
If a program weakens its own checks on top of that the model does not save it automatically.
- for example
- by failing to validate owners
- signers
- canonical PDAs
This is why improper PDA validation, arbitrary cross-program invocation, missing signer checks, and fake sysvar handling are recurring audit themes. The runtime gives a disciplined substrate, but application safety still depends on developers verifying that the accounts in front of them are the right ones.
Even outside adversarial cases, assumptions about state freshness can break. Reads at weaker commitment levels can be rolled back. Operational incidents have shown that optimistic progress and final state are not identical concepts. For wallet builders, this is a reminder that account state is not just data; it is data embedded in consensus.
Conclusion
Solana accounts are best understood as explicit on-chain state objects with standard fields and strict ownership rules. Once you stop thinking of an account as merely “a user address” and start thinking of it as “a named piece of state,” the rest of Solana’s design becomes more coherent.
Your wallet is an account. Your token balances usually live in other accounts. Programs are accounts too, but their code is separate from the data they control. PDAs let programs create deterministic, keyless authorities. And the minimum balance rules reflect that persistent on-chain bytes are a real resource. That is the part worth remembering tomorrow: on Solana, the address is the handle, but the account is the thing.
How do you secure your crypto setup before trading?
Secure your crypto setup before trading by verifying account ownership, funding, and transaction destinations. Cube uses a non-custodial MPC wallet model so you keep signing control while executing trades and transfers through Cube's workflows.
- Fund your Cube account with SOL or a supported token to cover rent‑exempt account creation and the funds you plan to trade.
- Compute or inspect the destination address (associated token account or PDA) and confirm the account’s owner program (for example, the SPL Token program for token accounts).
- Open the pending transaction and review the full account list and programs to be invoked; remove or reject transactions that include unexpected writable accounts or unknown program IDs.
- Set and wait for a commitment level appropriate to the action (use confirmed for typical trades and finalized for large withdrawals) before considering the operation settled.
- Sign the transaction with your Cube MPC key (or connected hardware key), submit it, then verify the transaction hash and monitor confirmations until your chosen commitment level is reached.
Frequently Asked Questions
- If only the owner program can modify account data, how does my wallet’s signature let me move tokens or change state? +
- On Solana, runtime permission is enforced by the account’s owner program: only the program listed in an account’s owner field may modify that account’s data or debit its lamports. A user’s signature does not directly change that runtime ownership; instead a signature authorizes a program (e.g., the SPL Token program) to act according to its rules on behalf of the signing key.
- Why does my wallet sometimes need SOL just to receive a token like USDC? +
- Because token balances are stored in separate token accounts, someone has to create and fund that token account before it can exist on‑chain; creating an account requires paying the rent‑exempt minimum in lamports proportional to its size, so wallets or other clients often need some SOL to pay that creation cost. The docs note that anyone can create the associated token account for a wallet+mint pair, but the account must be funded to persist.
- What is a PDA and why can’t anyone hold a private key for it? +
- A program‑derived address (PDA) is deterministically derived from a program ID and seeds and is constructed to be off the Ed25519 curve, so there is no private key for it; the runtime lets the owning program act on behalf of a PDA via invoke_signed rather than a cryptographic signature. That makes PDAs predictable, program-controlled addresses without secret keys.
- Can my wallet have multiple token accounts for the same token, or is there only one place my tokens live? +
- The Associated Token Account convention gives a canonical, deterministic account address per wallet+mint, but it is only a convention; a single wallet can control many token accounts for the same mint, and wallets typically present the associated account as the standard one. The runtime still treats each token balance as its own account object owned by the token program.
- Are there size limits on how much data an account can store or grow during execution? +
- Account data has hard limits: a single account’s data cannot exceed 10 MiB, and there are limits on how much data can grow per instruction and per transaction, so developers should preallocate and shard state rather than assuming a single object can expand indefinitely. These limits are consequences of making state explicit and addressable on‑chain.
- How do commitment levels like processed/confirmed/finalized affect account reads and their reliability? +
- When reading accounts via RPC you choose a commitment level: processed is freshest but least stable, confirmed is a common practical balance, and finalized is the safest but may lag; the RPC response includes the slot used for the read because account contents are meaningful relative to a slot. So reads trade off speed against finality and can be rolled back at weaker commitments.
- What can go wrong if a program doesn’t validate the accounts it’s given? +
- If a program fails to validate that the exact accounts, owners, sysvars, or program IDs it expects were passed in, callers can supply lookalike or malicious accounts and cause incorrect or exploitable behavior; the article cites Wormhole as a reminder that improper account validation has led to high‑severity incidents. The runtime provides disciplined invariants, but application safety requires explicit account checks.
- What are the limits and costs I should know about when deriving PDAs (seeds, bumps, and compute work)? +
- PDA derivation has practical limits and costs: you can use at most 16 seeds of up to 32 bytes each, a bump seed in 0..255 may be needed to find an off‑curve address, and repeated bump attempts have compute cost (e.g., create_program_address and failed bump iterations cost runtime compute), so seed design and bump strategy affect both namespace and execution cost. These implementation realities influence how programs choose seeds and bumps.