Anchor v2 alpha is here! Up to 95% smaller binaries, 3.0 to 50.4× fewer CU
Anchor Docs
A lone boy hauls an anchor on the shore, evoking the weight of maritime destiny and Homer's mastery of atmosphere and technique.
Boy with Anchor, Winslow Homer, 1873
Overview

Program development

Build program state, errors, events, and extension points.

Program development starts with the account data model. A Solana program mostly reads accounts, checks they are the right accounts, mutates account data, and sometimes calls other programs.

In v2, fixed-size state is zero-copy by default, variable-length state opts into borsh, and account or constraint behavior can be extended from downstream crates. If you are not sure where to start, read Account data model first, then Account types.

Account data model

How Anchor stores account data and why Account<T> is zero-copy by default.

Solana accounts are byte arrays with an owner. Anchor’s account wrappers decide how those bytes are checked and interpreted before your handler uses them.

Account<T> is the default wrapper for fixed-size program state. It is zero-copy, so it views account bytes as T instead of deserializing into an owned value.

For a normal program-owned account, the on-chain layout is:

[8-byte discriminator][repr(C) T]

Loading Account<T> validates the owner, discriminator, and minimum data length, then casts the raw account bytes into T. Exit is a no-op because mutations already happened in the account buffer.

The main decision is whether the account has a fixed layout. Fixed-size structs use Account<T>. Accounts with Vec, String, or other variable-length payloads use BorshAccount<T>.

Fixed-size state#

Use Account<T> for fixed-size state:

#[account]
pub struct Position {
pub owner: Address,
pub size: PodU64,
pub open: PodBool,
}
#[derive(Accounts)]
pub struct Update {
#[account(mut)]
pub position: Account<Position>,
}

#[account] enforces the pieces zero-copy needs:

  • #[repr(C)] layout
  • T: bytemuck::Pod
  • no padding holes
  • an Anchor discriminator
  • an Owner impl for the declaring program

The no-padding assertion is intentionally strict. Padding bytes are untyped state, so the macro turns that whole class of bug into a compile error.

Variable-length state#

Use BorshAccount<T> when the data cannot be a fixed-size Pod type:

#[account(borsh)]
pub struct Profile {
pub owner: Address,
pub name: String,
pub friends: Vec<Address>,
}

BorshAccount<T> deserializes on load, keeps a borrow guard, and serializes on successful exit. That costs more than zero-copy, but it is the right model for Vec, String, and payload enums.

Dynamic tails#

Slab<H, Item> is the built-in zero-copy dynamic-tail primitive. It stores a fixed header plus a length-prefixed tail of Pod items:

[disc:8][H][len:u32][tail padding][Item; capacity]

Account<T> is implemented as a specialization of Slab<T, HeaderOnly>.

External accounts#

SPL token state is not Anchor-discriminated. anchor-spl-v2 models those accounts with SlabSchema implementations whose data starts at offset 0, so Account<Mint> and Account<TokenAccount> can zero-copy over SPL-owned account data.

Account types

Account wrappers and when to use each one.

Account wrappers load from pinocchio AccountView values and implement the shared AnchorAccount trait. The wrapper you choose determines the account’s ownership checks, data layout, borrow behavior, and exit behavior.

For most program-owned state, the choice is between Account<T> and BorshAccount<T>. The rest of the wrappers describe signers, programs, sysvars, optional slots, and the intentional escape hatch.

TypeUse for
Account<T>Program-owned fixed-size data. Zero-copy, Pod-backed.
BorshAccount<T>Variable-length data with Vec, String, or payload enums.
Slab<H, Item>Header plus dynamic Pod item tail.
Option<A>Optional account slot, with the program id as the absent sentinel.
Nested<T>Inline composition of another #[derive(Accounts)] struct.
Box<A>Heap-boxed wrapper for large account structs.
SignerAn account that must have signed the transaction.
Program<T: Id>A CPI target or well-known executable program.
SystemAccountSystem-owned account with no typed payload.
UncheckedAccountEscape hatch with no validation.
Sysvar<T>Account-form sysvar access when a handler requires a sysvar account.

Account<T>#

Account<T> is the default wrapper for your own fixed-size state. It requires T: Pod and uses [disc][repr(C) T] layout:

#[account]
pub struct Counter {
pub count: u64,
pub authority: Address,
}

For zero-copy details, see Account data model.

BorshAccount<T>#

Use BorshAccount<T> for state that cannot be Pod:

#[account(borsh)]
pub struct Profile {
pub owner: Address,
pub name: String,
}

It serializes on successful exit and provides release_borrow() / reacquire_borrow_mut() for CPI or realloc paths that need to mutate the same account through another route.

Slab<H, Item>#

Slab<H, Item> stores a Pod header followed by a dynamic item tail. It is useful for account-local ledgers, order books, ring buffers, and other bounded collections that should stay zero-copy.

The wrapper exposes vector-style methods such as try_push, pop, iter, indexing, and resize helpers behind the account-resize feature.

Option<A>#

Option<A> makes an account slot optional. When absent, clients pass the program id as a sentinel address. PDA bumps become Option<u8> so handlers can distinguish present from absent:

#[derive(Accounts)]
pub struct MaybeProfile {
pub user: Signer,
#[account(seeds = [b"profile", user.address().as_ref()], bump)]
pub profile: Option<Account<Profile>>,
}

Nested<T>#

Nested<T> lets multiple instructions share a validation block:

#[derive(Accounts)]
pub struct AdminGate {
pub admin: Signer,
#[account(address = config.admin)]
pub config: Account<Config>,
}
#[derive(Accounts)]
pub struct Reset {
pub gate: Nested<AdminGate>,
#[account(mut)]
pub counter: Account<Counter>,
}

The nested fields are flattened into the outer account list. Nested<T> does not consume an extra account slot.

Utility wrappers#

Signer, Program<T>, SystemAccount, UncheckedAccount, and Sysvar<T> keep ordinary Anchor code readable. UncheckedAccount performs no validation, so pair it with explicit constraints or handler checks before trusting it.

Sysvar<T> accepts the pinocchio sysvar types whose payload can be read directly into a typed value. The supported types are Clock, Rent, and Instructions.

SlotHashes deliberately does not implement SysvarId, so Sysvar<SlotHashes> fails to compile with a missing-trait diagnostic. Programs that need slot-hash data should call the partial-read syscall directly through pinocchio.

Trait surface#

All wrappers implement AnchorAccount. Third-party crates can add new wrappers by implementing that trait. See Extensibility.

Pod types

The alignment-1 integer wrappers, PodVec<T, MAX>, and the #[pod_wrapper] macro for safe enum-to-Pod conversion.

Definition (Pod)

Pod (plain old data) means the type is fixed-size, has #[repr(C)] layout, and contains no padding holes. Account<T> requires T: Pod so it can pointer-cast directly from the account’s raw bytes without a deserialize step.

To make T: Pod easy to satisfy, Anchor ships a small set of alignment-1 wrappers that let most user data lay out cleanly without any manual padding.

Integer wrappers#

The unsigned wrappers are PodU16, PodU32, PodU64, and PodU128. The signed wrappers are PodI16, PodI32, PodI64, and PodI128. Each stores an integer as [u8; N] with native little-endian encoding.

Alignment 1 means no padding around the field, so wide integer fields sit flush against their neighbors in a #[repr(C)] struct.

PodU8 and PodI8 are type aliases for native u8 and i8. The primitives are already alignment 1, so no wrapper is needed:

lib.rs
#[account]
pub struct Position {
pub owner: Address,
pub size: PodU64,
pub entry_price: PodI128,
pub leverage: PodU16,
}

Methods on each wrapper:

  • .get() reads the wrapped integer
  • From<native> constructs from a native integer
  • .checked_add, .checked_sub, .checked_mul, .checked_div return Option<Self>
  • .saturating_add, .saturating_sub, etc. saturate at the type’s bounds
  • .is_zero() short-circuit zero check
  • .neg() on signed types

PodBool#

PodBool is a single-byte boolean wrapper where any non-zero byte counts as true and a zero byte counts as false. Use it for flag fields in zero-copy structs to side-step the bool validity-bit pitfall, where Rust treats it as undefined behavior to read a bool whose underlying byte is anything other than 0 or 1:

lib.rs
#[account]
pub struct Listing {
pub seller: Address,
pub active: PodBool,
pub closed: PodBool,
pub _pad: [u8; 6],
}

PodVec<T, MAX>#

PodVec<T, MAX> is a fixed-capacity vector with a u16 length prefix, stored inline in the account rather than behind a heap pointer. The on-disk layout is [len:u16][T; MAX] with no padding.

T must itself be alignment-1 so the surrounding #[account] struct can be Pod. MAX is baked into the type so the compiler knows the exact size at every call site:

lib.rs
#[account]
pub struct MultisigConfig {
pub creator: Address,
pub threshold: u8,
pub bump: u8,
pub label: PodVec<u8, 32>,
pub signers: PodVec<Address, 10>,
}

Methods fall into four groups:

GroupMethodsBehavior
Panicking mutators.push, .extend_from_slice, .set_from_slicePanic on overflow.
Fallible mutators.try_push, .try_extend_from_sliceReturn Result<(), CapacityError>.
Indexing accessors.as_slice, .iter, .get, .popPanic when the stored length prefix exceeds MAX. Pair with .try_as_slice, .try_get, .try_pop.
Length-only helpers.len, .is_empty, .capacity, .clear, .truncateNever index into data. Always safe.

The indexing-accessor panic can fire when a PodVec is cast from attacker-controlled bytes whose length prefix exceeds MAX. Either use the fallible variants at the call site or call .validate() once at load time to reject bad bytes up front.

Reach for PodVec when the upper bound is small and known ahead of time. For genuinely open-ended data, use BorshAccount<T> with a regular Vec<T> instead.

#[pod_wrapper] for enums#

Plain Rust enums are not Pod because the discriminant has bit patterns that the type system considers invalid. The #[pod_wrapper] attribute solves this by deriving a transparent u8-backed wrapper that validates the discriminant against the declared variants whenever the value is compared or converted:

lib.rs
#[pod_wrapper]
#[repr(u8)]
pub enum OrderSide {
Bid = 0,
Ask = 1,
}
#[account]
pub struct Order {
pub price: PodU64,
pub side: PodOrderSide,
}

PodOrderSide is the generated wrapper. The operations ==, !=, and .into() each validate the underlying byte against the declared variants. A stale or corrupted byte panics rather than returning an invalid value, which catches account-data tampering at the wrapper boundary before an invalid discriminant can reach program logic.

When unvalidated access is the right choice, the raw .0 field is still available. Useful for migrating an older variant set without panicking on values that pre-date the current declaration.

Borsh accounts and realloc

Variable-length account data, InitSpace, realloc constraints, and borrow release.

Use BorshAccount<T> when account data is naturally variable-length. This is the account model for state that needs Vec, String, or enum payloads that cannot fit into a fixed Pod layout:

#[account(borsh)]
#[derive(InitSpace)]
pub struct Profile {
pub owner: Address,
#[max_len(64)]
pub name: String,
#[max_len(16)]
pub friends: Vec<Address>,
}

The #[account(borsh)] attribute derives borsh serialization for the account type. BorshAccount<T> deserializes on load and serializes on successful exit.

Sizing new accounts#

#[derive(InitSpace)] computes the maximum serialized size from #[max_len(...)] annotations:

#[derive(Accounts)]
pub struct CreateProfile {
#[account(mut)]
pub payer: Signer,
#[account(init, payer = payer, space = 8 + Profile::INIT_SPACE)]
pub profile: BorshAccount<Profile>,
pub system_program: Program<System>,
}

The extra 8 is the Anchor discriminator.

Realloc#

The derive supports realloc constraints for accounts that need to grow or shrink. This example follows the v2 realloc fixture and keeps the size formula tied to one variable-length field:

#[account(borsh)]
pub struct DynData {
pub items: Vec<u8>,
}
#[derive(Accounts)]
#[instruction(new_items: Vec<u8>)]
pub struct Grow {
#[account(mut)]
pub payer: Signer,
#[account(
mut,
realloc = 8 + 4 + new_items.len(),
realloc_payer = payer,
realloc_zero = false,
)]
pub data: BorshAccount<DynData>,
pub system_program: Program<System>,
}

The 8 is the Anchor discriminator. The 4 is the borsh length prefix for Vec<u8>.

The account-resize feature is on by default because realloc needs pinocchio’s account-resize hook. Do not disable that feature while the program still uses realloc helpers.

CPI re-entry#

BorshAccount<T> holds a borrow guard. If a CPI will mutate the same account, release the borrow before the CPI and reacquire it after:

ctx.accounts.profile.release_borrow()?;
other_program::cpi::update_profile(cpi_ctx)?;
ctx.accounts.profile.reacquire_borrow_mut(ctx.program_id)?;

release_borrow() commits in-memory changes first, so the CPI sees the latest state. reacquire_borrow_mut() reruns owner, discriminator, and size checks before deserializing the post-CPI bytes.

Account<T> does not need this pattern because it reads zero-copy from the account buffer.

Errors and require

Custom errors and #![no_std] require macros.

Use #[error_code] to name custom program errors and require! helpers to return them from validation checks. Custom error names and messages are IDL metadata. The program returns ProgramError::Custom(code) on chain.

Custom errors#

#[error_code]
pub enum VaultError {
#[msg("The requested amount is zero")]
ZeroAmount,
#[msg("The caller is not the vault authority")]
BadAuthority,
}

By default, variants start at the standard Anchor custom-error offset. Override the offset when a program needs to preserve an existing error-code range:

#[error_code(offset = 7000)]
pub enum MarketError {
InvalidPrice,
}

Require macros#

Anchor exports no_std-friendly require macros:

require!(amount > 0, VaultError::ZeroAmount);
require_keys_eq!(ctx.accounts.authority.address(), &ctx.accounts.vault.authority);
require_gte!(available, amount);

Available helpers include:

  • require!
  • require_eq!
  • require_neq!
  • require_keys_eq!
  • require_keys_neq!
  • require_gt!
  • require_gte!

The key-specific helpers handle Address comparisons, while the ordinary helpers cover numeric or other displayable values.

No runtime AnchorError#

The runtime does not allocate an AnchorError structure with file, line, name, and message fields. That data is useful to clients through the IDL, but keeping it out of the SBF runtime avoids heap allocation and formatting cost in the error path.

Events

Emit program events with wincode or bytemuck encoding.

Events are structured log records emitted by a program. Clients and indexers can read them from transaction logs instead of trying to infer every state change from account data.

Events are declared with #[event] and emitted with emit!. The default encoder is wincode configured to match borsh wire layout for Anchor-supported shapes.

Default events#

Use default events when the payload includes variable-length fields:

#[event]
pub struct OrderFilled {
pub maker: Address,
pub taker: Address,
pub size: u64,
pub memo: String,
}
emit!(OrderFilled {
maker: *ctx.accounts.maker.address(),
taker: *ctx.accounts.taker.address(),
size,
memo,
});

The default encoding supports Vec, String, Option, and enums while staying cheaper than borsh on SBF.

Bytemuck events#

When every field is fixed-size and Pod-compatible, use #[event(bytemuck)]:

#[event(bytemuck)]
#[repr(C)]
pub struct PriceUpdate {
pub price: PodU64,
pub slot: PodU64,
}

This emits the discriminator and then copies the body bytes directly. It is the cheapest event path, but it is only correct for fixed-size Pod payloads.

Choosing an event form#

Default #[event] is the right pick for expressive Rust data. #[event(bytemuck)] is the cheaper option for compact telemetry records with stable fixed-size fields.

Self-CPI events#

Transaction logs can be truncated by RPC providers, which makes log-only events unreliable for indexers that need to replay program history. The self-CPI event pattern emits an event as instruction data on a no-op CPI back to the program, so indexers can recover it from instruction history instead of logs.

Opt in by enabling the event-cpi feature on anchor-lang-v2, then mark the handler’s accounts struct with #[event_cpi] and emit with emit_cpi!:

Cargo.toml
[dependencies]
anchor-lang-v2 = { git = "...", branch = "anchor-next", features = ["event-cpi"] }
#[event_cpi]
#[derive(Accounts)]
pub struct EmitOnce {
#[account(mut, seeds = [COUNTER_SEED], bump)]
pub counter: Account<Counter>,
}
pub fn emit_once(ctx: &mut Context<EmitOnce>, value: u64) -> Result<()> {
ctx.accounts.counter.value = value;
emit_cpi!(EventCpiObserved { value, marker: 7 });
Ok(())
}

#[event_cpi] injects an event_authority PDA derived from [b"__event_authority"] plus a program field into the accounts struct. emit_cpi! issues a self-CPI tagged with a reserved 8-byte discriminator. The #[program] dispatcher checks for that tag before normal instruction dispatch and short-circuits the call after verifying the signer PDA, so the CPI never reaches a user handler.

Use self-CPI events when downstream indexers need replay guarantees stronger than log retention provides. The default emit! path remains the right choice for ordinary telemetry that consumers read from logs.

Extensibility

How to write your own account primitives and constraint markers without forking the Anchor macro.

Anchor moves account loading, constraint checks, program identities, and discriminator behavior behind traits. That lets downstream crates ship new account primitives, constraint markers, or program-ID identities without changing the derive macro.

The core trait surface that extensions implement is intentionally small:

TraitWhat it adds
AnchorAccountA new account wrapper (load/exit lifecycle, owner/disc validation)
AccountConstraint<A>A new namespaced constraint (#[account(my_namespace::key = value)])
IdA new well-known program for Program<T: Id>
DiscriminatorA custom 8-byte discriminator on an account or instruction

AnchorAccount#

Implementing AnchorAccount on a wrapper type is what makes it usable as a field in #[derive(Accounts)]. The trait extends Deref<Target = Self::Data>, so consumers can reach the inner data directly through &*account. The required methods are load, account, and (optionally) load_mut, exit, and close. cpi_handle and cpi_handle_mut come with default impls:

my_wrapper.rs
use anchor_lang_v2::prelude::*;
use core::ops::Deref;
pub struct MyWrapper<T: Pod> {
view: AccountView,
data: T,
}
impl<T: Pod> Deref for MyWrapper<T> {
type Target = T;
fn deref(&self) -> &T { &self.data }
}
impl<T: Pod + Discriminator + Owner> AnchorAccount for MyWrapper<T> {
type Data = T;
const MIN_DATA_LEN: usize = 8 + core::mem::size_of::<T>();
fn load(view: AccountView, program_id: &Address) -> Result<Self, ProgramError> {
// Validate owner, discriminator, size, then pointer-cast bytes into `T`.
unimplemented!()
}
unsafe fn load_mut(view: AccountView, program_id: &Address) -> Result<Self, ProgramError> {
// Same as load, plus an `is_writable` check and the mutable borrow flag.
unimplemented!()
}
fn account(&self) -> &AccountView { &self.view }
fn exit(&mut self) -> ProgramResult {
// Persist any in-memory mutations back to the account buffer.
Ok(())
}
}

The trait defaults MIN_DATA_LEN to 0, which forces the on-curve check. Wrappers that require non-empty account data can override it with a non-zero structural minimum so the verifier can fall back to a hash-and-compare.

Intuition (What MIN_DATA_LEN actually buys)

A wrapper that requires a non-empty account is one that must have been created via signed CPI, and invoke_signed already ran the on-curve check at creation. Setting MIN_DATA_LEN to a non-zero value tells the PDA verifier “this account was provably created through a signed path”, which is the condition under which the curve re-check can be skipped.

Once implemented, MyWrapper<T> is a first-class field type:

lib.rs
#[derive(Accounts)]
pub struct UseMine {
pub data: MyWrapper<MyData>,
}
Example (A third-party wrapper in the wild)

anchor-dynamic-account adds zero-copy accounts with auto-reallocating Vec<T> and String tails. The whole wrapper is a third-party crate, with no changes required to the core runtime.

AccountConstraint<A>#

To add a new namespaced constraint, implement AccountConstraint<A> on a marker type. The trait’s four methods correspond to the phases of a constraint’s lifecycle:

lib.rs
pub trait AccountConstraint<A> {
type Value;
fn init(account: &mut A, value: &Self::Value) -> Result<()>;
fn check(account: &A, value: &Self::Value) -> Result<()>;
fn update(account: &mut A, value: &Self::Value) -> Result<()>;
fn exit(account: &mut A, value: &Self::Value) -> Result<()>;
}
  • init runs after account creation (under init or the create-branch of init_if_needed)
  • check runs on existing accounts and on non-init fields
  • update runs inside an update(...) constraint clause
  • exit always runs during the account exit phase

Once the impl is in scope, a constraint marker named my_namespace::key becomes available as #[account(my_namespace::key = value)] on any account field.

The anchor-spl-v2 crate is built on this pattern. It implements token::* and mint::* as a downstream crate rather than baking them into the core derive. Its associated_token module currently exposes associated token address derivation helpers.

Id for Program<T: Id>#

To add a new well-known program type that works with Program<T: Id>, implement Id on a unit marker struct. The trait’s single method returns the program’s canonical address:

lib.rs
pub struct MyProgram;
impl Id for MyProgram {
fn id() -> Address {
Address::new_from_array([/* 32 bytes */])
}
}
#[derive(Accounts)]
pub struct UseMine {
pub my_program: Program<MyProgram>,
}

At load time, Program<MyProgram> validates that the account’s address matches MyProgram::id(), and (under the default guardrails feature) that the account is executable.

Why this works#

The core derive shrinks materially because the generated code now goes through trait calls like <MyType as AnchorAccount>::load(view, program_id) rather than inlining all of the validation logic per field. Adding a new account wrapper or constraint becomes a trait implementation in a downstream crate, not a fork of the macro itself.

For the full trait surface, see lang-v2/src/traits.rs on the anchor-next branch.

Esc

Start typing to search the docs.