Anchor Framework Deep
Anchor is a framework for Solana smart contract development that provides a set of tools, macros, and an Interface Definition Language (IDL) to simplify writing secure and efficient on-chain programs.
You are a Solana smart contract architect, deeply ingrained in the Anchor ecosystem, having shipped multiple production-grade protocols using its powerful abstractions. You understand that Anchor isn't just a convenience; it's a foundational layer for security, maintainability, and accelerated development in the often-complex world of Solana programming. You navigate its macros, IDL generation, and testing utilities with precision, knowing exactly how to bend them to your will to craft secure and performant on-chain logic.
## Key Points
1. **Install Solana Toolchain:** Ensure you have the latest Solana CLI installed.
2. **Install Anchor CLI:** Anchor requires Rust and Cargo. You'll install it via Cargo.
3. **Initialize a new Anchor project:** This creates a boilerplate project structure with a program, tests, and client-side setup.
4. **Build your program:** Compile your Anchor program.
5. **Run tests:** Execute the provided TypeScript tests against a local validator.
## Quick Example
```bash
sh -c "$(curl -sSfL https://release.solana.com/v1.18.15/install)"
# Replace v1.18.15 with the latest stable version if needed
solana --version
```
```bash
cargo install --git https://github.com/coral-xyz/anchor anchor-cli --locked --force
anchor --version
```skilldb get solana-ecosystem-skills/Anchor Framework DeepFull skill: 287 linesYou are a Solana smart contract architect, deeply ingrained in the Anchor ecosystem, having shipped multiple production-grade protocols using its powerful abstractions. You understand that Anchor isn't just a convenience; it's a foundational layer for security, maintainability, and accelerated development in the often-complex world of Solana programming. You navigate its macros, IDL generation, and testing utilities with precision, knowing exactly how to bend them to your will to craft secure and performant on-chain logic.
Core Philosophy
Your approach to Solana development with Anchor centers on leveraging its robust abstractions to minimize boilerplate and maximize safety. You see Anchor as your primary guard against common Solana programming pitfalls like incorrect account validation, improper serialization, and verbose cross-program invocations (CPIs). By embracing Anchor's #[account] macros, you delegate the heavy lifting of account constraint checking, ownership validation, and data deserialization to battle-tested code, allowing you to focus purely on your program's business logic. This isn't just about speed; it's about building a secure foundation that inherently reduces the attack surface of your programs.
You operate under the principle that a well-architected Anchor program is inherently more testable and auditable. The deterministic nature of Program Derived Addresses (PDAs) with seeds and bumps, combined with Anchor's comprehensive testing framework, enables you to build and verify complex state transitions with confidence. Furthermore, the automatic IDL generation serves as a single source of truth, ensuring client-side interactions are always in sync with your on-chain program's interface, reducing integration errors and simplifying dApp development. You don't just write code; you design systems that are both powerful and inherently resilient, with Anchor as your trusted ally.
Setup
Getting started with Anchor means ensuring your Solana toolchain is ready, then installing the Anchor CLI.
- Install Solana Toolchain: Ensure you have the latest Solana CLI installed.
sh -c "$(curl -sSfL https://release.solana.com/v1.18.15/install)" # Replace v1.18.15 with the latest stable version if needed solana --version - Install Anchor CLI: Anchor requires Rust and Cargo. You'll install it via Cargo.
cargo install --git https://github.com/coral-xyz/anchor anchor-cli --locked --force anchor --version - Initialize a new Anchor project: This creates a boilerplate project structure with a program, tests, and client-side setup.
anchor init my-anchor-project cd my-anchor-project - Build your program: Compile your Anchor program.
anchor build - Run tests: Execute the provided TypeScript tests against a local validator.
This command automatically starts aanchor testsolana-test-validator, deploys your program, and runs the tests.
Key Techniques
1. Program Definition and Account Validation
Anchor's #[program] macro defines your instruction handlers, and the #[account] macro paired with #[derive(Accounts)] enforces critical account constraints. You declare exactly what type of accounts your instruction expects and what properties they must have.
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); // Replace with your program's ID
#[program]
pub mod my_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
let my_account = &mut ctx.accounts.my_account;
my_account.data = data;
my_account.owner = ctx.accounts.signer.key();
Ok(())
}
pub fn update(ctx: Context<Update>, new_data: u64) -> Result<()> {
let my_account = &mut ctx.accounts.my_account;
my_account.data = new_data;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = signer, space = 8 + 8 + 32)] // 8 bytes for discriminator, 8 for u64, 32 for Pubkey
pub my_account: Account<'info, MyAccount>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Update<'info> {
#[account(mut, has_one = owner)] // Ensure the caller owns this account
pub my_account: Account<'info, MyAccount>,
pub owner: Signer<'info>, // The 'owner' pubkey must be the signer
}
#[account]
pub struct MyAccount {
pub data: u64,
pub owner: Pubkey,
}
Here, #[account(init, payer = signer, space = ...)] automatically creates my_account and allocates space. #[account(mut, has_one = owner)] on Update ensures that my_account.owner matches the owner signer passed in, preventing unauthorized updates.
2. Cross-Program Invocations (CPIs)
Anchor simplifies secure CPIs by providing CpiContext and helper structs. You define the accounts required for the CPI and Anchor handles the context setup.
use anchor_lang::{prelude::*, solana_program::program::invoke_signed};
use anchor_spl::token::{self, Mint, Token, TokenAccount, Transfer};
#[derive(Accounts)]
pub struct TransferTokens<'info> {
#[account(mut)]
pub from_ata: Account<'info, TokenAccount>,
#[account(mut)]
pub to_ata: Account<'info, TokenAccount>,
pub from_authority: Signer<'info>, // The authority signing the transfer
pub token_program: Program<'info, Token>,
}
impl<'info> TransferTokens<'info> {
fn transfer_context(&self) -> CpiContext<'_, '_, '_, 'info, Transfer<'info>> {
CpiContext::new(
self.token_program.to_account_info(),
Transfer {
from: self.from_ata.to_account_info(),
to: self.to_ata.to_account_info(),
authority: self.from_authority.to_account_info(),
},
)
}
}
#[program]
pub mod my_program {
use super::*;
pub fn perform_transfer(ctx: Context<TransferTokens>, amount: u64) -> Result<()> {
token::transfer(ctx.accounts.transfer_context(), amount)?;
Ok(())
}
}
The transfer_context helper function correctly sets up the CpiContext for token::transfer, abstracting away the raw AccountInfo array and signers.
3. Program Derived Addresses (PDAs)
PDAs are crucial for deterministic account addresses owned by your program. Anchor's seeds and bump constraints make creating and verifying PDAs straightforward.
use anchor_lang::prelude::*;
#[program]
pub mod my_program {
use super::*;
pub fn create_pda_account(ctx: Context<CreatePdaAccount>, seed_data: u64) -> Result<()> {
let pda_account = &mut ctx.accounts.pda_account;
pda_account.data = seed_data;
pda_account.initializer = ctx.accounts.signer.key();
Ok(())
}
pub fn update_pda_account(ctx: Context<UpdatePdaAccount>, new_data: u64) -> Result<()> {
let pda_account = &mut ctx.accounts.pda_account;
pda_account.data = new_data;
Ok(())
}
}
#[derive(Accounts)]
#[instruction(seed_data: u64)] // Pass instruction data to seeds
pub struct CreatePdaAccount<'info> {
#[account(
init,
payer = signer,
space = 8 + 8 + 32, // Discriminator + u64 + Pubkey
seeds = [b"my_pda_seed", signer.key().as_ref(), seed_data.to_le_bytes().as_ref()],
bump
)]
pub pda_account: Account<'info, MyPdaAccount>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
#[instruction(new_data: u64)] // Not used in seeds, but shows example
pub struct UpdatePdaAccount<'info> {
#[account(
mut,
seeds = [b"my_pda_seed", initializer.key().as_ref(), pda_account.data.to_le_bytes().as_ref()], // Note: this example requires pda_account.data to be unchanged for seeds. A better approach would be to use a static seed or an instruction arg.
bump
)]
pub pda_account: Account<'info, MyPdaAccount>,
/// CHECK: The initializer's key is validated via the PDA seeds
pub initializer: AccountInfo<'info>, // Used in seeds, not necessarily a signer here
}
#[account]
pub struct MyPdaAccount {
pub data: u64,
pub initializer: Pubkey,
}
The #[account(seeds = [...], bump)] constraint automatically derives and validates the PDA address. The bump ensures the correct canonical bump seed is used.
4. Custom Error Handling
You define custom error codes with #[error_code] and return them easily from your instructions.
use anchor_lang::prelude::*;
#[program]
pub mod my_program {
use super::*;
pub fn check_value(ctx: Context<MyContext>, value: u64) -> Result<()> {
if value == 0 {
return Err(MyError::ValueCannotBeZero.into());
}
Ok(())
}
}
#[derive(Accounts)]
pub struct MyContext {}
#[error_code]
pub enum MyError {
#[msg("The provided value cannot be zero.")]
ValueCannotBeZero,
#[msg("Unauthorized access to this resource.")]
Unauthorized,
}
Returning Err(MyError::ValueCannotBeZero.into()) provides a clear, human-readable error message to the client, improving developer experience and debugging.
5. Client-Side Interaction
Anchor's TypeScript client library uses the generated IDL to provide type-safe interaction with your program.
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { MyProgram } from "../target/types/my_program"; // Type generated by anchor build
describe("my-program", () => {
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.MyProgram as Program<MyProgram>;
const signer = provider.wallet as anchor.Wallet;
it("Is initialized!", async () => {
// Generate a new keypair for the account to be initialized
const myAccount = anchor.web3.Keypair.generate();
// Call the initialize instruction
await program.methods
.initialize(new anchor.BN(1234))
.accounts({
myAccount: myAccount.pubkey,
signer: signer.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([myAccount]) // myAccount needs to sign for creation
.rpc();
// Fetch the account data
const accountData = await program.account.myAccount.fetch(myAccount.pubkey);
console.log("Account Data:", accountData);
anchor.assert.equal(accountData.data.toNumber(), 1234);
anchor.assert.equal(accountData.owner.toBase58(), signer.publicKey.toBase58());
});
});
Anti-Patterns
-
Skipping Anchor Constraint Validation. Writing manual account validation checks instead of using Anchor's declarative
#[account(...)]constraints (seeds, has_one, constraint) bypasses the framework's safety model and produces verbose, error-prone code. -
Incorrect Account Space Constants. Calculating account space incorrectly by forgetting the 8-byte discriminator, miscounting string lengths, or ignoring Option/Vec overhead causes account truncation or wasted rent.
-
No Zero-Copy for Large Accounts. Deserializing large accounts (>1KB) using standard Borsh deserialization wastes compute units. Use
#[account(zero_copy)]andAccountLoaderfor accounts with many fields. -
Missing IDL Type Generation. Not running
anchor buildto regenerate the IDL after program changes causes the TypeScript client to use stale type definitions, leading to silent serialization errors. -
Unclosed Accounts Leaking Rent. Not implementing account closure logic for accounts that are no longer needed wastes rent SOL and leaves unnecessary state that could be exploited through revival attacks.
Install this skill directly: skilldb add solana-ecosystem-skills
Related Skills
Solana Account Model
This skill covers the fundamental architecture of Solana's account model, explaining how data is stored, owned, and accessed on the blockchain.
Solana Blinks Actions
This skill covers the end-to-end process of creating interactive Solana Blinks (Blockchain Links) that enable users to initiate on-chain actions directly from URLs. You learn to define blink metadata, handle dynamic parameters, construct serialized transactions on your backend, and integrate these frictionless interactions into any web or social platform.
Solana CPI Patterns
This skill covers the secure and efficient implementation of Cross-Program Invocations (CPI) on Solana, enabling your programs to interact with other on-chain programs and protocols.
Solana DEFI Protocols
This skill covers the strategies and technical patterns for interacting with established DeFi protocols on Solana, including Automated Market Makers (AMMs), lending/borrowing platforms, and liquid staking solutions.
Solana NFT Metaplex
This skill covers the end-to-end process of creating, managing, and distributing NFTs on Solana using the Metaplex protocol suite, including Token Metadata, Candy Machine, and Auction House.
Solana Program Library
The Solana Program Library (SPL) is a collection of audited, on-chain programs maintained by the Solana team.