# Hopper Architecture
Canonical technical reference for the Hopper zero-copy state framework.
This document covers the pipeline model, the wire format, every public module,
the dependency graph, and the design invariants that hold them together.
## The Pipeline
Hopper is a typed state pipeline. Every program follows the same seven steps:
```
1. Define Layout state with hopper_layout!, declare errors, register discs
2. Resolve Parse accounts from the instruction via Frame
3. Validate Run checks, verify signatures, enforce policy
4. Execute Mutate state in a controlled phase
5. Record Capture a StateReceipt of what changed
6. Verify Assert invariants and compatibility
7. Inspect Use the CLI to explain, diff, and plan migrations
```
Steps 1-6 happen on-chain. Step 7 happens off-chain with the CLI. Simple
programs can skip steps 5 and 6. Complex protocols use all seven.
## Tiered Learning
The framework has depth. You do not need all of it at once.
**Tier 1 (Standard Hopper):** Versioned layouts, phased execution, validation
bundles, receipts, invariants, CLI inspect. This covers most programs.
**Tier 2 (Advanced Hopper):** Segmented accounts with roles, virtual
multi-account state, migration planning, trust profiles, validation graphs,
capability-policy binding.
**Tier 3 (Escape Hatch):** Raw overlay access, `load_unchecked`, manual wire
formats, custom collections, `segment_data_mut_unchecked`. The framework steps
aside when you need it to. Hopper Native provides direct syscall access for
anything below the framework layer.
Variable-length account data keeps the same tier model. Hopper keeps the fixed
body zero-copy and offers `#[hopper::state(dynamic_tail = T)]` for one bounded
dynamic payload after the fixed body. See
[DYNAMIC_TAILS_FROM_QUASAR.md](DYNAMIC_TAILS_FROM_QUASAR.md) for the Quasar
dynamic-field migration pattern.
## Overview
Hopper is `#![no_std]`, zero-allocation, built on
Hopper Native, Hopper's sovereign low-level runtime substrate. Every account is a flat byte
overlay with a 16-byte self-describing header. No proc macros are required,
no heap allocations, and no trait objects in the on-chain path.
The framework is organized into concentric rings:
| Ring | Crate | Scope |
|------|-------|-------|
| 0 | `hopper-core` | ABI types, header, overlay, pod, checks, collections, state, events, CPI, frame, dispatch |
| 0 | `hopper-macros` | `macro_rules!` code generation (layout, dispatch, init, close, error, PDA, etc.) |
| 1 | `hopper-solana` | SPL Token/Mint readers, Token-2022 screening, typed CPI helpers |
| 2 | `hopper-schema` | Layout manifests, field-level diffing, migration planning (usable off-chain and on) |
| -- | `hopper-cli` | CLI tooling: explain, inspect, decode, segments, compat, diff, plan, schema-export |
All on-chain crates are `#![no_std]` with `#![deny(unsafe_op_in_unsafe_fn)]`.
```
hopper (umbrella, re-exports macros + prelude)
|
+-- hopper-runtime <- hopper-native (primary), pinocchio / solana-program (compat)
+-- hopper-core <- hopper-runtime, sha2-const-stable
+-- hopper-macros <- references hopper-core / hopper-runtime paths
+-- hopper-schema <- hopper-core
+-- hopper-solana <- hopper-core, hopper-runtime, five8_const
+-- hopper-cli (std) <- hopper-schema
```
## Sovereign Boundary Ownership
Hopper's architecture depends on a hard split between substrate and semantics.
- `hopper-native` owns raw execution: loader parsing, duplicate-account
resolution, `raw_input`, `raw_account`, entrypoint macros, syscall wrappers,
lazy parsing, and the substrate `AccountView`.
- `hopper-runtime` owns Hopper semantics: typed state access, `LayoutContract`,
`Context`, checked CPI rules, and Hopper-facing PDA ergonomics.
- `hopper-runtime::compat/*` owns every backend bridge. If a file outside
`compat/` needs to name Pinocchio or solana-program identity directly, that is
an architectural regression.
This keeps Hopper Native sovereign at the execution boundary while letting
Hopper Runtime stay framework-owned instead of adapter-shaped.
---
## Wire Format
### Account Header (16 bytes)
Every Hopper account begins with the same 16-byte header:
```
Offset Size Field Description
------ ---- ---------- -----------
0 1 disc Account discriminator (unique per type)
1 1 version Layout version (starts at 1)
2 2 flags Status flags (u16 LE)
4 8 layout_id SHA-256 fingerprint of the layout (first 8 bytes)
12 4 reserved Reserved for future header format versions
```
`HEADER_LEN = 16`. `HEADER_FORMAT = 1`.
**layout_id computation** (deterministic, compile-time):
```
sha256("hopper:v1:{Name}:{version}:{field_name}:{canonical_type}:{size},"...)[..8]
```
Fields appear in declaration order. Each field contributes
`"{name}:{canonical_type}:{size},"` with a trailing comma. The hash is computed
at compile time via the `sha2-const-stable` crate. Any change to name, type,
size, or field order produces a different layout_id.
### Segmented Accounts
Accounts that need variable-length regions use a segment table starting at
byte 16:
```
Offset Size Field
------ ----- -----------
16 2 segment_count
18 2 registry_flags
20 16*N segment entries (SegmentEntry: id[4] + offset[4] + count[2] +
capacity[2] + element_size[2] + flags[2])
20+16*N ... segment data regions
```
Segment IDs are 4-byte FNV-1a hashes of the segment name (computed at compile
time). Flags encode the segment role in the upper 4 bits and operational flags
(LOCKED, FROZEN, DYNAMIC) in the lower bits.
### Segment Roles
Seven semantic roles classify segment behavior during migration and at runtime:
| Role | Upper bits | Migration | Runtime write |
|------|-----------|-----------|---------------|
| Core | 0x0 | Must preserve | Read/write |
| Extension | 0x1 | Must preserve | Read/write |
| Journal | 0x2 | Append-only, rebuildable | Append-only |
| Index | 0x3 | Clearable, rebuildable | Read/write |
| Cache | 0x4 | Clearable, rebuildable | Read/write |
| Audit | 0x5 | Must preserve | **Immutable after init** |
| Shard | 0x6 | Must preserve | Read/write |
Audit-role segments reject mutable borrows (`segment_data_mut()` returns an
error). The escape hatch `segment_data_mut_unchecked()` exists for init-time
writes only.
---
## Ring 0: hopper-core
### ABI Types (`abi/`)
All wire-level field types are `#[repr(transparent)]` over `[u8; N]` with
`align_of == 1`. This is non-negotiable: overlay structs must work at any
byte offset without alignment padding.
| Type | Backing | Canonical name |
|------|---------|---------------|
| `WireU16` | `[u8; 2]` | `WireU16` |
| `WireU32` | `[u8; 4]` | `WireU32` |
| `WireU64` | `[u8; 8]` | `WireU64` |
| `WireU128` | `[u8; 16]` | `WireU128` |
| `WireI16` | `[u8; 2]` | `WireI16` |
| `WireI32` | `[u8; 4]` | `WireI32` |
| `WireI64` | `[u8; 8]` | `WireI64` |
| `WireI128` | `[u8; 16]` | `WireI128` |
| `WireBool` | `[u8; 1]` | `WireBool` |
| `TypedAddress<T>` | `[u8; 32]` | `[u8;32]` |
The `WireType` unsafe trait marks types that are align-1 and valid for all bit
patterns. `LayoutFingerprint` is the 8-byte layout_id type.
### Pod and Overlay (`account/pod.rs`, `account/overlay.rs`)
`Pod` is an unsafe marker trait: the type is `repr(C)`, has no padding, and
all bit patterns are valid. `FixedLayout` adds a `SIZE` const.
- `pod_from_bytes(&[u8]) -> &T` -- zero-copy cast (validates length)
- `pod_from_bytes_mut(&mut [u8]) -> &mut T` -- mutable zero-copy cast
- `overlay(data) -> &T` / `overlay_mut(data) -> &mut T` -- thin wrappers
- `overlay_at(data, offset)` / `overlay_at_mut(data, offset)` -- at offset
### Account Header (`account/header.rs`)
- `write_header(buf, disc, version, layout_id)` -- writes the 16-byte header
- `check_header(data, disc, version, layout_id)` -- full validation
- `read_version(data)`, `read_layout_id(data)`, `read_header_flags(data)`
- `AccountHeader` -- `#[repr(C)]` struct matching the wire layout
### Whole-Layout Loading
Hopper keeps one canonical whole-layout loading path and exposes specialized
helpers when the guarantee changes:
| Helper | Checks |
|--------|--------|
| `load()` | owner + disc + version + layout_id + exact size |
| `load_foreign()` | expected foreign owner + layout_id + exact size |
| `load_compatible()` / `load_versioned()` | owner + disc + compatible version + min size |
| `load_unchecked()` | caller-owned validation |
| `load_unverified()` | best-effort tooling read |
These are generated per layout by the `hopper_layout!` macro, but they all map
back to the same Hopper account access model.
### Verified Accounts (`account/verified.rs`)
`VerifiedAccount<'a, T>` and `VerifiedAccountMut<'a, T>` are proof-of-validation
wrappers. If you hold a `VerifiedAccount`, the account has passed load validation.
They can expose `&T` / `&mut T`, but those references are tied to the wrapper,
and the wrapper owns the borrow guard or validated slice. Use `with()` /
`with_mut()` when you want closure-shaped guard access; use generated segment
accessors for the default hot path. Methods: `get()`, `get_mut()`, `with()`,
`with_mut()`, `map()`, `overlay_at()`.
### Lifecycle (`account/lifecycle.rs`)
- `zero_init(data)` -- memset to zero (required before header write)
- `safe_close(account, destination)` -- zero data + transfer lamports
- `safe_close_with_sentinel(account, destination)` -- writes `0xFF` sentinel to
prevent account revival via rent-exempt deposit
- `safe_realloc(account, new_size, payer)` -- handles rent delta
### Dynamic Tail Payloads (`tail.rs`)
Variable-length account metadata lives behind `#[hopper::dynamic_account]` or
the explicit `#[hopper::state(dynamic_tail = T)]` path:
```text
[ Hopper header ][ fixed account body ][ tail_len: u32 LE ][ encoded tail ]
```
The fixed body remains the zero-copy hot path. `#[hopper::dynamic_account]`
lets authors write bounded `#[tail(string<N>)]` and `#[tail(vec<T, N>)] where
T: TailElement` fields inline, then generates the compact tail struct, view,
owned editor, and allocation constants. `Address` / `Pubkey` vectors keep
borrowed-slice views; other vectors return `HopperVec<T, N>`. The explicit path
uses `tail_read` / `tail_write` with a bounded `TailCodec` payload.
`HopperString<N>` and `HopperVec<T, N>` cover the common string/list cases
without pulling heap allocation into SBF builds. `hopper_dynamic_fields!` lowers
`string<N>` to `HopperString<N>` and `vec<T, N>` to `HopperVec<T, N>` for custom
explicit tails.
Use dynamic tails for small bounded payloads that are read or written as one
logical unit. Use named extension segments or companion accounts for larger
regions that need independent borrow tracking, migration role metadata, or
collection-specific update paths.
### Bounded Dynamic Instruction Args
Authored handler arguments decode through `DecodeInstructionArg` over the bytes
after the one-byte instruction discriminator. Fixed scalars use their native
little-endian width. `HopperString<N>` and `HopperVec<T, N>` use the same
`TailCodec` encoding as account tails, so on-chain handlers and generated
clients share one deterministic bounded wire contract for small dynamic inputs.
Use a final `&[u8]` argument only when the protocol deliberately wants an opaque
remaining payload. Prefer `HopperString` / `HopperVec` for typed labels, signer
sets, memo fields, and other bounded dynamic instruction data.
### Realloc Guard (`account/realloc_guard.rs`)
`ReallocGuard<const N>` enforces a per-instruction cumulative growth budget.
Stack-only. Prevents runaway reallocation within a single instruction.
### Cursor and Reader (`account/cursor.rs`, `account/reader.rs`)
`SliceCursor` -- sequential reader: `read_u8/u16/u32/u64/i64`, `read_bytes`,
`skip`. `AccountReader` -- header-aware reader with `header()`, `body()`,
`body_bytes()`, `u64_at()`.
---
### Validation (`check/`)
Five-layer validation hierarchy:
1. **Account-local** (`check/mod.rs`): `check_signer`, `check_writable`,
`check_owner`, `check_size`, `check_discriminator`, `check_rent_exempt`
2. **Cross-account** (`check/mod.rs`): `check_has_one`, `check_keys_eq`,
`keys_eq_fast` (4x u64 compare), `require_all_unique`
3. **PDA** (`check/mod.rs`): `verify_pda` (~200 CU), `verify_pda_cached`,
`find_and_verify_pda`
4. **CPI guards** (`check/mod.rs`): `require_top_level`,
`detect_flash_loan_bracket`, `check_no_subsequent_invocation`
5. **Composition** (`check/guards.rs`): `check_lamport_conservation`,
`snapshot_lamports`, `check_writable_coherence`
#### Fast-path Checks (`check/fast.rs`)
Single-u32 compare: reads the RuntimeAccount 4-byte prefix
(`borrow_state|is_signer|is_writable|executable`) as one u32. Saves 4-8 CU
per account. `#[cfg(target_os = "solana")]` gated.
#### Modifier Wrappers (`check/modifier.rs`)
Composable type-level wrappers where each layer validates one property:
```rust
Signer<Mut<Account<'a, Vault>>>
```
If you can construct the type, validation has passed. Zero runtime cost after
construction.
#### Validation Graph (`check/graph.rs`)
`ValidationGraph<const N>` -- stack-allocated pipeline of `ValidateFn` closures.
- `run()` -- fail-fast, returns first error
- `run_all()` -- accumulates all errors, returns first
Combinators: `require_signer_at()`, `require_writable_at()`,
`require_owned_at()`, `require_data_min()`, `require_keys_equal()`,
`require_unique()`, `require_lamports_gte()`.
`TransitionRulePack` dispatches validation by instruction tag.
`PostMutationValidator` runs after state mutation.
#### Trust Profiles (`check/trust.rs`)
`TrustProfile` with `TrustLevel`:
- **Strict** -- owner + layout_id + exact size
- **Compatible** -- min size
- **Observational** -- layout_id only
Explicit declaration of trust assumptions for foreign accounts.
---
### Collections (`collections/`)
Eight zero-copy, zero-alloc collection types. All operate directly over
`&mut [u8]` slices with inline wire headers:
| `FixedVec<T>` | 4B count | push, pop, swap_remove, index | O(1) |
| `RingBuffer<T>` | 8B (head + count) | push, read | O(1) |
| `SlotMap<T>` | 8B (count + free_head) | insert, remove, access | O(1) |
| `BitSet` | none | get, set, toggle, count_ones | O(1) |
| `SortedVec<T>` | 4B count | binary_search, insert, remove | O(log n) / O(n) |
| `PackedMap<K,V>` | 4B count | get, insert, remove | O(n) |
| `Journal<T>` | 16B | append, read (strict/circular) | O(1) |
| `Slab<T>` | 16B + bitmap | alloc, free (double-free safe) | O(1) |
`SlotMap` uses generation counters to prevent ABA problems. `Journal` supports
strict mode (reject on full) and circular mode (overwrite oldest). `Slab` uses
an occupancy bitmap to prevent double-free.
---
### Frame / Execution (`frame/`)
#### Frame (`frame/mod.rs`)
`Frame<'a>` manages runtime mutable borrow tracking via a `u64` bitmask.
`account_mut()` checks the bit before granting exclusive access.
`FrameAccountMut` clears the bit on `Drop`. Supports up to 64 accounts.
#### Phased Execution (`frame/phase.rs`)
Typestate pattern enforcing execution phase ordering at compile time:
```
Unresolved -> Resolved -> Validated -> Executed
```
Each transition is a zero-cost move. The six conceptual phases:
1. **Resolve** -- parse accounts from the instruction
2. **Validate** -- run checks, verify signatures, verify PDAs
3. **Borrow** -- acquire mutable references
4. **Mutate** -- write account data
5. **Emit** -- fire events
6. **Commit** -- release borrows
#### Instruction Args (`frame/args.rs`)
`InstructionArgs<'a>` trait for zero-copy argument parsing with lifetime into
the instruction data buffer. `ValidateArgs` trait for independent arg validation
before account validation.
---
### Dispatch (`dispatch/`)
`dispatch_instruction()` reads a 1-byte discriminator tag from instruction data
and dispatches to the matching handler. `dispatch_instruction_u16()` for 2-byte
tags. The `hopper_dispatch!` macro generates the match statement.
### Events (`event/`)
`emit_event<T: Pod>()` serializes a Pod type via `sol_log_data` syscall (~100 CU).
`emit_event_tagged()` prepends a discriminator byte. `emit_slices()` emits raw
byte slices. Zero allocation.
### State Machine (`state/`)
`check_state_transition(current, from, to)` validates FSM transitions.
`transition_state()` validates and writes. `check_state()`, `check_state_not()`,
`check_state_in()` for guards.
### CPI (`cpi/`)
`HopperCpi<'a, ACCTS, DATA>` -- fully const-generic, stack-only CPI builder.
Uses `MaybeUninit` and `sol_invoke_signed_c`. Max 4 signers, 16 seeds each.
`HopperCpiBuf<'a, ACCTS, MAX>` for runtime-length data.
### State Diff (`diff/`)
`StateSnapshot<const SIZE>` captures a before-snapshot on the stack.
`StateDiff` computes byte-level changes: `has_changes()`, `range_changed()`,
`changed_regions::<N>()`, `field_diff_mask()`, `restore_into()`.
### Invariants (`invariant/`)
`check_invariant()` and `check_invariant_fn()` (lazy evaluation).
`InvariantSet` runs all invariants and reports the first failure.
Custom error codes per invariant via `InvariantDescriptor`.
### Migration (`migrate/`)
On-chain migration support: `migrate_append()` handles realloc + header update +
zero-fill for append-only upgrades. `MigrationDescriptor` and `MigrationKind`
(Append / SegmentAppend / Full).
### Policy (`policy.rs`)
Declarative capability/requirement system:
- `Capability` enum (10 capabilities: ReadsState, MutatesState, TouchesJournal,
ExternalCall, MutatesTreasury, ReallocatesAccount, CreatesAccount,
ClosesAccount, ModifiesAuthority, TransitionsState)
- `PolicyRequirement` enum (8 requirements: Authority, JournalCapacity,
PostMutationCheck, CpiGuard, RentExemption, InvariantCheck, StateSnapshot,
LamportConservation)
- `InstructionPolicy<const N>` maps capabilities to requirements and `.enforce()`s
them at runtime
### Receipt (`receipt.rs`)
`StateReceipt<const SNAP_SIZE>` captures a before-snapshot and computes a diff
on commit. Tracks: `layout_id`, `changed_fields` (u64 bitmask), `changed_bytes`,
`changed_regions`, `was_resized`, `invariants_passed`, `cpi_invoked`,
`before_fingerprint` (FNV-1a of pre-mutation data), `after_fingerprint`,
`segment_changed_mask`, `policy_flags` (capability bits), `journal_appends`,
and `cpi_count`.
`commit_with_segments(data, segments)` is the preferred commit path when segment
tracking matters. `to_bytes()` produces a 64-byte wire payload for event emission.
`DecodedReceipt` provides off-chain decoding via `from_bytes()`.
### Virtual State (`virtual_state/`)
`VirtualState<const N>` maps N logical slots to physical accounts for
multi-account patterns. `VirtualSlot` declares ownership and writability
requirements per slot. `validate()` checks bounds, ownership, and writability.
`overlay()` / `map()` provide typed access across constituent accounts.
### Math (`math/`)
Checked arithmetic: `checked_add/sub/mul/div` (u64), `checked_add_i64/sub_i64`,
`scale_bps()` (basis points via u128), `scale_fraction()`, `div_ceil()`.
All return `ProgramError::ArithmeticOverflow` on overflow.
### Time (`time/`)
Timestamp guards: `check_deadline_passed/not_passed`, `check_cooldown_elapsed`,
`check_staleness`, `check_in_future/past`. All operate on `i64` Unix timestamps.
### Sysvar (`sysvar/`)
`Clock` and `Rent` structs. `read_clock()` (40 bytes), `read_rent()`.
`CachedClock` / `CachedRent` / `SysvarContext` for parse-once reuse.
Guards: `check_not_expired()`, `check_expired()`, `check_within_window()`,
`check_cooldown()`, `check_slot_staleness()`.
---
## Ring 1: hopper-solana
| `token.rs` | Zero-copy SPL Token field readers at fixed offsets. `token_account_mint/owner/amount/state`, `check_token_initialized/owner/mint`, `check_not_frozen`, `check_token_balance_gte`. |
| `mint.rs` | SPL Mint readers. `mint_supply/decimals`, `mint_authority/freeze_authority`, `COption<Pubkey>` handling. |
| `cpi_guard.rs` | `assert_no_cpi()`, instruction index/count helpers, token program ID checks. |
| `typed_cpi.rs` | Typed wrappers: `create_account/signed`, `transfer_sol/signed`, `token_transfer/signed`, `token_mint_to/signed`, `token_burn`. |
---
## Ring 2: hopper-schema
Off-chain and on-chain schema tooling:
- `LayoutManifest` -- name, disc, version, layout_id, total_size, fields
- `FieldDescriptor` -- name, canonical_type, size, offset
- `is_append_compatible()` -- all old fields exist at same positions in new
- `is_backward_readable()` -- V(N) code can read V(N+1) accounts (prefix match)
- `requires_migration()` -- any structural change at all
- `compare_fields::<N>()` -- per-field diff (Identical / Changed / Added / Removed)
- `MigrationPlan::<N>::generate()` -- produces policy, steps, copy/zero byte
counts, and backward-readability flag
- `decode_header()`, `decode_segments()` -- hex data inspection
---
## Macros (hopper-macros)
All `macro_rules!`. No proc macros are required for correctness or core
functionality (see [PROC_MACRO_POLICY.md](PROC_MACRO_POLICY.md)).
| `hopper_layout!` | `#[repr(C)]` struct + compile-time LAYOUT_ID + DISC/VERSION/LEN + tiered load functions + overlay + HopperLayout trait |
| `hopper_check!` | Composable constraint: `owner=, writable, signer, disc=, size>=` |
| `hopper_error!` | Sequential error code generation |
| `hopper_require!` | Assert with custom error return |
| `hopper_init!` | CreateAccount or Allocate/Assign CPI + zero_init + write_header |
| `hopper_close!` | safe_close_with_sentinel wrapper |
| `hopper_dispatch!` | 1-byte tag match dispatch |
| `hopper_register_discs!` | Compile-time discriminator uniqueness |
| `hopper_verify_pda!` | PDA verify with BUMP_OFFSET (~200 CU) |
| `hopper_invariant!` | Inline invariant runner |
| `hopper_manifest!` | const LayoutManifest with field descriptors |
| `hopper_segment!` | Segmented account declaration |
| `hopper_validate!` | Inline validation pipeline |
| `hopper_virtual!` | Multi-account virtual state mapping |
| `hopper_assert_compatible!` | Compile-time layout version compat check |
| `hopper_assert_fingerprint!` | Compile-time fingerprint pinning |
| `hopper_interface!` | Cross-program read-only interface |
---
## Design Invariants
These are the rules that all code must satisfy. Violations are bugs.
1. **Align-1 wire types.** All ABI field types are `#[repr(transparent)]` over
`[u8; N]` with `align_of == 1`. No native integers in overlay structs.
2. **Deterministic layout_id.** The SHA-256 input string is
`"hopper:v1:{Name}:{version}:{field}:{type}:{size},"` per field in
declaration order. Any structural change must produce a different layout_id.
3. **Zero-init before header write.** Global invariant. `hopper_init!` enforces
this. Manual paths must call `zero_init()` before `write_header()`.
4. **Append-only versioning.** V(N+1) is a strict superset of V(N). Fields
are never reordered or removed. New fields go at the end.
5. **No proc macros required.** All macros are `macro_rules!`. Proc macros
are allowed for optional ergonomics (see
[PROC_MACRO_POLICY.md](PROC_MACRO_POLICY.md)).
6. **No std, no alloc.** All on-chain crates are `#![no_std]` with zero heap usage.
7. **Every unsafe has a SAFETY comment.** Justifying alignment, length, aliasing.
8. **`size_of == LEN` and `align_of == 1` compile-time assertions** for every
`#[repr(C)]` overlay struct.
9. **Explicit error codes.** Every error path returns a specific `ProgramError`
or custom error code. No panics on-chain.
10. **No hidden runtime behavior.** No global state, no lazy init, no implicit
allocations, no trait objects, no dynamic dispatch in on-chain code.
---
## Examples
Start with `hopper-showcase`. It is the canonical reference program that uses
every layer of the pipeline.
| `hopper-showcase` | Full pipeline: layout, dispatch, phased frame, policy, receipts, invariants, segment roles | 1+2 |
| `hopper-vault` | Macro-first SOL vault: `#[account]`, `#[derive(Accounts)]`, `#[program]` | 1 |
| `hopper-escrow` | Macro-first escrow with authority checks and raw-account escape hatch | 1 |
| `hopper-treasury` | Multi-segment treasury with permissions | 2 |
| `hopper-registry` | Segmented registry with journal and virtual state | 2 |
| `hopper-migration` | V1 to V2 layout evolution with migration planner | 2 |
| `hopper-virtual-state` | Multi-account entities with VirtualState | 2 |
| `cross-program-read` | Interface pinning across two programs, zero dependencies | 2 |
---
## Test Coverage
| Unit tests | 36 | Core module-level tests |
| Property tests | 75 | Randomized invariant checking |
| Trust tests | 96 | CPI guards, collections, migration, receipts, validation, segments, backward compat, danger zone golden tests |
| Migration tests | 9 | On-chain migration paths |
| Schema tests | 4 | Manifest generation and diffing |
| Virtual-state tests | 5 | Multi-account mapping |
| **Total** | **225** | |
---
## CLI
The CLI is a host-side inspection tool. It reads hex-encoded account data and
schema manifests to verify layouts, segments, compatibility, and receipts.
It does not connect to RPC or interact with live clusters.
```
hopper explain <hex> Human-readable account explanation
hopper inspect <hex> Raw header decode
hopper decode <hex> Alias for inspect
hopper segments <hex> Segment registry map
hopper compat <v1.json> <v2> Compatibility report (append-safe, backward-readable, migration)
hopper diff <v1.json> <v2> Field-level diff
hopper plan <v1.json> <v2> Migration plan with steps, byte counts, backward readability
hopper receipt <hex> Decode and explain a 64-byte state receipt
hopper schema-export Schema format reference
```