What you’ll build
You’ll create a counter program that:- Creates a counter account to store data
- Increments the counter value
- Validates account ownership and authority
- Demonstrates Anchor’s account constraints
Prerequisites
Before you begin, ensure you have installed:- Rust
- Solana CLI
- Anchor CLI
- Node.js and Yarn
Build the counter program
/programs/counter/src/ - Program source code/tests/ - TypeScript testsAnchor.toml - Project configurationuse anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod counter {
use super::*;
pub fn create(ctx: Context<Create>, authority: Pubkey) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.authority = authority;
counter.count = 0;
msg!("Counter created. Authority: {}", authority);
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
msg!("Counter incremented. Current count: {}", counter.count);
Ok(())
}
}
#[derive(Accounts)]
pub struct Create<'info> {
#[account(init, payer = user, space = 8 + 32 + 8)]
pub counter: Account<'info, Counter>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut, has_one = authority)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
#[account]
pub struct Counter {
pub authority: Pubkey,
pub count: u64,
}
Understanding the code:
declare_id!()specifies the program’s on-chain address#[program]marks the module containing instruction handlerscreateinstruction initializes a new counter accountincrementinstruction increases the counter value#[derive(Accounts)]validates accounts for each instruction#[account]defines the counter’s data structure
#[derive(Accounts)]
pub struct Create<'info> {
#[account(init, payer = user, space = 8 + 32 + 8)]
pub counter: Account<'info, Counter>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
init - Creates a new account owned by the programpayer = user - The user pays for account creationspace = 8 + 32 + 8 - Allocates space for discriminator (8) + authority (32) + count (8)mut - The user account must be mutable to pay rent#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut, has_one = authority)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
mut - The counter account must be mutable to update the counthas_one = authority - Validates that counter.authority matches the authority account/target/deploy/counter.so/target/idl/counter.json/target/types/counter.tsAfter building, sync the program ID with
anchor keys sync to update declare_id!() with the generated program address.import * as anchor from "@anchor-lang/core";
import { Program } from "@anchor-lang/core";
import { Counter } from "../target/types/counter";
import { expect } from "chai";
describe("counter", () => {
// Configure the client to use the local cluster
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.Counter as Program<Counter>;
const counter = anchor.web3.Keypair.generate();
it("Creates a counter", async () => {
const tx = await program.methods
.create(provider.wallet.publicKey)
.accounts({
counter: counter.publicKey,
user: provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([counter])
.rpc();
console.log("Create transaction signature:", tx);
const counterAccount = await program.account.counter.fetch(
counter.publicKey
);
expect(counterAccount.authority.toString()).to.equal(
provider.wallet.publicKey.toString()
);
expect(counterAccount.count.toNumber()).to.equal(0);
});
it("Increments the counter", async () => {
const tx = await program.methods
.increment()
.accounts({
counter: counter.publicKey,
authority: provider.wallet.publicKey,
})
.rpc();
console.log("Increment transaction signature:", tx);
const counterAccount = await program.account.counter.fetch(
counter.publicKey
);
expect(counterAccount.count.toNumber()).to.equal(1);
});
it("Increments the counter again", async () => {
await program.methods
.increment()
.accounts({
counter: counter.publicKey,
authority: provider.wallet.publicKey,
})
.rpc();
const counterAccount = await program.account.counter.fetch(
counter.publicKey
);
expect(counterAccount.count.toNumber()).to.equal(2);
});
});
counter
✔ Creates a counter (462ms)
✔ Increments the counter (421ms)
✔ Increments the counter again (415ms)
3 passing (1s)
anchor test automatically starts a local validator, deploys your program, runs tests, and stops the validator.Deploying cluster: devnet
Upgrade authority: YourPublicKeyHere
Deploying program "counter"...
Program path: /path/to/counter/target/deploy/counter.so
Program Id: Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS
Deploy success
Once deployed to devnet, you can interact with your program using the Anchor client, Solana Explorer, or build a frontend application.
View your program on Solana Explorer by searching for your program ID.
Understanding the code
Let’s break down the key components:Program structure
Space calculation
When initializing accounts, you must specify the space:- 8 bytes: Account discriminator (automatically added by Anchor)
- 32 bytes:
Pubkeyfor authority - 8 bytes:
u64for count
Account constraints
Anchor provides powerful constraints for security:init- Create and initialize an accountmut- Account data can be modifiedhas_one- Validate account relationshipsconstraint- Custom validation logicsigner- Account must sign the transaction
Next steps
Congratulations! You’ve built and deployed your first Anchor program. Here’s what to explore next:Program structure
Learn about Anchor macros and program architecture
Account constraints
Explore all available account validation constraints
TypeScript client
Build frontend applications with the Anchor TS client
Examples
Browse more example programs and tutorials