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

Reference

Reference pages for macros, constraints, account wrappers, feature flags, CLI, and alpha limits.

Macros and derives

Reference for #[program], #[derive(Accounts)], #[account], #[event], and related attributes.

Anchor programs use attributes and derives to declare instruction handlers, account validation, account layouts, events, errors, and constants. This page collects the core macros and the code they generate.

#[program]#

The #[program] attribute marks a module as the program entry point. The macro generates a discriminator-keyed dispatcher that matches incoming instruction data against the handler’s auto-derived 8-byte discriminator:

lib.rs
#[program]
pub mod counter {
use super::*;
pub fn init(ctx: &mut Context<Init>) -> Result<()> { /* ... */ }
pub fn increment(ctx: &mut Context<Increment>) -> Result<()> { /* ... */ }
}

Each handler takes &mut Context<Accs> and any extra arguments declared after ctx. The dispatcher decodes the extras through wincode using a borsh-compatible wire format.

The default instruction discriminator is sha256("global:" + name)[..8], where name is the handler’s identifier. CU-sensitive programs can override per handler with #[discrim = N] for a compact 1-byte discriminator. The override is all-or-nothing. If any handler in the module uses #[discrim = N], every other handler must also declare a unique byte value:

lib.rs
#[program]
pub mod vault {
use super::*;
#[discrim = 0]
pub fn deposit(ctx: &mut Context<Deposit>, amount: u64) -> Result<()> { /* ... */ }
#[discrim = 1]
pub fn withdraw(ctx: &mut Context<Withdraw>, amount: u64) -> Result<()> { /* ... */ }
}

#[derive(Accounts)]#

#[derive(Accounts)] declares an instruction’s account list and generates the validation code that runs before the handler. For each struct it expands to:

  • A TryAccounts impl that loads each declared account from the pre-walked view slice, applies constraints, and returns the validated struct
  • A Bumps impl whose associated type carries one u8 per PDA seed group (or Option<u8> for optional accounts)
  • A Resolved companion struct: a subset of fields the caller must supply when building an instruction from a client or test. The rest auto-derive (PDAs) or auto-fill (system / token programs)
  • A to_account_metas() method for client-side instruction building
lib.rs
#[derive(Accounts)]
pub struct Create {
#[account(mut)]
pub creator: Signer,
#[account(init, payer = creator, seeds = [b"multisig", creator.address().as_ref()])]
pub config: Account<MultisigConfig>,
pub system_program: Program<System>,
}

Constraints#

The derive handles built-in clauses directly. Namespaced extension clauses such as token::mint and third-party my_ns::key route through AccountConstraint, which exposes the four lifecycle hooks init, check, update, and exit. The most common built-in clauses are:

ClauseEffect
mutMarks the account writable. Validated against is_writable.
initCreates the account via System Program CPI. Requires payer.
init_if_neededCreates only if the account doesn’t exist.
payer = payerFunds rent for init / init_if_needed.
seeds = [...] and bumpValidates the account is a PDA from the listed seeds.
bump = fieldUses a stored bump instead of searching at runtime.
address = exprValidates account.address() == expr. Prefer this for checking that a sibling account matches a stored address field.
owner = program_idValidates account.owner() == program_id.
space = NAccount size in bytes for init.
close = destOn exit, transfers all lamports to dest and zeros the data.

Macro-time PDA bump precomputation#

When all seed values in seeds = [...] are byte literals, the derive runs find_program_address at compile time and bakes the canonical bump in as a const. The runtime PDA search is skipped. See Performance and optimizations.

Topological seed resolution#

When seeds reference other fields in the same struct, the macro sorts derivations topologically. The example below derives vault from config.address(), which is itself a PDA derived from creator:

lib.rs
#[derive(Accounts)]
pub struct ExecuteTransfer {
pub creator: UncheckedAccount,
#[account(seeds = [b"multisig", creator.address().as_ref()], bump = config.bump)]
pub config: Account<MultisigConfig>,
#[account(mut, seeds = [b"vault", config.address().as_ref()], bump)]
pub vault: UncheckedAccount,
}

#[account]#

The #[account] attribute marks a struct as an account type. It has two modes. The default form is zero-copy, and the opt-in form uses borsh.

Default zero-copy form#

In the default form, the macro generates:

  • A compile-time assertion that every field is bytemuck::Pod
  • A #[repr(C)] layout with #[derive(Clone, Copy)]
  • A Discriminator impl with sha256("account:" + name)[..8]
  • An Owner impl returning program_id (program-owned)
  • A no-padding assertion (struct size must equal the sum of field sizes)
  • An IDL type entry under idl-build

The struct must be #[repr(C)] (added by the macro) and every field must itself be Pod. Pairing it with Account<T> then yields a fully zero-copy load.

Borsh form#

For accounts whose fields are variable-length (Vec, String, enums with payload variants), use #[account(borsh)] and pair the struct with BorshAccount<T>. The macro auto-derives BorshSerialize, BorshDeserialize, and Default so the struct can round-trip through borsh:

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

Borsh accounts deserialize on load and serialize on exit, so reach for the borsh form only when variable-length fields are genuinely needed. Fixed-size structs should stay in the default zero-copy form.

#[event] and #[event(bytemuck)]#

Program events have two encoding modes. The right choice depends on whether the event has any variable-length fields.

The default mode encodes through wincode with a borsh-wire-compatible config: u8 enum tags, u32 little-endian length prefixes, and no alignment checks. It still supports Vec, String, Option, and enums, while running roughly 3 to 10 times cheaper than borsh on SBF:

lib.rs
#[event]
pub struct OrderFilled {
pub maker: Address,
pub taker: Address,
pub price: u64,
pub size: u64,
pub note: String,
}
emit!(OrderFilled {
maker: *ctx.accounts.maker.address(),
taker: *ctx.accounts.taker.address(),
price: 1_000,
size: 1,
note: String::from("partial fill"),
});

#[event(bytemuck)] swaps the encoder for a zero-copy repr(C) memcpy and is the right choice when every event field is fixed-size. With this mode, emission collapses to one discriminator write plus a memcpy of the body, with no per-field encoding step:

lib.rs
#[event(bytemuck)]
#[repr(C)]
pub struct PriceUpdate {
pub price: PodU64,
pub timestamp: PodU64,
}

#[derive(InitSpace)]#

#[derive(InitSpace)] adds a Space implementation whose INIT_SPACE constant equals the struct’s borsh-serialized size. Variable-length fields are not derivable on their own, so annotate them with #[max_len(N)] (or #[max_len(N, M)] for nested vectors) to give the macro an explicit upper bound:

lib.rs
#[account(borsh)]
#[derive(InitSpace)]
pub struct UserProfile {
pub owner: Address,
#[max_len(50)]
pub name: String,
#[max_len(10)]
pub friends: Vec<Address>,
}

UserProfile::INIT_SPACE then drops into the space constraint:

lib.rs
#[account(init, payer = signer, space = 8 + UserProfile::INIT_SPACE)]
pub profile: BorshAccount<UserProfile>,

#[constant]#

The #[constant] attribute exposes a const value to the IDL and generated clients. It is useful for documenting program-level constants, like seed strings or maximum sizes, that off-chain code has to reproduce verbatim:

lib.rs
#[constant]
pub const MAX_SIGNERS: usize = 10;

#[error_code]#

The #[error_code] attribute declares a custom error enum. Each variant maps to an Error::Custom(discriminant + offset) where offset defaults to 6000. The variant names and #[msg(...)] strings land in the generated IDL so clients can decode the codes back into named variants:

lib.rs
#[error_code]
pub enum MultisigError {
#[msg("Threshold must be between 1 and the number of signers")]
InvalidThreshold,
#[msg("Too many signers; max is MAX_SIGNERS")]
TooManySigners,
#[msg("A required signer did not sign")]
MissingRequiredSignature,
}

Override the starting offset with #[error_code(offset = N)] when a program needs a non-default error-code base.

Remark (No runtime AnchorError)

The runtime does not allocate an AnchorError struct. The #[msg(...)] text is IDL-only metadata, so emitting an error costs only the Custom(u32) write.

#[access_control]#

The #[access_control] attribute wraps a handler with a guard call that runs before the handler body. The guard returns Result<()>, and a failure short-circuits the instruction without ever entering the handler. The pattern is most useful for invariants that depend on instruction arguments, since #[derive(Accounts)] constraints fire before args are unpacked:

lib.rs
#[program]
pub mod admin_only {
use super::*;
#[access_control(Reset::is_admin(&ctx))]
pub fn reset(ctx: &mut Context<Reset>) -> Result<()> {
ctx.accounts.counter.value = 0;
Ok(())
}
}
impl Reset {
pub fn is_admin(ctx: &Context<Reset>) -> Result<()> {
require_keys_eq!(ctx.accounts.signer.address(), &ADMIN_PUBKEY);
Ok(())
}
}

#[pod_wrapper]#

Generates a transparent u8-backed wrapper that validates discriminants on equality and conversion. See Pod types.

Account constraints

Built-in and namespaced account constraints.

Account constraints are declared on fields inside a #[derive(Accounts)] struct. They run before the handler and cover account-metadata checks, PDA derivation, account creation and resizing, account closing, and extension-specific checks.

ConstraintNotes
mutRequires writable account metadata and loads the wrapper mutably.
signerRequires is_signer. Prefer Signer for ordinary signers.
initCreates and initializes the account. Requires payer.
init_if_neededCreates on the absent branch, otherwise validates existing data.
payer = fieldPays rent for init paths.
space = exprAllocation size for init.
seeds = [...]PDA seeds. Array form gives the best IDL metadata.
bumpFinds and records the canonical bump.
bump = exprVerifies with an explicit bump.
seeds::program = exprDerives against another program id.
address = exprChecks the account address against an expression. Accepts any T: Into<Address> on the right.
owner = exprChecks the owner address.
executableRequires the account’s executable flag.
constraint = exprRuns a custom boolean expression. Repeatable.
constraint(expr [@ err])Parenthesized form of constraint. Repeatable.
has_one = fieldDeprecated relation check. Prefer address = parent.field on the sibling account.
zeroedInitializes an allocated account whose discriminator is still all zero bytes.
close = destCloses account on successful exit.
realloc = exprResizes account data when enabled.
realloc_payer = fieldPays for realloc growth.
realloc_zero = boolControls zeroing for new bytes.
unsafe(dup)Opts out of duplicate mutable account rejection for this field.

Prefer address = parent.field when a sibling account must match an address stored in another account.

Namespaced constraints#

Namespaced constraints are implemented by marker types that satisfy AccountConstraint<A>:

#[account(token::mint = mint, token::authority = owner)]
pub vault: Account<TokenAccount>,

The derive routes each marker through lifecycle hooks:

SyntaxHook
ns::key = valuecheck
init, ns::key = valueinit during creation for runtime-only markers. Built-in namespace markers are handled by init params.
init_if_needed, ns::key = valueinit then check on create, check on existing
update(ns::key = value)update
any successful pathexit during account exit

The anchor-spl-v2 crate uses this extension point for token::* and mint::* constraints.

Custom constraints#

constraint runs an arbitrary boolean expression and accepts either the equals form or a parenthesized form. Both spellings can appear multiple times in one #[account(...)], and the checks fire in source order:

#[account(
mut,
constraint = pool.is_open,
constraint(amount > 0 @ MyError::AmountTooSmall),
constraint(pool.cap >= pool.total + amount),
)]
pub pool: Account<Pool>,

A bare failure raises ErrorCode::ConstraintRaw. Append @ err to use a custom error. constraint(a, b) is a parse error rather than two checks: chain checks by writing separate entries.

address expressions#

The right-hand side of address = expr is converted through Into<Address>, so wrappers, byte arrays, and references all flow through the same constraint:

#[account(address = pool.authority)]
pub authority: UncheckedAccount,
#[account(address = MY_AUTHORITY_BYTES)]
pub admin: UncheckedAccount,

Use this when an account address comes from a sibling account’s field, a constant, or a domain-specific newtype that implements Into<Address>.

Account types

Quick reference for Anchor account wrappers.

WrapperValidatesData model
Account<T>owner, discriminator, minimum data lengthzero-copy Pod data
BorshAccount<T>owner, discriminator, minimum data lengthborsh deserialize on load, serialize on exit
Slab<H, Item>owner, discriminator, tail layoutzero-copy header plus item tail
Option<A>absent sentinel or inner wrapperoptional account slot
Nested<T>inner accounts structinline account composition
Box<A>inner wrapperheap-boxed wrapper
Signeris_signerno account data
Program<T>T::id(), plus executable under guardrailsno account data
SystemAccountsystem ownerno typed data
UncheckedAccountnothingraw account view
Sysvar<T>sysvar idsyscall-loaded sysvar value

Use Program account types for examples and guidance.

Feature flags

Reference for the alloc, guardrails, account-resize, const-rent, compat, and event-cpi feature flags on anchor-lang-v2.

anchor-lang-v2 ships seven feature flags, three of which are on by default and the other four opt-in. Most programs never need to touch them, but tuning the set is useful for trimming binary size and CU cost in production builds.

FeatureDefaultEffect
alloconPulls in extern crate alloc for Vec, String, BTreeMap.
guardrailsonRuntime safety checks on dispatch and load_mut.
account-resizeonEnables realloc_account and pinocchio’s resize hook.
const-rentoffFolds the rent formula to a compile-time constant.
compatoffExposes v1-shaped helpers such as debug!, error!, and pubkey!.
event-cpioffEnables the #[event_cpi] accounts attribute and the emit_cpi! macro.
testingoffEnables anchor_lang_v2::testing with mock account scaffolding.

alloc (default)#

The alloc feature enables extern crate alloc and re-exports the alloc crate under anchor_lang_v2::__alloc. Generated macro code reaches Vec, String, and other heap-allocated types through that re-export, so user crates do not have to import alloc themselves.

Note (When to disable)

Only embedded targets without a heap have a reason to turn alloc off. The ergonomic cost is significant. Most realistic user code that touches Vec or String will not compile without it.

guardrails (default)#

The guardrails feature enables a small set of runtime checks that catch caller bugs the borrow checker cannot see statically:

  • check_program_id on dispatch rejects misrouted instructions
  • is_writable enforcement in data-account load_mut paths returns an error when the account is not marked mut
  • executable checks on Program<T> reject non-program accounts before CPI

Disabling the feature drops those guardrail-only emits, which saves roughly 300 bytes of binary and 1 to 2 CU per account:

Cargo.toml
[dependencies]
anchor-lang-v2 = { git = "...", branch = "anchor-next", default-features = false, features = ["alloc", "account-resize"] }
Warning (Don't disable lightly)

The runtime safety net is what catches accidental misuse during development. Only turn guardrails off in production after the program has been thoroughly tested.

account-resize (default)#

The account-resize feature enables realloc_account and adds a pinocchio entrypoint hook. The hook writes data_len into RuntimeAccount.padding for every non-duplicate account, which is what lets AccountView::resize() enforce the MAX_PERMITTED_DATA_INCREASE cap.

Disabling the feature saves roughly 300 bytes of binary and 2 CU per account, but only when the program never calls realloc_account in the first place.

Danger (Don't disable while call sites exist)

If realloc_account could run without the resize hook, it would corrupt the stored original_data_len. The function is feature-gated so this fails at compile time instead. Disable account-resize only after removing every realloc path.

const-rent (off)#

The const-rent feature folds rent_exempt_lamports to a compile-time constant by combining pinocchio’s DEFAULT_LAMPORTS_PER_BYTE and ACCOUNT_STORAGE_OVERHEAD. With the constant in place, create_account skips the Rent::get() sysvar call entirely. The savings come out to roughly 90 CU per create_account CPI.

Warning (Rent-formula drift)

If Solana changes the rent formula in the future (for example, through a SIMD like SIMD-0194), programs built with const-rent will compute stale values until they are rebuilt. The feature is off by default so programs pick up runtime formula changes transparently.

Tip (When to enable)

Reach for const-rent when 90 CU per create_account matters more than rent-formula drift safety, and when the program is rebuilt and redeployed often enough that refreshing the constant is part of the normal release cycle.

compat (off)#

The compat feature exposes a small set of v1-shaped helpers for programs in the middle of porting. The most useful is debug!(msg), a logging macro shaped like msg! that accepts any Rust format string by routing through alloc::format!. That covers {:?}, {:x}, and dynamic width specifiers, none of which native msg! supports.

The remaining helpers re-expose v1 names so existing call sites compile without rewrites:

HelperForm
error!, err!, pubkey!Macros
PubkeyType alias for Address
AccountViewCompatExtension trait on pinocchio’s AccountView
Warning (debug! cost)

debug! is meaningfully more expensive than the native msg! because of the heap allocation and fmt-trait dispatch. The feature is off by default to keep production binaries from accidentally shipping the heavier path.

event-cpi (off)#

The event-cpi feature wires the self-CPI event emit pattern through Anchor’s derives. When enabled, it adds the #[event_cpi] accounts attribute and the emit_cpi! macro.

The #[program] dispatcher then reserves an 8-byte tag at the top of instruction data. Indexers that cannot read transaction logs can recover emitted events from instruction history through that tag.

Note (Why off by default)

The dispatcher would pay the 8-byte tag check on every instruction. Programs that do not use event CPIs avoid that cost by keeping event-cpi off.

See Events for the call shape.

testing (off)#

The testing feature exposes the anchor_lang_v2::testing module, which provides stack-backed mock pinocchio types (such as AccountView and the SBF input buffer) that the lang-v2/tests/ integration tests and Miri witnesses depend on. Enabling testing also turns on const-rent so host-side tests can exercise rent-dependent helpers without needing SVM sysvar support:

Terminal window
cargo test -p anchor-lang-v2 --features testing
Note (Why off by default)

Production BPF binaries should never ship the test scaffold, so testing stays opt-in.

Recipe: minimal binary, prod profile#

Cargo.toml
[dependencies]
anchor-lang-v2 = { git = "...", branch = "anchor-next", default-features = false, features = ["alloc"] }

This profile drops both guardrails and account-resize for the smallest possible binary.

Danger (Verify before shipping)

Only adopt this profile once the program has been thoroughly tested and you have verified that no instruction calls realloc_account. The guardrails net catches caller bugs the borrow checker cannot see statically. Ship without it only after fuzzing and review have closed those holes.

Recipe: maximum CU savings#

Cargo.toml
[dependencies]
anchor-lang-v2 = { git = "...", branch = "anchor-next", default-features = false, features = ["alloc", "const-rent"] }

This profile adds rent-formula constant folding on top of the minimal recipe. Rebuild and redeploy whenever Solana changes the rent formula so the constants stay in sync.

Anchor.toml

Reference for the fields available in an Anchor workspace's Anchor.toml file.

Anchor.toml is the workspace configuration file used by the Anchor CLI. The CLI discovers it by walking up from the current directory, and reads it for program paths, the active provider, scripts, test infrastructure, and toolchain settings.

The scaffold from anchor init is the best starting point:

Anchor.toml
[toolchain]
package_manager = "yarn"
[features]
resolution = true
skip-lint = false
[programs.localnet]
counter = "..."
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"
[scripts]
test = "cargo test"
skip_local_validator = true

[provider]#

The provider sets the default cluster and wallet for CLI commands:

[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

cluster can be a named cluster such as localnet, devnet, testnet, or mainnet, or a custom URL. wallet is the keypair used for commands that need a signer.

You can override either value on a command with global flags:

Terminal window
anchor build --provider.cluster devnet --provider.wallet ~/.config/solana/devnet.json

[programs.<cluster>]#

Program sections map program names to declared program IDs for a cluster:

[programs.localnet]
counter = "Counter111111111111111111111111111111111111"
[programs.devnet]
counter = "DevCounter11111111111111111111111111111111"

The program name should match the Rust crate name after Anchor normalizes it to snake case. Commands such as anchor keys list, anchor keys sync, anchor build, anchor program deploy, and anchor test use this map to connect source crates, keypair files, IDLs, and deployed program addresses.

The CLI also accepts object-form program entries when a command needs a custom deployment path or IDL path:

[programs.localnet]
counter = { address = "Counter111111111111111111111111111111111111", path = "target/deploy/counter.so", idl = "target/idl/counter.json" }

[scripts]#

Scripts are named shell commands. Run a script with anchor run <script-name>:

[scripts]
test = "cargo test"
lint = "cargo clippy --workspace --all-targets"

anchor test runs the test script after the build and deployment setup. The generated LiteSVM workspace uses cargo test because the tests run in process rather than through an RPC validator.

skip_local_validator#

Set skip_local_validator = true when the test harness does not need a local RPC validator:

skip_local_validator = true

The LiteSVM scaffold sets this because the test VM runs inside the Rust process. Use the same setting for other in-process Rust harnesses that do not need an RPC validator. If the flag is absent and the provider cluster is localnet, anchor test can start a local validator before running the test script.

[features]#

Workspace features configure CLI behavior:

[features]
resolution = true
skip-lint = false
FieldDefaultMeaning
resolutiontrueEmit and use IDL account-resolution metadata where available.
skip-lintfalseSkip safety-comment lint checks during IDL generation.

[workspace]#

The workspace section controls which Rust program crates the CLI builds and where generated TypeScript IDL types are copied:

[workspace]
members = ["programs/*"]
exclude = ["programs/experimental"]
types = "app/src/idl"
FieldMeaning
membersProgram manifest paths relative to Anchor.toml. When omitted, the CLI looks under programs/*.
excludePaths to skip after resolving members.
typesDirectory where generated TypeScript IDL type files are copied.

[toolchain]#

Toolchain settings choose versions and JavaScript package tooling:

[toolchain]
anchor_version = "1.0.1"
solana_version = "3.1.10"
package_manager = "pnpm"

package_manager accepts npm, yarn, pnpm, or bun. When the field is omitted, Anchor resolves an available package manager from the local environment.

anchor_version is used by workflows that delegate version management to AVM. Git-installed alpha builds are usually run directly from the checkout or from the installed anchor binary on PATH.

[hooks]#

Hooks run shell commands before or after major CLI stages:

[hooks]
pre-build = "cargo fmt --check"
post-build = "cargo clippy --workspace --all-targets"
pre-test = ["cargo test --workspace --no-run", "cargo nextest list"]
post-test = "echo done"

Supported hook names are pre-build, post-build, pre-test, post-test, pre-deploy, and post-deploy. Snake-case aliases such as pre_build also parse. A hook can be a single command string or a list of command strings. Nonzero exit codes stop the CLI command.

[test]#

The test section configures validator-backed test runs:

[test]
startup_wait = 10000
shutdown_wait = 2000
upgradeable = true
FieldMeaning
startup_waitMilliseconds to wait for the validator to start.
shutdown_waitMilliseconds to wait during validator shutdown.
upgradeableDeploy the program under test as upgradeable, with the provider wallet as initial upgrade authority.

[[test.genesis]] preloads programs at genesis:

[[test.genesis]]
address = "Dex111111111111111111111111111111111111111"
program = "fixtures/dex.so"
upgradeable = true

[test.validator]#

[test.validator] forwards settings to solana-test-validator for validator-backed tests:

[test.validator]
url = "https://api.mainnet-beta.solana.com"
warp_slot = 100
slots_per_epoch = "32"
rpc_port = 8899
ledger = "test-ledger"
limit_ledger_size = "10000"

Common nested entries include cloned accounts, account JSON files, account directories, and deactivated features:

[[test.validator.clone]]
address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
[[test.validator.account]]
address = "Ev8WSPQsGb4wfjybqff5eZNcS3n6HaMsBkMk9suAiuM"
filename = "fixtures/account.json"
[[test.validator.account_dir]]
directory = "fixtures/accounts"
[test.validator]
deactivate_feature = [
"Feat111111111111111111111111111111111111111",
]

Use solana-test-validator --help for the full set of validator flags. The fields in Anchor.toml intentionally mirror those options where practical.

[surfpool]#

Surfpool is the default local network backend for supported CLI flows. Configure it with [surfpool]:

[surfpool]
startup_wait = 5000
shutdown_wait = 2000
rpc_port = 8899
ws_port = 8900
host = "127.0.0.1"
online = true
datasource_rpc_url = "https://api.mainnet-beta.solana.com"

Additional Surfpool fields include airdrop_addresses, manifest_file_path, runbooks, slot_time, log_level, and block_production_mode.

Test.toml#

Large workspaces can put suite-specific test config in Test.toml files. The CLI discovers them under the workspace and merges them with optional base files:

tests/token/Test.toml
extends = ["../Test.base.toml"]
[scripts]
test = "cargo test -p token-tests"
[test]
startup_wait = 15000

Test.toml supports the same test and scripts shapes used by Anchor.toml. Relative paths inside test validator config are resolved from the Test.toml file that declares them.

Anchor CLI

CLI reference for project scaffolding, builds, tests, profiling, debugger, coverage, IDL, and workspace commands.

The Anchor CLI is the workspace toolchain. It scaffolds projects, builds programs, runs tests, generates IDLs, deploys, and drives the trace tooling.

This alpha is installed from git:

Terminal window
cargo install --git https://github.com/otter-sec/anchor.git --branch anchor-next anchor-cli --locked --force
Warning (Overwrites anchor on PATH)

cargo install overwrites the anchor binary on your PATH, including one managed by AVM. To keep another Anchor install available, run this binary from a source checkout.

Run anchor --help or anchor <command> --help for the exact flags supported by your checkout.

Common commands#

CommandPurpose
anchor initCreate a new workspace.
anchor newAdd a program to an existing workspace.
anchor buildBuild workspace programs and emit IDLs.
anchor testBuild, deploy when needed, and run tests.
anchor test --profileRun tests with register tracing and flamegraphs.
anchor debuggerOpen the instruction-level debugger.
anchor coverageGenerate LCOV from SBF register traces.
anchor idlBuild, fetch, initialize, and manage IDLs.
anchor codamaConvert IDLs to Codama and render client libraries.
anchor accountDeserialize an account using an IDL type.
anchor keysInspect and sync program keypairs.
anchor programDeploy and manage programs.

anchor init#

Terminal window
anchor init counter --no-install

Initializes a new workspace. The default program template is the modular Rust template, and the default test template is LiteSVM.

The generated workspace includes:

  • programs/Solana program crates
  • migrations/Deploy scripts
  • tests/Workspace-level tests when selected
  • Anchor.toml Anchor workspace config
  • Cargo.toml Rust workspace config
  • package.json JavaScript dependencies when installed

Useful flags:

FlagDescription
--no-installSkip JavaScript dependency installation.
--no-gitSkip git initialization.
--template multipleUse the modular Rust program layout. This is the default.
--template singleUse a single lib.rs program layout.
--test-template litesvmGenerate Rust LiteSVM tests. This is the default.
--test-template molluskGenerate Mollusk Rust tests.
--javascriptUse JavaScript instead of TypeScript for JS templates.
--package-manager pnpmChoose a package manager instead of auto-detecting.

The LiteSVM template sets skip_local_validator = true in Anchor.toml and adds this feature to the program crate:

[features]
profile = ["anchor-v2-testing/profile"]

That feature is what powers anchor test --profile, anchor debugger, and anchor coverage.

anchor new#

Terminal window
anchor new escrow

Creates a new program under programs/ in an existing workspace. It uses the modular template by default:

Terminal window
anchor new escrow --template multiple
anchor new prototype --template single

anchor new does not create the workspace-level test harness. Use anchor init when starting a new project from scratch.

anchor build#

Terminal window
anchor build

Builds the workspace programs through cargo build-sbf, writes deploy artifacts under target/deploy/, and emits IDLs under target/idl/.

Useful flags:

FlagDescription
--program-name <name>Build a single program.
--no-idlBuild the SBF program without emitting IDL.
--idl <dir>Write IDL JSON to a custom directory.
--idl-ts <dir>Write TypeScript IDL types to a custom directory.
--ignore-keysSkip the program-keypair and declare_id! mismatch check.
--verifiableBuild in a deterministic Docker environment.
--no-docsSuppress doc strings in IDL output.

Forward arguments to cargo build-sbf after --:

Terminal window
anchor build -- --features my-feature

anchor test#

Terminal window
anchor test

Builds the workspace, deploys programs when needed, and runs the configured test script.

In LiteSVM workspaces, the scaffolded skip_local_validator = true setting means anchor test runs Rust tests without starting a validator. If the setting is absent and the configured cluster is localnet, the CLI can still start a local validator unless --skip-local-validator is passed.

Useful flags:

FlagDescription
--program-name <name>Build and test one program.
--skip-buildReuse existing build artifacts.
--skip-deployRun tests against already deployed programs.
--skip-local-validatorDo not start a local validator.
--no-idlSkip IDL generation.
--run <path>Run tests under a specific path.
--validator surfpoolUse Surfpool as the local network backend.
--validator legacyUse solana-test-validator.

Profiling#

Terminal window
anchor test --profile

Records per-test SBF register traces and renders flamegraph SVGs under target/anchor-v2-profile/. This requires Rust tests that call anchor_v2_testing::svm() and a crate feature that forwards to anchor-v2-testing/profile.

anchor debugger#

Terminal window
anchor debugger
anchor debugger initialize
anchor debugger --skip-run

Runs the same trace-producing path as anchor test --profile, then opens a TUI over captured instructions. The optional positional argument filters captured traces to tests whose name contains that substring.

Useful flags:

FlagDescription
--skip-runOpen the TUI over existing traces in target/anchor-v2-profile/.
--skip-buildSkip cargo build-sbf when the deploy artifact is fresh.
--skip-lintForwarded to the underlying test invocation.
--gdbCapture traces through the sbpf gdb-stub path. This is much slower.

The debugger only works with trace-producing Rust tests. If no traces are found, check that the tests call anchor_v2_testing::svm() and that the program crate has profile = ["anchor-v2-testing/profile"].

anchor coverage#

Terminal window
anchor coverage

Builds programs with DWARF info, runs LiteSVM tests with register tracing, maps executed program counters back to Rust source lines, and writes LCOV.

Useful flags:

FlagDescription
--skip-runGenerate coverage from existing traces.
--skip-buildSkip cargo build-sbf when artifacts are fresh.
--output <path>Write LCOV somewhere other than target/coverage/sbf.lcov.
--trace-dir <dir>Read or write traces from a custom directory.
Terminal window
anchor coverage --output target/coverage/sbf.lcov
anchor coverage --skip-run --trace-dir target/coverage/traces

anchor idl#

The idl subcommand builds local IDL files and manages on-chain IDL metadata accounts.

Terminal window
anchor idl build

Builds the IDL for the current program. Use -o to write JSON to a file and -t to write the generated TypeScript IDL type:

Terminal window
anchor idl build --program-name counter -o target/idl/counter.json -t target/types/counter.ts
Terminal window
anchor idl init -f target/idl/counter.json <program-id>

Creates an on-chain IDL account for the given program and writes the IDL file into it.

Terminal window
anchor idl fetch -o counter.json <program-id>

Fetches an on-chain IDL.

Terminal window
anchor idl upgrade -f target/idl/counter.json <program-id>

Upgrades an existing on-chain IDL metadata account.

Other useful IDL commands:

CommandPurpose
anchor idl type <path/to/idl.json>Generate a TypeScript type from an IDL file.
anchor idl convert <legacy-idl.json>Convert a legacy IDL to the newer IDL spec.
anchor idl create-buffer -f <path/to/idl.json>Create a buffer metadata account.
anchor idl set-buffer-authority <buffer> -n <new-authority>Set a new authority on a buffer account.
anchor idl write-buffer <program-id> --buffer <buffer>Write metadata from a buffer account.
anchor idl close <program-id>Close a metadata account and recover rent.

Use on-chain IDLs carefully while this version is alpha because IDL shapes may change between commits.

anchor codama#

The codama subcommand converts an Anchor IDL to a Codama IDL tree and renders client libraries through the @codama/cli toolchain. It is the path for generating cross-language clients from a single program description.

Convert#

Terminal window
anchor codama convert target/idl/counter.json -o target/codama-counter.json

convert translates an Anchor IDL JSON file into a Codama IDL JSON tree rooted at a rootNode. The conversion runs in-process and mirrors the reference TypeScript implementation in @codama/nodes-from-anchor, so the output is stable against the JavaScript toolchain.

Generate#

Terminal window
anchor codama generate -l js,rust -p clients target/idl/counter.json

generate runs the same conversion in-process, then hands the result to @codama/cli (run via npx --yes codama by default) to render clients in one or more languages. Each language is written under <path>/<language>/ and uses the matching @codama/renderers-* package:

LanguageRenderer package
js@codama/renderers-js
js-umi@codama/renderers-js-umi
rust@codama/renderers-rust
go@codama/renderers-go

Flags#

FlagDescription
-l <language>Languages to generate. Repeat or comma-separate: -l js,go -l rust.
-p <path>Base output directory. Default: clients.
-o <file>(Convert only) Write Codama IDL JSON to a file instead of stdout.
Note (Operational notes)

generate shells out to npx, so the host needs a Node.js toolchain on PATH. The Codama IDL is versioned independently from the Anchor IDL, so rendered clients depend on the @codama/nodes version stamped into rootNode.version.

anchor account#

Terminal window
anchor account <program-name>.<AccountTypeName> <account-pubkey>
anchor account <program-name>.<AccountTypeName> <account-pubkey> --idl <path/to/idl.json>

Fetches an account and deserializes it to JSON using an IDL type. Not every account wrapper is a borsh account with an Anchor discriminator. SPL accounts and Pod-backed zero-copy accounts have different layouts.

Workspace utilities#

anchor keys#

Terminal window
anchor keys list
anchor keys sync

Lists program keypairs and syncs each program’s declare_id!() value with the keypair file.

anchor cluster#

Terminal window
anchor cluster list

Lists configured cluster endpoints.

anchor run#

Terminal window
anchor run <script-name>

Runs a script declared in the workspace’s Anchor.toml.

anchor shell#

Terminal window
anchor shell

Starts a Node.js shell with an Anchor client configured from the local workspace. This workflow is most useful for IDL inspection and client experiments while TypeScript package support is alpha.

anchor clean#

Terminal window
anchor clean

Removes generated artifacts except program keypairs.

anchor verify#

Terminal window
anchor verify <program-id>

Verifies that on-chain bytecode matches a locally compiled artifact. Use anchor build --verifiable for deterministic builds.

Examples and benchmarks

Checked-in example programs, benchmark families, and current size and compute numbers.

Examples are checked into the repository rather than published as a separate examples package. The most important worked programs are under bench/programs/.

These examples do three things:

  • They show complete Anchor programs in realistic shapes.
  • They provide repeatable benchmark fixtures for binary size and compute units.
  • They act as source material for docs, since they compile with the current branch.

If you are learning the framework, read helloworld first, then vault, then multisig. That path moves from one instruction, to PDA-owned state, to a larger program with multiple handlers and shared validation.

Benchmark programs#

ProgramPathWhat it demonstrates
helloworldbench/programs/helloworld/anchor-v2Minimal counter-style init path and baseline framework overhead.
vaultbench/programs/vault/anchor-v2SOL deposit and withdraw flows with PDA validation.
nestedbench/programs/nested/anchor-v2Shared account validation through Nested<T>.
multisigbench/programs/multisig/anchor-v2Multi-instruction SOL multisig with dynamic signer data.
prop-ammbench/programs/prop-amm/anchor-v2Oracle feed with a small assembly fast path.
humidifibench/programs/humidifi/anchor-v2Standalone benchmark program checked in with a built artifact.

The benchmark harness is the anchor-bench crate under bench/. It builds each program, loads the resulting .so into LiteSVM, runs the relevant instruction cases, records binary sizes, and measures compute units from the executed transaction metadata.

Current headline numbers#

The current overview numbers are directional, not a stability guarantee. This version is alpha and exact values can move as codegen, pinocchio, and tooling change.

ProgramDescriptionBinaryCU rangeBin downCU down
helloworldSingle-instruction counter6.5 KB1,39519.2x4.2x
prop-ammOracle feed with asm fast path8.3 KB26-1,37916.8x3.1-50.4x
vaultSingle-depositor SOL vault5.4 KB393-1,90519.8x3.0-6.3x
nestedShared validation via Nested<T>13.3 KB476-2,87211.9x6.9-10.0x
multisigFour-instruction SOL multisig31.2 KB475-3,0395.4x3.0-9.1x

Binary and CU range are from the current Anchor implementation in each family. Bin down and CU down compare against the older paired Anchor implementation used by the benchmark harness.

What each family compares#

The harness groups programs into families so the same instruction shape can be compared across implementations.

FamilyCompared variantsInstruction cases
hello_worldAnchor, older Anchor, Quasar, Pinocchio, Steelinit
vaultAnchor, older Anchor, Quasar, Pinocchio, Steeldeposit, withdraw
nestedAnchor and older Anchorinitialize, increment, reset
multisigAnchor and older Anchorcreate, deposit, set_label, execute_transfer
prop_ammAnchor plus asm fast path and older Anchorinitialize, update, rotate_authority

The non-Anchor variants help calibrate the benchmark against lower-level frameworks. The Anchor rows are the rows used for the headline documentation table.

Running benchmarks#

From the repository root, run a specific benchmark integration test:

Terminal window
cargo test -p anchor-bench helloworld_end_to_end -- --nocapture

Other benchmark tests are:

Terminal window
cargo test -p anchor-bench vault_end_to_end -- --nocapture
cargo test -p anchor-bench nested_end_to_end -- --nocapture
cargo test -p anchor-bench multisig_end_to_end -- --nocapture
cargo test -p anchor-bench prop_amm_end_to_end -- --nocapture

Set BENCH_SKIP_BUILD=1 in your shell to reuse existing .so artifacts:

Terminal window
BENCH_SKIP_BUILD=1 cargo test -p anchor-bench helloworld_end_to_end -- --nocapture

Run the full benchmark test suite when updating all headline numbers:

Terminal window
cargo test -p anchor-bench --tests -- --nocapture

Reading benchmark output#

Each test asserts that every expected suite produced a nonzero binary size and a sane compute-unit measurement. With --nocapture, the tests also print comparison tables sorted by compute units.

The output table includes:

ColumnMeaning
rankPosition within the family for one instruction.
variantHuman-readable implementation label.
bytesSize of the built deploy artifact.
CUCompute units consumed by the measured transaction.
vs bestDifference from the lowest-CU variant in that table.

How comparisons stay fair#

The paired programs are written to keep the business logic and addresses comparable:

  • Anchor variants share program IDs within each benchmark family.
  • PDA seeds are kept equivalent so bump differences do not skew results.
  • The harness measures the same instruction cases for each variant.
  • LiteSVM executes the transactions and reports compute units.
  • Binary size comes from the built deploy artifact.
  • The benchmark payer keypair is derived deterministically from a fixed label.

The suite table in bench/src/lib.rs is the source of truth for program IDs, manifest paths, instruction names, and case builders.

Reading the examples#

Use the examples as source material for real Anchor code:

ExampleUseful for
helloworld/anchor-v2Minimal #[program], PDA init, generated Rust instruction builders.
vault/anchor-v2Lamport movement, PDA resolution, and comparison with lower-level implementations.
nested/anchor-v2Reusing validation logic across instructions with Nested<T>.
multisig/anchor-v2Fixed-size state, PodVec<T, MAX>, dynamic signer validation, and resolved account builders.
prop-amm/anchor-v2Performance-oriented program structure and custom fast paths.

For SPL token examples, also read tests-v2/programs/spl/src/lib.rs. That test program exercises anchor-spl-v2 init constraints, CPI helpers, interface accounts, and Token-2022 extension readers.

Updating the docs#

When updating benchmark numbers, rerun the relevant anchor-bench tests and update both Anchor overview and this page together. The overview table is the summary. This page is the reference that explains where to find the examples, how the measurements are produced, and how to reproduce them.

Keep benchmark prose modest. The numbers are strong, but this version is still alpha, not audited, and not published on crates.io.

Alpha limitations

Known caveats and incomplete surfaces while v2 is alpha.

This version is alpha. It is not audited, not on crates.io, and APIs may break between commits.

Keep these limitations in mind while depending on the anchor-next branch:

  • Rust crates are consumed from git on anchor-next.
  • no stable v2 TypeScript package has been published yet. Current scaffolds remain pinned to @anchor-lang/core ^1.0.0.
  • anchor-spl-v2 covers core token, mint, interface account, and several Token-2022 extension paths. Less-common SPL surfaces, especially metadata-oriented constraints, may not be covered yet.
  • generated program::cpi::* wrappers are available for supported account structs, but account structs containing Option<_> or Nested<_> are currently skipped by CPI wrapper generation.
  • runtime PDA validation supports both array-form seeds and opaque seed expressions. Client-side derivation and rich IDL seed metadata work best for array-form seeds that the macro can inspect.

Do not treat the safety defaults as an audit. Review, fuzz, and test before depending on this version for production funds.

Esc

Start typing to search the docs.