Anchor In Depth
Anchor is the dominant framework for writing Solana programs in Rust. It sits on the same Agave runtime as native programs (SBF bytecode, explicit account metas, compute units, CPI), but it generates the glue you would otherwise hand-write: instruction discriminators, entrypoint dispatch, declarative account checks, typed errors, events, and an IDL that clients consume.
This page is the umbrella for anchor-basics: what Anchor is, how the #[program] module and #[derive(Accounts)] structs form the instruction surface, how a workspace is laid out, how build/deploy and local testing fit together, and how Anchor compares to native programs.
Summary
- Anchor 0.32.1 turns Solana program development into a constrained, IDL-backed workflow: declare program ID and handlers in
#[program], declare accounts and constraints inAccountsstructs, thenanchor build/anchor test/anchor deployship binary plus client schema. - Insight: Most production bugs on Solana are account-validation and client-schema bugs, not clever business-logic bugs. Anchor fails closed on missing signers, wrong owners, and bad PDA seeds, and it keeps TypeScript (and other) clients honest via the IDL.
- Key Concepts:
declare_id!,#[program],Context<T>,#[derive(Accounts)], constraints,#[account]state, IDL,Anchor.toml, SBF /.so, native vs Anchor. - When to Use: Building application programs, admin surfaces, escrow and marketplace logic, SPL-adjacent apps, and anything that benefits from typed clients on @solana/kit 7.0.0 or Anchor's TS stack.
- Limitations/Trade-offs: Macro expansion adds binary size and some CU overhead; extreme hot paths may want Pinocchio or native; you still must design layouts, threats, and migrations yourself.
- Related Topics: Anchor basics examples, project structure, program module, accounts struct, workflow, build and deploy, Anchor vs native.
Foundations
What Anchor is
At the runtime level, a Solana program still implements roughly process_instruction(program_id, accounts, instruction_data). Anchor does not change that contract. It generates the entrypoint and unpacks instruction data into named handlers with typed args.
What you write is a workspace: Rust crates under programs/, TypeScript (or other) tests under tests/, and Anchor.toml for cluster, program IDs, and scripts. The framework crate is anchor-lang 0.32.1 (plus anchor-spl when you touch Token/Associated Token).
Anchor's product promise is three-fold:
- Safety defaults - account validation runs before your handler body; missing constraints error instead of silently trusting client accounts.
- Ergonomics - less boilerplate for discriminators, CPI helpers, and common account types (
Signer,Account<T>,Program<T>). - Client contract -
anchor buildemits IDL JSON so off-chain code and CPI modules stay aligned with on-chain instructions.
The program module
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod counter {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.counter.count = 0;
ctx.accounts.counter.authority = ctx.accounts.authority.key();
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
ctx.accounts.counter.count = ctx.accounts.counter.count.checked_add(1).unwrap();
Ok(())
}
}declare_id!hard-codes the program public key the binary expects; it must match deployment (useanchor keys syncafter deploy keypair changes).#[program]marks the module whose public functions become instructions.- Each handler takes
Context<SomeAccounts>(and optional instruction args), returnsResult<()>, and mutates only through validated account fields.
Instruction data is not free-form bytes you parse ad hoc: Anchor derives 8-byte discriminators and Borsh-serializes arguments. Clients that skip the IDL (or regenerate it late) break in confusing ways.
The Accounts struct
Every instruction names an accounts struct with #[derive(Accounts)]. Field types and attributes are the validation program.
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
payer = authority,
space = 8 + 8 + 32,
seeds = [b"counter", authority.key().as_ref()],
bump
)]
pub counter: Account<'info, Counter>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct Counter {
pub count: u64,
pub authority: Pubkey,
}Before initialize runs, Anchor checks signers, mutability, PDA seeds, system program id, and that init allocates and assigns ownership correctly. Account<'info, Counter> deserializes with the 8-byte account discriminator. Failures return Anchor errors instead of partial writes.
Project structure (minimal map)
workspace/
Anchor.toml # toolchain, programs.<cluster>, scripts
Cargo.toml # workspace members = ["programs/*"]
programs/my_program/
Cargo.toml # anchor-lang = "0.32.1"
src/lib.rs
tests/*.ts
target/deploy/*.so # after build
target/idl/*.json # after buildAnchor.toml pins which program id maps to which cluster name. Root Cargo.toml is a Rust workspace; each program is its own crate. Tests typically call the program through a provider (local validator / Surfpool) using the generated IDL.
Build, deploy, and workflow in one sentence
anchor build compiles SBF and regenerates the IDL; anchor test deploys locally and runs tests; anchor deploy pushes the .so to a cluster; clients and CPI consumers must track the new IDL and program id.
Anchor vs native (orientation)
Native programs use entrypoint!, manual account walking, and hand-maintained client codecs. Anchor generates that surface. You pay some size/CU and gain constraints plus IDL. Hot settlement cores sometimes leave Anchor; most product programs should not start native "for purity."
Mechanics & Interactions
How the pieces interact end to end:
Edit lib.rs (#[program] + Accounts + #[account] state)
|
v
anchor build --> target/deploy/*.so + target/idl/*.json
|
v
anchor test --> local validator/Surfpool + TS tests
|
v
anchor deploy --> upgradeable program on cluster
|
v
keys sync / IDL publish --> clients & declare_program! consumers
Dispatch path on-chain
- Transaction lists program id, accounts, and instruction data.
- Runtime loads the program
.soand invokes the generated entrypoint. - Anchor matches the 8-byte instruction discriminator to a handler.
- Anchor deserializes args and builds
Context<T>by validating theAccountsstruct. - Your handler runs business logic and returns
Ok(())or an error. - On success, account data writes and lamport changes commit with the transaction; on error, the whole transaction fails.
Context is the handoff
Context<T> holds accounts: T, program_id, bumps (for PDA bumps discovered during validation), and remaining accounts when you use them. Handlers should not re-parse AccountInfo lists from scratch; they should trust (and only extend with require!) what constraints already enforced.
Constraints are the security boundary
Common attributes:
| Constraint / type | Role |
|---|---|
Signer | Transaction signature present |
mut | Writable account meta required |
init / init_if_needed | Create (careful with the latter) |
seeds + bump | PDA derivation and address check |
has_one | Field equality (e.g. stored authority) |
Program<T> | Known program id (System, Token, …) |
constraint = expr | Custom boolean invariant |
Order matters for init and CPI safety; treat validation order as part of design, not an accident of attribute listing. Custom business rules belong in constraints or early require!, not "we will check later if we remember."
Workspace config interactions
- Program id drift: binary
declare_id!,Anchor.toml[programs.*], and on-chain deploy keypair must agree. After deploy,anchor keys syncrewrites sources/config when the keypair is the source of truth. - IDL drift: public API changes (new ix, renamed fields, account layout) require rebuild and client regen. Shipping only a new
.sowithout IDL is how dapps call wrong discriminators. - Cluster isolation: localnet, devnet, and mainnet-beta are separate account universes; never reuse a devnet program id assumption on mainnet clients.
Client side
Tests and apps build instructions from the IDL. @solana/kit 7.0.0 does not require Anchor specifically, but it needs correct wire formats. Anchor's strength is producing that contract from the same macros that generate the program. Prefer regenerating typed clients in CI when IDL changes.
Advanced Considerations & Applications
Designing instructions as an API surface
Think of each #[program] function as a versioned public method. Stable discriminators and stable account orders matter more than clever Rust modules. Prefer additive instructions and versioned account layouts over silent field reorder.
When you change account space, plan realloc or migration instructions; clients holding old layouts will fail discriminator or deserialize checks for good reason.
Fail-closed defaults vs residual risk
Anchor removes entire classes of mistakes (unsigned authority, wrong program owner on Account<T>, wrong system program address). It does not invent your threat model:
- Who may call this instruction?
- Which accounts are still
UncheckedAccountand why? - Are token accounts the expected mint/authority?
- Is
init_if_neededcreating re-init attack surface? - Do CPI targets and remaining accounts bounds hold under adversarial metas?
Security review still focuses on constraints, seeds, and CPI graphs - not only handler math.
Multi-program workspaces and CPI
A workspace can hold multiple crates under programs/. Cross-program calls need correct account metas and, in Anchor 0.32, often declare_program! or shared types for the callee. Framework boundaries still compose via CPI: an Anchor program can call native/Pinocchio if bytes and seeds match.
Release discipline that matches the framework
- Pin Anchor CLI / anchor-lang 0.32.1, Rust 1.91.1, Solana CLI 3.0.10 in docs and CI.
anchor buildand commit or publish IDL artifacts intentionally (policy varies; do not leave them accidental).anchor testgreen on local stack before devnet.- Devnet deploy + integration tests against real RPC.
- Mainnet only with verifiable builds, upgrade authority hygiene, and a rollback story.
When to stay on Anchor vs leave
| Situation | Prefer |
|---|---|
| MVP, admin, marketplace, standard DeFi app logic | Anchor 0.32.1 |
| Strong need for IDL-driven clients and fast iteration | Anchor |
| Measured CU ceiling on a hot instruction | Optimize Anchor first; extract Pinocchio/native core if still tight |
| Extreme binary size / audit wants fully explicit checks | Native or Pinocchio (see Anchor vs native) |
| Mixed system | Anchor periphery + CPI to lean core |
Profile with fixed transactions and unitsConsumed; do not rewrite frameworks on vibes.
Common Misconceptions
- "Anchor is a different runtime or a separate chain." It compiles to ordinary SBF programs on Agave; validators do not special-case Anchor.
- "Constraints mean I can skip security design." Constraints enforce what you declare. Undeclared trust assumptions remain exploitable.
- "
declare_id!is decorative." Mismatch with the deployed program causes runtime failures (DeclaredProgramIdMismatch). Sync after key changes. - "Handlers run before account checks." Validation of the
Accountsstruct runs first; the handler assumes a validContext. - "IDL is optional documentation." For Anchor programs, the IDL is the client contract. Treat it as a release artifact.
- "Native is always faster, so always start native." Often false for team velocity and safety. Measure; default to Anchor for application programs.
- "
anchor deployalone updates all clients." Deploy updates on-chain bytecode; apps, indexers, and CPI modules need the matching IDL and program id. - "Any
AccountInfoin remaining accounts is safe if the main struct is strict." Remaining accounts are a common bypass surface; bound and validate them explicitly.
FAQs
What is Anchor in one sentence?
A Rust framework for Solana programs that generates instruction dispatch, declarative account validation, errors/events, and an IDL from macros on top of the standard Agave runtime.
Which versions does this site pin?
Anchor 0.32.1, Rust 1.91.1, Solana CLI 3.0.10, Agave 4.1.1, and @solana/kit 7.0.0 as the reference client stack.
What does #[program] generate?
The program entrypoint, instruction discriminator matching, argument deserialization, and the wiring from each public function to its Context<T> validation path.
What is Context<T>?
The handler argument that holds validated accounts (T), the program id, PDA bumps discovered during validation, and optional remaining accounts.
What does #[derive(Accounts)] do?
It builds a typed accounts list and runs type-level plus attribute constraints (signer, mut, owner, seeds, init, custom expressions) before the handler body executes.
Why do Accounts structs use a lifetime <'info>?
Account references are tied to the instruction's AccountInfo borrow scope for that invocation; the lifetime tracks that short-lived borrow correctly in Rust.
What is the difference between Account<T> and UncheckedAccount?
Account<T> enforces program ownership and deserializes T with Anchor's account discriminator. UncheckedAccount skips those checks; you must validate manually or you are open to attack.
How does project structure relate to deploy?
programs/*/src is the on-chain crate; target/deploy holds the .so; Anchor.toml maps program names to public keys per cluster; tests and clients consume target/idl.
What does anchor build produce?
An SBF shared object under target/deploy/ and IDL JSON under target/idl/, plus related keypair artifacts for deployable programs.
How should I iterate day to day?
Change program code and accounts, run anchor build, run anchor test locally, fix failures, then promote to devnet deploy and client updates before mainnet.
When should I choose native over Anchor?
When measured CU or binary size requirements, or audit/process preferences for fully manual validation, outweigh IDL and macro productivity - often as a hot-path extract, not a whole-app rewrite.
Can Anchor programs call native programs?
Yes, via CPI, when account metas, ownership, data layouts, and PDA signer seeds match what the callee expects.
Do I still need Solana CLI if I use Anchor CLI?
Yes. Solana CLI 3.0.10 handles keypairs, balances, airdrops, cluster config, and solana program inspection alongside anchor commands.
Where should I go next in this section?
Start with Anchor Basics for concrete examples, then Project Structure, The Accounts Struct, The Anchor Workflow, Building & Deploying, and Anchor vs Native.
Related
- Anchor Basics - scaffold, handlers, IDL, and first tests
- Project Structure - workspace layout, Anchor.toml, and crates
- The Accounts Struct - derive(Accounts), types, and constraints
- The Anchor Workflow - local build, test, promote to devnet/mainnet
- Building & Deploying - anchor build, deploy, keys sync, IDL
- Anchor vs Native - what macros generate and when native wins
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1, and @solana/kit 7.0.0.