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.

The counter program is the canonical “Hello, World” of Quasar: it stores a single u64 counter in a PDA and increments it whenever an authorized signer calls the increment instruction. Despite its simplicity, the counter demonstrates every core Quasar primitive — account declaration, accounts context validation, and the #[program] dispatcher — in the fewest possible lines of code. Because Quasar pointer-casts accounts directly from the SVM input buffer, the write ctx.accounts.counter.count += 1 mutates the underlying account data without any intermediate deserialization or heap allocation.

Full Program Listing

declare_id!("22222222222222222222222222222222222222222222");

#[account(discriminator = 1)]
pub struct Counter {
    pub authority: Address,
    pub count: u64,
}

#[derive(Accounts)]
pub struct Increment<'info> {
    #[account(has_one = authority)]
    pub counter: &'info mut Account<Counter>,
    pub authority: &'info Signer,
}

#[program]
mod counter_program {
    use super::*;

    #[instruction(discriminator = 0)]
    pub fn increment(ctx: Ctx<Increment>) -> Result<(), ProgramError> {
        ctx.accounts.counter.count += 1;
        Ok(())
    }
}

Annotated Walkthrough

Declaring the Counter Account

#[account(discriminator = 1)]
pub struct Counter {
    pub authority: Address,
    pub count: u64,
}
#[account(discriminator = 1)] tells Quasar to tag this account type with the single byte 0x01 at offset zero of its on-chain data. Every time Quasar loads a Counter from the account list it checks this byte first, rejecting any account whose first byte differs. This is a compact, user-controlled discriminator — you choose the value; Quasar enforces it at load time with zero runtime overhead beyond the single comparison.
FieldTypePurpose
authorityAddressThe pubkey allowed to increment this counter
countu64The current counter value
Address is Quasar’s alias for a 32-byte Solana public key. Both fields are plain-old-data types, so the struct is Copy and requires no heap.

The Increment Accounts Context

#[derive(Accounts)]
pub struct Increment<'info> {
    #[account(has_one = authority)]
    pub counter: &'info mut Account<Counter>,
    pub authority: &'info Signer,
}
#[derive(Accounts)] generates the deserialization and validation logic for the instruction’s account list. Each field in the struct maps to one account in the instruction’s account array, in order. #[account(has_one = authority)] — This constraint instructs Quasar to verify that counter.authority == authority.key(). In other words, the authority account passed in the instruction must match the address stored inside the Counter data. If the check fails, the instruction returns an error before your handler runs. This pattern replaces an explicit require! call and documents the relationship directly on the account. &'info mut Account<Counter> — The mut borrow means the account’s data may be written through this reference. Under the hood, Account<Counter> is a thin zero-copy wrapper; the Counter fields it exposes are pointers directly into the SVM input buffer, so a write like counter.count += 1 modifies account bytes in-place. &'info SignerSigner is Quasar’s read-only signer account type. It carries no data fields; its presence in the struct signals that this account must have set the signer bit in the instruction header, which Quasar checks automatically.

The #[program] Module

#[program]
mod counter_program {
    use super::*;

    #[instruction(discriminator = 0)]
    pub fn increment(ctx: Ctx<Increment>) -> Result<(), ProgramError> {
        ctx.accounts.counter.count += 1;
        Ok(())
    }
}
#[program] turns the module into the program entrypoint. It generates a dispatcher that reads the first byte of the instruction data, matches it against declared discriminators, and calls the corresponding handler — all without any dynamic dispatch or heap allocation. #[instruction(discriminator = 0)] assigns the byte 0x00 to this instruction. The client sets this byte in data[0] when building the transaction; the dispatcher matches it on-chain. You control every discriminator value explicitly, making the wire format deterministic and easy to document. Ctx<Increment> is the typed context parameter. It holds a reference to the parsed and validated Increment accounts struct and the auto-generated bump seed map (ctx.bumps). You access accounts through ctx.accounts.field_name. ctx.accounts.counter.count += 1 — Because counter is a &mut Account<Counter> pointing into the SVM input buffer, this single line performs an in-place write. There is no serialization step; the modified byte is already in the right memory location when the instruction returns Ok(()).

Extending the Counter

Adding a decrement Instruction

You can add a second instruction to the same #[program] block. Reuse the same Increment accounts struct because the account requirements are identical:
#[program]
mod counter_program {
    use super::*;

    #[instruction(discriminator = 0)]
    pub fn increment(ctx: Ctx<Increment>) -> Result<(), ProgramError> {
        ctx.accounts.counter.count += 1;
        Ok(())
    }

    #[instruction(discriminator = 1)]
    pub fn decrement(ctx: Ctx<Increment>) -> Result<(), ProgramError> {
        ctx.accounts.counter.count = ctx.accounts.counter.count
            .checked_sub(1)
            .ok_or(ProgramError::ArithmeticOverflow)?;
        Ok(())
    }
}
Use checked_sub for the decrement to guard against underflow. The counter’s count is a u64, so subtracting from zero would wrap around without the overflow check.

Initializing the Counter

To create a new Counter account, add an initialize instruction with an init account constraint. The init constraint tells Quasar to allocate the account with the right size, set the owner to your program, and write the discriminator:
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(mut)]
    pub payer: Signer,
    #[account(init, payer = payer)]
    pub counter: Account<Counter>,
    pub system_program: Program<SystemProgram>,
}
In the handler you populate the fields directly:
#[instruction(discriminator = 2)]
pub fn initialize(ctx: Ctx<Initialize>) -> Result<(), ProgramError> {
    ctx.accounts.counter.authority = *ctx.accounts.payer.address();
    ctx.accounts.counter.count = 0;
    Ok(())
}

Building and Testing

1

Install the Quasar CLI

cargo install --path cli
2

Initialise a new project

quasar init counter
cd counter
The scaffolded workspace contains a Cargo.toml, src/lib.rs with placeholder boilerplate, and a client/ crate for tests.
3

Write the program

Replace src/lib.rs with the counter code shown above. Run the formatter to verify syntax:
cargo fmt
4

Build

quasar build
The compiled .so is written to target/deploy/.
5

Run tests

quasar test
Quasar’s test runner spins up an embedded SVM, deploys the compiled program, and runs the test suite from your client/ crate — no validator process required.
Account discriminators must never be all-zero. Quasar rejects #[account(discriminator = 0)] at compile time because an all-zero discriminator is indistinguishable from uninitialised account data. Instruction discriminators have a narrower restriction: single-byte #[instruction(discriminator = 0)] is explicitly allowed (the README example above uses it), and only multi-byte all-zero instruction discriminators are banned.

Build docs developers (and LLMs) love