01

The Big Picture

What is Quasar, who is it for, and why does it exist at all?

What did you build?

Quasar is a framework for writing programs that run on Solana. Think of it as the toolbox + rulebook that makes writing Solana programs less painful.

Solana already has a popular framework called Anchor. Quasar is a direct competitor, but with one obsession: spending as few compute units as possible.

💡
The core bet: Anchor deserializes account data (reads bytes and converts them into Rust structs). Quasar skips that step entirely — it just reads the raw bytes directly, like looking at a photo instead of reading a description of the photo. Faster, cheaper, but more complex internally.
🔗
On-chain programs
Smart contracts that live on the Solana blockchain. They hold logic, not just data.
Zero-copy access
Reads account data without converting it — pointer straight to the bytes. The key performance trick.
🏗️
Anchor-compatible ergonomics
Looks and feels like Anchor — same macro patterns — but different internals.
🛠️
Full toolchain
Includes a CLI, profiler, IDL generator, and SPL helpers — not just a library.

The project's anatomy

The repo is a Cargo workspace — one repo, many crates, all compiled together. Here's what each folder is responsible for:

📁lang/ Runtime brain — the framework itself
📄src/lib.rs Exports all modules
📄src/accounts/ Account wrapper types
📄src/cpi/ Cross-program calls
📁derive/ The magic macros (#[program] etc)
📁idl/ Interface description + client generator
📁pod/ Special integer types (alignment-1)
📁spl/ SPL Token / Token-2022 helpers
📁profile/ Compute unit profiler
📁cli/ The `quasar` command-line tool
📁examples/ Escrow, vault, multisig — real programs
📁tests/ Integration test programs
🎯
AI steering tip: When asking Claude to add a runtime feature (new account type, new check), say "add it to lang/src/". When asking about how macros work, say "look in derive/src/". This immediately cuts the search space in half.

Check your understanding

A colleague says "Quasar is just an Anchor clone." You want to correct them. What's the key architectural difference?

02

The Cast

After this module you'll know which crate does what, so you can point AI at the right file.

Who owns what?

Click any component below to see what it's responsible for and how to steer AI when working with it.

quasar-lang Runtime framework quasar-derive Proc macros quasar-idl IDL + codegen quasar-pod Alignment-1 ints quasar-spl Token helpers quasar-profile CU profiler quasar CLI build · test · profile · deploy · dump

← Click any component to learn what it owns

How the crates talk at build time

When you run quasar build, the crates collaborate in a chain. Here's what they say to each other:

Where would you look?

🔍 Navigation Scenario

You want to add a new macro called #[authority] that automatically checks if an account's authority field matches the signer. Which crate do you touch first?

03

The Journey

Trace a real user action — token escrow deposit — from client call to on-chain state change.

The escrow example — your guiding story

The most complete example in the repo is examples/escrow/. It implements a classic token escrow: Alice locks Token A, Bob sends Token B, they swap atomically. Three instructions: make, take, refund.

Alice signs a transaction saying "I'll give 1337 Token A if someone gives me 1337 Token B." The program creates an escrow account holding her offer, and moves her tokens into a vault.
A make instruction is sent with Alice's accounts. The program validates the PDA seeds, initializes the Escrow account with Alice's terms, and CPIs to the Token program to transfer Alice's tokens to a vault.
ctx.accounts.make_escrow(receive, &ctx.bumps) writes to the on-chain account. ctx.accounts.deposit_tokens(deposit) CPI-calls the Token program. All in examples/escrow/src/instructions/make.rs.

The make instruction — traced end to end

Press play to walk through every step from client call to final state:

💻
Client
⚙️
Quasar runtime
📜
Program logic
🪙
Token program
1
Transaction sent with discriminator [0] + accounts
2
parse_accounts() validates headers (signer? writable? owner?)
3
Dispatch to make() — zero-copy pointer cast to Escrow struct
4
make_escrow() writes maker, mints, receive, bump to account data
5
CPI: Transfer deposit lamports of Token A to vault
6
Success — escrow live on-chain, Alice's tokens locked

The make instruction — decoded

Here's the actual lib.rs for the escrow, translated line by line:

CODE — examples/escrow/src/lib.rs
#[program]
mod quasar_escrow {
  use super::*;

  #[instruction(discriminator = 0)]
  pub fn make(
    ctx: Ctx<Make>,
    deposit: u64,
    receive: u64,
  ) -> Result<(), ProgramError> {
    ctx.accounts.make_escrow(
      receive, &ctx.bumps
    )?;
    ctx.accounts.emit_event(
      deposit, receive
    )?;
    ctx.accounts.deposit_tokens(deposit)
  }
}
PLAIN ENGLISH

#[program] — "this module IS the on-chain program; generate its entrypoint and dispatch logic"

discriminator = 0 — this instruction's ID number. The first byte of every call must match or the program rejects it

Ctx<Make> — a wrapper holding all the accounts this instruction needs, already validated

make_escrow() — writes Alice's terms (what she wants, her bump seed) to the escrow account on-chain

emit_event() — logs the transaction so off-chain indexers can track it

deposit_tokens() — CPIs to the Token program: move Alice's tokens into the vault

🎯
AI steering tip: When adding a new instruction, tell Claude: "Add a new instruction to lib.rs with discriminator N, create a corresponding Make-style accounts struct in instructions/, and wire up the method calls." That structure is exactly how every existing instruction is laid out.

What lives on-chain — the Escrow account

After make runs, an Escrow account is written to the blockchain. This is its exact shape:

CODE — examples/escrow/src/state.rs
#[account(discriminator = 1)]
pub struct Escrow {
  pub maker:      Address,  // 32 bytes
  pub mint_a:     Address,  // 32 bytes
  pub mint_b:     Address,  // 32 bytes
  pub maker_ta_b: Address,  // 32 bytes
  pub receive:    u64,      //  8 bytes
  pub bump:       u8,       //  1 byte
}
PLAIN ENGLISH

#[account(discriminator = 1)] — the first byte of this account's data will always be 1, so the program can verify it's reading an Escrow and not some other account type

maker — Alice's wallet address. Who created this deal

mint_a / mint_b — the two token types involved in the swap

maker_ta_b — where to send Bob's tokens when the deal completes

receive — how many Token B Alice wants in return

bump — the PDA bump seed, saved so future instructions can re-derive the same address

💡
The discriminator trick: Quasar requires all-zero discriminators to be banned at compile time. A discriminator of [0,0,0,0] would look identical to an uninitialized account full of zeros — a security hole. The compiler catches this for you.

Trace the take instruction

Bob calls the take instruction. Which sequence is correct?

04

The Macros

Quasar's macros are its superpower — understand what they generate so you know what you're actually writing.

Macros — code that writes code

A proc-macro in Rust is like a stamp. You press it on your code and it produces a much larger imprint. Quasar ships five core stamps, each in derive/src/:

📦
#[program]
Turns a module into a full Solana program — generates the entrypoint, dispatch table, and (off-chain) client builder.
🗂️
#[account]
Turns a struct into an on-chain account type — generates a zero-copy companion struct, discriminator check, and space calculation.
🔢
#[instruction]
Attaches a discriminator to a function. The first byte of a transaction must match, or the call is rejected.
📣
#[event]
Defines a loggable event. Generates emit!() (cheap, ~100 CU) and emit_cpi!() (spoofing-resistant, ~1000 CU).
#[error_code]
Defines custom program errors. Each variant is numbered from 6000 (Anchor convention), with a human-readable message.

The Accounts derive — your program's front door

Every instruction has an accounts struct. The #[derive(Accounts)] macro reads your field annotations and generates the entire parse_accounts() function:

CODE — what you write
#[derive(Accounts)]
pub struct Make<'info> {
  #[account(mut, signer)]
  maker: UncheckedAccount<'info>,

  #[account(
    init,
    seeds = [b"escrow", maker],
    payer = maker,
    space = Escrow::SPACE,
  )]
  escrow: Account<'info, Escrow>,

  #[account(address = system_program::ID)]
  system_program: UncheckedAccount<'info>,
}
WHAT GETS GENERATED

maker: check header byte = NOT_BORROWED | signer | writable. If not, error immediately.

escrow (init): if account doesn't exist, CPI to System Program to create it with the right rent. Then verify PDA seeds match [b"escrow", maker's key]. Write discriminator. Return pointer.

system_program: check that its address equals the known System Program ID. Reject anything else.

All of this runs in a single pass over the account slice — no heap allocation.

💡
The header trick: Quasar reads the first 4 bytes of each account's metadata as a single u32 and compares it to a pre-computed constant (NODUP_MUT_SIGNER etc.). One comparison checks signer + writable + not-duplicated simultaneously. This is like checking three ID cards in one glance instead of one at a time.

PDAs — deterministic addresses you control

A PDA is how Quasar programs create accounts they control. Like giving each escrow deal its own pigeonhole in a post office — always at the same slot, always only accessible to the program.

1
Choose seeds
Seeds are bytes that uniquely identify this account. For an escrow: [b"escrow", maker_pubkey] — one escrow per maker wallet.
2
Derive the address
Solana hashes seeds + program ID to get a 32-byte address. This is deterministic — same seeds always give the same address.
3
Save the bump
A "bump" is a small number added until the hash lands off the ed25519 curve (ensuring no private key exists). Save it in the account — future instructions need it to re-sign.
4
Only your program can sign
Since there's no private key, only CPI calls from your program can authorize actions on this account. No one else can touch it.
🎯
AI steering tip: When adding a PDA-backed account, tell Claude: "Create a PDA with seeds [b'my-label', user_key]. Add it to the accounts struct with init, seeds = [...], payer = user, space = MyStruct::SPACE. Save the bump in the struct."

Know your macros

You want to add a new on-chain account type called UserProfile. Which macro do you use, and what does it generate for you?

05

The Clever Bits

The engineering decisions that make Quasar unusual — and how they affect you as a builder.

Zero-copy — the core trick

This is Quasar's central design decision. Let's make it concrete.

❌ Anchor's approach (Borsh)
  • Account bytes arrive from the runtime
  • Borsh reads each field, converts bytes to Rust types
  • Allocates a new struct on the heap
  • Program works with the nice Rust struct
  • ~200–500 extra CU per account access
  • Easy to program, some overhead
✓ Quasar's approach (zero-copy)
  • Account bytes arrive from the runtime
  • After bounds + discriminator check — done
  • Cast a pointer directly to a #[repr(C)] struct
  • Program reads fields through the pointer
  • ~0 extra CU for the cast itself
  • Requires careful unsafe Rust internals
💡
The blueprint analogy: Borsh is like ordering a house — you describe it, someone builds a new one for you. Zero-copy is like getting the blueprints and reading the measurements directly off the page. No house built, just a pointer to the original drawings. Faster, but you have to trust the blueprints are in the right format.

How safety is maintained despite unsafe Rust

Quasar uses unsafe for its zero-copy pointer casts. Three guarantees make this sound:

1
Alignment-1 guarantee
All account structs and the Pod integer types have alignment 1 — meaning any byte address is a valid start. Compile-time assertions enforce this. No misaligned reads possible.
2
Bounds checking before casting
The account data length is validated against the expected struct size before any pointer cast. If it's too short — hard error, no cast attempted.
3
Discriminator validation
The first byte(s) of account data are checked against the expected discriminator. Reading an unrelated account as an Escrow struct is caught here.
4
Miri verification
Every unsafe block is validated by Miri (Rust's interpreter) under Tree Borrows with symbolic alignment checking. It's like a mathematical proof-checker for the unsafe code.

CPI — how Quasar programs call other programs

CPI is how Quasar programs talk to external programs (Token, System, etc.). Quasar's CPI builder is const-generic — the number of accounts is baked in at compile time, so it allocates on the stack, not the heap.

CODE — lang/src/cpi/system.rs pattern
// Stack-allocated CPI — no heap needed
let cpi = TransferCpi::new(
  system_program,
  [from_account, to_account],
);
cpi.invoke_signed(&[
  &[b"escrow", maker_key, &[bump]],
])?;
PLAIN ENGLISH

Build a CPI call targeting the System Program — the account list lives on the call stack (no memory allocation)

The [from_account, to_account] array size is fixed at compile time — the compiler knows exactly how much stack space to reserve

invoke_signed executes the CPI, passing our PDA's seeds as the "signature" that proves we have authority

The IDL — your program's public contract

The IDL is generated by quasar idl <path>. It parses your Rust source and produces three outputs:

📋
JSON IDL
Machine-readable description of every instruction, account, and type. Lives in target/idl/.
🟦
TypeScript client
Typed instruction builders for frontend code. Auto-generated from the IDL — no manual writing.
🦀
Rust client
Instruction builder structs for Rust tests and off-chain tools. Same source, different target.
🎯
AI steering tip: When writing tests, tell Claude: "Use the generated IDL client in src/idl_client.rs to build the instruction — don't construct the bytes manually." The generated client handles discriminators and account ordering for you.

Spot the design principle

🔍 Debugging Scenario

A junior dev on your team adds a new account type with #[account(discriminator = [0, 0, 0, 0])]. The code doesn't compile. They're confused. What do you tell them?

06

The CLI & Profiler

The `quasar` binary is your daily driver — know every command and you'll work 10× faster.

Every quasar command, explained

1
quasar init [name]
Interactive wizard that scaffolds a complete project: Cargo.toml, Quasar.toml, source files, test setup, and a program keypair. Saves your preferences for next time.
2
quasar build [--watch] [--debug]
Generates the IDL first, then runs cargo build-sbf. Prints binary size + delta: "Build complete in 1.2s (56.6 KB, -1.2 KB)". --debug is required for profiling.
3
quasar test [--filter] [--watch]
Builds first, then runs Rust tests (Mollusk/QuasarSVM) or TypeScript tests (Mocha). Parses results into structured pass/fail output.
4
quasar profile [--expand] [--share]
Walks the sBPF binary's call graph statically. Shows top 5 hottest functions with CU counts. Starts a local flamegraph server automatically.
5
quasar dump [--function] [--source]
Disassembles the compiled binary. --source interleaves your Rust source code with the assembly. Useful for understanding exactly what the compiler produced.
6
quasar deploy
Deploys to Solana using your CLI config's cluster and wallet. Auto-detects the program keypair from target/deploy/.

Quasar.toml — your project's control panel

Every Quasar project has a Quasar.toml that tells the CLI how to behave. It's small but important:

Quasar.toml
[project]
name = "my-program"

[toolchain]
type = "solana"
# or "upstream" for nightly BPF

[testing]
framework = "mollusk"
# or: "none", "quasarsvm-rust",
# "quasarsvm-web3js", "quasarsvm-kit"
PLAIN ENGLISH

name — the program's crate name. Used in build outputs and IDL filenames

toolchain: "solana" — use the stable Solana SDK build tool (cargo build-sbf)

toolchain: "upstream" — use nightly Rust's BPF target directly (for advanced use)

framework: "mollusk" — use Mollusk for tests: a fast in-process SVM simulator, no validator needed

Changing framework changes how quasar test runs — it knows whether to launch a TypeScript runner or cargo test

The profiler — your CU budget dashboard

The profiler in profile/ statically walks the compiled binary's call graph using DWARF debug info. No transaction needed — it reads the binary like an X-ray.

💡
The flight recorder analogy: Other profilers run your code and measure it like a stopwatch. Quasar's profiler reads the compiled binary like reading a flight plan — it knows every instruction that will execute before the plane takes off. Deterministic and instant.
📊
First run
Shows the 5 hottest functions — where your compute budget is being spent.
🔄
Subsequent runs
Shows regressions and improvements since last run. "Initialize::verify +42 CU" means your recent change added 42 CU to that function.
🔥
Flamegraph
Interactive browser visualization served at localhost:7777. Width = CU cost. See the full call graph at a glance.
🔗
--share
Uploads the profile as a GitHub Gist for sharing with teammates or issues.
🎯
AI steering tip: If Claude adds code that spikes your CU usage, run quasar profile --expand and share the output. Then tell Claude: "The profiler shows Deposit::process gained 200 CU. Look at that function in instructions/deposit.rs and reduce compute usage."

How tests work in Quasar

Tests use Mollusk — a fast in-process SVM. The test sets up fake accounts, builds an instruction using the generated IDL client, and checks the resulting account state:

CODE — examples/escrow/src/tests.rs (pattern)
fn setup() -> Mollusk {
  Mollusk::new(
    &crate::ID,
    "../../target/deploy/quasar_escrow"
  )
}

let result = mollusk.process_instruction(
  &instruction,
  &[(maker, maker_account), ...],
);
assert!(result.program_result.is_ok());
// Then inspect result.resulting_accounts
PLAIN ENGLISH

Create a Mollusk instance pointed at your compiled program binary

Build a fake account for the maker (just a struct with lamports and owner)

process_instruction runs the instruction through the SVM — same VM as mainnet, but in-process

resulting_accounts contains the final state of every account after the instruction ran — inspect these to verify your program wrote the right data

Put it all together

🔍 Real-world Scenario

You've added a new close instruction to your escrow program. Tests pass but a teammate says the transaction now uses 500 extra CU. What's your debugging workflow?

💡
You now understand Quasar. You know the crate boundaries, the zero-copy trick, how macros generate your boilerplate, how PDAs work, how instructions are dispatched, and how to measure compute costs. Every conversation with Claude about this codebase can now be precise — you know exactly which file to point at.