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
| Concept | EVM (Ethereum) | Solana |
|---|---|---|
| Smart Contracts | Contract (code + state combined) | Program (code only) + Account (state only) |
| State Storage | Internal contract storage | Independent Accounts (rent-based) |
| Concurrency | Sequential execution | Parallel (can parallelize if AccountSets don't overlap) |
| Fees | gas | transaction fee + rent |
| Token Standard | ERC-20 | SPL 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
| Error | Cause | Solution |
|---|---|---|
| AccountNotFound | Account not initialized | Initialize account first |
| InsufficientFunds | Insufficient lamports | Ensure rent-exempt |
| ConstraintMut | Missing mut marker | Add mut constraint |
| InvalidAccountOwner | Incorrect account ownership | Check account owner |
| AccountAlreadyInitialized | Duplicate init | Use init_if_needed instead |