Quasar
SPL Tokens

Token Program

Zero-copy token accounts, typed CPI, and declarative initialization.

The quasar-spl crate provides zero-copy wrappers and typed CPI for the SPL Token program. Token accounts and mints are pointer-cast directly from the SVM input buffer -- no deserialization.

Account Types

Two marker types: Token for token accounts (165 bytes) and Mint for mint accounts (82 bytes). Use them with Account<T>:

use quasar_spl::{Mint, Token, TokenCpi};

#[derive(Accounts)]
pub struct TransferTokens<'info> {
    pub authority: &'info Signer,
    pub from: &'info mut Account<Token>,
    pub to: &'info mut Account<Token>,
    pub token_program: &'info Program<Token>,
}

Account<Token> validates that the account is owned by the SPL Token program and has at least 165 bytes. Account<Mint> does the same for 82-byte mint accounts. Both deref to zero-copy structs with typed field accessors:

// TokenAccountState fields
token_account.mint()           // &Address
token_account.owner()          // &Address
token_account.amount()         // u64
token_account.delegate()       // Option<&Address>
token_account.is_frozen()      // bool

// MintAccountState fields
mint.mint_authority()          // Option<&Address>
mint.supply()                  // u64
mint.decimals()                // u8
mint.freeze_authority()        // Option<&Address>

CPI

Program<Token> implements the TokenCpi trait, giving you method-style calls that return CpiCall values:

// Transfer
self.token_program
    .transfer(self.from, self.to, self.authority, amount)
    .invoke()?;

// Transfer with decimal verification
self.token_program
    .transfer_checked(self.from, self.mint, self.to, self.authority, amount, decimals)
    .invoke()?;

// Mint new tokens
self.token_program
    .mint_to(self.mint, self.destination, self.mint_authority, amount)
    .invoke()?;

// Burn
self.token_program
    .burn(self.source, self.mint, self.authority, amount)
    .invoke()?;

// Approve / revoke delegate
self.token_program.approve(self.source, self.delegate, self.authority, amount).invoke()?;
self.token_program.revoke(self.source, self.authority).invoke()?;

All CPI methods work with .invoke() for regular signers and .invoke_signed(&seeds) for PDA signers.

Initializing Token Accounts

Use init with token::mint and token::authority to declaratively create and initialize token accounts:

#[account(init, token::mint = mint, token::authority = payer)]
pub token_account: &'info mut Account<Token>,

This chains System::create_account + InitializeAccount3 in a single step. For conditional creation, use init_if_needed -- it skips creation if the account already exists and validates that mint and authority match:

#[account(init_if_needed, payer = maker, token::mint = mint_a, token::authority = escrow)]
pub vault: &'info mut Account<Token>,

Initializing Mints

Use mint::decimals and mint::authority. Optional mint::freeze_authority:

#[account(init, mint::decimals = 6, mint::authority = mint_authority)]
pub mint: &'info mut Account<Mint>,

Closing Token Accounts

Token accounts are owned by the SPL Token program, not yours. Use the TokenClose trait instead of Account::close() -- it performs a CPI to the token program's CloseAccount instruction:

use quasar_spl::TokenClose;

self.vault_ta_a
    .close(self.token_program, self.taker, self.escrow)
    .invoke_signed(&seeds)?;

PDA-Signed Operations

When a PDA owns tokens, pass the seeds to invoke_signed. From the escrow refund:

pub fn withdraw_tokens_and_close(&mut self, bumps: &RefundBumps) -> Result<(), ProgramError> {
    let seeds = bumps.escrow_seeds();

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

    self.vault_ta_a
        .close(self.token_program, self.maker, self.escrow)
        .invoke_signed(&seeds)
}

On this page