Quasar
References

Account Constraints

Reference for all #[account(...)] constraint attributes in Quasar.

Account constraints are attributes on fields in a #[derive(Accounts)] struct. They generate validation code that runs during account parsing.

#[derive(Accounts)]
pub struct MyInstruction<'info> {
    #[account(mut, has_one = authority)]
    pub vault: &'info mut Account<Vault>,
    pub authority: &'info Signer,
}

Custom Errors

has_one, constraint, and address support custom error codes with @:

#[account(
    has_one = authority @ MyError::Unauthorized,
    constraint = vault.balance.get() > 0 @ MyError::EmptyVault,
    address = EXPECTED_ADDRESS @ MyError::WrongAddress
)]

Without @, these attributes use their default errors (HasOneMismatch, ConstraintViolation, AddressMismatch respectively).


General Constraints

mut

Marks the account as mutable. Checks that the account was passed as writable.

Syntax#[account(mut)]
Error on failureProgramError::Immutable
Required forAny account whose data or lamports are modified
#[account(mut)]
pub vault: &'info mut Account<Vault>,

dup

Allows this account to share the same address as a previous account in the struct. By default, duplicate accounts are rejected. Use dup when intentional duplication is needed.

Syntax#[account(dup)]
EffectSkips the duplicate-address check for this field
#[derive(Accounts)]
pub struct SelfTransfer<'info> {
    #[account(mut)]
    pub source: &'info mut Account<TokenAccount>,
    #[account(mut, dup)]
    pub destination: &'info mut Account<TokenAccount>,
}

address = <expr>

Validates that the account's address matches a constant or expression.

Syntax#[account(address = EXPR)] or #[account(address = EXPR @ ErrorVariant)]
Error on failureQuasarError::AddressMismatch (default) or custom
use solana_address::address;
const ADMIN: Address = address!("Admin111111111111111111111111111111111111111");

#[account(address = ADMIN)]
pub admin: &'info Signer,

has_one = <field>

Validates that a stored address field matches another account in the struct. The account type must have a field with the same name as the referenced account.

Syntax#[account(has_one = field_name)] or #[account(has_one = field_name @ ErrorVariant)]
Error on failureQuasarError::HasOneMismatch (default) or custom
Generated checkaccount.field_name == field_name.address()
#[account(discriminator = 1)]
pub struct Vault {
    pub authority: Address,
    pub mint: Address,
}

#[derive(Accounts)]
pub struct Withdraw<'info> {
    #[account(mut, has_one = authority, has_one = mint)]
    pub vault: &'info mut Account<Vault>,
    pub authority: &'info Signer,
    pub mint: &'info Account<Mint>,
}

Multiple has_one checks can be specified on the same field:

#[account(has_one = authority, has_one = mint @ MyError::WrongMint)]

constraint = <expr>

Evaluates a boolean expression. If it returns false, the instruction fails.

Syntax#[account(constraint = EXPR)] or #[account(constraint = EXPR @ ErrorVariant)]
Error on failureQuasarError::ConstraintViolation (default) or custom
#[account(
    constraint = vault.balance.get() >= amount @ MyError::InsufficientBalance
)]
pub vault: &'info Account<Vault>,

Multiple constraints can be specified:

#[account(
    constraint = clock.slot.get() > vault.unlock_slot.get(),
    constraint = vault.is_active.get()
)]

Initialization

init

Creates a new account via System program CPI. Derives size from T::SPACE (or space = <expr>), calculates rent-exempt lamports, and writes the discriminator.

Syntax#[account(init, payer = field_name)]
Requirespayer field, system_program field in the accounts struct
Error on failureAccountAlreadyInitialized if discriminator is already set
#[derive(Accounts)]
pub struct CreateVault<'info> {
    #[account(init, payer = payer)]
    pub vault: &'info mut Account<Vault>,
    #[account(mut)]
    pub payer: &'info Signer,
    pub system_program: &'info Program<System>,
}

For PDA accounts, combine init with seeds and bump:

#[account(init, payer = payer, seeds = [b"vault", authority.address().as_ref()], bump)]
pub vault: &'info mut Account<Vault>,

init_if_needed

Like init, but skips creation if the account already exists. Creates and initializes only if the account is uninitialized (all zeros).

Syntax#[account(init_if_needed, payer = field_name)]
Requirespayer field, system_program field
#[account(init_if_needed, payer = payer, seeds = [b"profile", user.address().as_ref()], bump)]
pub profile: &'info mut Account<Profile>,

payer = <field>

Specifies which signer pays for account creation.

Syntax#[account(init, payer = field_name)]
Auto-detectionIf omitted on init, looks for a field named payer

space = <expr>

Overrides the default account size (T::SPACE). Use for dynamic accounts or when you need extra space.

Syntax#[account(init, payer = payer, space = EXPR)]
DefaultT::SPACE (discriminator + sizeof fixed fields)
#[account(init, payer = payer, space = 8 + 32 + 4 + name.len())]
pub profile: &'info mut Account<Profile>,

PDA (Program Derived Address)

seeds = [...]

Seeds used to derive a PDA. Combined with bump, verifies the account address matches the derived PDA.

Syntax#[account(seeds = [seed1, seed2, ...], bump)]
Error on failureQuasarError::InvalidPda

Seed expressions can be:

  • Byte literals: b"prefix"
  • Field references: authority (uses .address().as_ref())
  • Byte slices: &some_value.to_le_bytes()
  • Any expression that evaluates to &[u8]
#[account(
    seeds = [b"vault", authority.address().as_ref(), &vault_id.to_le_bytes()],
    bump
)]
pub vault: &'info Account<Vault>,

bump

Used with seeds to handle the PDA bump seed. Two modes:

Without a value -- the framework finds the bump by calling find_program_address:

#[account(seeds = [b"vault"], bump)]

The discovered bump is stored in ctx.bumps.<field_name>.

With a value -- the framework verifies the PDA using the provided bump:

#[account(seeds = [b"vault"], bump = vault.bump)]

This is more CU-efficient since it skips the bump search.


Lifecycle

close = <destination>

Closes the account after the instruction executes. Zeros the discriminator, drains lamports to the destination, and reassigns ownership to the System program.

Syntax#[account(mut, close = destination_field)]
Requiresmut on this field, destination field must be writable
#[derive(Accounts)]
pub struct CloseVault<'info> {
    #[account(mut, close = authority)]
    pub vault: &'info mut Account<Vault>,
    #[account(mut)]
    pub authority: &'info Signer,
}

realloc = <expr>

Resizes account data, adjusting lamports for rent exemption. Growing takes lamports from the payer; shrinking returns excess.

Syntax#[account(mut, realloc = EXPR, realloc::payer = field_name)]
Requiresmut on this field
#[account(mut, realloc = Vault::SPACE + extra_space, realloc::payer = payer)]
pub vault: &'info mut Account<Vault>,

realloc::payer = <field>

Account that pays for (or receives excess from) reallocation.

Syntax#[account(realloc::payer = field_name)]
Auto-detectionFalls back to the init payer, then a field named payer

SPL Token Constraints

Used with init or init_if_needed to initialize a token account. token::mint and token::authority must be specified together.

token::mint = <field>

Sets the mint for a newly initialized token account.

Syntax#[account(init, payer = p, token::mint = mint_field, token::authority = auth_field)]
Requiresinit or init_if_needed, paired with token::authority

token::authority = <field>

Sets the authority for a newly initialized token account.

Syntax#[account(init, payer = p, token::mint = mint_field, token::authority = auth_field)]
Requiresinit or init_if_needed, paired with token::mint
#[derive(Accounts)]
pub struct InitTokenAccount<'info> {
    #[account(init, payer = payer, token::mint = mint, token::authority = authority)]
    pub vault_token: &'info mut InterfaceAccount<Token>,
    pub mint: &'info InterfaceAccount<Mint>,
    pub authority: &'info Signer,
    #[account(mut)]
    pub payer: &'info Signer,
    pub token_program: &'info Interface<TokenInterface>,
    pub system_program: &'info Program<System>,
}

SPL Mint Constraints

Used with init to initialize a new mint account.

mint::decimals = <expr>

Sets the decimals for a newly initialized mint.

Syntax#[account(init, mint::decimals = EXPR, mint::authority = field)]

mint::authority = <field>

Sets the mint authority for a newly initialized mint.

Syntax#[account(init, mint::authority = authority_field)]

mint::freeze_authority = <field>

Sets the freeze authority for a newly initialized mint.

Syntax#[account(init, mint::freeze_authority = field)]
#[derive(Accounts)]
pub struct CreateMint<'info> {
    #[account(
        init,
        payer = payer,
        mint::decimals = 6,
        mint::authority = authority,
        mint::freeze_authority = authority
    )]
    pub mint: &'info mut InterfaceAccount<Mint>,
    #[account(mut)]
    pub payer: &'info Signer,
    pub authority: &'info Signer,
    pub token_program: &'info Interface<TokenInterface>,
    pub system_program: &'info Program<System>,
}

Associated Token Account (ATA) Constraints

Create and validate associated token accounts.

associated_token::mint = <field>

Mint for ATA address derivation and validation.

Syntax#[account(associated_token::mint = mint_field)]

associated_token::authority = <field>

Authority (wallet) for ATA address derivation and validation.

Syntax#[account(associated_token::authority = authority_field)]

associated_token::token_program = <field>

Token program for ATA derivation (needed for Token-2022).

Syntax#[account(associated_token::token_program = token_program_field)]
#[derive(Accounts)]
pub struct InitAta<'info> {
    #[account(
        init,
        payer = payer,
        associated_token::mint = mint,
        associated_token::authority = owner,
        associated_token::token_program = token_program
    )]
    pub ata: &'info mut InterfaceAccount<Token>,
    pub mint: &'info InterfaceAccount<Mint>,
    pub owner: &'info Signer,
    #[account(mut)]
    pub payer: &'info Signer,
    pub token_program: &'info Interface<TokenInterface>,
    pub associated_token_program: &'info Program<AssociatedTokenProgram>,
    pub system_program: &'info Program<System>,
}

Metadata Constraints

Used with init to create Metaplex metadata and master edition accounts.

metadata::name = <expr>

Sets the NFT/token name in the metadata account.

metadata::symbol = <expr>

Sets the token symbol.

metadata::uri = <expr>

Sets the metadata URI.

metadata::seller_fee_basis_points = <expr>

Sets the seller fee in basis points (0-10000).

metadata::is_mutable = <expr>

Sets whether the metadata can be updated after creation.

master_edition::max_supply = <expr>

Sets the maximum supply for a master edition. Use 0 for unlimited.

#[derive(Accounts)]
#[instruction(name: &[u8], symbol: &[u8], uri: &[u8])]
pub struct CreateNft<'info> {
    #[account(
        init,
        payer = payer,
        mint::decimals = 0,
        mint::authority = authority
    )]
    pub mint: &'info mut InterfaceAccount<Mint>,
    #[account(
        init,
        payer = payer,
        metadata::name = name,
        metadata::symbol = symbol,
        metadata::uri = uri,
        metadata::seller_fee_basis_points = 500,
        metadata::is_mutable = true
    )]
    pub metadata: &'info mut Account<MetadataAccount>,
    #[account(
        init,
        payer = payer,
        master_edition::max_supply = 0
    )]
    pub master_edition: &'info mut Account<MasterEditionAccount>,
    #[account(mut)]
    pub payer: &'info Signer,
    pub authority: &'info Signer,
    pub token_program: &'info Interface<TokenInterface>,
    pub system_program: &'info Program<System>,
    pub metadata_program: &'info Program<MetadataProgram>,
    pub rent: &'info Sysvar<Rent>,
}

Quick reference

AttributeSyntaxDefault Error
mut#[account(mut)]Immutable
dup#[account(dup)]--
address#[account(address = EXPR)]AddressMismatch
has_one#[account(has_one = field)]HasOneMismatch
constraint#[account(constraint = EXPR)]ConstraintViolation
init#[account(init, payer = p)]AccountAlreadyInitialized
init_if_needed#[account(init_if_needed, payer = p)]--
payer#[account(payer = field)]--
space#[account(space = EXPR)]--
seeds#[account(seeds = [...], bump)]InvalidPda
bump#[account(bump)] or #[account(bump = EXPR)]--
close#[account(close = dest)]--
realloc#[account(realloc = EXPR)]InvalidRealloc
realloc::payer#[account(realloc::payer = field)]--
token::mint#[account(init, token::mint = field)]--
token::authority#[account(init, token::authority = field)]--
mint::decimals#[account(mint::decimals = EXPR)]--
mint::authority#[account(mint::authority = field)]--
mint::freeze_authority#[account(mint::freeze_authority = field)]--
associated_token::mint#[account(associated_token::mint = field)]--
associated_token::authority#[account(associated_token::authority = field)]--
associated_token::token_program#[account(associated_token::token_program = field)]--
metadata::name#[account(metadata::name = EXPR)]--
metadata::symbol#[account(metadata::symbol = EXPR)]--
metadata::uri#[account(metadata::uri = EXPR)]--
metadata::seller_fee_basis_points#[account(metadata::seller_fee_basis_points = EXPR)]--
metadata::is_mutable#[account(metadata::is_mutable = EXPR)]--
master_edition::max_supply#[account(master_edition::max_supply = EXPR)]--

On this page