# Anchor Framework Reference This reference covers Anchor-specific features and patterns. For general Solana concepts (accounts, PDAs, CPIs, etc.), see the other reference files in this directory. ## Table of Contents - [Installation and Setup](#installation-and-setup) - [Anchor Macros](#anchor-macros) - [Program Structure](#program-structure) - [Account Validation Constraints](#account-validation-constraints) - [IDL (Interface Description Language)](#idl-interface-description-language) - [TypeScript Client](#typescript-client) - [Rust Client](#rust-client) - [Anchor CLI Commands](#anchor-cli-commands) - [Token Integration (anchor-spl)](#token-integration-anchor-spl) - [Testing with Anchor](#testing-with-anchor) - [Anchor Features](#anchor-features) - [Common Patterns](#common-patterns) - [Error Handling](#error-handling) --- ## Installation and Setup ### Quick Install (Mac/Linux) ```bash # Install all dependencies (Rust, Solana CLI, Anchor) curl --proto '=https' --tlsv1.2 -sSfL https://solana-install.solana.workers.dev | bash ``` ### Install Anchor with AVM Anchor Version Manager (avm) manages multiple Anchor CLI versions: ```bash # Install AVM cargo install --git https://github.com/coral-xyz/anchor avm --force # Install latest Anchor avm install latest avm use latest # Install specific version avm install 0.32.1 avm use 0.32.1 # Install from commit hash avm install 0.30.1-cfe82aa682138f7c6c58bf7a78f48f7d63e9e466 avm use 0.30.1-cfe82aa ``` ### Verify Installation ```bash anchor --version # Should output: anchor-cli 0.32.1 solana --version # Recommended: solana-cli 2.3.0+ rustc --version # Required: 1.89.0+ for IDL builds ``` ### Solana Playground (No Install) Develop in browser at https://beta.solpg.io/ --- ## Anchor Macros ### Core Macros Overview 1. **`declare_id!`** - Declares the program's on-chain address 2. **`#[program]`** - Defines the program module containing instructions 3. **`#[derive(Accounts)]`** - Defines account validation structs 4. **`#[account]`** - Defines custom account types 5. **`#[error_code]`** - Defines custom error enums 6. **`#[event]`** - Defines event structs for logging ### declare_id! Macro ```rust use anchor_lang::prelude::*; // Program ID from /target/deploy/program_name.json declare_id!("11111111111111111111111111111111"); ``` Sync program IDs after building: ```bash anchor keys sync ``` ### #[program] Macro Marks the module containing instruction handlers: ```rust #[program] pub mod my_program { use super::*; pub fn initialize(ctx: Context, data: u64) -> Result<()> { ctx.accounts.new_account.data = data; msg!("Data set to: {}!", data); Ok(()) } pub fn update(ctx: Context, new_data: u64) -> Result<()> { ctx.accounts.account.data = new_data; Ok(()) } } ``` **Context provides:** - `ctx.accounts` - Validated accounts (type T) - `ctx.program_id` - Current program's ID - `ctx.remaining_accounts` - Additional accounts not in struct - `ctx.bumps` - PDA bump seeds (struct with fields matching PDA account names) ### #[derive(Accounts)] Macro Defines account validation structs: ```rust #[derive(Accounts)] pub struct Initialize<'info> { #[account(init, payer = signer, space = 8 + 8)] pub new_account: Account<'info, NewAccount>, #[account(mut)] pub signer: Signer<'info>, pub system_program: Program<'info, System>, } ``` **Validation happens in two ways:** 1. **Account Types** - Signer, Account<'info, T>, Program<'info, T>, etc. 2. **Account Constraints** - `#[account(...)]` attribute constraints ### #[account] Macro Defines custom account data structures: ```rust #[account] pub struct NewAccount { pub data: u64, // 8 bytes pub owner: Pubkey, // 32 bytes pub bump: u8, // 1 byte } ``` **Automatically implements:** - Account discriminator (first 8 bytes) - Serialization/deserialization (Borsh) - Owner validation (owned by program) **Account discriminator** = first 8 bytes of SHA256(`"account:NewAccount"`) ### #[error_code] Macro Defines custom program errors: ```rust #[error_code] pub enum ErrorCode { #[msg("Amount must be greater than zero")] InvalidAmount, #[msg("Insufficient funds")] InsufficientFunds, } ``` Usage: ```rust require!(amount > 0, ErrorCode::InvalidAmount); ``` ### #[event] Macro Defines event structs for logging: ```rust #[event] pub struct TransferEvent { pub from: Pubkey, pub to: Pubkey, pub amount: u64, } // Emit via program logs pub fn transfer(ctx: Context, amount: u64) -> Result<()> { emit!(TransferEvent { from: ctx.accounts.from.key(), to: ctx.accounts.to.key(), amount, }); Ok(()) } ``` --- ## Program Structure ### Complete Example ```rust use anchor_lang::prelude::*; declare_id!("YourProgramIdHere11111111111111111111111"); #[program] mod my_program { use super::*; pub fn initialize(ctx: Context, data: u64) -> Result<()> { ctx.accounts.new_account.data = data; ctx.accounts.new_account.authority = ctx.accounts.authority.key(); Ok(()) } pub fn update(ctx: Context, new_data: u64) -> Result<()> { ctx.accounts.account.data = new_data; Ok(()) } } #[derive(Accounts)] pub struct Initialize<'info> { #[account(init, payer = authority, space = 8 + 8 + 32)] pub new_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 account: Account<'info, MyAccount>, pub authority: Signer<'info>, } #[account] pub struct MyAccount { pub data: u64, pub authority: Pubkey, } ``` ### Space Calculation Use `InitSpace` derive macro: ```rust #[account] #[derive(InitSpace)] pub struct MyAccount { pub data: u64, // 8 bytes #[max_len(50)] pub name: String, // 4 + 50 bytes pub authority: Pubkey, // 32 bytes } // INIT_SPACE = 8 + 4 + 50 + 32 = 94 bytes #[account(init, payer = payer, space = 8 + MyAccount::INIT_SPACE)] pub account: Account<'info, MyAccount>, ``` **Space = 8 (discriminator) + account data size** --- ## Account Validation Constraints ### Common Constraints #### init - Create New Account ```rust #[account( init, payer = payer, space = 8 + 8 )] pub new_account: Account<'info, Counter>, ``` #### init_if_needed - Create if Doesn't Exist ```rust #[account( init_if_needed, payer = payer, space = 8 + 8 )] pub account: Account<'info, Counter>, ``` Requires `init-if-needed` feature in Cargo.toml: ```toml [dependencies] anchor-lang = { version = "0.32.1", features = ["init-if-needed"] } ``` #### mut - Mutable Account ```rust #[account(mut)] pub account: Account<'info, Counter>, ``` #### signer - Requires Signature ```rust #[account(signer)] pub authority: AccountInfo<'info>, // Or use Signer<'info> type pub authority: Signer<'info>, ``` #### close - Close Account ```rust #[account( mut, close = receiver // Send lamports to receiver )] pub account_to_close: Account<'info, MyAccount>, #[account(mut)] pub receiver: SystemAccount<'info>, ``` ### PDA Constraints #### seeds + bump - Validate PDA ```rust #[account( seeds = [b"vault", authority.key().as_ref()], bump )] pub vault: Account<'info, Vault>, ``` Access bump in instruction: ```rust pub fn initialize(ctx: Context) -> Result<()> { let bump = ctx.bumps.vault; ctx.accounts.vault.bump = bump; Ok(()) } ``` #### seeds + bump + init - Create PDA Account ```rust #[account( init, payer = payer, space = 8 + 32 + 1, seeds = [b"vault", authority.key().as_ref()], bump )] pub vault: Account<'info, Vault>, ``` ### Validation Constraints #### has_one - Field Matches Account ```rust #[account( mut, has_one = authority // Checks account.authority == authority.key() )] pub account: Account<'info, MyAccount>, pub authority: Signer<'info>, ``` #### address - Matches Specific Address ```rust #[account(address = admin_pubkey)] pub admin: Signer<'info>, ``` #### owner - Validates Owner Program ```rust #[account(owner = token::ID)] pub token_account: AccountInfo<'info>, ``` #### constraint - Custom Validation ```rust #[account( constraint = account.data > 0 @ ErrorCode::InvalidData )] pub account: Account<'info, MyAccount>, ``` ### Token Constraints #### mint - Create/Validate Mint ```rust use anchor_spl::token_interface::{Mint, TokenInterface}; #[account( init, payer = payer, mint::decimals = 6, mint::authority = mint_authority, mint::freeze_authority = mint_authority, )] pub mint: InterfaceAccount<'info, Mint>, pub token_program: Interface<'info, TokenInterface>, ``` #### token - Create/Validate Token Account ```rust use anchor_spl::token_interface::{TokenAccount, TokenInterface}; #[account( init, payer = payer, token::mint = mint, token::authority = owner, token::token_program = token_program, seeds = [b"vault"], bump )] pub vault: InterfaceAccount<'info, TokenAccount>, ``` #### associated_token - Create/Validate ATA ```rust use anchor_spl::{ associated_token::AssociatedToken, token_interface::{Mint, TokenAccount, TokenInterface}, }; #[account( init, payer = payer, associated_token::mint = mint, associated_token::authority = owner, associated_token::token_program = token_program, )] pub token_account: InterfaceAccount<'info, TokenAccount>, pub mint: InterfaceAccount<'info, Mint>, pub owner: SystemAccount<'info>, pub token_program: Interface<'info, TokenInterface>, pub associated_token_program: Program<'info, AssociatedToken>, pub system_program: Program<'info, System>, ``` --- ## IDL (Interface Description Language) ### What is the IDL? The IDL is a JSON file describing your program's interface: - Instructions (name, accounts, arguments) - Account types (structs) - Custom types (enums, type aliases) - Events - Errors - Discriminators ### IDL Generation Enable IDL build feature in `Cargo.toml`: ```toml [features] idl-build = ["anchor-lang/idl-build"] ``` Build program and generate IDL: ```bash anchor build # Builds program + IDL anchor idl build # Only builds IDL ``` IDL output location: `target/idl/.json` ### IDL Structure Example ```json { "address": "8HupNBr7SBhBLcBsLhbtes3tCarBm6Bvpqp5AfVjHuj8", "metadata": { "name": "example", "version": "0.1.0", "spec": "0.1.0" }, "instructions": [ { "name": "initialize", "discriminator": [175, 175, 109, 31, 13, 152, 155, 237], "accounts": [ { "name": "new_account", "writable": true, "signer": true }, { "name": "signer", "writable": true, "signer": true }, { "name": "system_program", "address": "11111111111111111111111111111111" } ], "args": [ { "name": "data", "type": "u64" } ] } ], "accounts": [ { "name": "NewAccount", "discriminator": [123, 45, 67, 89, 101, 112, 131, 145] } ], "types": [ { "name": "NewAccount", "type": { "kind": "struct", "fields": [ { "name": "data", "type": "u64" } ] } } ] } ``` ### Instruction Discriminator 8-byte identifier for each instruction: ``` discriminator = SHA256("global:initialize")[0..8] ``` Automatically handled by Anchor client. ### Account Discriminator 8-byte identifier for each account type: ``` discriminator = SHA256("account:NewAccount")[0..8] ``` Used for: - Account type verification on deserialization - Type safety checks ### IDL Deployment Deploy IDL on-chain: ```bash anchor deploy # Deploys program + IDL anchor deploy --no-idl # Deploy program only anchor idl init -f target/idl/program.json ``` Fetch IDL from chain: ```bash anchor idl fetch ``` --- ## TypeScript Client ### Installation ```bash npm install @coral-xyz/anchor @solana/web3.js # or yarn add @coral-xyz/anchor @solana/web3.js ``` **Note:** Only compatible with `@solana/web3.js` v1, not v2. ### Setup Program Instance #### With Wallet (Frontend) ```typescript import { Program, AnchorProvider, setProvider } from "@coral-xyz/anchor"; import { useAnchorWallet, useConnection } from "@solana/wallet-adapter-react"; import type { MyProgram } from "./types/my_program"; import idl from "./idl/my_program.json"; const { connection } = useConnection(); const wallet = useAnchorWallet(); const provider = new AnchorProvider(connection, wallet, {}); setProvider(provider); const program = new Program(idl as MyProgram, { connection }); ``` #### Without Wallet (Read-Only) ```typescript import { Connection, PublicKey } from "@solana/web3.js"; import { Program } from "@coral-xyz/anchor"; import idl from "./idl/my_program.json"; const connection = new Connection("https://api.devnet.solana.com"); const program = new Program(idl, { connection }); ``` ### Invoke Instructions #### Using .rpc() - Send Transaction ```typescript import { Keypair, SystemProgram } from "@solana/web3.js"; import BN from "bn.js"; const newAccountKp = new Keypair(); const data = new BN(42); const txSignature = await program.methods .initialize(data) .accounts({ newAccount: newAccountKp.publicKey, signer: wallet.publicKey, systemProgram: SystemProgram.programId, }) .signers([newAccountKp]) .rpc(); console.log("Transaction:", txSignature); ``` #### Using .instruction() - Build Instruction ```typescript const ix = await program.methods .initialize(data) .accounts({ newAccount: newAccountKp.publicKey, signer: wallet.publicKey, systemProgram: SystemProgram.programId, }) .instruction(); // Add to transaction const tx = new Transaction().add(ix); ``` #### Using .transaction() - Build Transaction ```typescript const tx = await program.methods .initialize(data) .accounts({ /* ... */ }) .transaction(); // Sign and send tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash; tx.sign(wallet, newAccountKp); const signature = await connection.sendRawTransaction(tx.serialize()); ``` ### Fetch Accounts #### Fetch Single Account ```typescript const accountData = await program.account.myAccount.fetch(accountPubkey); console.log("Data:", accountData.data.toString()); ``` #### Fetch All Accounts ```typescript const accounts = await program.account.myAccount.all(); accounts.forEach((account) => { console.log("Pubkey:", account.publicKey.toString()); console.log("Data:", account.account.data); }); ``` #### Fetch with Filters ```typescript const accounts = await program.account.myAccount.all([ { memcmp: { offset: 8, // After discriminator bytes: authority.toBase58(), }, }, ]); ``` ### Event Listeners ```typescript const listenerId = program.addEventListener( "TransferEvent", (event, slot) => { console.log("From:", event.from.toString()); console.log("To:", event.to.toString()); console.log("Amount:", event.amount.toString()); } ); // Remove listener program.removeEventListener(listenerId); ``` --- ## Rust Client ### Dependencies Add to `Cargo.toml`: ```toml [dependencies] anchor-client = { version = "0.32.1", features = ["async"] } anchor-lang = "0.32.1" solana-sdk = "2.3.0" tokio = { version = "1.0", features = ["full"] } ``` ### Generate Client with declare_program! Place IDL in `/idls/program_name.json`: ```rust use anchor_lang::prelude::*; declare_program!(example); use example::{ accounts::Counter, client::{accounts, args}, }; ``` ### Example Client ```rust use anchor_client::{ solana_client::rpc_client::RpcClient, solana_sdk::{ commitment_config::CommitmentConfig, signature::Keypair, signer::Signer, system_program, }, Client, Cluster, }; use std::rc::Rc; #[tokio::main] async fn main() -> anyhow::Result<()> { let connection = RpcClient::new_with_commitment( "http://127.0.0.1:8899", CommitmentConfig::confirmed(), ); let payer = Keypair::new(); let counter = Keypair::new(); // Create program client let provider = Client::new_with_options( Cluster::Localnet, Rc::new(payer), CommitmentConfig::confirmed(), ); let program = provider.program(example::ID)?; // Build instruction let ix = program .request() .accounts(accounts::Initialize { counter: counter.pubkey(), payer: program.payer(), system_program: system_program::ID, }) .args(args::Initialize) .instructions()? .remove(0); // Send transaction let signature = program .request() .instruction(ix) .signer(&counter) .send() .await?; println!("Transaction: {}", signature); // Fetch account let account: Counter = program.account::(counter.pubkey()).await?; println!("Count: {}", account.count); Ok(()) } ``` --- ## Anchor CLI Commands ### Project Commands ```bash # Initialize new project anchor init my-project anchor init my-project --test-template rust # Rust tests anchor init my-project --test-template mollusk # Mollusk tests # Create new program in workspace anchor new my-program ``` ### Build Commands ```bash # Build all programs anchor build # Build specific program anchor build --program-name my-program # Build without IDL generation anchor build --no-idl # Verifiable build (uses solana-verify) anchor build --verifiable ``` ### Deploy Commands ```bash # Deploy to cluster in Anchor.toml anchor deploy # Deploy specific program anchor deploy --program-name my-program # Deploy without IDL anchor deploy --no-idl # Deploy with additional program args anchor deploy -- --max-len 200000 ``` ### Test Commands ```bash # Build + deploy + test anchor test # Skip local validator (use running validator) anchor test --skip-local-validator # Test specific program anchor test --program-name my-program # Skip IDL build anchor test --no-idl ``` ### IDL Commands ```bash # Build IDL only anchor idl build # Initialize IDL on-chain anchor idl init -f target/idl/program.json # Fetch IDL from chain anchor idl fetch # Upgrade on-chain IDL anchor idl upgrade -f target/idl/program.json # Get IDL authority anchor idl authority # Set new IDL authority anchor idl set-authority --new-authority ``` ### Other Commands ```bash # Sync program IDs anchor keys sync # List program keypairs anchor keys list # Expand macros anchor expand anchor expand --program-name my-program # Verify deployed program anchor verify -p # Run migration script anchor migrate # Close deployed program (reclaim rent) solana program close ``` ### Local Validator ```bash # Start local validator solana-test-validator # Start with program loaded solana-test-validator --bpf-program target/deploy/program.so # Configure in Anchor.toml [test.validator] url = "https://api.devnet.solana.com" [[test.validator.clone]] address = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" # Clone USDC mint ``` --- ## Token Integration (anchor-spl) ### Dependencies ```toml [dependencies] anchor-spl = { version = "0.32.1", features = ["metadata"] } ``` ### Token Interface (Token + Token-2022) Use `token_interface` for compatibility with both Token Program and Token Extensions: ```rust use anchor_spl::token_interface::{ self, Mint, MintTo, TokenAccount, TokenInterface, TransferChecked }; ``` ### Create Mint ```rust #[derive(Accounts)] pub struct CreateMint<'info> { #[account(mut)] pub payer: Signer<'info>, #[account( init, payer = payer, mint::decimals = 6, mint::authority = mint_authority, )] pub mint: InterfaceAccount<'info, Mint>, /// CHECK: Mint authority pub mint_authority: UncheckedAccount<'info>, pub token_program: Interface<'info, TokenInterface>, pub system_program: Program<'info, System>, } ``` ### Create Token Account (ATA) ```rust use anchor_spl::associated_token::AssociatedToken; #[derive(Accounts)] pub struct CreateTokenAccount<'info> { #[account(mut)] pub payer: Signer<'info>, #[account( init, payer = payer, associated_token::mint = mint, associated_token::authority = owner, associated_token::token_program = token_program, )] pub token_account: InterfaceAccount<'info, TokenAccount>, pub mint: InterfaceAccount<'info, Mint>, pub owner: SystemAccount<'info>, pub token_program: Interface<'info, TokenInterface>, pub associated_token_program: Program<'info, AssociatedToken>, pub system_program: Program<'info, System>, } ``` ### Mint Tokens ```rust pub fn mint_tokens(ctx: Context, amount: u64) -> Result<()> { let cpi_accounts = MintTo { mint: ctx.accounts.mint.to_account_info(), to: ctx.accounts.token_account.to_account_info(), authority: ctx.accounts.authority.to_account_info(), }; let cpi_program = ctx.accounts.token_program.to_account_info(); let cpi_context = CpiContext::new(cpi_program, cpi_accounts); token_interface::mint_to(cpi_context, amount)?; Ok(()) } ``` ### Transfer Tokens ```rust pub fn transfer_tokens(ctx: Context, amount: u64) -> Result<()> { let decimals = ctx.accounts.mint.decimals; let cpi_accounts = TransferChecked { from: ctx.accounts.from.to_account_info(), mint: ctx.accounts.mint.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_context = CpiContext::new(cpi_program, cpi_accounts); token_interface::transfer_checked(cpi_context, amount, decimals)?; Ok(()) } ``` ### PDA as Token Authority ```rust #[derive(Accounts)] pub struct MintWithPDA<'info> { #[account(mut)] pub payer: Signer<'info>, #[account( init, payer = payer, mint::decimals = 6, mint::authority = mint, // PDA is authority seeds = [b"mint"], bump )] pub mint: InterfaceAccount<'info, Mint>, pub token_program: Interface<'info, TokenInterface>, pub system_program: Program<'info, System>, } pub fn mint_with_pda(ctx: Context, amount: u64) -> Result<()> { let seeds = &[b"mint".as_ref(), &[ctx.bumps.mint]]; let signer_seeds = &[&seeds[..]]; let cpi_accounts = MintTo { mint: ctx.accounts.mint.to_account_info(), to: ctx.accounts.token_account.to_account_info(), authority: ctx.accounts.mint.to_account_info(), }; let cpi_context = CpiContext::new( ctx.accounts.token_program.to_account_info(), cpi_accounts ).with_signer(signer_seeds); token_interface::mint_to(cpi_context, amount)?; Ok(()) } ``` --- ## Testing with Anchor ### TypeScript Tests (Default) Test file location: `tests/my-program.ts` ```typescript import * as anchor from "@coral-xyz/anchor"; import { Program } from "@coral-xyz/anchor"; import { MyProgram } from "../target/types/my_program"; import { expect } from "chai"; describe("my-program", () => { const provider = anchor.AnchorProvider.env(); anchor.setProvider(provider); const program = anchor.workspace.MyProgram as Program; it("Initializes account", async () => { const newAccount = anchor.web3.Keypair.generate(); await program.methods .initialize(new anchor.BN(42)) .accounts({ newAccount: newAccount.publicKey, signer: provider.wallet.publicKey, systemProgram: anchor.web3.SystemProgram.programId, }) .signers([newAccount]) .rpc(); const account = await program.account.myAccount.fetch( newAccount.publicKey ); expect(account.data.toNumber()).to.equal(42); }); }); ``` ### Rust Tests with LiteSVM Initialize project with Rust tests: ```bash anchor init my-project --test-template rust ``` Test file: `tests/src/test_initialize.rs` ```rust use anchor_client::anchor_lang::prelude::*; use anchor_client::anchor_lang::solana_program::system_program; use anchor_lang_lite_svm::LiteSVM; #[test] fn test_initialize() { let mut svm = LiteSVM::new(); let program_id = svm.deploy_program("target/deploy/my_program.so"); let payer = Keypair::new(); let counter = Keypair::new(); svm.airdrop(&payer.pubkey(), 10_000_000_000).unwrap(); let ix = my_program::instruction::Initialize { new_account: counter.pubkey(), signer: payer.pubkey(), system_program: system_program::ID, }; let tx = svm.send_transaction(vec![ix], &[&payer, &counter]).unwrap(); // Fetch and verify account let account_data = svm.get_account(&counter.pubkey()).unwrap(); // Verify data... } ``` ### Test Configuration (Anchor.toml) ```toml [test] # Startup timeout for validator startup_wait = 10000 [test.validator] # URL to clone accounts from url = "https://api.mainnet-beta.solana.com" # Clone accounts [[test.validator.clone]] address = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" # Load account from JSON [[test.validator.account]] address = "MyAccount111111111111111111111111111111111" filename = "tests/fixtures/my-account.json" # Set program as upgradeable [test.validator.upgradeable] my_program = true ``` --- ## Anchor Features ### Custom Errors ```rust #[error_code] pub enum ErrorCode { #[msg("Amount must be greater than zero")] InvalidAmount, #[msg("Authority mismatch")] Unauthorized, } // Usage require!(amount > 0, ErrorCode::InvalidAmount); require_keys_eq!(account.owner, authority.key(), ErrorCode::Unauthorized); ``` **Error macros:** - `require!(condition, error)` - Condition must be true - `require_eq!(a, b, error)` - Values must be equal - `require_neq!(a, b, error)` - Values must not be equal - `require_keys_eq!(a, b, error)` - Pubkeys must match - `require_keys_neq!(a, b, error)` - Pubkeys must not match - `require_gt!(a, b, error)` - a > b - `require_gte!(a, b, error)` - a >= b ### Events #### emit! (Program Logs) ```rust #[event] pub struct TransferEvent { pub from: Pubkey, pub to: Pubkey, pub amount: u64, } pub fn transfer(ctx: Context, amount: u64) -> Result<()> { emit!(TransferEvent { from: ctx.accounts.from.key(), to: ctx.accounts.to.key(), amount, }); Ok(()) } ``` #### emit_cpi! (CPI Data) Enable feature: ```toml [dependencies] anchor-lang = { version = "0.32.1", features = ["event-cpi"] } ``` Usage: ```rust #[event_cpi] #[derive(Accounts)] pub struct EmitEvent {} pub fn emit_event(ctx: Context, msg: String) -> Result<()> { emit_cpi!(CustomEvent { message: msg }); Ok(()) } ``` ### Zero-Copy Accounts For large accounts (>10KB): ```toml [dependencies] bytemuck = { version = "1.20.0", features = ["min_const_generics"] } ``` ```rust #[account(zero_copy)] pub struct LargeData { pub data: [u8; 10000], } #[derive(Accounts)] pub struct Initialize<'info> { #[account( init, payer = payer, space = 8 + std::mem::size_of::() )] pub large_account: AccountLoader<'info, LargeData>, #[account(mut)] pub payer: Signer<'info>, pub system_program: Program<'info, System>, } pub fn initialize(ctx: Context) -> Result<()> { let mut large_account = ctx.accounts.large_account.load_init()?; large_account.data[0] = 42; Ok(()) } pub fn update(ctx: Context) -> Result<()> { let mut large_account = ctx.accounts.large_account.load_mut()?; large_account.data[0] = 100; Ok(()) } ``` **For >10240 bytes**, use `zero` constraint + create account separately: ```rust #[account(zero)] // Instead of init pub large_account: AccountLoader<'info, LargeData>, ``` ### declare_program! (Dependency-Free CPI) Place IDL in `/idls/program_name.json`: ```rust declare_program!(example); use example::{ accounts::Counter, cpi::{self, accounts::Initialize}, program::Example, }; // CPI to other program pub fn call_example(ctx: Context) -> Result<()> { let cpi_ctx = CpiContext::new( ctx.accounts.example_program.to_account_info(), Initialize { counter: ctx.accounts.counter.to_account_info(), payer: ctx.accounts.payer.to_account_info(), system_program: ctx.accounts.system_program.to_account_info(), }, ); cpi::initialize(cpi_ctx)?; Ok(()) } ``` --- ## Common Patterns ### Store Bump Seed ```rust #[account] pub struct Vault { pub authority: Pubkey, pub bump: u8, } pub fn initialize(ctx: Context) -> Result<()> { ctx.accounts.vault.authority = ctx.accounts.authority.key(); ctx.accounts.vault.bump = ctx.bumps.vault; Ok(()) } // Use stored bump let seeds = &[ b"vault", ctx.accounts.vault.authority.as_ref(), &[ctx.accounts.vault.bump] ]; ``` ### Multi-Seed PDAs ```rust #[account( init, payer = payer, space = 8 + UserAccount::INIT_SPACE, seeds = [ b"user", user.key().as_ref(), &counter.to_le_bytes() ], bump )] pub user_account: Account<'info, UserAccount>, ``` ### CPI with PDA Signer ```rust pub fn transfer_with_pda(ctx: Context, amount: u64) -> Result<()> { let seeds = &[ b"vault", &[ctx.bumps.vault] ]; let signer_seeds = &[&seeds[..]]; let cpi_accounts = TransferChecked { from: ctx.accounts.from.to_account_info(), mint: ctx.accounts.mint.to_account_info(), to: ctx.accounts.to.to_account_info(), authority: ctx.accounts.vault.to_account_info(), }; let cpi_ctx = CpiContext::new( ctx.accounts.token_program.to_account_info(), cpi_accounts ).with_signer(signer_seeds); token_interface::transfer_checked(cpi_ctx, amount, decimals)?; Ok(()) } ``` ### Remaining Accounts ```rust pub fn process_multiple(ctx: Context) -> Result<()> { for account_info in ctx.remaining_accounts.iter() { let account = Account::::try_from(account_info)?; msg!("Processing: {}", account_info.key()); // Process account... } Ok(()) } #[derive(Accounts)] pub struct Process<'info> { pub authority: Signer<'info>, // Additional accounts in remaining_accounts } ``` ### Close Account Pattern ```rust #[derive(Accounts)] pub struct CloseAccount<'info> { #[account( mut, close = receiver, // Sends lamports to receiver has_one = authority )] pub account: Account<'info, MyAccount>, pub authority: Signer<'info>, #[account(mut)] /// CHECK: Receives lamports pub receiver: UncheckedAccount<'info>, } ``` ### Dynamic Account Space ```rust #[derive(Accounts)] #[instruction(name: String)] pub struct Create<'info> { #[account( init, payer = payer, space = 8 + 4 + name.len() + 8 )] pub item: Account<'info, Item>, #[account(mut)] pub payer: Signer<'info>, pub system_program: Program<'info, System>, } #[account] pub struct Item { pub name: String, pub count: u64, } ``` --- ## Error Handling ### Built-in Anchor Errors Anchor provides built-in errors in `ErrorCode` enum. Examples: - `ConstraintHasOne` - has_one constraint failed - `ConstraintSigner` - Account not a signer - `ConstraintMut` - Account not mutable - `ConstraintSeeds` - Seeds constraint failed - `AccountNotInitialized` - Account discriminator is zero ### Custom Error Implementation ```rust #[error_code] pub enum ErrorCode { #[msg("Amount must be greater than zero")] InvalidAmount, #[msg("Insufficient balance: required {}, available {}")] InsufficientBalance, #[msg("Unauthorized access")] Unauthorized, } pub fn validate_amount(ctx: Context, amount: u64) -> Result<()> { require!(amount > 0, ErrorCode::InvalidAmount); require!( ctx.accounts.account.balance >= amount, ErrorCode::InsufficientBalance ); require_keys_eq!( ctx.accounts.account.owner, ctx.accounts.authority.key(), ErrorCode::Unauthorized ); Ok(()) } ``` ### Error Numbers Anchor errors use this numbering: - `0-1000` - Internal Anchor errors - `1000-2000` - Reserved - `2000-3000` - Custom program errors (from #[error_code]) - `3000+` - Additional custom errors ### TypeScript Error Handling ```typescript try { await program.methods .transfer(amount) .accounts({ /* ... */ }) .rpc(); } catch (error) { if (error.code === 6000) { // Custom error code console.log("Custom error:", error.msg); } console.log("Error logs:", error.logs); } ``` --- ## Anchor.toml Configuration ```toml [toolchain] anchor_version = "0.32.1" solana_version = "2.3.0" [features] resolution = true # IDL account resolution seeds = false skip-lint = false [programs.localnet] my_program = "YourProgramIdHere11111111111111111111111" [programs.devnet] my_program = "YourProgramIdHere11111111111111111111111" [programs.mainnet] my_program = "YourProgramIdHere11111111111111111111111" [provider] cluster = "localnet" # or devnet, mainnet-beta wallet = "~/.config/solana/id.json" [scripts] test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" [test] startup_wait = 10000 [test.validator] url = "https://api.devnet.solana.com" [[test.validator.clone]] address = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" [test.validator.upgradeable] my_program = true [workspace] types = "app/src/idl/" members = ["programs/*"] package_manager = "yarn" # npm, yarn, pnpm, bun ``` --- ## Additional Resources - **Official Docs**: https://www.anchor-lang.com - **GitHub**: https://github.com/coral-xyz/anchor - **Examples**: https://github.com/coral-xyz/anchor/tree/master/tests - **Discord**: https://discord.gg/anchor For general Solana concepts (accounts, PDAs, CPIs, transactions, etc.), refer to other reference files in this directory.