Skip to content
hopper
Get started
Start / first-five

First five minutes

The shortest path through account declarations, typed contexts, handlers, dynamic tails, and raw escape hatches.

This reference covers Hopper 0.5.0, published on crates.io. See the release status for package verification, dated benchmarks, and remaining audit work.

Start in framework mode. Import the prelude, declare account bytes, derive account validation, and put handler logic behind ctx.accounts.*.

Hopper in one sentence: write Rust handlers with typed accounts, then let Hopper verify the selected account contract before program code receives a typed zero-copy borrow. Default/headered layouts check owner, role, discriminator, version, and fingerprint; opt-in compact layouts check owner, exact size, and discriminator.

1. Counter

use hopper::prelude::*;

#[derive(Clone, Copy)]
#[repr(C)]
#[account(discriminator = 1, version = 1)]
pub struct Counter {
    pub authority: Address,
    pub value: WireU64,
}

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

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

    #[instruction(0)]
    pub fn increment(ctx: Ctx<Increment>) -> ProgramResult {
        let mut counter = ctx.accounts.counter.get_mut()?;
        counter.value.checked_add_assign(1)?;
        Ok(())
    }
}

This is the default mental model: validated accounts enter through #[derive(Accounts)], then the handler mutates typed zero-copy state through ctx.accounts.

The counter excerpt assumes an already initialized account. It does not create one or accept a zero-filled account as valid state. The vault below includes the account-creation path; use its complete example for an end-to-end program.

2. Vault

A SOL deposit must transfer lamports as well as update its recorded balance. This excerpt from the tested vault declares the writable signer, vault authority constraint, and System Program account. It calls the real transfer CPI before updating state; propagate an error so Solana can roll back both operations.

#[derive(Accounts)]
pub struct Deposit<'info> {
    #[account(mut)]
    pub authority: Signer<'info>,

    #[account(mut, has_one = authority)]
    pub vault: Account<'info, Vault>,

    pub system_program: Program<'info, System>,
}

impl<'info> Deposit<'info> {
    pub fn deposit(&self, amount: u64) -> ProgramResult {
        hopper::hopper_require!(amount > 0, ZeroAmount);

        let authority = self.authority.as_account();
        let vault_account = self.vault.as_account();
        hopper::system::Transfer {
            from: authority,
            to: vault_account,
            lamports: amount,
        }
        .invoke()?;

        self.vault
            .with_mut(|vault| vault.balance.checked_add_assign(amount))
    }
}

See examples/hopper-vault/src/lib.rs for the complete SOL-vault flow.

Initialize a fresh account with named values generated from its fixed fields:

ctx.init_vault_with(VaultFields {
    authority: *ctx.accounts.payer.key(),
    balance: 0,
    bump: 0,
})?;

For PDA accounts, store the validated ctx.bumps.vault instead of 0. Existing init_vault() followed by load_init() or with_mut_after_init() remains available, as do positional set_inner(...) and named set_fields(...). The combined helper is for explicit fresh initialization, not init_if_needed or automatic lifecycle. Propagate errors for transaction rollback. See named initialization for custom inputs and tail limits.

Mutability is declared on the account field with #[account(mut)]; Hopper does not use &mut Account<T> field types because writable exclusivity is enforced by Hopper's account-data guards, not by moving the role wrapper.

3. Dynamic Multisig

Use #[hopper::account] with bounded fields when a fixed zero-copy body needs dynamic metadata.

use hopper::prelude::*;

#[hopper::account(discriminator = 7, version = 1)]
pub struct Multisig<'a> {
    pub threshold: u64,
    pub label: String<'a, 32>,
    pub signers: Vec<'a, Address, 10>,
    pub weights: Vec<'a, u16, 8>,
}

In #[hopper::account] authoring syntax, fixed multi-byte scalars are lowered to wire-safe wrappers in the emitted layout (for example u64 -> WireU64). Direct typed overlay APIs still require wire types; raw_ref::<u64> and segment_ref::<u64> are intentionally rejected.

Address and Pubkey vectors keep the borrowed zero-copy view path. Other TailElement vectors use HopperVec<T, N> through the same compact tail codec and generated editor helpers.

Hopper lets you author bounded dynamic fields inline, then lowers them into a compact dynamic tail so fixed fields remain segment-borrowable and the dynamic schema is layout-fingerprinted.

let view = Multisig::tail_view(data)?;
let signers: &[Address] = view.signers()?;
let weights: HopperVec<u16, 8> = view.weights()?;

4. Token Transfer

For token programs, keep account validation and CPI construction explicit. The prelude exposes the polymorphic Token / Token-2022 interface helpers:

let source_kind = TokenProgramKind::for_account(source.as_account())?;
let mint_kind = TokenProgramKind::for_account(mint.as_account())?;
require_eq!(source_kind, mint_kind);

interface_transfer_checked(
    source.as_account(),
    mint.as_account(),
    destination.as_account(),
    authority.as_account(),
    amount,
    decimals,
)?;

Use the Token-2022 extension constraints when a mint must carry transfer hooks, close authority, non-transferable semantics, or metadata pointers. See docs/TOKEN_2022_GUIDE.md.

5. Raw Escape Hatch

Most programs stay in framework mode. When a protocol needs lower-level control, move deliberately down the access tiers:

let mut vault = ctx.accounts.vault.get_mut()?;

// Systems-mode code can opt into segment leasing when disjoint field borrows
// matter more than whole-layout ergonomics.
let mut balance = ctx.vault_balance_mut()?;

Reach for hopper::systems::* only when you need layout fingerprints, segment leases, receipts, migrations, or raw policy-controlled access. The first-touch path remains ctx.accounts.*.