Skip to content
hopper
Get started
Start / byte-allowance

On-chain byte allowance

A four-cell application quota program with captured selectors, delegate checks, revisions, and strict byte policies.

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

Hopper can constrain a handler to selected bytes inside a writable Solana account. The policy runs in the program: no indexer, manifest server, Grillo report, or off-chain proof is needed to admit or refuse a supported write. Solana still locks the whole writable account and charges its normal fees.

An application you can build

hopper-byte-allowance stores four application quotas in a 272-byte account. Each cell has a delegate, a limit, spent units, and a revision. These units are application accounting, not lamports or SPL tokens; the example does not hold or transfer token custody.

Initialization creates the account through a System Program CPI. A consume instruction requires the selected delegate's signature and the current revision, rejects zero amounts and overflow, checks the limit, and changes only that cell's spent units and revision. The authority can change a limit without reducing it below the amount already spent. That also advances the revision, so a consumption prepared against an older limit is refused. There is no close instruction or rent-recovery path in this small example.

#[derive(Accounts)]
#[accounts(strict_writes, lamports())]
#[instruction(slot: u16)]
pub struct Consume<'info> {
    pub delegate: Signer<'info>,
    #[account(cells(slot; spent, revisions))]
    pub book: Account<'info, AllowanceBook>,
}

// After authenticating the delegate and checking quota/revision:
*ctx.book_spent_cell_mut()? = WireU64::new(next_spent);
*ctx.book_revisions_cell_mut()? = WireU64::new(next_revision);

The generated accessors are available in the published 0.3.2 framework. The accessor captures the selector during context binding, infers the column's element type, checks the array bound and offset arithmetic, and acquires the normal policy-checked segment lease. Callers do not repeat a byte offset or provide a second selector. book_spent_cell_ref() supplies the matching read convenience; it does not restrict other reads. Out-of-range access returns InvalidInstructionData.

Selectors must be u8, u16, or u32; a context supports up to eight distinct selectors. Composite contexts do not yet support cells(...). The existing whole-column accessor does not gain whole-column write permission from a selected-cell declaration.

What is enforced

Typed binding checks the account's owner and layout as well as its declared signer and writable requirements. cells(slot; spent, revisions) grants only the selected elements in those columns. Neighboring cells, authority, delegates, limits, and the header stay outside the consumer's mutable grant. Explicit lamports() permits no lamport mutation through the governed APIs. Supported direct AccountView writes are subject to the macro-bound ambient gate too. With this explicit lamport dimension, writable delegation through validated CPI helpers requires whole-account data permission and a lamport grant; these selected-cell contexts provide neither. Bare strict_writes without the lamport dimension retains legacy ungoverned writable-CPI behavior.

The handler still owns business rules: who may spend, what a unit means, which revision is acceptable, and how much is allowed. SEALED rejects unsafe code in handlers unless explicitly opted in; it is not an independent audit or a sandbox for arbitrary unsafe dependencies. Raw-pointer writes outside the governed APIs can bypass Hopper's policy. Always propagate failures when transaction rollback is required.

Where Cicada and Grillo fit

Cicada applies the same cell-policy idea to a larger on-chain intent lifecycle, with custody checks, route commitments, and settlement postconditions. Its application logic remains useful independently of a report producer. A route program ID or call commitment does not freeze upgradeable callee code.

Grillo checks supplied execution evidence separately. It can support release inspection and incident analysis, but it is not a dependency of byte-policy enforcement and should not be marketed as consensus-enforced runtime proof.

Reproduce the program tests

Build the example with pinned SBF tools, then run the compiled suite:

cargo build-sbf --tools-version v1.54 --arch v0 \
  --manifest-path examples/hopper-byte-allowance/Cargo.toml \
  --sbf-out-dir target/byte-allowance -- --locked
HOPPER_BYTE_ALLOWANCE_SBF="$PWD/target/byte-allowance/hopper_byte_allowance.so" \
  cargo test --manifest-path bench/framework-comparison/verifier/Cargo.toml \
  --test byte_allowance_sbf --locked -- --ignored --nocapture

Repeat with --arch v3 and a separate output directory. The suite checks the full resulting accounts, all four selectors, failed authentication, stale revisions, quota failures, overflow, malformed accounts, and reinitialization. scripts/test-byte-allowance-devnet.py exercises a deployed ELF on public devnet and records finalized transactions and exact account snapshots.

Verified 0.3.2 release

The September 25 capture passed 40 finalized allowance transactions, 20 runtime-gate transactions, and two orderbook transactions. It includes successful updates, expected refusals, exact account-state checks, and exact deployed ELF checks before and after each lane. A prior harness attempt stopped at CLI account parsing after two delegate-funding transactions; those are outside the 62-case completed capture.

The v0 allowance ELF is 24,920 bytes. On devnet, ordinary consumption used 889 CU and a limit update used 831 CU. These are whole-instruction measurements for this program and input shape, not intrinsic accessor costs or a framework ranking. Fresh programs using only crates.io dependencies reproduce the allowance and runtime-gate ELFs exactly and pass all four compiled suites.

See the on-chain evidence and registry verification.

For current network boundaries, see the September 25 source and activation review. Alpenglow is a consensus change, not a sub-account locking feature or a new Hopper byte-fee mechanism.

Published 0.4.0 revalidation — September 26

The 0.4.0 byte-allowance artifact passed 40 fresh finalized devnet transactions, including all four quota slots and refusal cases. Complete expected account snapshots were checked after every transaction. Program dumps before and after match the gated ELF and the registry-only consumer build. Consumption measured 889 CU and limit updates 831 CU in this fixture. See the use-case review evidence. The earlier 0.3.2 capture above remains separately dated.

Example instruction ABI

Opcode Accounts in order Arguments after the one-byte opcode
0 initialize authority signer+writable, new book signer+writable, System Program four delegate addresses, u64 initial limit
1 consume delegate signer, book writable u16 slot, u64 expected revision, u64 amount
2 set_limit authority signer, book writable u16 slot, u64 expected revision, u64 limit

Integers are little-endian. Instruction payload lengths are exact. The bounded entrypoint may ignore surplus account metas after the declared accounts; sealed does not enforce an exact account count. The compiled suite checks that an extra account remains unchanged and only the expected cells change. There is no reset, close, or rent-recovery instruction.