Skip to main content
Instruction handlers are the core functions that execute when your program receives an instruction. In Anchor, these handlers are defined as public functions within a module annotated with the #[program] attribute.

Basic Structure

Every instruction handler follows this pattern:
pub fn handler_name(ctx: Context<AccountsStruct>, ...args) -> Result<()> {
    // Instruction logic
    Ok(())
}
The first parameter is always a Context<T> type where T is a struct that defines the accounts required by the instruction.

The Context Type

The Context type provides access to non-argument inputs needed by the instruction:
pub struct Context<'a, 'b, 'c, 'info, T: Bumps> {
    /// Currently executing program id
    pub program_id: &'a Pubkey,
    /// Deserialized accounts
    pub accounts: &'b mut T,
    /// Remaining accounts given but not deserialized or validated
    pub remaining_accounts: &'c [AccountInfo<'info>],
    /// Bump seeds found during constraint validation
    pub bumps: T::Bumps,
}

Accessing Context Fields

program_id: The currently executing program’s address
pub fn check_program(ctx: Context<CheckProgram>) -> Result<()> {
    msg!("Program ID: {}", ctx.program_id);
    Ok(())
}
accounts: Access to the deserialized and validated accounts
pub fn update_data(ctx: Context<UpdateData>, new_value: u64) -> Result<()> {
    ctx.accounts.my_account.data = new_value;
    Ok(())
}
remaining_accounts: Accounts passed to the instruction but not specified in the Accounts struct
pub fn process_remaining(ctx: Context<ProcessData>) -> Result<()> {
    for account in ctx.remaining_accounts.iter() {
        msg!("Remaining account: {}", account.key);
    }
    Ok(())
}
bumps: PDA bump seeds automatically discovered during validation
pub fn use_bump(ctx: Context<UsePda>) -> Result<()> {
    let bump = ctx.bumps.my_pda;
    msg!("PDA bump: {}", bump);
    Ok(())
}

Complete Example

Here’s a complete example demonstrating instruction handlers with various parameter types:
use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

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

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

    pub fn update(ctx: Context<Update>, new_value: u64) -> Result<()> {
        let account = &mut ctx.accounts.my_account;
        require!(new_value > account.data, ErrorCode::InvalidValue);
        account.data = new_value;
        msg!("Updated to: {}", new_value);
        Ok(())
    }

    pub fn close_account(ctx: Context<CloseAccount>) -> Result<()> {
        msg!("Closing account: {}", ctx.accounts.my_account.key());
        Ok(())
    }
}

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

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

#[derive(Accounts)]
pub struct CloseAccount<'info> {
    #[account(
        mut,
        has_one = authority,
        close = authority
    )]
    pub my_account: Account<'info, MyAccount>,
    #[account(mut)]
    pub authority: Signer<'info>,
}

#[account]
#[derive(InitSpace)]
pub struct MyAccount {
    pub data: u64,
    pub authority: Pubkey,
}

#[error_code]
pub enum ErrorCode {
    #[msg("New value must be greater than current value")]
    InvalidValue,
}

Instruction Arguments

You can pass additional arguments to instruction handlers after the Context parameter:
pub fn transfer(
    ctx: Context<Transfer>,
    amount: u64,
    memo: String,
) -> Result<()> {
    // Use amount and memo
    msg!("Transferring {} with memo: {}", amount, memo);
    Ok(())
}

Supported Argument Types

  • Primitive types: u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, bool
  • String and Vec<T>
  • Pubkey
  • Custom structs that implement AnchorSerialize and AnchorDeserialize

Return Values

All instruction handlers must return Result<()>. To return an error, use the err! macro or require! macro:
pub fn validate(ctx: Context<Validate>, value: u64) -> Result<()> {
    require!(value <= 100, ErrorCode::ValueTooLarge);
    Ok(())
}

Best Practices

  1. Keep handlers focused: Each instruction should have a single, clear purpose
  2. Validate early: Perform validation checks at the start of the handler
  3. Use descriptive names: Choose clear, action-oriented names for your handlers
  4. Log important events: Use msg! to log important state changes
  5. Handle errors gracefully: Use custom errors to provide clear feedback

CPI Context

For cross-program invocations, use CpiContext instead of Context:
pub fn do_cpi(ctx: Context<DoCpi>, amount: u64) -> Result<()> {
    let cpi_accounts = Transfer {
        from: ctx.accounts.from.to_account_info(),
        to: ctx.accounts.to.to_account_info(),
        authority: ctx.accounts.authority.to_account_info(),
    };
    let cpi_program = ctx.accounts.token_program.to_account_info();
    let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
    
    token::transfer(cpi_ctx, amount)?;
    Ok(())
}
For CPIs with PDA signers:
pub fn do_cpi_with_signer(ctx: Context<DoCpiWithSigner>) -> Result<()> {
    let bump = ctx.bumps.pda_authority;
    let seeds = &[
        b"authority",
        &[bump],
    ];
    let signer_seeds = &[&seeds[..]];
    
    let cpi_ctx = CpiContext::new_with_signer(
        ctx.accounts.token_program.to_account_info(),
        cpi_accounts,
        signer_seeds,
    );
    
    token::transfer(cpi_ctx, amount)?;
    Ok(())
}

Build docs developers (and LLMs) love