Skip to content
hopper
Get started
Hopper framework v0.3.0

Zero-copy Solana state,
validated before you cast.

Write Solana programs with Anchor ergonomics. Hopper checks owner, role, discriminator, version, and layout fingerprint before account bytes become typed state. No deserialize-then-hope. No unchecked casts hidden in macros. In the first published router-class head-to-head, Hopper beats Quasar on every compute row and lands within 1.8 to 2.4% of hand-written Pinocchio, while carrying receipts, migrations, byte-range write policies, and generated clients none of them have.

Start buildingRead the docsSupport Hopper
$cargo add hopper-lang --rename hopper --features proc-macros
2 = 2 CU
safe overlay cost
identical to a raw pointer cast, measured, not claimed
3 / 3
router rows vs Quasar
first published three-way; 1.8 to 2.4% from hand-written Pinocchio (2026-07-09)
0.027 SOL
deploy cost
complete program in 3,736 bytes (2026-07-09 build); Anchor-class artifacts ≈ 1.36 SOL
1,570
tests, 0 failures
line-by-line audit trail + competitor bug classes pinned
built for production

The fast path still has types, names, and guardrails.

validation first

Check before cast.

Layout validation happens before your handler code runs. Headered accounts prove owner, discriminator, version, and layout ID; compact accounts prove exact size and discriminator. No unchecked casts. No runtime surprises.

compact state

One byte when the hot path needs it.

Opt into `[disc][body]` accounts for dense state. Manifests, IDL, registries, and generated TS/Kotlin clients carry the fingerprint while the on-chain account stays 41 bytes when 41 bytes is all it needs.

compact vault
0
disc = 1
1..33
authority pubkey
33..41
balance u64
fingerprint source
manifest / IDL / generated client
token-2022

TLV scanning, zero overhead.

Validate transfer hooks, permanent delegates, confidential transfers and more with direct byte scanning. No detours. No helper methods. Check what you need, skip what you don't.

cryptography

Native precompile access.

Hash with SHA-256, Keccak, BLAKE3. Verify Ed25519, secp256k1, secp256r1 signatures. Recover Ethereum addresses. All inline, all explicit, all reviewable.

state receipts

Prove what changed.

Stamp mutations with before/after fingerprints, field changes, invariant tracking, and CPI flags. 72 bytes. Self-describing. Backwards compatible with 64-byte legacy.

self-describing

CLI is part of the framework.

program_manifest! exports your schema in one line, the same generated consts the runtime enforces, so published equals enforced by construction. hopper compile --emit manifest regenerates it from source, tx send fires instructions, and tx explain decodes what they did. No Node anywhere.

$hopper compile --emit manifest --package vault
$hopper compile --emit ts --package vault
$hopper tx send --program 2YPB... --account payer:sw --data 02...
$hopper tx explain 3XbE...signature
$hopper manager crank run hopper.manifest.json --once
events over cpi

Anchor’s emit_cpi. Three bytes of wire.

One attribute option plus one call mirrors #[event_cpi] + emit_cpi! exactly (same two auto-appended accounts, authenticated sink) at 3 bytes of instruction-data overhead per event vs Anchor’s 16. Live on devnet the named decode cost 3,586 CU, matching Mollusk exactly. Trade-off, stated: Hopper derives the event authority at runtime (~148 CU per attempt); Anchor pins a compile-time constant for ~free and wins that axis.

$hopper tx explain 3XbE...zqrT
event: DepositReceipt (tag 0x02)
balance: 1500000 deposit_count: 3
sink-authenticated self-CPI (inner-ix metadata)
wire overhead: 3 bytes (Anchor emit_cpi: 16)
lazy migration

Every instruction is a migration crank.

Declare migrate(from = V1, with = transform) on a field and accounts upgrade in place as they are touched, typed both sides, one stack copy, no (de)serialization, no dedicated migration instruction. Live on devnet: a real account cranked v1 to v2 for a one-time 29 CU premium (280 vs 251 CU steady-state), both matching Mollusk exactly. Pair with reserved padding and realloc is never needed.

self-describing txs

Transactions that name what they wrote.

Opt a context into emit_touch_map and every successful instruction emits byte-range write records hopper tx explain can decode. Live on devnet, a withdraw explains itself as W vault [48..56) -> Vault.balance. Wrapper get_mut writes record too (whole-account, honest but coarse). Per-context opt-in by design: each record costs a sol_log_data (+52 CU measured in the touch-map-enabled smoke crate), and we don’t hide CU in defaults.

$hopper tx explain 4uc6...KnfP
W slot 1 (vault) [48..56) -> Vault.balance
$hopper tx explain 4yTv...yXMB
W slot 1 [0..76) (wrapper get_mut, whole account)
Ok-path only; per-context opt-in
composite contexts

Contexts nest. Options compose.

#[composite] embeds one context in another with Anchor’s flattening and nested bumps (an explicit marker, because Hopper refuses to guess). Anchor had nesting first; what it doesn’t have is the rest: strict_writes splices the inner context’s declared write ranges into one compile-time write-set, so published-equals-enforced survives nesting, and event/touch-map slots land after the flattened set.

§02 - code

The authoring model stays Rust-native.

Start with the proc-macro path when you want speed. Drop to declarative macros or raw tiers when you need control. The layout, context, dispatcher, and CPI surface all stay inspectable.

1#[derive(Clone, Copy)]
2#[repr(C)]
3#[account(discriminator = 1, version = 1)]
4pub struct Counter {
5 pub authority: Address,
6 pub value: WireU64,
7}
Versioned zero-copy account with Hopper layout ID.
03 - protocol sample

Forward-secret messaging with verifier-bound ferry proofs.

The Styx ferry example uses Hopper accounts for prekeys, ratchet threads, and proof counters while exposing the Solana crypto surface: Keccak domain derivation, Ed25519 precompile checks, sibling instruction inspection, and typed verifier CPI.

Open Styx doc
01
Signed prekeys

Ed25519 precompile inspection ties the owner to the current signed prekey

02
Ratchet envelope

bounded ciphertext events with monotonic counters and sealed-message hashes

03
VSL proof gate

513-byte proof v2 with checked domain, fee tier, and base fee inputs

04
Verifier CPI

proof bytes are forwarded only to the config-pinned verifier program

examples/hopper-styx-ferry/src/lib.rscrypto + CPI
#[instruction(5)]
pub fn submit_zk_ferry(
    ctx: Ctx<SubmitZkFerry>,
    proof: HopperVec<u8, STYX_ZK_PROOF_V2_LEN>,
    encrypted_outputs: [u8; 64],
) -> ProgramResult {
    ctx.accounts.submit(proof, encrypted_outputs)
}
04 - flagship program

A compromised solver still cannot touch what you did not hand it.

Cicada is a transport-neutral protected-execution intent program. Users publish execution constraints in one shared, column-oriented shard; a solver settles an intent by writing only the exact per-slot settlement cells the runtime granted it. It can never rewrite the user’s limits, the committed route, or a neighbor’s intent, because those columns are never declared writable and the policy is enforced at borrow time, before any byte moves. Anchor rewrites the whole account on exit and Quasar hands back a raw &mut T; neither can refuse a single field write.

See example programs
01
Column-separated authority

owners, limits, and route policy live in columns a claim or execute handler is never granted mutable access to

02
Exact-cell writes

cells(slot; ...) grants one intent’s settlement cells; a write to a neighbor’s cell is refused before a byte changes

03
Owner-bound vault PDA

route CPI is signed by a PDA derived from the user and their source account, not a shared protocol key

04
Deltas decide

settlement needs the observed output delta to clear min_output; any unused input is returned atomically first

examples/hopper-cicada/src/lib.rsexact-cell write policy
#[derive(Accounts)]
#[accounts(strict_writes, emit_touch_map)]
#[instruction(slot: u16)]
pub struct ExecuteIntent<'info> {
    pub executor: Signer<'info>,

    // The executor may write only these settlement cells, for this
    // one slot. Owners, limits, and route policy sit in other columns
    // and are never declared writable here.
    #[account(
        cells(slot;
            statuses, claimants, claim_expiries,
            settled_inputs, settled_outputs,
            settlement_hashes, revisions),
        has_one = config,
    )]
    pub shard: Account<'info, IntentShard>,

    // Route CPI signer power is scoped to one user's vault.
    #[account(seeds = [
        VAULT_AUTHORITY_SEED,
        intent_owner.address().as_array(),
        source_token.address().as_array(),
    ], bump)]
    pub vault_authority: UncheckedAccount<'info>,
}
§03 - what ships

Same authoring shape. More contract.

Write like Anchor. Get Hopper validation. Add layout identity, field-level borrows, Token-2022 checks, dynamic tails, receipts, manifests, migrations, and generated clients when you need them. Anchor ergonomics plus serious state safety.

Zero-copy account access
Hopper
native
Quasar
supported
Anchor ZC
partial
Pinocchio
supported
Bounded dynamic fields + final raw tails
Hopper
native
Quasar
partial
Anchor ZC
partial
Pinocchio
missing
Token-2022 TLV validators
Hopper
native
Quasar
partial
Anchor ZC
missing
Pinocchio
missing
Hash wrappers + precompile payload checkers
Hopper
native
Quasar
missing
Anchor ZC
missing
Pinocchio
missing
Segment-level borrow tracking
Hopper
native
Quasar
missing
Anchor ZC
missing
Pinocchio
missing
72-byte receipts with invariant attribution
Hopper
native
Quasar
missing
Anchor ZC
missing
Pinocchio
missing
Schema-epoch migrations
Hopper
native
Quasar
missing
Anchor ZC
missing
Pinocchio
missing
Migrate-at-bind crank (accounts upgrade as touched)
Hopper
native
Quasar
missing
Anchor ZC
missing
Pinocchio
missing
Self-describing txs (byte-range touch maps)
Hopper
native
Quasar
missing
Anchor ZC
missing
Pinocchio
missing
Self-CPI events (emit_cpi parity)
Hopper
native
Quasar
missing
Anchor ZC
supported
Pinocchio
missing
Composite contexts with composed write-sets
Hopper
native
Quasar
missing
Anchor ZC
partial
Pinocchio
missing
On-chain manifest PDA
Hopper
native
Quasar
missing
Anchor ZC
missing
Pinocchio
missing
Generated clients: TS, Kotlin, Python, Go, C, Rust
Hopper
native
Quasar
supported
Anchor ZC
supported
Pinocchio
missing
Policy knobs (tiny / strict / sealed / raw)
Hopper
native
Quasar
missing
Anchor ZC
missing
Pinocchio
missing
Declarative init / close / realloc / has_one
Hopper
native
Quasar
supported
Anchor ZC
supported
Pinocchio
missing
ELF profiler + flamegraph CLI
Hopper
native
Quasar
supported
Anchor ZC
missing
Pinocchio
missing
secp256r1 / passkey precompile checks
Hopper
native
Quasar
missing
Anchor ZC
missing
Pinocchio
missing
Token-2022 transfer-hook account resolver
Hopper
native
Quasar
missing
Anchor ZC
missing
Pinocchio
missing
Cluster feature-gate aware deploys (SIMD-0321)
Hopper
native
Quasar
missing
Anchor ZC
missing
Pinocchio
missing
measured vault snapshot

Same harness, same seeds, scoped claims.

Current release-facing numbers come from the sibling hopper-bench Mollusk harness, re-measured 2026-07-09 and averaged across 8 deterministic seeds. Quasar's upstream vault exposes only deposit and withdraw, so validation-only rows stay n/a instead of being synthesized.

Withdraw moved 442 → 486 CU versus the previous published table: the +44 CU is the mutation-complete lamport write-gate actually enforcing on the one lamport-moving instruction, a measured safety feature no other column carries (the error-path lowering that bought a 10% .text cut cost +8 here, and the gate-check fast-out recovered it). Every Quasar-comparable row still wins. And the Hopper vault .so now measures smaller than hand-written Pinocchio's on the identical contract (7.46 vs 7.73 KiB, zero writable sections); Quasar's 5.47 KiB still wins the size row.

The large Pinocchio gaps are mostly a PDA-strategy difference: the Pinocchio target uses idiomatic find_program_address (a bump search), while Hopper verifies a stored canonical bump in one hash. The honest claim is that Hopper is fast by default, the cheap path is the one the macros steer you toward.

Scenario
Hopper
Pinocchio
Quasar
Authorize
420 CU
2512 CU
n/a
Auth-fail
66 CU
41 CU
n/a
Counter access
518 CU
2539 CU
n/a
Deposit
1653 CU
3856 CU
1756 CU
Withdraw
486 CU
2548 CU
592 CU
Binary size
7.46 KiB
7.73 KiB
5.47 KiB
router parity lab, first published three-way

A real workload, and Hopper wins its class.

Multi-hop swap routing against a shared mock-AMM: measured amount forwarding between hops, dynamic accounts, and a mandatory min-out safety gate every framework must reject. Hopper beats Quasar on every compute row with the smallest binary, and lands within 1.8 to 2.4% of hand-written Pinocchio, while carrying validation, receipts, write policies, and generated clients the others don't have.

These rows were re-earned on 2026-07-09: a routine re-run caught a +52 CU/hop regression, the claim was suspended in public, a per-commit bisect attributed every CU, and the fix landed the rows better than the originals. Hand-written Pinocchio wins raw CU, as it should, it carries no framework. The contract, fixtures, and both snapshots are published in the hopper-bench repo so every delta is checkable.

Scenario
Hopper
Pinocchio
Quasar
Swap, 1 hop
1,559 CU
1,523 CU
1,582 CU
Swap, 2 hops
3,035 CU
2,975 CU
3,064 CU
Swap, 3 hops
4,512 CU
4,431 CU
4,546 CU
Binary size
10.74 KiB
10.98 KiB
11.05 KiB
Min-out gate
rejected
rejected
rejected
live on devnet, lab-exact

The lab numbers survive contact with a real cluster.

Every instruction was fired with hopper tx send and decoded back with hopper tx explain, pure Rust on both sides, no JS toolchain. The withdraw names the exact field it wrote, the event decodes by name from inner-instruction metadata, and a real account was cranked v1 → v2 in place by the migrate-at-bind demo for a one-time premium of 29 CU.

The live emit_receipt cost (3,586 CU) and both live migration touches (280 / 251 CU) match their Mollusk lab measurements exactly, two consecutive lab-to-cluster exact matches, the strongest validation the in-process numbers can get. Full signatures and provenance are on the benchmarks page.

Instruction
Live CU
Decoded / receipt
initialize
1,839 CU
sig 4R2W…QKyS
deposit
2,221 CU
sig 4Jnp…XGAV
withdraw
892 CU
W vault [48..56) -> Vault.balance
bump_whole_vault
762 CU
W [0..76), wrapper get_mut captured
emit_receipt (event_cpi)
3,586 CU
DepositReceipt { balance: 1500000, deposit_count: 3 }
touch_note, migrating (07-11)
280 CU
v1 → v2 in place, +29 CU one-time
touch_note, steady-state
251 CU
already v2, no probe cost
get started

Four steps from nothing to devnet.

Add the framework, install the CLI, scaffold a program, and deploy. Hopper gives you native runtime, layout fingerprints, self-describing manifests, and a full validation layer. No detours. No legacy compatibility cruft.

01
$ cargo add hopper-lang --rename hopper --features proc-macros
add the framework
02
$ cargo install hopper-cli
install the CLI
03
$ hopper init my-program --template minimal --yes
scaffold a program
Hopper Zero | Zero-copy state framework for Solana