Skip to main content
In Solana, all state is stored in accounts. Anchor provides strongly-typed account wrappers that automatically handle validation and deserialization.

Account types

Anchor provides several account types that handle common validation patterns:

Account<‘info, T>

The most common account type that wraps a custom account type with automatic validation.
#[derive(Accounts)]
pub struct UpdateData&lt;'info&gt; {
    #[account(mut)]
    pub my_account: Account<'info, MyAccount>,
    pub authority: Signer&lt;'info&gt;,
}

#[account]
pub struct MyAccount {
    pub data: u64,
    pub authority: Pubkey,
}
The Account&lt;'info, T&gt; type automatically:
  • Deserializes the account data into type T
  • Checks the account owner matches the program ID
  • Validates the account discriminator matches type T

Signer<‘info>

Verifies that an account signed the transaction.
#[derive(Accounts)]
pub struct Initialize&lt;'info&gt; {
    #[account(mut)]
    pub authority: Signer&lt;'info&gt;,
    pub system_program: Program<'info, System>,
}
The Signer type automatically checks that authority.is_signer is true.

SystemAccount<‘info>

Represents a native Solana account owned by the System Program.
#[derive(Accounts)]
pub struct TransferSol&lt;'info&gt; {
    #[account(mut)]
    pub from: SystemAccount&lt;'info&gt;,
    #[account(mut)]
    pub to: SystemAccount&lt;'info&gt;,
}

Program type

Verifies that an account is an executable program.
#[derive(Accounts)]
pub struct Initialize&lt;'info&gt; {
    pub system_program: Program<'info, System>,
    pub token_program: Program<'info, Token>,
}
The Program type checks that the account is executable.

UncheckedAccount<‘info>

Raw AccountInfo with no automatic checks. Use with caution.
#[derive(Accounts)]
pub struct Dangerous&lt;'info&gt; {
    /// CHECK: This is not dangerous because we don't read or write from this account
    pub unchecked: UncheckedAccount&lt;'info&gt;,
}
Always add a /// CHECK: doc comment explaining why the account doesn’t need checks.

Interface types

For Token-2022 compatibility, use interface types:
use anchor_spl::token_interface::{TokenAccount, Mint, TokenInterface};

#[derive(Accounts)]
pub struct Transfer&lt;'info&gt; {
    #[account(mut)]
    pub from: InterfaceAccount<'info, TokenAccount>,
    #[account(mut)]
    pub to: InterfaceAccount<'info, TokenAccount>,
    pub token_program: Interface<'info, TokenInterface>,
}

Account validation

Anchor provides constraints for automatic account validation:

Initialization constraints

#[derive(Accounts)]
pub struct Initialize&lt;'info&gt; {
    #[account(
        init,
        payer = user,
        space = 8 + 32 + 8
    )]
    pub my_account: Account<'info, MyAccount>,
    #[account(mut)]
    pub user: Signer&lt;'info&gt;,
    pub system_program: Program<'info, System>,
}

Mutable accounts

#[account(mut)]
pub my_account: Account<'info, MyAccount>,

Field validation

#[derive(Accounts)]
pub struct Update&lt;'info&gt; {
    #[account(
        mut,
        has_one = authority,
        constraint = my_account.count < 100
    )]
    pub my_account: Account<'info, MyAccount>,
    pub authority: Signer&lt;'info&gt;,
}
The has_one = authority constraint checks that my_account.authority == authority.key().

PDA validation

#[derive(Accounts)]
pub struct Initialize&lt;'info&gt; {
    #[account(
        init,
        payer = user,
        space = 8 + 32,
        seeds = [b"vault", user.key().as_ref()],
        bump
    )]
    pub vault: Account<'info, Vault>,
    #[account(mut)]
    pub user: Signer&lt;'info&gt;,
    pub system_program: Program<'info, System>,
}

Accessing account data

Access account fields directly through the validated account:
pub fn update(ctx: Context<Update>, new_value: u64) -> Result<()> {
    let account = &mut ctx.accounts.my_account;
    account.data = new_value;
    account.authority = ctx.accounts.authority.key();
    Ok(())
}

Account lifetime

The 'info lifetime indicates that account references are valid for the duration of the instruction:
#[derive(Accounts)]
pub struct MyAccounts&lt;'info&gt; {
    pub account: Account<'info, MyAccount>,
}
This ensures accounts can’t be used after the instruction completes.

Complete example

use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

#[program]
pub mod accounts_example {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
        let account = &mut ctx.accounts.my_account;
        account.data = data;
        account.authority = ctx.accounts.authority.key();
        msg!("Account initialized with data: {}", data);
        Ok(())
    }

    pub fn update(ctx: Context<Update>, new_data: u64) -> Result<()> {
        let account = &mut ctx.accounts.my_account;
        require!(
            account.authority == ctx.accounts.authority.key(),
            ErrorCode::Unauthorized
        );
        account.data = new_data;
        msg!("Account updated to: {}", new_data);
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize&lt;'info&gt; {
    #[account(
        init,
        payer = authority,
        space = 8 + 8 + 32
    )]
    pub my_account: Account<'info, MyAccount>,
    #[account(mut)]
    pub authority: Signer&lt;'info&gt;,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Update&lt;'info&gt; {
    #[account(
        mut,
        has_one = authority
    )]
    pub my_account: Account<'info, MyAccount>,
    pub authority: Signer&lt;'info&gt;,
}

#[account]
pub struct MyAccount {
    pub data: u64,
    pub authority: Pubkey,
}

#[error_code]
pub enum ErrorCode {
    #[msg("You are not authorized to perform this action")]
    Unauthorized,
}

Next steps

Account constraints

Complete reference of all account constraints

PDAs

Learn about Program Derived Addresses

Build docs developers (and LLMs) love