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.

Instructions are the entry points into your Quasar program. Each handler consists of two parts: an accounts struct annotated with #[derive(Accounts)] that declares and validates every account the instruction touches, and a function inside the #[program] module annotated with #[instruction(discriminator = N)] that contains the business logic.

#[instruction(discriminator = N)]

Place #[instruction(discriminator = N)] on a handler function inside a #[program] module to assign it a fixed dispatch byte:
#[instruction(discriminator = 0)]
pub fn increment(ctx: Ctx<Increment>) -> Result<(), ProgramError> {
    ctx.accounts.counter.count += 1;
    Ok(())
}
The discriminator byte must be unique within the program. The dispatch! macro generated by #[program] matches the first byte(s) of the incoming instruction data against each registered discriminator and routes execution to the correct handler.

#[derive(Accounts)]

The accounts struct carries every account the handler needs. Annotate it with #[derive(Accounts)] and describe each field with #[account(...)] constraint attributes:
#[derive(Accounts)]
pub struct Increment<'info> {
    #[account(has_one(authority))]
    pub counter: &'info mut Account<Counter>,
    pub authority: &'info Signer,
}
The derive macro generates a ParseAccounts impl that walks the SVM input buffer, validates every account (owner, discriminator, signer flags, writable flags, PDA addresses, and custom constraints), and returns the typed struct ready for use in the handler.

Account Constraint Attributes

Constraint attributes are placed on individual fields inside the #[derive(Accounts)] struct using #[account(...)]:
Mark an account as writable. The runtime rejects instructions where this account was not passed as writable.
#[account(mut)]
pub maker: Signer,
Initialize a new on-chain account via a System Program CPI. Requires payer = <field> to designate who pays rent.
#[account(init, payer = maker, address = Escrow::seeds(maker.address()))]
pub escrow: Account<Escrow>,
Use init(idempotent) to silently accept an account that is already initialized:
#[account(init(idempotent), payer = maker, token(mint = mint_a, authority = escrow, token_program = token_program))]
pub vault_ta_a: Account<Token>,
Verify that a field on the account’s inner data equals the address of another account in the struct. Prevents substituting a different account for a related one.
#[account(has_one(maker), close(dest = maker))]
pub escrow: Account<Escrow>,
The check reads escrow.maker and asserts it equals maker.address().
Verify the account’s public key matches an expression. Used to enforce PDA addresses:
#[account(address = Escrow::seeds(maker.address()))]
pub escrow: Account<Escrow>,
Transfer this account’s lamports to dest and zero its data at the end of the instruction:
#[account(mut, has_one(maker), close(dest = maker))]
pub escrow: Account<Escrow>,
Evaluate a boolean expression and return ConstraintViolation if it is false:
#[account(constraints(escrow.receive > 0))]
pub escrow: Account<Escrow>,
Allow the same account to appear more than once in a transaction. By default Quasar rejects duplicate accounts to prevent accidental aliasing:
#[account(dup)]
pub signer: Signer,
When combined with init or init(idempotent), configure token account initialization parameters:
#[account(init(idempotent), payer = maker, token(mint = mint_b, authority = maker, token_program = token_program))]
pub maker_ta_b: Account<Token>,

Ctx<T> — the Handler Context

Every instruction handler receives a Ctx<T> where T is the accounts struct:
pub struct Ctx<'input, T> {
    pub accounts: T,
    pub bumps: <T as ParseAccounts<'input>>::Bumps,
    pub program_id: &'input [u8; 32],
    pub data: &'input [u8],
}
  • ctx.accounts — the fully parsed and validated accounts struct
  • ctx.bumps — auto-generated bump seeds for every PDA account
  • ctx.program_id — the calling program’s address
  • ctx.data — instruction data with the discriminator already consumed

CtxWithRemaining<T> for Remaining Accounts

When your handler needs to inspect or forward trailing accounts, use CtxWithRemaining<T> instead:
#[instruction(discriminator = 3)]
pub fn process_batch(ctx: CtxWithRemaining<Batch>) -> Result<(), ProgramError> {
    for item in ctx.remaining_accounts() {
        // inspect each trailing account
    }
    Ok(())
}
remaining_accounts() returns a RemainingAccounts iterator over account views beyond the declared fields.

Auto-Generated Bumps Struct

For every PDA account in the #[derive(Accounts)] struct, Quasar generates a <StructName>Bumps companion with one u8 field per PDA. Access bumps through ctx.bumps:
#[derive(Accounts)]
pub struct Make {
    #[account(init, payer = maker, address = Escrow::seeds(maker.address()))]
    pub escrow: Account<Escrow>,
    // ...
}

// In the handler:
let bump: u8 = ctx.bumps.escrow;
Store the bump in account state so later instructions can sign CPIs cheaply without re-deriving:
self.escrow.set_inner(EscrowInner {
    bump: bumps.escrow,
    // ...
});

Instruction Data Parameters

Handler functions can accept additional typed parameters after ctx. These are deserialized from the remaining instruction data bytes after the discriminator:
#[instruction(discriminator = 0)]
pub fn make(ctx: Ctx<Make>, deposit: u64, receive: u64) -> Result<(), ProgramError> {
    ctx.accounts.make_escrow(receive, &ctx.bumps)?;
    ctx.accounts.deposit_tokens(deposit)
}
Supported parameter types implement InstructionArg. Built-in implementations cover all primitive integer types, bool, Address, and user types derived with #[derive(QuasarSerialize)]:
#[derive(QuasarSerialize)]
pub struct SwapParams {
    pub amount_in: u64,
    pub min_amount_out: u64,
    pub slippage_bps: u16,
}

Full Escrow Example

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 {
    #[inline(always)]
    pub fn make_escrow(&mut self, receive: u64, bumps: &MakeBumps) -> Result<(), ProgramError> {
        self.escrow.set_inner(EscrowInner {
            maker: *self.maker.address(),
            mint_a: *self.mint_a.address(),
            mint_b: *self.mint_b.address(),
            maker_ta_b: *self.maker_ta_b.address(),
            receive,
            bump: bumps.escrow,
        });
        Ok(())
    }

    #[inline(always)]
    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 #[inline(always)] attribute on impl methods is a Quasar convention. Because handlers call these small methods only once, inlining eliminates the call overhead and allows the compiler to optimize across the combined body.

Build docs developers (and LLMs) love