Documentation Index
Fetch the complete documentation index at: https://mintlify.com/solana-foundation/anchor/llms.txt
Use this file to discover all available pages before exploring further.
State management in Anchor programs involves creating, reading, updating, and closing accounts that store your program’s data. Anchor provides powerful abstractions through the #[account] macro and account constraints to make state management safe and ergonomic.
The #[account] Macro
The #[account] macro is applied to structs to create custom account types for your program. It automatically implements several traits:
- Owner: Sets the account owner to the program ID (from
declare_id!)
- AccountSerialize and AccountDeserialize: Handles serialization
- Discriminator: Adds an 8-byte discriminator to distinguish account types
Basic Account Definition
#[account]
pub struct MyAccount {
pub data: u64,
pub authority: Pubkey,
pub created_at: i64,
}
The #[account] macro adds an 8-byte discriminator at the start, so when calculating space:
// Space = 8 (discriminator) + account data size
space = 8 + 8 + 32 + 8 // discriminator + u64 + Pubkey + i64
InitSpace Derive Macro
For easier space calculation, use the InitSpace derive macro:
#[account]
#[derive(InitSpace)]
pub struct MyAccount {
pub data: u64,
pub authority: Pubkey,
#[max_len(50)]
pub name: String,
#[max_len(10)]
pub tags: Vec<u8>,
}
// Usage in accounts struct:
#[account(
init,
payer = authority,
space = 8 + MyAccount::INIT_SPACE
)]
pub my_account: Account<'info, MyAccount>,
Account Initialization
To create and initialize a new account, use the init constraint:
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
payer = user,
space = 8 + MyAccount::INIT_SPACE
)]
pub my_account: Account<'info, MyAccount>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
The init constraint:
- Creates the account via CPI to the system program
- Allocates the specified space
- Assigns ownership to your program
- Sets the account discriminator
- Requires
payer and space parameters
- Requires
system_program in the accounts struct
Initialization with PDA
For PDA (Program Derived Address) accounts:
#[derive(Accounts)]
pub struct InitializePda<'info> {
#[account(
init,
payer = user,
space = 8 + MyAccount::INIT_SPACE,
seeds = [b"my_account", user.key().as_ref()],
bump
)]
pub my_account: Account<'info, MyAccount>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
Reading Account Data
Access account data through the Context:
pub fn read_data(ctx: Context<ReadData>) -> Result<()> {
let account = &ctx.accounts.my_account;
msg!("Data: {}", account.data);
msg!("Authority: {}", account.authority);
Ok(())
}
#[derive(Accounts)]
pub struct ReadData<'info> {
pub my_account: Account<'info, MyAccount>,
}
Updating Account Data
To modify account data, mark the account as mut (mutable):
pub fn update_data(ctx: Context<UpdateData>, new_value: u64) -> Result<()> {
let account = &mut ctx.accounts.my_account;
account.data = new_value;
msg!("Updated data to: {}", new_value);
Ok(())
}
#[derive(Accounts)]
pub struct UpdateData<'info> {
#[account(mut)]
pub my_account: Account<'info, MyAccount>,
}
Anchor automatically serializes the account data back to the account when the instruction completes successfully.
Account Constraints
Use constraints to enforce security and business logic:
has_one Constraint
Verifies that an account field matches a provided account:
#[derive(Accounts)]
pub struct UpdateData<'info> {
#[account(
mut,
has_one = authority
)]
pub my_account: Account<'info, MyAccount>,
pub authority: Signer<'info>,
}
This checks that my_account.authority == authority.key().
constraint Constraint
Custom validation logic:
#[derive(Accounts)]
pub struct UpdateData<'info> {
#[account(
mut,
constraint = my_account.data < 100 @ ErrorCode::DataTooLarge
)]
pub my_account: Account<'info, MyAccount>,
}
Closing Accounts
Reclaim SOL rent by closing accounts with the close constraint:
pub fn close_account(ctx: Context<CloseAccount>) -> Result<()> {
msg!("Closing account");
Ok(())
}
#[derive(Accounts)]
pub struct CloseAccount<'info> {
#[account(
mut,
close = authority,
has_one = authority
)]
pub my_account: Account<'info, MyAccount>,
#[account(mut)]
pub authority: Signer<'info>,
}
The close constraint:
- Transfers all lamports to the specified account
- Zeroes out the account data
- Marks the account for garbage collection
Reallocating Accounts
Increase or decrease account size with realloc:
#[derive(Accounts)]
pub struct ReallocAccount<'info> {
#[account(
mut,
realloc = 8 + MyAccount::INIT_SPACE + 100,
realloc::payer = authority,
realloc::zero = true,
)]
pub my_account: Account<'info, MyAccount>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
Complete Example
Here’s a complete counter program demonstrating state management:
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod counter {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.authority = ctx.accounts.authority.key();
counter.count = 0;
msg!("Counter initialized");
Ok(())
}
pub fn increment(ctx: Context<Update>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count = counter.count.checked_add(1)
.ok_or(ErrorCode::Overflow)?;
msg!("Counter: {}", counter.count);
Ok(())
}
pub fn decrement(ctx: Context<Update>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count = counter.count.checked_sub(1)
.ok_or(ErrorCode::Underflow)?;
msg!("Counter: {}", counter.count);
Ok(())
}
pub fn close(ctx: Context<Close>) -> Result<()> {
msg!("Counter closed");
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
payer = authority,
space = 8 + Counter::INIT_SPACE,
seeds = [b"counter", authority.key().as_ref()],
bump
)]
pub counter: Account<'info, Counter>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Update<'info> {
#[account(
mut,
has_one = authority
)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
#[derive(Accounts)]
pub struct Close<'info> {
#[account(
mut,
close = authority,
has_one = authority
)]
pub counter: Account<'info, Counter>,
#[account(mut)]
pub authority: Signer<'info>,
}
#[account]
#[derive(InitSpace)]
pub struct Counter {
pub authority: Pubkey,
pub count: u64,
}
#[error_code]
pub enum ErrorCode {
#[msg("Counter overflow")]
Overflow,
#[msg("Counter underflow")]
Underflow,
}
init_if_needed
For accounts that might already exist, use init_if_needed:
#[account(
init_if_needed,
payer = user,
space = 8 + MyAccount::INIT_SPACE
)]
pub my_account: Account<'info, MyAccount>,
Be careful with init_if_needed as it can introduce security vulnerabilities if not used properly. Always validate the account state after initialization.
Best Practices
- Use InitSpace: Leverage
#[derive(InitSpace)] for automatic space calculation
- Validate ownership: Always use
has_one or other constraints to verify account relationships
- Check arithmetic: Use checked math operations to prevent overflows
- Close unused accounts: Reclaim rent by closing accounts when done
- Use PDAs for deterministic addresses: Prefer PDAs over keypair-based accounts
- Minimize account size: Only store necessary data to reduce rent costs