Account Model Basics
10 examples to get you started with the Solana account model - 7 basic and 3 intermediate.
Prerequisites
solana config set --url devnet
npm install @solana/kit@7.0.0 @coral-xyz/anchor@0.32.1Anchor 0.32.1 / anchor-lang 0.32.1 for on-chain examples. Agave 4.1.1 validator semantics apply throughout.
Basic Examples
1. Fetch Raw Account Fields
Every account exposes the same five core fields over RPC.
import { createSolanaRpc, address } from "@solana/kit";
const rpc = createSolanaRpc("https://api.devnet.solana.com");
const { value } = await rpc.getAccountInfo(address("YOUR_PUBKEY"), { encoding: "base64" }).send();
console.log({
lamports: value?.lamports,
owner: value?.owner,
executable: value?.executable,
dataLength: value?.data.length,
rentEpoch: value?.rentEpoch,
});lamportsis the SOL balance;owneris the program allowed to writedataexecutable: truemeans the account is a deployed program- Empty addresses return
null- no account exists yet
Related: Account Anatomy - field-by-field breakdown
2. Inspect Account Size and Rent
Account data size drives the rent-exempt deposit.
solana rent 165- Output shows lamports required for rent exemption at that byte size
- SPL token accounts are typically 165 bytes
- Always fund to rent-exempt minimum at account creation
Related: Rent & Rent-Exemption - calculation details
3. Distinguish System-Owned vs Program-Owned
The owner field determines who can write data.
const SYSTEM_PROGRAM = "11111111111111111111111111111111";
const TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
// System-owned: only System Program can mutate (e.g., wallet accounts)
// Token-owned: SPL Token program manages token account data- Wallet accounts are owned by the System Program
- SPL token accounts are owned by the Token Program
- Your program owns accounts it creates via
init
Related: System Accounts vs. Program Accounts
4. Create an Account with System Program
Account creation allocates space and transfers ownership.
solana create-account --help
# Typically done via program init or CLI allocate commands// Anchor handles this via #[account(init, payer = ..., space = N)]- Creation requires a payer to fund rent-exempt lamports
spaceis the data buffer size in bytes- Owner is set at creation and only changeable via specific programs
Related: Creating & Closing Accounts
5. Program-Owned State Pattern
Programs store application state in separate accounts.
#[account]
pub struct Counter {
pub count: u64,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut)]
pub counter: Account<'info, Counter>,
}- The
Counteraccount holds data; the program binary holds logic Account<'info, Counter>deserializes the byte buffer via Borsh- One account per counter instance - not one global storage slot
Related: Program-Owned State
6. Enforce Ownership in Instructions
Only the owner program can write; your program must validate signers.
#[derive(Accounts)]
pub struct Update<'info> {
#[account(mut, has_one = authority)]
pub data: Account<'info, MyData>,
pub authority: Signer<'info>,
}has_one = authoritychecks a stored pubkey matches the signer- Signer must be marked in the transaction account list
- Missing validation is the #1 source of account model exploits
Related: Account Ownership & Permissions
7. Calculate Account Space for Borsh
Size the data buffer before init.
#[account]
pub struct Profile {
pub authority: Pubkey, // 32
pub name: String, // 4 + len
}
// space = 8 (discriminator) + 32 + (4 + 32) for max name length- Anchor adds an 8-byte discriminator automatically
StringandVecneed 4-byte length prefix plus max content size- Undersized accounts fail at init; oversized accounts waste rent
Related: Data Accounts & Layout
Intermediate Examples
8. Derive a PDA for Deterministic Storage
PDAs are accounts with no private key, owned by your program.
#[derive(Accounts)]
pub struct InitVault<'info> {
#[account(
init,
seeds = [b"vault", user.key().as_ref()],
bump,
payer = user,
space = 8 + 8,
)]
pub vault: Account<'info, Vault>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}- Seeds + program ID produce a deterministic off-curve address
bumpis stored to avoid recomputing the canonical bump- Program signs for the PDA in CPIs via
seedsandbump
Related: Program-Owned State
9. Close an Account and Reclaim Rent
Closing returns lamports to a destination account.
#[derive(Accounts)]
pub struct CloseAccount<'info> {
#[account(mut, close = receiver)]
pub data: Account<'info, MyData>,
#[account(mut)]
pub receiver: SystemAccount<'info>,
}closeconstraint transfers all lamports toreceiver- Data is zeroed and the account becomes system-owned with zero data
- Rent reclamation is a common user incentive for cleanup
Related: Creating & Closing Accounts
10. Read Account Data from RPC
Decode on-chain state from a client.
import { createSolanaRpc, address } from "@solana/kit";
const rpc = createSolanaRpc("https://api.devnet.solana.com");
const account = await rpc
.getAccountInfo(address("COUNTER_ACCOUNT_PUBKEY"), { encoding: "base64" })
.send();
if (account.value) {
const data = Buffer.from(account.value.data[0], "base64");
const discriminator = data.subarray(0, 8);
const count = data.readBigUInt64LE(8);
console.log("Count:", count);
}- Raw RPC returns base64-encoded bytes - you need the layout to decode
- Prefer Anchor IDL + Codama-generated clients for typed decoding
- Discriminator is the first 8 bytes in Anchor accounts
Related: Data Accounts & Layout
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.