Anchor Accounts in Solana: #[account], Init, Constraints
By Mudassir Khan — Agentic AI Consultant & AI Systems Architect, Islamabad, Pakistan
Accounts are the fundamental storage unit in Solana. Every piece of on-chain state lives in an account. Anchor provides two macros that handle everything around accounts: #[account] defines the data shape, and #[derive(Accounts)] validates and deserializes them before your instruction handler runs. If you are working through this for the first time, the Solana Hello World with Anchor guide builds a complete counter program that demonstrates both macros in action.
Data shape
The #[account] Macro
#[account] is the annotation that turns a plain Rust struct into a type Anchor can read from and write to on-chain.
Quick answer
What does the #[account] macro do in Anchor? #[account] tells Anchor to implement Borsh serialization and deserialization for the struct, and to prepend an 8-byte discriminator when writing the account. It also registers the struct as a known account type in the generated IDL.
use anchor_lang::prelude::*;
#[account]
#[derive(InitSpace)]
pub struct UserProfile {
pub authority: Pubkey, // 32 bytes
pub score: u64, // 8 bytes
pub level: u8, // 1 byte
pub is_active: bool, // 1 byte
#[max_len(50)]
pub username: String, // 4 + 50 = 54 bytes
}
// Total data: 32 + 8 + 1 + 1 + 54 = 96 bytes
// Total space: 8 (discriminator) + 96 = 104 bytesInitSpace calculates space automatically
Adding #[derive(InitSpace)] lets you use UserProfile::INIT_SPACE in the init constraint instead of hardcoding a number. For Strings, annotate with #[max_len(N)] to tell InitSpace the maximum byte length.
The 8-byte discriminator is the first thing Anchor writes when an account is created. When your program reads the account later, Anchor checks that the discriminator matches the expected struct before deserializing. This prevents one account type from being passed where another is expected — a common attack vector in raw Solana programs.
Account validation
How Anchor Validates Accounts
Every instruction in an Anchor program has an associated Accounts struct. Anchor runs all constraints in that struct before your handler executes.
Quick answer
What is #[derive(Accounts)] in Anchor? #[derive(Accounts)] is a procedural macro that generates the account deserialization and constraint-checking code for an instruction. You declare what you need; Anchor generates the validation.
#[derive(Accounts)]
#[instruction(bump: u8)]
pub struct UpdateProfile<'info> {
/// The user profile account to update.
#[account(
mut,
has_one = authority,
seeds = [b"profile", authority.key().as_ref()],
bump
)]
pub profile: Account<'info, UserProfile>,
/// The wallet that owns the profile.
pub authority: Signer<'info>,
}Account types in Anchor structs serve different purposes:
Account<'info, T>— deserializes an account into type T and checks the discriminatorSigner<'info>— verifies the account signed the transactionProgram<'info, T>— verifies the account is a specific program (e.g. Token, System)SystemAccount<'info>— verifies it is a system-owned account (e.g. for wallets)UncheckedAccount<'info>— no automatic checks; you handle validation manually
Account<'info, T> vs AccountInfo<'info>
Account<'info, T> is the Anchor wrapper. It deserializes the raw bytes into T, checks the discriminator, and validates the owner. AccountInfo is the raw Solana type — use it when you need low-level access or when working outside Anchor.
Account creation
The init Constraint
init is the most important Anchor constraint. It creates a new on-chain account with rent-exempt lamports and proper ownership in one step.
Quick answer
What does the init constraint do in Anchor? init creates a new account via the System Program, transfers enough lamports for rent exemption, sets the account owner to your program, and writes the discriminator.
#[derive(Accounts)]
pub struct CreateProfile<'info> {
#[account(
init,
payer = user,
space = 8 + UserProfile::INIT_SPACE
)]
pub profile: Account<'info, UserProfile>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}system_program is required with init
Anchor calls System Program's create_account instruction under the hood when init is used. If you omit system_program from the struct, the program will not compile.
The space parameter is the total bytes to allocate, including the 8-byte discriminator. Using INIT_SPACE is safer than a hardcoded number because it stays correct when you add or remove fields from the struct. For a full working example with init, see the Anchor framework overview.
Constraint reference
The Six Constraints You Will Use Most
These six constraints cover the large majority of real program validation requirements.
| Constraint | What it checks | Example |
|---|---|---|
| mut | Account is marked writable in the transaction | #[account(mut)] |
| has_one | Field on the account matches a named account in the struct | #[account(has_one = authority)] |
| constraint | Custom boolean expression must be true | #[account(constraint = profile.score < 1000)] |
| close = target | Closes the account and sends lamports to target | #[account(mut, close = user)] |
| seeds + bump | Account is the canonical PDA for the given seeds | #[account(seeds=[b"x"], bump)] |
| owner | Account is owned by a specific program | #[account(owner = token_program.key())] |
close reclaims rent lamports
When an account is closed with the close constraint, Anchor zeroes out the data, transfers all lamports to the target account, and sets the account's lamports to zero. The account will be garbage collected by the runtime.
PDA accounts
PDA Accounts with Seeds and Bump
Program Derived Addresses are the standard pattern for program-owned storage. Anchor handles PDA creation and validation with seeds and bump in the account constraint.
Quick answer
How do I create a PDA account in Anchor? Use seeds and bump in the #[account] constraint for init to create the PDA, and the same seeds + bump for subsequent instructions that need to access it.
#[derive(Accounts)]
pub struct CreateVault<'info> {
#[account(
init,
payer = user,
space = 8 + Vault::INIT_SPACE,
seeds = [b"vault", user.key().as_ref()],
bump
)]
pub vault: Account<'info, Vault>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}#[derive(Accounts)]
pub struct WithdrawFromVault<'info> {
#[account(
mut,
seeds = [b"vault", user.key().as_ref()],
bump = vault.bump,
has_one = user
)]
pub vault: Account<'info, Vault>,
pub user: Signer<'info>,
}import * as anchor from "@coral-xyz/anchor";
const [vaultPDA, bump] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from("vault"), provider.wallet.publicKey.toBuffer()],
program.programId
);
console.log("Vault PDA:", vaultPDA.toBase58());
console.log("Bump:", bump);Store the bump in the account
Add a 'pub bump: u8' field to your PDA account struct and save ctx.bumps.vault to it during init. This avoids recomputing the bump on every subsequent instruction.
FAQ
Frequently Asked Questions
Common questions about Anchor account macros, constraints, and PDAs.
What is #[account] in Anchor?
#[account] is an Anchor procedural macro applied to a Rust struct. It generates Borsh serialization and deserialization code and adds an 8-byte discriminator to the account data. This lets Anchor automatically handle reading and writing on-chain state without manual byte parsing.
What is the difference between #[account] and #[derive(Accounts)]?
#[account] is applied to the data struct — it defines the shape of on-chain state. #[derive(Accounts)] is applied to the instruction context struct — it defines which accounts an instruction needs and validates them. You use both together: the data struct defines what is stored, the context struct defines who provides it.
What does init do in Anchor?
The init constraint instructs Anchor to create a new on-chain account. It calls the System Program to allocate space and lamports for rent exemption, sets the account owner to your program, and writes the 8-byte discriminator. You must include system_program in the accounts struct when using init.
What is a PDA in Solana?
A Program Derived Address (PDA) is an account address derived deterministically from a program ID and a set of seeds. PDAs are not on the ed25519 curve, so no private key controls them — only the owning program can sign for them. They are used for program-owned accounts that need to be found by address without storing the keypair.
How do I close an account in Anchor?
Add the close = target constraint to the account field in your #[derive(Accounts)] struct, where target is the account that will receive the refunded lamports. Anchor handles zeroing the data, transferring the rent-exempt lamports, and marking the account for garbage collection.
Written by Mudassir Khan
Agentic AI consultant and AI systems architect based in Islamabad, Pakistan. CEO of Cube A Cloud. 38+ agentic AI launches delivered for global founders and CTOs.
View blockchain development service →See ChainTrust case study →
What Is Anchor? The Solana Development Framework
Anchor is the most popular Solana development framework. Learn how it simplifies account handling, instruction routing, and program security checks.
Solana Hello World with Anchor: First Program Guide
Build your first Solana program with Anchor. This step by step guide covers setup, writing the program, testing locally, and deploying to devnet.
Need an AI systems architect?
Book a 30-minute architecture call. I will sketch the high-level design for your use case and give you an honest view of the trade-offs.