← All guides
intermediate May 17, 2026

Build and deploy your first Solana program with Anchor

Write, test and deploy a Solana on-chain program to Devnet using the Anchor framework — the standard toolkit for Solana development in Rust.


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 serialisation
  • space = 8 + 8 + 32 — 8 bytes discriminator + 8 bytes u64 + 32 bytes Pubkey
  • checked_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