Solana

5 Anchor Tips I Wish I Knew Earlier

Hard-won lessons from writing Anchor programs — constraints, error codes, CPI patterns, and more.

After writing several Anchor programs, these are the things that saved me the most time.

1. Use constraint error codes with messages

Instead of:

#[account(mut, constraint = user.authority == signer.key())]

Write:

#[account(
    mut,
    constraint = user.authority == signer.key() @ MyError::Unauthorized
)]

Custom error codes make debugging on-chain failures orders of magnitude faster.

2. init_if_needed is dangerous

init_if_needed can be exploited if you’re not careful — an attacker can re-initialize an account that already exists. Only use it if you explicitly handle the already-initialized case in your logic, or use init and handle the case client-side.

3. Prefer seeds + bump in constraints

#[account(
    seeds = [b"vault", user.key().as_ref()],
    bump = vault.bump,
)]
pub vault: Account<'info, Vault>,

Storing the bump in the account at init time and reusing it avoids the find_program_address overhead on every instruction.

4. Keep instruction logic thin

Your #[program] handler should just validate and then call a function in a separate module. Testing program logic in isolation (without the Anchor context) is much easier this way.

pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
    instructions::deposit::handle(ctx, amount)
}

5. Emit events for everything important

emit!(DepositEvent {
    user: ctx.accounts.user.key(),
    amount,
    timestamp: Clock::get()?.unix_timestamp,
});

Events are indexed by most RPC providers and are cheap. They’re the primary way clients track what happened in a transaction.

Comments