solana/program-model

Solana Program Development Fundamentals: Account Model & PDA

solanaguide👥 Communityconfidence highhealth 100%
v1.0.0·Updated 3/20/2026

Solana vs EVM Core Differences

ConceptEVM (Ethereum)Solana
Smart ContractsContract (code + state combined)Program (code only) + Account (state only)
State StorageInternal contract storageIndependent Accounts (rent-based)
ConcurrencySequential executionParallel (can parallelize if AccountSets don't overlap)
Feesgastransaction fee + rent
Token StandardERC-20SPL Token (single official standard)

Account Model

Everything on Solana is an Account:

Regular account (user wallet): lamports(balance) + data(empty) + owner(System Program) + executable(false) Program Account: data(BPF bytecode) + executable(true) Data Account: data(custom serialized data) + owner(your program) — only the owner program can modify

Rent (Account Rent)

Accounts must maintain rent-exempt status (store enough SOL), otherwise they get garbage collected. 128 bytes ≈ 0.00204 SOL, 1KB ≈ 0.00714 SOL.

solana rent 128  # Calculate rent-exempt amount

PDA (Program Derived Address)

PDAs are program-controlled account addresses with no private key; only the program can sign.

// Define PDA account in Anchor
#[account(
  init, payer = user, space = 8 + 32 + 8,
  seeds = [b"vault", user.key().as_ref()],
  bump
)]
pub vault: Account<'info, Vault>,

// Derive PDA off-chain
let (pda, bump) = Pubkey::find_program_address(
  &[b"vault", user_pubkey.as_ref()], &program_id
);

Common PDA use cases: Token vault custody, user state storage, cross-program invocation signing.

Anchor Framework Quick Start

cargo install --git https://github.com/coral-xyz/anchor avm --locked
avm install latest && avm use latest
anchor init my-program && cd my-program
anchor test  # Compile + local test
use anchor_lang::prelude::*;
declare_id!("YourProgramId");

#[program]
pub mod counter {
  use super::*;
  pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
    ctx.accounts.counter.count = 0; Ok(())
  }
  pub fn increment(ctx: Context<Increment>) -> Result<()> {
    ctx.accounts.counter.count += 1; Ok(())
  }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
  #[account(init, payer = user, space = 8 + 8)]
  pub counter: Account<'info, Counter>,
  #[account(mut)] pub user: Signer<'info>,
  pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Increment<'info> {
  #[account(mut)] pub counter: Account<'info, Counter>,
}

#[account]
pub struct Counter { pub count: u64 }

SPL Token

Each user + each token = separate Token Account (ATA).

import { getOrCreateAssociatedTokenAccount, transfer } from "@solana/spl-token"
const ata = await getOrCreateAssociatedTokenAccount(connection, payer, mintAddress, ownerAddress)
await transfer(connection, payer, senderATA.address, recipientATA.address, owner, 1000n)

Common Errors Quick Reference

ErrorCauseSolution
AccountNotFoundAccount not initializedInitialize account first
InsufficientFundsInsufficient lamportsEnsure rent-exempt
ConstraintMutMissing mut markerAdd mut constraint
InvalidAccountOwnerIncorrect account ownershipCheck account owner
AccountAlreadyInitializedDuplicate initUse init_if_needed instead