Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/blueshift-gg/quasar/llms.txt

Use this file to discover all available pages before exploring further.

quasar-spl gives you direct, zero-copy access to SPL Token mint and token account data and a clean CPI layer for every standard SPL Token instruction. All reads happen in-place against the account’s raw data buffer — no deserialization, no heap allocation. CPI calls are built as stack-allocated CpiCall values and dispatched with .invoke() or .invoke_signed().

Zero-Copy Account Wrappers

Mint — Account<Mint>

Mint wraps an 82-byte SPL Token mint account. The underlying zero-copy view type is MintDataZc. Every field is available through a generated accessor:
FieldTypeDescription
mint_authority()Option<&Address>The authority that can mint new tokens, if any
supply()u64Total token supply (in base units)
decimals()u8Number of decimal places
is_initialized()boolWhether the mint has been initialized
freeze_authority()Option<&Address>Authority that can freeze token accounts, if any
// Read mint fields directly — no copy
let supply   = self.mint.supply();
let decimals = self.mint.decimals();
let is_init  = self.mint.is_initialized();
The compile-time assertion in the crate guarantees the layout is exactly 82 bytes:
const _: () = assert!(core::mem::size_of::<MintDataZc>() == 82);

Token — Account<Token>

Token wraps a 165-byte SPL Token account. The underlying zero-copy view type is TokenDataZc:
FieldTypeDescription
mint()&AddressThe mint this token account holds
owner()&AddressThe wallet or program that owns this account
amount()u64Current token balance (in base units)
delegate()Option<&Address>Approved delegate, if any
state()u8Raw account state (0 = uninitialized, 1 = initialized, 2 = frozen)
delegated_amount()u64Amount delegated to delegate
close_authority()Option<&Address>Authority that can close this account, if any
is_initialized()boolWhether state != 0
is_frozen()boolWhether state == 2
native_amount()Option<u64>SOL balance for native (wrapped SOL) accounts
// Read token account fields — no copy, no allocation
let amount = self.vault_ta_a.amount();
let mint   = self.vault_ta_a.mint();
let owner  = self.vault_ta_a.owner();
The layout is fixed:
const _: () = assert!(core::mem::size_of::<TokenDataZc>() == 165);

Validating Accounts with #[account(...)]

The token, mint, and associated_token behavior arguments let you declaratively validate token accounts at instruction dispatch time. The framework calls the appropriate validation logic before your handler runs. Validate a token account (mint + authority):
#[derive(Accounts)]
pub struct ValidateTokenCheck {
    #[account(token(mint = mint, authority = authority, token_program = token_program))]
    pub token_account: Account<Token>,
    pub mint: Account<Mint>,
    pub authority: Signer,
    pub token_program: Program<TokenProgram>,
}
Validate a mint (authority + decimals + freeze authority):
#[derive(Accounts)]
pub struct ValidateMintCheck {
    #[account(mint(authority = mint_authority, decimals = 6, freeze_authority = None, token_program = token_program))]
    pub mint: Account<Mint>,
    pub mint_authority: Signer,
    pub token_program: Program<TokenProgram>,
}
Validate an associated token account:
#[derive(Accounts)]
pub struct ValidateAtaCheck {
    #[account(associated_token(mint = mint, authority = wallet, token_program = token_program))]
    pub ata: Account<Token>,
    pub mint: Account<Mint>,
    pub wallet: Signer,
    pub token_program: Program<TokenProgram>,
}

Initializing Token Accounts

Add init (or init(idempotent)) alongside the behavior argument to have the framework run the necessary create_account + initialize_* CPIs before your handler executes.
1

Initialize a Mint

Provide decimals, authority, an optional freeze_authority, and the token_program. The framework calls system_program.create_account (with rent-exempt lamports) then token_program.initialize_mint2:
#[derive(Accounts)]
pub struct InitMint {
    #[account(mut)]
    pub payer: Signer,
    #[account(mut,
        init,
        mint(decimals = 6, authority = mint_authority, freeze_authority = None, token_program = token_program),
    )]
    pub mint: Account<Mint>,
    pub mint_authority: Signer,
    pub token_program: Program<TokenProgram>,
    pub system_program: Program<SystemProgram>,
}
2

Initialize a Token Account

Provide mint, authority, and token_program. The framework allocates 165 bytes and calls initialize_account3:
#[derive(Accounts)]
pub struct InitToken {
    #[account(mut)]
    pub payer: Signer,
    #[account(mut,
        init,
        token(mint = mint, authority = payer, token_program = token_program),
    )]
    pub token_account: Account<Token>,
    pub mint: Account<Mint>,
    pub token_program: Program<TokenProgram>,
    pub system_program: Program<SystemProgram>,
}
3

Initialize an Associated Token Account

Use associated_token(...) with ata_program and system_program. This routes through the ATA program’s Create instruction:
#[derive(Accounts)]
pub struct InitAta {
    #[account(mut)]
    pub payer: Signer,
    #[account(mut,
        init,
        associated_token(authority = wallet, mint = mint, token_program = token_program,
                         system_program = system_program, ata_program = ata_program),
    )]
    pub ata: Account<Token>,
    pub wallet: Signer,
    pub mint: Account<Mint>,
    pub token_program: Program<TokenProgram>,
    pub system_program: Program<SystemProgram>,
    pub ata_program: Program<AssociatedTokenProgram>,
}
4

Idempotent Init (init-if-needed)

Use init(idempotent) to skip creation if the account already exists. This is safe to call repeatedly:
#[account(mut,
    init(idempotent),
    token(mint = mint, authority = payer, token_program = token_program),
)]
pub token_account: Account<Token>,

TokenCpi Methods

TokenCpi is a trait implemented by Program<TokenProgram>. Import quasar_spl::prelude::* and call methods directly on your program field. Every method returns a CpiCall — chain .invoke() to execute or .invoke_signed(&seeds) to sign with a PDA.

transfer

Transfer tokens between two accounts. The authority must be the source account’s owner or an approved delegate.
#[derive(Accounts)]
pub struct Transfer {
    pub authority: Signer,
    #[account(mut)]
    pub from: Account<Token>,
    #[account(mut)]
    pub to: Account<Token>,
    pub token_program: Program<TokenProgram>,
}

impl Transfer {
    pub fn handler(&self, amount: u64) -> Result<(), ProgramError> {
        self.token_program
            .transfer(&self.from, &self.to, &self.authority, amount)
            .invoke()
    }
}
Accounts: [WRITE] source, [WRITE] destination, [SIGNER] authority

transfer_checked

Transfer tokens while also verifying the mint’s decimal count. Prefer this over transfer when the decimal count is known.
#[derive(Accounts)]
pub struct TransferChecked {
    pub authority: Signer,
    #[account(mut)]
    pub from: Account<Token>,
    pub mint: Account<Mint>,
    #[account(mut)]
    pub to: Account<Token>,
    pub token_program: Program<TokenProgram>,
}

impl TransferChecked {
    pub fn handler(&self, amount: u64, decimals: u8) -> Result<(), ProgramError> {
        self.token_program
            .transfer_checked(
                &self.from,
                &self.mint,
                &self.to,
                &self.authority,
                amount,
                decimals,
            )
            .invoke()
    }
}
Accounts: [WRITE] source, [] mint, [WRITE] destination, [SIGNER] authority

mint_to

Mint new tokens into a destination account. The caller must hold the mint authority.
#[derive(Accounts)]
pub struct MintTo {
    pub authority: Signer,
    #[account(mut)]
    pub mint: Account<Mint>,
    #[account(mut)]
    pub to: Account<Token>,
    pub token_program: Program<TokenProgram>,
}

impl MintTo {
    pub fn handler(&self, amount: u64) -> Result<(), ProgramError> {
        self.token_program
            .mint_to(&self.mint, &self.to, &self.authority, amount)
            .invoke()
    }
}
Accounts: [WRITE] mint, [WRITE] destination, [SIGNER] mint authority

burn

Reduce the token supply by burning tokens from an account.
#[derive(Accounts)]
pub struct Burn {
    pub authority: Signer,
    #[account(mut)]
    pub from: Account<Token>,
    #[account(mut)]
    pub mint: Account<Mint>,
    pub token_program: Program<TokenProgram>,
}

impl Burn {
    pub fn handler(&self, amount: u64) -> Result<(), ProgramError> {
        self.token_program
            .burn(&self.from, &self.mint, &self.authority, amount)
            .invoke()
    }
}
Accounts: [WRITE] source account, [WRITE] mint, [SIGNER] authority

close_account

Close a token account and send its remaining lamports to a destination. Use .invoke_signed(&seeds) when the authority is a PDA.
#[derive(Accounts)]
pub struct CloseTokenAccount {
    #[account(mut)]
    pub account: Account<Token>,
    #[account(mut)]
    pub destination: Signer,
    #[account(dup)]
    pub authority: Signer,
    pub token_program: Program<TokenProgram>,
}

impl CloseTokenAccount {
    pub fn handler(&self) -> Result<(), ProgramError> {
        self.token_program
            .close_account(&self.account, &self.destination, &self.authority)
            .invoke()
    }
}
Accounts: [WRITE] account to close, [WRITE] lamport destination, [SIGNER] close authority

approve

Grant a delegate permission to transfer up to amount tokens from the source.
#[derive(Accounts)]
pub struct Approve {
    pub authority: Signer,
    #[account(mut)]
    pub source: Account<Token>,
    pub delegate: UncheckedAccount,
    pub token_program: Program<TokenProgram>,
}

impl Approve {
    pub fn handler(&self, amount: u64) -> Result<(), ProgramError> {
        self.token_program
            .approve(&self.source, &self.delegate, &self.authority, amount)
            .invoke()
    }
}
Accounts: [WRITE] source, [] delegate, [SIGNER] owner

revoke

Revoke the current delegate’s authority.
#[derive(Accounts)]
pub struct Revoke {
    pub authority: Signer,
    #[account(mut)]
    pub source: Account<Token>,
    pub token_program: Program<TokenProgram>,
}

impl Revoke {
    pub fn handler(&self) -> Result<(), ProgramError> {
        self.token_program
            .revoke(&self.source, &self.authority)
            .invoke()
    }
}
Accounts: [WRITE] source, [SIGNER] owner

PDA-Signed CPIs

When the authority is a program-derived address, pass the PDA seeds to .invoke_signed(). The seeds are stack-allocated Seed slices — no heap allocation required.
use quasar_lang::cpi::Seed;

let bump = [bumps.escrow];
let seeds = [
    Seed::from(b"escrow" as &[u8]),
    Seed::from(self.maker.address().as_ref()),
    Seed::from(bump.as_ref()),
];

self.token_program
    .transfer(
        &self.vault_ta_a,
        &self.taker_ta_a,
        &self.escrow,
        self.vault_ta_a.amount(),
    )
    .invoke_signed(&seeds)?;

Exit Behaviors: token_close and token_sweep

In addition to calling CPIs manually, you can attach close and sweep logic directly to an account field so the framework runs them automatically at instruction exit: token_close — calls close_account after your handler returns:
#[account(
    mut,
    token_close(dest = receiver, authority = authority, token_program = token_program)
)]
pub vault: Account<Token>,
token_sweep — transfers all tokens out (via transfer_checked) before closing:
#[derive(Accounts)]
pub struct SweepAndClose {
    pub authority: Signer,
    #[account(
        mut,
        token(mint = mint, authority = authority, token_program = token_program),
        token_sweep(receiver = receiver, mint = mint, authority = authority, token_program = token_program),
        token_close(dest = destination, authority = authority, token_program = token_program)
    )]
    pub source: Account<Token>,
    #[account(mut)]
    pub receiver: Account<Token>,
    pub mint: Account<Mint>,
    #[account(mut)]
    pub destination: UncheckedAccount,
    pub token_program: Program<TokenProgram>,
}
token_sweep reads the balance and decimals from the zero-copy account data and builds a transfer_checked CPI. If the balance is already zero it is a no-op.

Full Example: Token Escrow

The escrow example in the repository uses quasar-spl throughout its three instructions. Here is how Make, Take, and Refund are implemented:
The maker deposits token A into a vault and creates a receive account for token B, both initialized idempotently:
use {
    crate::{ events::MakeEvent, state::{Escrow, EscrowInner} },
    quasar_lang::prelude::*,
    quasar_spl::prelude::*,
};

#[derive(Accounts)]
pub struct Make {
    #[account(mut)]
    pub maker: Signer,
    #[account(init, payer = maker, address = Escrow::seeds(maker.address()))]
    pub escrow: Account<Escrow>,
    pub mint_a: Account<Mint>,
    pub mint_b: Account<Mint>,
    #[account(mut)]
    pub maker_ta_a: Account<Token>,
    #[account(init(idempotent), payer = maker, token(mint = mint_b, authority = maker, token_program = token_program))]
    pub maker_ta_b: Account<Token>,
    #[account(init(idempotent), payer = maker, token(mint = mint_a, authority = escrow, token_program = token_program))]
    pub vault_ta_a: Account<Token>,
    pub rent: Sysvar<Rent>,
    pub token_program: Program<TokenProgram>,
    pub system_program: Program<SystemProgram>,
}

impl Make {
    pub fn deposit_tokens(&mut self, amount: u64) -> Result<(), ProgramError> {
        self.token_program
            .transfer(&self.maker_ta_a, &self.vault_ta_a, &self.maker, amount)
            .invoke()
    }
}
The escrow instructions use init(idempotent) on all token account fields so each instruction can be retried safely even if a previous attempt partially succeeded.

Build docs developers (and LLMs) love