Creating & Closing Accounts
Account creation allocates space, assigns an owner, and funds rent-exemption. Closing reclaims lamports. realloc grows or shrinks data in place.
Recipe
// Create
#[account(init, payer = payer, space = 8 + MyData::INIT_SPACE)]
pub data: Account<'info, MyData>,
// Grow
#[account(mut, realloc = new_size, realloc::payer = payer, realloc::zero = false)]
pub data: Account<'info, MyData>,
// Close
#[account(mut, close = receiver)]
pub data: Account<'info, MyData>,When to reach for this:
- User onboarding flows that create PDAs or config accounts
- Migration instructions that expand account size
- Cleanup incentives returning rent to users
- Protocol shutdown reclaiming vault accounts
Working Example
use anchor_lang::prelude::*;
#[program]
pub mod lifecycle {
use super::*;
pub fn create(ctx: Context<Create>, label: String) -> Result<()> {
ctx.accounts.data.authority = ctx.accounts.payer.key();
ctx.accounts.data.label = label;
Ok(())
}
pub fn expand(ctx: Context<Expand>, extra: u32) -> Result<()> {
ctx.accounts.data.extra = extra;
Ok(())
}
pub fn close_account(ctx: Context<CloseData>) -> Result<()> {
Ok(())
}
}
#[account]
#[derive(InitSpace)]
pub struct MyData {
pub authority: Pubkey,
#[max_len(20)]
pub label: String,
pub extra: u32,
}
#[derive(Accounts)]
pub struct Create<'info> {
#[account(init, payer = payer, space = 8 + MyData::INIT_SPACE)]
pub data: Account<'info, MyData>,
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Expand<'info> {
#[account(
mut,
realloc = 8 + MyData::INIT_SPACE,
realloc::payer = payer,
realloc::zero = false,
)]
pub data: Account<'info, MyData>,
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct CloseData<'info> {
#[account(mut, close = receiver, has_one = authority)]
pub data: Account<'info, MyData>,
pub authority: Signer<'info>,
#[account(mut)]
pub receiver: SystemAccount<'info>,
}What this demonstrates:
initCPIs to System Program for allocation + rent transferreallocmay require additional lamports fromrealloc::payerclosezeros data and sends all lamports toreceiver
Deep Dive
Creation Steps (Runtime)
- Payer signs and provides lamports
- System Program allocates
spacebytes - Owner set to calling program
- Program writes initial data
Close Safety
- Anchor
closesets lamports to 0 and assigns owner to System Program - Always verify authority before close to prevent griefing
- Cannot close accounts that still hold unexpected funds (add constraints)
Gotchas
initwithoutsystem_program- compile/runtime failure. Fix: always includeProgram<'info, System>.- Realloc shrink losing data - truncates bytes. Fix: migrate data before shrinking or only grow.
- Closing accounts referenced elsewhere - downstream indexers break. Fix: emit event and document lifecycle.
- Forgot
muton payer for realloc - insufficient funding. Fix: mark payermutwhen realloc may cost lamports. - Closing token accounts with balance - SPL requires empty balance first. Fix: transfer tokens then close via SPL CPI.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
init_if_needed | Idempotent onboarding | Strict one-time creation security |
| Manual System CPI | Native programs without Anchor | Anchor projects (use macros) |
| New account migration | Breaking layout changes | Minor field additions |
realloc | Modest size growth | Large dataset changes (new PDA) |
FAQs
What does init do under the hood?
CPI to System Program create_account with space, owner, and lamports from payer.
Who can close an account?
Your program via close constraint - typically requiring authority signer.
Where do reclaimed lamports go?
The account specified in close = receiver.
When is realloc needed?
When account data grows beyond originally allocated space.
Does realloc cost extra rent?
Yes, if larger size requires higher rent-exempt minimum. Payer funds difference.
Can I close a PDA?
Yes. Program signs if needed; lamports go to receiver.
What is init_if_needed?
Creates account only if it doesn't exist - useful for ATAs-like patterns with care.
Can closed accounts be revived?
Same address can be re-initialized if recreated with init - treat as new account.
How do I close multiple accounts at once?
Multiple close constraints in one instruction - watch CU limits.
Does closing emit an event?
Not automatically. Add emit! for indexers.
Can users close accounts without program?
System-owned empty accounts can be drained; program-owned require program instruction.
What is realloc::zero?
Whether new bytes are zeroed. false preserves existing data during growth.
Related
- Rent & Rent-Exemption - funding amounts
- Data Accounts & Layout - space planning
- Program-Owned State - PDA creation
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.