Program-Owned State
Solana programs are stateless executables. All durable state lives in program-owned accounts - separate addresses whose owner field is your program ID.
Search across all documentation pages
Solana programs are stateless executables. All durable state lives in program-owned accounts - separate addresses whose owner field is your program ID.
#[account]
pub struct Escrow {
pub maker: Pubkey,
pub amount: u64,
}
#[account(
init,
payer = maker,
space = 8 + Escrow::INIT_SPACE,
seeds = [b"escrow", maker.key().as_ref()],
bump,
)]
pub escrow: Account<'info, Escrow>,When to reach for this:
mapping(address => Data) patternsuse anchor_lang::prelude::*;
declare_id!("Escrow1111111111111111111111111111111111111");
#[program]
pub mod escrow_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>, amount: u64) -> Result<()> {
let escrow = &mut ctx.accounts.escrow;
escrow.maker = ctx.accounts.maker.key();
escrow.amount = amount;
Ok(())
}
pub fn release(ctx: Context<Release>) -> Result<()> {
let escrow = &ctx.accounts.escrow;
require!(escrow.amount > 0, EscrowError::Empty);
// transfer lamports via CPI...
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
payer = maker,
space = 8 + Escrow::INIT_SPACE,
seeds = [b"escrow", maker.key().as_ref()],
bump,
)]
pub escrow: Account<'info, Escrow>,
#[account(mut)]
pub maker: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
#[derive(InitSpace)]
pub struct Escrow {
pub maker: Pubkey,
pub amount: u64,
}What this demonstrates:
escrow_programamount - only the escrow account does| Pattern | Seeds | Use |
|---|---|---|
| Per-user PDA | [b"user", pubkey] | Profiles, positions |
| Global config | [b"config"] | Admin settings |
| Per-mint vault | [b"vault", mint] | Token custody |
seeds::program with bump.init sets owner automatically.close constraint when lifecycle ends.| Alternative | Use When | Don't Use When |
|---|---|---|
| PDA per entity | User-specific mutable state | Tiny global flags |
| Single config account | Protocol-wide settings | High-write user data |
| Zero-copy account | Large account data | Small structs |
| State compression | Mass mints / claims | Frequently updated records |
In a program-owned account (often a PDA) - not inside the program binary.
Yes, if passed in the transaction and deserialization is known. Writing still requires owner program logic.
Unlimited distinct addresses. Each is a separate account on-chain.
One account holding all user records. Causes size limits, CU blowups, and parallelization failure.
Yes. PDAs are accounts owned by the deriving program.
State accounts persist across program upgrades. Only bytecode changes.
Yes via realloc, new accounts, or deserialization versioning in instruction handlers.
The payer in init - user or subsidizing protocol.
Program owns the account; user controls via signer checks on instructions that mutate it.
getProgramAccounts RPC with memcmp filters on discriminators.
Yes - lamports field. Common in escrow and vault patterns.
Derive macro calculating Borsh serialized size for space attribute.
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.
Reviewed by Chris St. John·Last updated Jul 16, 2026