Quick Start
Anchor Framework

Anchor Framework

  • anchor framework
  • program/testing
  • pda
  • cpi

Anchor Program Structure

Anchor uses Rust macros to reduce boilerplate code and simplify the application of common security checks associated with writing Solana programs. The main macros include:

  • declare_id: Specifies the program’s on-chain address
  • #[program]: Specifies the module containing the program’s instruction logic
  • #[derive(Accounts)]: Applied to structs to indicate a list of accounts required for an instruction
  • #[account]: Applied to structs to create custom account types specific to the program

Below is a simple Anchor program with a single instruction that creates a new account. We’ll walk through it to explain the basic structure of an Anchor program.

lib.rs
use anchor_lang::prelude::*;
 
declare_id!("11111111111111111111111111111111");
 
#[program]
mod hello_anchor {
    use super::*;
    pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
        ctx.accounts.new_account.data = data;
        msg!("Changed data to: {}!", data);
        Ok(())
    }
}
 
#[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>,
}
 
#[account]
pub struct NewAccount {
    data: u64,
}

declare_id!()

The declare_id macro is used to specify the on-chain address of the program (i.e. the program ID). When you build an Anchor program for the first time, the framework will generate a new keypair. This becomes the default keypair used to deploy the program unless specified otherwise. The public key from this keypair should be used as the program ID in the declare_id macro.

lib.rs
use anchor_lang::prelude::*;
 
declare_id!("11111111111111111111111111111111");

You can update the value in declare_id with the generated program ID using the following command:

Terminal
anchor keys sync

#[program]

The #[program] macro specifies the module containing all of your program’s instructions. Each public function in the module represents a separate instruction for the program.

In every function, the initial parameter is always of the Context type. Subsequent parameters, which are optional, define instruction data required by the instruction.

lib.rs
use anchor_lang::prelude::*;
 
declare_id!("11111111111111111111111111111111");
 
#[program]
mod hello_anchor {
    use super::*;
    pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
        ctx.accounts.new_account.data = data;
        msg!("Changed data to: {}!", data);
        Ok(())
    }
}
 
#[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>,
}
 
#[account]
pub struct NewAccount {
    data: u64,
}

The Context type provides the instruction with access to the following information:

pub struct Context<'a, 'b, 'c, 'info, T> {
    /// Currently executing program id.
    pub program_id: &'a Pubkey,
    /// Deserialized accounts.
    pub accounts: &'b mut T,
    /// Remaining accounts given but not deserialized or validated.
    /// Be very careful when using this directly.
    pub remaining_accounts: &'c [AccountInfo<'info>],
    /// Bump seeds found during constraint validation. This is provided as a
    /// convenience so that handlers don't have to recalculate bump seeds or
    /// pass them in as arguments.
    pub bumps: BTreeMap<String, u8>,
}

Context is a generic type where T represents the set of accounts required by an instruction. When defining the instruction’s Context, the T type is a struct that implements to the Accounts trait (e.g. Context<Initialize>).

This context parameter allows the instruction to access:

  • ctx.accounts: The instruction’s accounts
  • ctx.program_id: The executing program’s ID
  • ctx.remaining_accounts: All remaining accounts provided to the instruction but not specified in the Accounts struct
  • ctx.bumps: Bump seeds for any Program Derived Address (PDA) accounts specified in the Accounts struct

#[derive(Accounts)]

The #[derive(Accounts)] macro is used to annotate a struct which specifies a set of accounts required for a particular instruction.

#[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>,
}

Each field in the struct represents an account that must be provided by the client. The naming of each field is arbitrary, but it is recommended to use a descriptive name that indicates the purpose of the account.

#[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>,
}

When building Solana programs, it’s essential to validate the accounts provided by the client for security reasons. This validation is achieved in Anchor through account constraints and specifying appropriate account types:

  • Account Constraints: Constraints define additional conditions that an account must satisfy to be considered valid for the instruction. Constraints are applied using the #[account(..)] attribute, which is placed above an account field in the Accounts struct.
  • Account Types: Anchor provides various account types to help ensure that the account provided by the client matches with what the program expects.

For more detailed examples, refer to the constraints and account types sections in the documentation.

#[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>,
}

Accounts within the Accounts struct are accessible in an instruction through the Context, using the ctx.accounts syntax.

lib.rs
use anchor_lang::prelude::*;
 
declare_id!("11111111111111111111111111111111");
 
#[program]
mod hello_anchor {
    use super::*;
    pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
        ctx.accounts.new_account.data = data;
        msg!("Changed data to: {}!", data);
        Ok(())
    }
}
 
#[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>,
}
 
#[account]
pub struct NewAccount {
    data: u64,
}

When an instruction in an Anchor program is called, the program performs the following checks:

  • Account Type Verification: It verifies that the accounts passed into the instruction correspond to the account types defined in the Accounts struct in the Context.

  • Constraint Checks: It checks the accounts against any additional constraints specified.

This helps ensure that the accounts provided by the client are valid. If any checks fail, then the instruction fails with an error before reaching the main logic of the function.

#[account]

The #[account] macro is applied to structs defining the format of a custom data account type within a program. Each field in the struct represents a field that will be stored in the account data.

#[account]
pub struct NewAccount {
    data: u64,
}

This macro implements various traits detailed here. The key functionalities of the #[account] macro include:

  • Assign Ownership: When creating an account, the ownership of the account is automatically assigned to the program specified in the declare_id.
  • Set Discriminator: A unique 8-byte discriminator, specific to the account type, is appended to the account data during its initialization. This helps in differentiating account types and account validation.
  • Data Serialization and Deserialization: The account data corresponding to the account type is automatically serialized and deserialized.
lib.rs
use anchor_lang::prelude::*;
 
declare_id!("11111111111111111111111111111111");
 
#[program]
mod hello_anchor {
    use super::*;
    pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
        ctx.accounts.new_account.data = data;
        msg!("Changed data to: {}!", data);
        Ok(())
    }
}
 
#[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>,
}
 
#[account]
pub struct NewAccount {
    data: u64,
}

In Anchor, an account discriminator is an 8-byte identifier, unique to each account type. This identifier is derived from the first 8 bytes of the SHA256 hash of the account type’s name. The first 8 bytes in an account’s data are specifically reserved for this discriminator.

The discriminator is used during the following two scenarios:

  • Initialization: During the initialization of an account, the discriminator is set with the account type’s discriminator.
  • Deserialization: When account data is deserialized, the discriminator within the data is checked against the expected discriminator of the account type.

If there’s a mismatch, it indicates that the client has provided an unexpected account. This mechanism serves as an account validation check in Anchor programs, ensuring the correct and expected accounts are used.

Anchor Typescript Client

Anchor provides a Typescript client library (@coral-xyz/anchor) that simplifies the process of interacting with Solana programs from the client. To use the client library, you first need to set up an instance of the Program object using the IDL file generated by Anchor.

Anchor IDL

When an Anchor program is built, Anchor generates an interface description language (IDL) file representing the structure of the program. This IDL file provides a standardized format for building program instructions and fetching program accounts.

Below is an example of how an IDL file relates to the program and client code.

IDL
{
  "version": "0.1.0",
  "name": "hello_anchor",
  "instructions": [
      {
        "name": "initialize",
        "accounts": [
            { "name": "newAccount", "isMut": true, "isSigner": true },
            { "name": "signer", "isMut": true, "isSigner": true },
            { "name": "systemProgram", "isMut": false, "isSigner": false }
        ],
        "args": [{ "name": "data", "type": "u64" }]
      }
  ],
  "accounts": [
      {
        "name": "NewAccount",
        "type": {
            "kind": "struct",
            "fields": [{ "name": "data", "type": "u64" }]
        }
      }
  ]
}
lib.rs
use anchor_lang::prelude::*;
 
declare_id!("11111111111111111111111111111111");
 
#[program]
mod hello_anchor {
    use super::*;
    pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
        ctx.accounts.new_account.data = data;
        msg!("Changed data to: {}!", data);
        Ok(())
    }
}
 
#[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>,
}
 
#[account]
pub struct NewAccount {
    data: u64,
}
anchor.test.ts
import * as anchor from "@coral-xyz/anchor";
import { Program, BN } from "@coral-xyz/anchor";
import { HelloAnchor } from "../target/types/hello_anchor";
import { Keypair, SystemProgram } from "@solana/web3.js";
import assert from "assert";
 
describe("hello_anchor", () => {
  const provider = anchor.AnchorProvider.env();
  anchor.setProvider(provider);
  const wallet = provider.wallet as anchor.Wallet;
  const connection = provider.connection;
 
  const program = anchor.workspace.HelloAnchor as Program<HelloAnchor>;
 
  it("initialize", async () => {
      // Generate keypair for the new account
      const newAccountKp = new Keypair();
 
      // Send transaction
      const data = new BN(42);
      const txHash = await program.methods
        .initialize(data)
        .accounts({
            newAccount: newAccountKp.publicKey,
            signer: wallet.publicKey,
            systemProgram: SystemProgram.programId,
        })
        .signers([newAccountKp])
        .rpc();
      console.log(`Use 'solana confirm -v ${txHash}' to see the logs`);
 
      // Confirm transaction
      await connection.confirmTransaction(txHash);
 
      // Fetch the created account
      const newAccount = await program.account.newAccount.fetch(
        newAccountKp.publicKey
      );
 
      console.log("On-chain data is:", newAccount.data.toString());
 
      assert(data.eq(newAccount.data));
  });
});

Anchor Client Program

Creating an instance of the Program requires the program’s IDL, its on-chain address (programId), and an AnchorProvider. An AnchorProvider combines two things:

  • Connection - the connection to a Solana cluster (i.e. localhost, devnet, mainnet)
  • Wallet - (optional) a default wallet used to pay and sign transactions

When building an Anchor program locally, the setup for creating an instance of the Program is done automatically in the test file. The IDL file can be found in the target folder. Note that this workspace setup is slightly different from the setup when integrating with a frontend using the wallet adapter.

import * as anchor from "@coral-xyz/anchor";
import { Program, BN } from "@coral-xyz/anchor";
import { HelloAnchor } from "../target/types/hello_anchor";
 
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.HelloAnchor as Program<HelloAnchor>;

When integrating with a frontend using the wallet adapter, you’ll need to manually set up the AnchorProvider and Program.

import { Program, Idl, AnchorProvider, setProvider } from "@coral-xyz/anchor";
import { useAnchorWallet, useConnection } from "@solana/wallet-adapter-react";
import { IDL, HelloAnchor } from "./idl";
 
const { connection } = useConnection();
const wallet = useAnchorWallet();
 
const provider = new AnchorProvider(connection, wallet, {});
setProvider(provider);
 
const programId = new PublicKey("...");
const program = new Program<HelloAnchor>(IDL, programId);

Alternatively, you can create an instance of the Program using only the IDL and the Connection to a Solana cluster. This means there is no default Wallet, but allows you to use the Program to fetch accounts before a wallet is connected.

import { Program } from "@coral-xyz/anchor";
import { clusterApiUrl, Connection, PublicKey } from "@solana/web3.js";
import { IDL, HelloAnchor } from "./idl";
 
const programId = new PublicKey("...");
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
 
const program = new Program<HelloAnchor>(IDL, programId, {
  connection,
});

Invoke Program Instructions

Once the Program is set up, you can use the Anchor Methods Builder to build an instruction, a transaction, or build and send a transaction. The basic format looks like this:

program.methods - This is the builder API for creating instruction calls related to the program’s IDL

await program.methods
  .instructionName(instructionData)
  .accounts({})
  .signers([])
  .rpc()

Below are examples of how to invoke an instruction using the methods builder.

Sends a signed transaction with the specified instruction and returns a TransactionSignature. When using .rpc, the Wallet from the Provider is automatically included as a signer.

// Generate keypair for the new account
const newAccountKp = new Keypair();
 
const data = new BN(42);
const transactionSignature = await program.methods
  .initialize(data)
  .accounts({
      newAccount: newAccountKp.publicKey,
      signer: wallet.publicKey,
      systemProgram: SystemProgram.programId,
  })
  .signers([newAccountKp])
  .rpc();

Fetch Program Accounts

The Program also allows you to easily fetch and filter program accounts. Simply use program.account and then specify the name of the account type on the IDL. Anchor then deserializes and returns all accounts as specified.

Use all to fetch all existing accounts for a specific account type

const accounts = await program.account.newAccount.all();