Skip to main content

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

  1. Use InitSpace: Leverage #[derive(InitSpace)] for automatic space calculation
  2. Validate ownership: Always use has_one or other constraints to verify account relationships
  3. Check arithmetic: Use checked math operations to prevent overflows
  4. Close unused accounts: Reclaim rent by closing accounts when done
  5. Use PDAs for deterministic addresses: Prefer PDAs over keypair-based accounts
  6. Minimize account size: Only store necessary data to reduce rent costs