Anchor
Introduction to Anchor

Introduction to Anchor

Instructions & CPIs

Instructions are the building blocks of Solana programs, defining the actions that can be performed. In Anchor, instructions are implemented as functions with specific attributes and constraints. Let's explore how to work with them effectively.

Instruction Structure

In Anchor, instructions are defined using the #[program] module and individual instruction functions. Here's the basic structure:

use anchor_lang::prelude::*;
 
#[program]
pub mod my_program {
    use super::*;
 
    pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
        // Instruction logic here
        Ok(())
    }
}

Instruction Context

Every instruction function receives a Context struct as its first parameter. This context contains:

  • accounts: The accounts passed to the instruction
  • program_id: The program's public key
  • remaining_accounts: Any additional accounts not explicitly defined in the context struct
  • bumps: The bumps field is particularly useful when working with PDAs, as it provides the bump seeds that were used to derive the PDA addresses (only if you're deriving them in the account struct)

That can be accessed by doing:

// Accessing accounts
ctx.accounts.account_1
ctx.accounts.account_2
 
// Accessing program ID
ctx.program_id
 
// Accessing remaining accounts
for remaining_account in ctx.remaining_accounts {
    // Process remaining account
}
 
// Accessing bumps for PDAs
let bump = ctx.bumps.pda_account;

Instruction Discriminator

Like accounts, instructions in Anchor use discriminators to identify different instruction types. The default discriminator is an 8-byte prefix generated using sha256("global:<instruction_name>")[0..8]. The instruction name should be in snake_case.

Anchor Discriminator Calculator
Instruction
sha256("global:" + snake_case(seed))[0..8]
[0, 0, 0, 0, 0, 0, 0, 0]

Custom Instruction Discriminator

You can also specify a custom discriminator for your instructions:

#[instruction(discriminator = 1)]
pub fn custom_discriminator(ctx: Context<Custom>) -> Result<()> {
    // Instruction logic
    Ok(())
}

Instruction Scaffold

You can write your instruction in different way, in this section we're going to teach some of the style and way you can actually set them up

Instruction Logic

Instruction logic can be organized in different ways, depending on your program's complexity and your preferred coding style. Here are the main approaches:

  1. Inline Instruction Logic

For simple instructions, you can write the logic directly in the instruction function:

pub fn initialize(ctx: Context<Transfer>, amount: u64) -> Result<()> {
    // Transfer tokens logic
 
    // Close token account logic
    
    Ok(())
}
  1. Separate Module Implementation

For very complex programs, you can organize the logic in separate modules:

// In a separate file: transfer.rs
pub fn execute(ctx: Context<Transfer>, amount: u64) -> Result<()> {
    // Transfer tokens logic
 
    // Close token account logic
 
    Ok(())
}
 
// In your lib.rs
pub fn transfer(ctx: Context<Transfer>, amount: u64) -> Result<()> {
    transfer::execute(ctx, amount)
}
  1. Separate Context Implementation

For more complex instructions, you can move the logic to the context struct's implementation:

// In a separate file: transfer.rs
pub fn handler(ctx: Context<Transfer>, amount: u64) -> Result<()> {
    ctx.accounts.transfer_tokens(amount)?;
    ctx.accounts.close_token_account()?;
 
    Ok(())
}
 
impl<'info> Transfer<'info> {
    /// Transfers tokens from source to destination account
    pub fn transfer_tokens(&mut self, amount: u64) -> Result<()> {
        // Transfer tokens logic
 
        Ok(())
    }
 
    /// Closes the source token account after transfer
    pub fn close_token_account(&mut self) -> Result<()> {
        // Close token account logic
 
    }
}
 
// In your lib.rs
pub fn transfer(ctx: Context<Transfer>, amount: u64) -> Result<()> {
    transfer::execute(ctx, amount)
}

Instruction Parameters

Instructions can accept parameters beyond the context. These parameters are serialized and deserialized automatically by Anchor. Here are the key points about instruction parameters:

  1. Basic Types

Anchor supports all Rust primitive types and common Solana types:

pub fn complex_instruction(
    ctx: Context<Complex>,
    amount: u64,
    pubkey: Pubkey,
    vec_data: Vec<u8>,
) -> Result<()> {
    // Instruction logic
    Ok(())
}
  1. Custom Types

You can use custom types as parameters, but they must implement AnchorSerialize and AnchorDeserialize:

#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct InstructionData {
    pub field1: u64,
    pub field2: String,
}
 
pub fn custom_type_instruction(
    ctx: Context<Custom>,
    data: InstructionData,
) -> Result<()> {
    // Instruction logic
    Ok(())
}

Best Practices

  1. Keep Instructions Focused: Each instruction should do one thing well. If an instruction is doing too much, consider splitting it into multiple instructions.

  2. Use Context Implementation: For complex instructions, use the context implementation approach to:

    • Keep your code organized
    • Make it easier to test
    • Improve reusability
    • Add proper documentation
  3. Error Handling: Always use proper error handling and return meaningful error messages:

#[error_code]
pub enum TransferError {
    #[msg("Insufficient balance")]
    InsufficientBalance,
    #[msg("Invalid amount")]
    InvalidAmount,
}
 
impl<'info> Transfer<'info> {
    pub fn transfer_tokens(&mut self, amount: u64) -> Result<()> {
        require!(amount > 0, TransferError::InvalidAmount);
        require!(
            self.source.amount >= amount,
            TransferError::InsufficientBalance
        );
 
        // Transfer logic
        Ok(())
    }
}
  1. Documentation: Always document your instruction logic, especially when using context implementation:
impl<'info> Transfer<'info> {
    /// # Transfers tokens
    /// 
    /// Transfers tokens from source to destination account
    pub fn transfer_tokens(&mut self, amount: u64) -> Result<()> {
        // Implementation
        Ok(())
    }
}

Cross-Program Invocations (CPIs)

Cross Program Invocations (CPI) refer to the process of one program invoking instructions of another program, which enables the composability of Solana programs. Anchor provides a convenient way to make CPIs through the CpiContext and program-specific builders.

Note: You can find all the System Program CPI by using the main anchor crate and doing: use anchor_lang::system_program::*; and for the one relative to the SPL token program we'll need to import the anchor_spl crate and do: use anchor_spl::token::*

Basic CPI Structure

Here's how to make a basic CPI:

use anchor_lang::solana_program::program::invoke_signed;
use anchor_lang::system_program::{transfer, Transfer};
 
pub fn transfer_lamport(ctx: Context<TransferLamport>, 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);
    
    transfer(cpi_ctx, amount)?;
    
    Ok(())
}

CPI with PDA Signers

When making CPIs that require PDA signatures, use CpiContext::new_with_signer:

use anchor_lang::solana_program::program::invoke_signed;
use anchor_lang::system_program::{transfer, Transfer};
 
pub fn transfer_lamport_with_pda(ctx: Context<TransferLamportWithPda>) -> Result<()> {
    let seeds = &[
        b"vault".as_ref(),
        &[ctx.bumps.vault],
    ];
    let signer = &[&seeds[..]];
    
    let cpi_accounts = Transfer {
        from: ctx.accounts.vault.to_account_info(),
        to: ctx.accounts.recipient.to_account_info(),
        authority: ctx.accounts.vault.to_account_info(),
    };
    
    let cpi_program = ctx.accounts.token_program.to_account_info();
    let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
    
    transfer(cpi_ctx, amount)?;
    
    Ok(())
}

Error Handling

Anchor provides a robust error handling system for instructions. Here's how to implement custom errors and handle them in your instructions:

#[error_code]
pub enum MyError {
    #[msg("Custom error message")]
    CustomError,
    #[msg("Another error with value: {0}")]
    ValueError(u64),
}
 
pub fn handle_errors(ctx: Context<HandleErrors>, value: u64) -> Result<()> {
    require!(value > 0, MyError::CustomError);
    require!(value < 100, MyError::ValueError(value));
    
    Ok(())
}
Contents
View Source
Blueshift © 2025Commit: cc5f933