Quasar
Getting Started

Migrating from Anchor

What changes when you port an Anchor program to Quasar.

alt

Quasar is designed to feel like Anchor. #[derive(Accounts)], #[program], seeds, bump, has_one, constraint, init, close, require! -- all work the same way. The runtime difference: Quasar is zero-copy and no_std, so there is no deserialization and no heap allocation.

Imports

// anchor_lang::prelude::*;
use quasar_lang::prelude::*;

Instructions

#[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)
}
AnchorQuasar
Context<T>Ctx<T>
Result<()>Result<(), ProgramError>
Discriminator optional (defaults to SHA-256 hash)Discriminator required -- no implicit hashing

Logic lives in impl methods on the accounts struct. The #[program] block is just dispatch.

Accounts struct

#[derive(Accounts)]
pub struct Take<'info> {
    pub taker: &'info mut Signer,
    #[account(
        has_one = maker,
        constraint = escrow.receive > 0,
        close = taker,
        seeds = [b"escrow", maker],
        bump = escrow.bump
    )]
    pub escrow: &'info mut Account<Escrow>,
    pub maker: &'info mut UncheckedAccount,
    pub token_program: &'info Program<Token>,
    pub system_program: &'info Program<System>,
}
AnchorQuasar
Signer<'info>&'info Signer or &'info mut Signer
Account<'info, T>&'info Account<T> or &'info mut Account<T>
#[account(mut)] attribute&'info mut in the type
seeds = [b"escrow", maker.key().as_ref()]seeds = [b"escrow", maker] -- field name directly

Account types

#[account(discriminator = 1)]
pub struct Escrow {
    pub maker: Address,
    pub mint_a: Address,
    pub receive: u64,
    pub bump: u8,
}
AnchorQuasar
#[account]#[account(discriminator = N)] -- explicit, must be non-zero
Borsh deserializationZero-copy -- fields read directly from account memory

Address replaces Pubkey and .address() replaces .key() -- this is a Solana SDK change, not Quasar-specific.

CPI

Method-style calls instead of CpiContext:

// Transfer tokens (no PDA signer)
self.token_program
    .transfer(self.maker_ta_a, self.vault_ta_a, self.maker, amount)
    .invoke()
// PDA-signed transfer + close
let seeds = bumps.escrow_seeds();

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

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

#[derive(Accounts)] generates seed helpers on the bumps struct -- bumps.escrow_seeds() returns the full seeds array ready for invoke_signed. No manual seed construction needed.

Events

#[event(discriminator = 0)]
pub struct MakeEvent {
    pub escrow: Address,
    pub maker: Address,
    pub deposit: u64,
}

// Emit via sol_log_data (~100 CU)
emit!(MakeEvent {
    escrow: *self.escrow.address(),
    maker: *self.maker.address(),
    deposit: amount,
});

Same as Anchor but with an explicit discriminator. Supported field types: Address, u8--u128, i8--i128, bool.

Error codes

#[error_code]
pub enum MyError {
    Unauthorized,
    InsufficientFunds,
}

require!(amount > 0, MyError::InsufficientFunds);

require!, require_eq!, and require_keys_eq! work the same. Variants map to ProgramError::Custom(N) with an offset of 6000.

no_std

Quasar programs are #![no_std] on-chain (std is available during tests via cfg_attr).

No std::string::String, std::vec::Vec, or heap allocation. Quasar provides its own zero-copy replacements for use in account structs:

#[account(discriminator = 1)]
pub struct MultisigConfig<'a> {
    pub creator: Address,
    pub threshold: u8,
    pub label: String<'a, 32>,         // max 32 bytes, accessed as &str
    pub signers: Vec<'a, Address, 10>, // max 10 elements, accessed as &[Address]
}

String<'a, MAX> and Vec<'a, T, MAX> are stored inline in the account data with a length prefix. They are read as &str and &[T] slices -- zero-copy, zero allocation. The MAX parameter sets the upper bound enforced at write time.

Use log() instead of println!() for on-chain logging.

Quick reference

AnchorQuasar
anchor_lang::prelude::*quasar_lang::prelude::*
Context<T>Ctx<T>
Result<()>Result<(), ProgramError>
Signer<'info>&'info Signer
#[account(mut)]&'info mut
#[account]#[account(discriminator = N)]
Discriminator optional#[instruction(discriminator = N)] required
CpiContext::new(...).transfer(...).invoke()
Manual PDA seedsbumps.name_seeds() auto-generated
String / Vec (heap)String<'a, MAX> / Vec<'a, T, MAX> (zero-copy)

On this page