Migrating from Anchor
What changes when you port an Anchor program to Quasar.

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)
}| Anchor | Quasar |
|---|---|
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>,
}| Anchor | Quasar |
|---|---|
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,
}| Anchor | Quasar |
|---|---|
#[account] | #[account(discriminator = N)] -- explicit, must be non-zero |
| Borsh deserialization | Zero-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
| Anchor | Quasar |
|---|---|
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 seeds | bumps.name_seeds() auto-generated |
String / Vec (heap) | String<'a, MAX> / Vec<'a, T, MAX> (zero-copy) |
