Skip to main content
TLDR:
  • Anchor is the standard framework for building Solana programs. It handles account validation, serialization, and security checks through Rust macros.
  • anchor init scaffolds a full workspace: program source, tests, deployment config, and TypeScript types.
  • The #[program] macro defines instructions, #[derive(Accounts)] defines account validation, and #[account] defines on-chain state.
  • Anchor 1.0.0 (April 2026) ships breaking changes: @anchor-lang/core replaces @coral-xyz/anchor, Surfpool replaces solana-test-validator, and LiteSVM is the default test template.

Prerequisites

Install the following before starting. The guide targets Anchor 1.0.0. Verify your setup:
Anchor 1.0.0 uses Surfpool instead of solana-test-validator for anchor test. If Surfpool is not installed, anchor test will fail with a connection error. If you cannot install Surfpool, use anchor test --validator legacy to fall back to solana-test-validator.
Anchor 1.0.0 no longer requires the Solana CLI to be on your PATH for most commands. The anchor CLI ships native implementations of balance, airdrop, address, and deploy. You still need the Solana CLI for operations like solana-keygen and solana program close.

Creating a project

Scaffold a new Anchor workspace:
This creates a modular project structure with separate files for instructions, state, constants, and errors. If you prefer everything in a single lib.rs, use --template single:
The modular template (default) is recommended for production programs. It scales better as your program grows.

Test templates

Anchor 1.0.0 generates a LiteSVM Rust test by default. You can pick a different template:

Project structure

After anchor init, the workspace looks like this:
Key directories:
  • programs/ — your on-chain programs. A workspace can contain multiple programs.
  • target/deploy/ — the compiled program binary (.so file) and program keypair.
  • target/idl/ — the generated IDL JSON file that clients use to interact with your program.
  • target/types/ — TypeScript type definitions generated from the IDL.

Anchor.toml

The workspace configuration file controls which cluster you deploy to, which wallet signs transactions, and what scripts run during testing:
You can optionally pin versions in the [toolchain] section:
The [registry] section has been removed in Anchor 1.0.0. If you see it in older examples, delete it.

Program anatomy

An Anchor program is built around four macros. Here is a complete counter program that demonstrates all of them:
The sections below break down each macro.

declare_id!

Sets the program’s on-chain address. By default, this matches the public key from /target/deploy/my_counter-keypair.json.
If the keypair and source code fall out of sync (for example, after cloning a repo), run:
Anchor 1.0.0 checks that the declare_id! value matches the keypair file during anchor build. A mismatch produces a compile error. Use anchor build --ignore-keys to skip this check if needed.

#[program]

Defines the module containing your instruction handlers. Each public function in this module becomes a callable instruction:
Every handler receives a Context<T> as its first parameter, where T is the accounts struct. Additional parameters become the instruction arguments that clients pass in. The Context struct provides:
  • ctx.accounts — the validated accounts
  • ctx.program_id — the program’s public key
  • ctx.remaining_accounts — extra accounts not in the struct
  • ctx.bumps — PDA bump seeds found during validation

#[derive(Accounts)]

Defines which accounts an instruction requires and how to validate them. Each field specifies an account with its type and constraints:
Anchor validates all constraints before executing the instruction handler. If any check fails, the instruction aborts with a descriptive error. Common account types:

#[account]

Defines the data layout for accounts your program creates. Anchor adds an 8-byte discriminator as a prefix to distinguish account types:
The discriminator is the first 8 bytes of SHA256("account:Counter"). It is automatically set during init and verified during deserialization.

Account constraints

Constraints are the core of Anchor’s security model. They go inside #[account(...)] attributes and tell Anchor what to validate before executing your instruction.

Initialization constraints

Create a new account and set its discriminator:
The init constraint requires:
  • payer — the account paying the rent
  • space — total bytes (8-byte discriminator + your data)
Use init_if_needed to create only if the account does not already exist. This requires the init-if-needed feature in your Cargo.toml:

PDA constraints

Validate that an account is a PDA derived from specific seeds:
When combined with init, this creates the PDA account:
Anchor finds and stores the canonical bump automatically. Access it in your handler with ctx.bumps.counter.
Use fixed-length seeds or include a separator between variable-length seeds. Without this, seeds = [b"ab", b"cd"] and seeds = [b"abcd"] produce the same PDA, which can lead to seed collision vulnerabilities.

Mutability and ownership

Reallocation

Resize an existing account:

SPL token constraints

Create or validate token accounts:

Accessing instruction arguments in constraints

Use #[instruction(...)] to reference instruction arguments in your constraints:
In Anchor 1.0.0, the compiler enforces that #[instruction(...)] argument types and order match the handler signature. Mismatches that were previously silent now produce errors.

Space calculation

Every account needs enough space for the 8-byte discriminator plus its data fields.

Type sizes

Manual calculation

Using InitSpace

The InitSpace derive macro calculates space automatically. Use #[max_len(...)] for dynamic types:
The #[max_len(5, 50)] on Vec<String> means: up to 5 strings, each up to 50 bytes.

Building and the IDL

Build your program:
This produces three outputs:
  1. Program binarytarget/deploy/my_counter.so — the compiled BPF bytecode.
  2. IDL filetarget/idl/my_counter.json — a JSON description of your program’s instructions, accounts, and types.
  3. TypeScript typestarget/types/my_counter.ts — type definitions generated from the IDL.

IDL structure

The IDL maps your Rust program to a format that clients can consume:

On-chain IDL via Program Metadata Program

Anchor 1.0.0 replaces the legacy IDL storage mechanism with the Program Metadata Program (PMP). When you run anchor program deploy, the IDL is uploaded automatically. To skip the upload:

Testing

Anchor 1.0.0 defaults to LiteSVM for Rust-based testing. LiteSVM runs an in-process Solana VM—faster startup and teardown than solana-test-validator.

TypeScript tests

For TypeScript tests (generated with --test-template mocha), the test file uses @anchor-lang/core:
Anchor 1.0.0 renamed the TypeScript package from @coral-xyz/anchor to @anchor-lang/core. All existing tutorials and Stack Overflow answers that show @coral-xyz/anchor need the import updated.

Running tests

Anchor 1.0.0 uses Surfpool as the default local validator for anchor test:
This starts Surfpool, builds and deploys your program, runs the test suite, and shuts down the validator. To use the legacy solana-test-validator instead:
To run tests against an already-running local validator:

Deploying

Local deployment

Start a local validator and deploy:
If anchor program deploy fails with a 503 error on get_recommended_micro_lamport_fee, pass a manual compute unit price:
This is a known issue that affects some local RPC configurations.

Deploying to devnet

  1. Set your cluster to devnet in Anchor.toml:
  1. Fund your wallet:
  1. Build and deploy:
anchor program deploy in 1.0.0 automatically uploads the IDL to the on-chain Program Metadata Program. The IDL becomes fetchable by any client using Program.fetchIdl.

Deploying to mainnet with Chainstack

  1. Deploy a Solana node on Chainstack or use the Chainstack Solana endpoint.
  2. Set your cluster to your Chainstack endpoint in Anchor.toml:
  1. Build and deploy:
Mainnet deployment requires real SOL. Verify your program thoroughly on devnet before deploying to mainnet.

Client interaction

The IDL enables typed client interactions. Anchor’s TypeScript client (in the @anchor-lang/core package) deserializes accounts and instruction arguments automatically.

Connecting to a program

The anchor.workspace object is only available inside anchor test. For standalone scripts and frontend code, load the IDL JSON file directly and instantiate the program with new Program(idl, provider).

Sending transactions

The wallet must be funded before sending transactions. On devnet, request an airdrop first:

Fetching accounts

Listening to events

If your program emits events with the emit! macro:
Subscribe from the client:

Common gotchas

CpiContext takes Pubkey, not AccountInfo

Anchor 1.0.0 removed the redundant AccountInfo copy from CpiContext. If you are adapting pre-1.0 code:

Duplicate mutable accounts are now rejected

Passing the same mutable account twice in an instruction errors by default. If you intentionally need this, opt in with dup:

AccountInfo is deprecated in Accounts structs

Using AccountInfo directly inside #[derive(Accounts)] now produces a compile warning. Use UncheckedAccount instead:

Space must include the 8-byte discriminator

Every init constraint needs 8 extra bytes for the discriminator:

Error codes are limited to one block

Anchor 1.0.0 restricts #[error_code] to a single definition per program. If you have multiple error blocks, consolidate them:

Surfpool vs solana-test-validator

anchor test now uses Surfpool by default. If you encounter compatibility issues:
Surfpool is faster but less feature-complete than solana-test-validator. Use the legacy validator when you need full RPC method support or when cloning accounts from mainnet.

Reference repos

These are the source repositories we worked against while writing this guide. They stay closer to reality than docs — check them first when something here looks off.
Last modified on April 20, 2026