The counter program is the canonical “Hello, World” of Quasar: it stores a singleDocumentation 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.
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
Annotated Walkthrough
Declaring the Counter Account
#[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.
| Field | Type | Purpose |
|---|---|---|
authority | Address | The pubkey allowed to increment this counter |
count | u64 | The 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)] 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 Signer — Signer 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] 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:
Initializing the Counter
To create a newCounter 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:
Building and Testing
Initialise a new project
Cargo.toml, src/lib.rs with placeholder boilerplate, and a client/ crate for tests.Write the program
Replace
src/lib.rs with the counter code shown above. Run the formatter to verify syntax: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.