| name | anchor-core |
| description | Core Anchor programming patterns including program structure, account types, constraints, space calculation, and error handling. Always load this skill when writing or reviewing any Anchor program code. |
Anchor Core
Core Anchor programming patterns for building secure Solana programs. This skill covers program structure, account types, constraints, space calculation, and error handling.
Quick Reference
Minimal Program Template
use anchor_lang::prelude::*;
declare_id!("YourProgramIDHere");
#[program]
mod your_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = user, space = 8 + 8)]
pub my_account: Account<'info, MyAccount>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct MyAccount {
pub data: u64,
}
Common Account Types
| Type | Purpose | Example |
|---|
Account<'info, T> | Owned program account | Account<'info, EscrowAccount> |
Signer<'info> | Signature verification | pub authority: Signer<'info> |
Program<'info, T> | Program validation | Program<'info, System> |
SystemProgram | System program reference | pub system_program: Program<'info, System> |
UncheckedAccount<'info> | No automatic checks | Requires /// CHECK: comment |
Most-Used Constraints
| Constraint | Purpose | Example |
|---|
mut | Account is mutable | #[account(mut)] |
signer | Account must sign | #[account(signer)] (use Signer type instead) |
init | Initialize new account | #[account(init, payer = user, space = 8 + 32)] |
has_one | Validate relationship | #[account(has_one = authority)] |
constraint | Custom validation | #[account(constraint = amount > 0)] |
seeds, bump | PDA validation | #[account(seeds = [b"escrow"], bump)] |
close | Close account | #[account(mut, close = destination)] |
Error Handling Quick Pattern
#[error_code]
pub enum ErrorCode {
#[msg("Amount must be greater than zero")]
InvalidAmount,
#[msg("Insufficient balance")]
InsufficientBalance,
}
require!(amount > 0, ErrorCode::InvalidAmount);
Program Structure
The Three Components
Every Anchor program has three essential parts:
declare_id! - Stores your program's on-chain address
#[program] - Contains your business logic (handler functions)
#[derive(Accounts)] - Validates incoming accounts
Source: Anchor Book - High-level Overview
Program ID Declaration
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
The declare_id! macro creates an ID field that stores your program's address. Anchor uses this for:
- Security checks (verifying account ownership)
- Allowing other programs to reference your program
- PDA derivation
Program Module
#[program]
mod hello_anchor {
use super::*;
pub fn set_data(ctx: Context<SetData>, data: u64) -> Result<()> {
ctx.accounts.my_account.data = data;
Ok(())
}
}
Handler Function Signature:
- First argument:
ctx: Context<T> where T is your Accounts struct
- Additional arguments: Instruction data (automatically deserialized)
- Return type:
Result<()> or Result<T> for return values
Source: Anchor Book - The Program Module
Context Access
Through the ctx argument you can access:
ctx.accounts
ctx.program_id
ctx.remaining_accounts
ctx.bumps
Instruction Data
Add arguments after ctx for instruction data:
pub fn transfer(ctx: Context<Transfer>, amount: u64, memo: String) -> Result<()> {
Ok(())
}
For custom types, derive AnchorSerialize and AnchorDeserialize:
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct TransferData {
pub amount: u64,
pub memo: String,
}
pub fn transfer(ctx: Context<Transfer>, data: TransferData) -> Result<()> {
Ok(())
}
Note: The #[account] macro automatically implements these traits, so you can use account structs directly as instruction data.
Account Types Reference
Source: Anchor Accounts Documentation
Source: Anchor Book - The Accounts Struct
Account<'info, T>
The most common account type. Validates that an account is owned by your program and deserializes its data.
#[derive(Accounts)]
pub struct SetData<'info> {
#[account(mut)]
pub my_account: Account<'info, MyAccount>,
}
#[account]
pub struct MyAccount {
pub data: u64,
pub authority: Pubkey,
}
Account<'info, T> automatically checks:
- Account is owned by the program declared in the crate where
T is defined
- Account discriminator matches
T (prevents type cosplay attacks)
- Account data deserializes correctly to
T
When to use: Any account owned by your program that stores structured data.
Signer<'info>
Validates that an account signed the transaction.
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub authority: Signer<'info>,
}
Automatically checks:
- Account's
is_signer flag is true
When to use: Any account that must provide a signature (user authority, payer, etc.).
Note: Using Signer<'info> is preferred over #[account(signer)] constraint for clarity.
Program<'info, T>
Validates that an account is a specific program.
use anchor_spl::token::Token;
#[derive(Accounts)]
pub struct TransferTokens<'info> {
pub token_program: Program<'info, Token>,
}
Automatically checks:
- Account address matches the expected program ID
When to use: Validating programs before making CPIs (prevents arbitrary CPI attacks).
SystemProgram
Type alias for Program<'info, System>.
#[derive(Accounts)]
pub struct Initialize<'info> {
pub system_program: Program<'info, System>,
}
When to use: When you need to make CPIs to the system program (create accounts, transfer SOL).
UncheckedAccount<'info>
No automatic validation. Requires documentation explaining why checks aren't needed.
#[derive(Accounts)]
pub struct Initialize<'info> {
pub authority: UncheckedAccount<'info>,
}
Checks: None (you must validate manually if needed)
When to use:
- PDA signers (accounts that will sign via CPI)
- Accounts where you only need the address
- Accounts you'll validate manually
IMPORTANT: Must include /// CHECK: doc comment explaining why no checks are needed, or compilation will fail.
AccountInfo<'info>
Raw Solana account. Similar to UncheckedAccount but provides direct access to account fields.
#[derive(Accounts)]
pub struct RawAccess<'info> {
pub raw_account: AccountInfo<'info>,
}
When to use: Advanced cases requiring direct account manipulation.
IMPORTANT: Also requires /// CHECK: documentation.
Using Non-Anchor Program Accounts
Anchor provides wrapper types for common programs (SPL Token, Associated Token, etc.):
use anchor_spl::token::TokenAccount;
#[derive(Accounts)]
pub struct CheckBalance<'info> {
#[account(constraint = token_account.amount > 0)]
pub token_account: Account<'info, TokenAccount>,
}
The TokenAccount type is a wrapper that:
- Validates the account is owned by the Token program
- Deserializes the account data
- Provides typed access to fields (amount, mint, owner, etc.)
Constraints Catalog
Complete Reference: Anchor Account Constraints
Constraints validate accounts before your handler runs. Apply them with #[account(...)]:
#[account(<constraint1>, <constraint2>, ...)]
pub account_name: AccountType
Account Validation Constraints
mut - Account is Mutable
#[account(mut)]
pub my_account: Account<'info, MyAccount>,
Checks that is_writable is true. Required for any account that will be modified.
signer - Account Must Sign
#[account(signer)]
pub authority: AccountInfo<'info>,
Checks that is_signer is true.
Note: Using Signer<'info> type is preferred over this constraint.
owner = <expr> - Validate Owner
#[account(owner = token_program.key())]
pub token_account: AccountInfo<'info>,
Checks that account.owner == <expr>.
Note: Account<'info, T> automatically validates owner, so this is mainly for AccountInfo or UncheckedAccount.
address = <expr> - Exact Address Check
#[account(address = expected_address)]
pub special_account: AccountInfo<'info>,
Checks that account.key() == <expr>.
Common uses:
- Validating sysvar addresses
- Hardcoded authority addresses
- Program-specific constants
executable - Account is a Program
#[account(executable)]
pub program: AccountInfo<'info>,
Checks that executable flag is true.
Relationship Validation Constraints
has_one = <target> - Validate Account Relationship
#[account(has_one = authority)]
pub my_account: Account<'info, MyAccount>,
pub authority: Signer<'info>,
Checks that my_account.authority == authority.key().
This is equivalent to:
#[account(constraint = my_account.authority == authority.key())]
Critical for security: Prevents unauthorized access by validating account relationships.
Example from sealevel-attacks:
#[derive(Accounts)]
pub struct UpdateUser<'info> {
#[account(has_one = authority)]
user: Account<'info, User>,
authority: Signer<'info>,
}
Source: Sealevel Attacks - Account Data Matching
constraint = <expr> - Custom Validation
#[account(
constraint = amount > 0,
constraint = amount <= max_amount
)]
pub transfer: Account<'info, Transfer>,
Arbitrary boolean expressions. All must evaluate to true.
With custom error:
#[account(
constraint = amount > 0 @ ErrorCode::InvalidAmount
)]
Common patterns:
- Numeric validations:
amount > 0, balance >= amount
- Key comparisons:
account_a.key() != account_b.key()
- State checks:
escrow.status == Status::Active
Lifecycle Constraints
init - Initialize New Account
#[account(
init,
payer = user,
space = 8 + 32 + 8
)]
pub new_account: Account<'info, MyAccount>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
Required with init:
payer = <account> - Who pays for account creation
space = <bytes> - Account size (must include 8-byte discriminator)
Automatically:
- Creates account via CPI to system program
- Sets account owner to your program
- Writes discriminator
- Initializes account data
Security: Prevents reinitialization attacks (account can only be init once).
init_if_needed - Conditional Initialization
#[account(
init_if_needed,
payer = user,
space = 8 + 32
)]
pub maybe_account: Account<'info, MyAccount>,
Initializes only if account doesn't exist. If it exists, validates like normal.
Requires: init-if-needed feature in Cargo.toml
Use with caution: Can have unexpected behavior if account data is important.
close = <target> - Close Account
#[account(mut, close = destination)]
pub account_to_close: Account<'info, MyAccount>,
#[account(mut)]
pub destination: AccountInfo<'info>,
At end of instruction:
- Transfers all lamports from account to destination
- Zeroes account data
- Resets account owner to system program
Security: Prevents account revival attacks. Rent is returned to destination.
Source: Sealevel Attacks - Closing Accounts
realloc - Resize Account
#[account(
mut,
realloc = new_size,
realloc::payer = payer,
realloc::zero = false
)]
pub my_account: Account<'info, MyAccount>,
Resizes account at beginning of instruction.
Parameters:
realloc = <size> - New size in bytes
realloc::payer = <account> - Who pays/receives if size changes
realloc::zero = <bool> - Whether to zero new space
PDA Constraints
seeds + bump - PDA Derivation and Validation
#[account(
seeds = [b"escrow", owner.key().as_ref(), &escrow.index.to_le_bytes()],
bump
)]
pub escrow: Account<'info, EscrowAccount>,
With init (finds canonical bump):
#[account(
init,
payer = owner,
space = 8 + EscrowAccount::INIT_SPACE,
seeds = [b"escrow", owner.key().as_ref(), &index.to_le_bytes()],
bump
)]
Access the bump: ctx.bumps.escrow
With stored bump (more efficient):
#[account(
seeds = [b"escrow", owner.key().as_ref(), &escrow.index.to_le_bytes()],
bump = escrow.bump
)]
pub escrow: Account<'info, EscrowAccount>,
Seeds can be:
- Byte literals:
b"escrow"
- Account keys:
owner.key().as_ref()
- Numbers:
&id.to_le_bytes()
- Account data:
escrow.index.to_le_bytes()
See: anchor-pdas skill for comprehensive PDA patterns.
seeds::program = <expr> - Cross-Program PDA
#[account(
seeds = [b"metadata", mint.key().as_ref()],
bump,
seeds::program = metadata_program.key()
)]
pub metadata: AccountInfo<'info>,
Derives PDA using a different program ID (not current program).
Token-Specific Constraints
token::mint and token::authority - Token Account Validation
use anchor_spl::token::TokenAccount;
#[account(
token::mint = mint,
token::authority = authority
)]
pub token_account: Account<'info, TokenAccount>,
pub mint: Account<'info, Mint>,
pub authority: Signer<'info>,
Validates token account's mint and authority fields.
See: anchor-token-operations skill for complete token patterns.
Constraint Custom Errors
Add custom errors to most constraints:
#[account(
has_one = authority @ ErrorCode::UnauthorizedAccess,
constraint = amount > 0 @ ErrorCode::InvalidAmount
)]
Space Calculation
Source: Anchor Space Reference
Source: Anchor Book - Space Reference
When using init, you must specify the account size. Always add 8 bytes for the discriminator.
Type Size Reference
| Type | Bytes | Notes |
|---|
bool | 1 | |
u8, i8 | 1 | |
u16, i16 | 2 | |
u32, i32 | 4 | |
u64, i64 | 8 | |
u128, i128 | 16 | |
f32 | 4 | Serialization fails for NaN |
f64 | 8 | Serialization fails for NaN |
Pubkey | 32 | |
[T; N] | size(T) * N | Fixed array |
Vec<T> | 4 + size(T) * count | Must allocate max size upfront |
String | 4 + byte_length | Must allocate max size upfront |
Option<T> | 1 + size(T) | |
Enum | 1 + largest_variant | |
Manual Calculation Example
#[account]
pub struct MyData {
pub val: u16,
pub state: GameState,
pub players: Vec<Pubkey>,
}
impl MyData {
pub const MAX_SIZE: usize = 2 + 33 + 324;
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Eq)]
pub enum GameState {
Active,
Tie,
Won { winner: Pubkey },
}
#[derive(Accounts)]
pub struct InitializeMyData<'info> {
#[account(init, payer = signer, space = 8 + MyData::MAX_SIZE)]
pub acc: Account<'info, MyData>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
The InitSpace Macro
Anchor can calculate space automatically:
#[account]
#[derive(InitSpace)]
pub struct MyAccount {
pub data: u64,
#[max_len(50)]
pub name: String,
#[max_len(10, 5)]
pub nested: Vec<Vec<u8>>,
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = payer, space = 8 + MyAccount::INIT_SPACE)]
pub my_account: Account<'info, MyAccount>,
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program<'info, System>,
}
Notes:
#[max_len(n)] for String and Vec specifies max element count
- For nested collections:
#[max_len(outer_count, inner_count)]
INIT_SPACE constant is automatically generated
- Still need to add 8 for discriminator in
space constraint
Important: max_len is element count, not bytes. For Vec<u32> with #[max_len(10)]:
- Element count: 10
- Bytes per element: 4
- Total: 4 (length prefix) + (10 * 4) = 44 bytes
Error Handling
Source: Anchor Book - Errors
Source: Anchor Error Documentation
Defining Custom Errors
#[error_code]
pub enum ErrorCode {
#[msg("Amount must be greater than zero")]
InvalidAmount,
#[msg("Session key has expired")]
SessionKeyExpired,
#[msg("Authorization has expired")]
AuthorizationExpired,
#[msg("Insufficient balance in escrow account")]
InsufficientBalance,
}
Automatic:
- Error codes start at 6000 (custom error offset)
- Each variant gets sequential number (6000, 6001, 6002, ...)
- Message attribute provides user-friendly error text
Using Errors with require!
pub fn transfer(ctx: Context<Transfer>, amount: u64) -> Result<()> {
require!(amount > 0, ErrorCode::InvalidAmount);
require!(
ctx.accounts.escrow.balance >= amount,
ErrorCode::InsufficientBalance
);
Ok(())
}
The require! macro:
- Checks the condition
- If false, returns the error
- If true, continues execution
Using Errors with err!
pub fn set_data(ctx: Context<SetData>, data: u64) -> Result<()> {
if data >= 100 {
return err!(ErrorCode::DataTooLarge);
}
ctx.accounts.my_account.data = data;
Ok(())
}
Require Macro Family
| Macro | Purpose | Example |
|---|
require! | General condition | require!(amount > 0, ErrorCode) |
require_eq! | Equality (non-Pubkey) | require_eq!(a, b, ErrorCode) |
require_neq! | Inequality (non-Pubkey) | require_neq!(a, b, ErrorCode) |
require_keys_eq! | Pubkey equality | require_keys_eq!(key1, key2, ErrorCode) |
require_keys_neq! | Pubkey inequality | require_keys_neq!(key1, key2, ErrorCode) |
require_gt! | Greater than | require_gt!(a, b, ErrorCode) |
require_gte! | Greater or equal | require_gte!(balance, amount, ErrorCode) |
Important: Use require_keys_eq! for Pubkey comparisons (more efficient than require_eq!).
Error Number Scheme
| Range | Type |
|---|
| >= 100 | Instruction errors |
| >= 1000 | IDL errors |
| >= 2000 | Constraint errors |
| >= 3000 | Account errors |
| >= 4100 | Misc errors |
| >= 6000 | Custom user errors (your errors) |
Safety Checks
Source: Anchor Book - Safety Checks
The CHECK Requirement
UncheckedAccount and AccountInfo require documentation:
#[derive(Accounts)]
pub struct Initialize<'info> {
pub authority_pda: UncheckedAccount<'info>,
#[account(constraint = metadata.owner == metadata_program.key())]
pub metadata: AccountInfo<'info>,
}
Without /// CHECK: comment, compilation fails:
Error: Struct field "authority_pda" is unsafe, but is not documented.
Please add a `/// CHECK:` doc comment explaining why no checks through types are necessary.
Must be a doc comment:
/// (line doc comment) - Valid
/** */ (block doc comment) - Valid
// (regular comment) - Invalid (not a doc comment)
When to Use UncheckedAccount vs AccountInfo
Use UncheckedAccount<'info> when:
- Account will be a PDA signer in a CPI
- You only need the account address
- Account will be validated via custom constraints
- Account is from a non-Anchor program without a wrapper type
Use AccountInfo<'info> when:
- You need direct access to account fields (lamports, data, owner)
- Performing manual deserialization
- Advanced account manipulation
Prefer typed accounts when possible:
Account<'info, T> - Program-owned accounts
Signer<'info> - Signature verification
Program<'info, T> - Program validation
Best Practices
Constraint Organization
Order constraints logically:
#[account(
// 1. Lifecycle (init, close, realloc)
init,
payer = owner,
space = 8 + EscrowAccount::INIT_SPACE,
// 2. PDA (seeds, bump)
seeds = [b"escrow", owner.key().as_ref(), &index.to_le_bytes()],
bump,
)]
pub escrow: Account<'info, EscrowAccount>,
Account Struct Organization
Group related accounts and add comments:
#[derive(Accounts)]
pub struct SubmitAuthorization<'info> {
#[account(
mut,
has_one = facilitator,
seeds = [b"escrow", owner.key().as_ref(), &escrow.index.to_le_bytes()],
bump = escrow.bump,
)]
pub escrow: Account<'info, EscrowAccount>,
#[account(
init,
payer = facilitator,
space = 8 + PendingSettlement::INIT_SPACE,
seeds = [b"pending", escrow.key().as_ref(), &authorization_id.to_le_bytes()],
bump,
)]
pub pending: Account<'info, PendingSettlement>,
pub facilitator: Signer<'info>,
pub system_program: Program<'info, System>,
}
Handler Function Design
Keep handlers focused and readable:
pub fn submit_authorization(
ctx: Context<SubmitAuthorization>,
mint: Pubkey,
recipient: Pubkey,
amount: u64,
authorization_id: u64,
expires_at_slot: u64,
signature: [u8; 64],
) -> Result<()> {
let clock = Clock::get()?;
require!(clock.slot < expires_at_slot, ErrorCode::AuthorizationExpired);
require!(
expires_at_slot <= clock.slot + ctx.accounts.escrow.refund_timeout_slots,
ErrorCode::AuthorizationExpired
);
require!(amount > 0, ErrorCode::InvalidAmount);
let escrow = &mut ctx.accounts.escrow;
escrow.pending_count += 1;
let pending = &mut ctx.accounts.pending;
pending.amount = amount;
pending.authorization_id = authorization_id;
pending.expires_at_slot = expires_at_slot;
Ok(())
}
When to Use Custom Constraints vs Built-in
Use built-in constraints:
has_one for simple field equality
mut, signer for standard checks
seeds, bump for PDAs
Use custom constraints when:
- Comparing multiple fields
- Complex boolean logic
- Calculations or transformations
- Not expressible with built-in constraints
#[account(has_one = authority)]
#[account(
constraint = account.start_time < clock.slot
&& account.end_time > clock.slot
@ ErrorCode::OutsideTimeWindow
)]
Skill Loading Guidance
Always Load With
- anchor-security - Security is paramount; always load together
Commonly Paired With
- anchor-pdas - Most programs use PDAs
- anchor-cpis - Most programs make cross-program invocations
- anchor-token-operations - For token-related programs
Load This Skill When
- Writing any Anchor program code
- Reviewing Anchor program implementations
- Designing account structures
- Implementing error handling
- Calculating account space
Related Skills
- anchor-pdas - For PDA-specific patterns (seeds, bumps, program signing)
- anchor-security - For security constraints and validation patterns
- anchor-cpis - For cross-program invocation patterns
- anchor-token-operations - For SPL token account patterns
- anchor-testing - For testing Anchor programs
- rust-solana - For Solana-specific Rust patterns
Reference Links
Official Documentation
Source Material
Acknowledgment
At the start of a session, after reviewing this skill, state: "I have reviewed the anchor-core skill and am ready to build secure Solana programs."