Anchor is to Solana what Hardhat is to Ethereum — the framework that handles the boilerplate so you can focus on your program logic. It adds a layer of type safety over the raw Solana runtime, generates client-side TypeScript bindings automatically, and dramatically reduces the chance of account confusion bugs.
Prerequisites
- Rust installed —
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - Solana CLI —
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)" - Node.js 20+ (for tests)
- Basic Rust knowledge (you do not need to be an expert)
1. Install Anchor
cargo install --git https://github.com/coral-xyz/anchor avm --force
avm install latest
avm use latest
anchor --version
2. Create a new project
anchor init counter
cd counter
The scaffold creates:
counter/
├── programs/counter/src/lib.rs ← your on-chain program
├── tests/counter.ts ← TypeScript integration tests
├── Anchor.toml ← project config
└── package.json
3. Write the program
Open programs/counter/src/lib.rs and replace it:
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod counter {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count = 0;
counter.authority = ctx.accounts.authority.key();
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count = counter.count.checked_add(1).unwrap();
Ok(())
}
pub fn reset(ctx: Context<Reset>) -> Result<()> {
require_keys_eq!(
ctx.accounts.counter.authority,
ctx.accounts.authority.key(),
CounterError::Unauthorised
);
ctx.accounts.counter.count = 0;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = authority, space = 8 + 8 + 32)]
pub counter: Account<'info, Counter>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut)]
pub counter: Account<'info, Counter>,
}
#[derive(Accounts)]
pub struct Reset<'info> {
#[account(mut)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
#[account]
pub struct Counter {
pub count: u64,
pub authority: Pubkey,
}
#[error_code]
pub enum CounterError {
#[msg("Only the original authority can reset the counter")]
Unauthorised,
}
Key concepts in this program:
declare_id!— the program’s on-chain address (changes after the first deploy)#[account]— defines a struct stored on-chain; Anchor handles serialisationspace = 8 + 8 + 32— 8 bytes discriminator + 8 bytes u64 + 32 bytes Pubkeychecked_add— prevents integer overflow without panicking
4. Build
anchor build
This compiles the Rust program and generates TypeScript types in target/types/counter.ts. After the first build, copy the generated program ID:
solana address -k target/deploy/counter-keypair.json
Paste this address into declare_id!("...") in lib.rs and into Anchor.toml under [programs.localnet].
5. Write tests
Replace tests/counter.ts:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Counter } from "../target/types/counter";
import { expect } from "chai";
describe("counter", () => {
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.Counter as Program<Counter>;
it("initialises at zero", async () => {
const counter = anchor.web3.Keypair.generate();
await program.methods
.initialize()
.accounts({ counter: counter.publicKey })
.signers([counter])
.rpc();
const account = await program.account.counter.fetch(counter.publicKey);
expect(account.count.toNumber()).to.equal(0);
});
it("increments", async () => {
const counter = anchor.web3.Keypair.generate();
await program.methods.initialize().accounts({ counter: counter.publicKey }).signers([counter]).rpc();
await program.methods.increment().accounts({ counter: counter.publicKey }).rpc();
const account = await program.account.counter.fetch(counter.publicKey);
expect(account.count.toNumber()).to.equal(1);
});
});
Run tests against a local validator:
anchor test
Anchor starts a local Solana validator automatically, deploys the program and runs the TypeScript tests. All of this happens in one command.
6. Deploy to Devnet
Configure the Solana CLI to use Devnet:
solana config set --url devnet
Create a wallet if you do not have one:
solana-keygen new --outfile ~/.config/solana/id.json
solana address # copy your public key
Airdrop Devnet SOL:
solana airdrop 2
If the CLI airdrop fails, use the Solana faucet web interface instead.
Update Anchor.toml to target Devnet:
[provider]
cluster = "devnet"
wallet = "~/.config/solana/id.json"
Deploy:
anchor deploy
You will see:
Program Id: <YOUR_PROGRAM_ID>
Deploy success
Verify on Solana Explorer (Devnet) by searching for the program ID.
7. Interact from a script
Create scripts/interact.ts:
import * as anchor from "@coral-xyz/anchor";
async function main() {
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.Counter;
const counter = anchor.web3.Keypair.generate();
console.log("Initialising counter...");
await program.methods.initialize().accounts({ counter: counter.publicKey }).signers([counter]).rpc();
console.log("Incrementing...");
await program.methods.increment().accounts({ counter: counter.publicKey }).rpc();
const account = await program.account.counter.fetch(counter.publicKey);
console.log("Count:", account.count.toNumber()); // 1
}
main().catch(console.error);
Run it:
ANCHOR_PROVIDER_URL=https://api.devnet.solana.com \
ANCHOR_WALLET=~/.config/solana/id.json \
npx ts-node scripts/interact.ts
Common errors
| Error | Cause | Fix |
|---|---|---|
declared program id does not match |
declare_id! out of sync |
Copy the keypair address into declare_id! and rebuild |
insufficient funds |
Wallet has no Devnet SOL | Run solana airdrop 2 or use the faucet |
account not found |
Wrong cluster | Check solana config get matches your target |
space too small |
Account struct grew | Recalculate space and redeploy |
Next steps
- Add PDAs for deterministic account addresses
- Explore Token extensions for custom token logic
- Read the Anchor book for the full API reference