Skip to content
hopper
Get started
Operations / tx-tooling

Manifests and tx tooling

program_manifest! one-liners, manifest generation from source, and the no-Node hopper tx send / tx explain loop.

Hopper's tooling loop is pure Rust end to end: the program exports its schema in one line, the CLI generates the manifest from source, and the same CLI sends instructions and decodes confirmed transactions by name, no npx, no node_modules, no JS toolchain anywhere.

program_manifest!: the one-liner

Every Hopper program can export its full schema as a static:

hopper::program_manifest! {
    program = vault_program,
    layouts = [Vault],
    events  = [DepositReceipt],
}

name / version / description default from the crate's CARGO_PKG env consts. The macro glues together metadata the macros already emit: per-layout manifests with real field types and header-relative offsets, per-handler instruction descriptors (typed args, account roles, strict_writes / write ranges / mutation_complete / lamport accounts, whether a receipt is expected), and per-event descriptors with payload-relative field offsets. Those are the SAME generated consts the runtime enforces, so the published manifest equals the enforced contract by construction, not by a build step you have to remember to re-run.

The proof of the loop: the counter example's 82-line hand-assembled manifest block deletes into the 4-line macro block, and the regenerated manifest's layoutId is byte-identical to the checked-in one, the enforced identity const flowing through, not a re-derivation.

hopper compile --emit manifest: generate from source

hopper compile --emit manifest --package my-program

Closes the same loop anchor idl build does, without the Node leg: it builds a scratch harness against the target package, prints the package's PROGRAM_MANIFEST, and writes hopper.manifest.json next to the package (--out to override), with a friendly error naming the expected shape when the package exports no manifest static. The generated file immediately drives --emit ts (and the other client generators, see Client generation) unchanged. Both manifest dialects (the older snake_case files and the rendered camelCase output) are accepted by the loader, additively. Old files keep working.

The manifest carries the program's byte-range write-set end to end: writeRanges, strictWrites, mutationComplete, lamportAccounts, and cuEstimate. One honest note on cu_estimate: it is author-supplied and must come from a measured worst-case run of the actual instruction, not from summing the per-primitive tables (see the CU costs page).

hopper tx send: the generic no-Node sender

The instruction sender stock tooling lacks (devs usually shell out to JS scratch scripts):

hopper tx send --program <program-id> \
    --account payer:sw --account <vault>:w \
    --data 015a00000000000000 \
    --keypair ~/.config/solana/id.json --rpc https://api.devnet.solana.com
  • Ordered --account <pubkey|payer>[:s][:w] metas, one per slot; the literal spelling payer resolves to the fee payer.
  • Hex --data, local keypair signing, and refuse-before-send signer coverage: it will not broadcast a transaction whose declared signers it cannot sign.
  • --dry-run previews the exact instruction plan and signer coverage; after confirmation it fetches and prints the measured CU and fee. A send you cannot budget from is half a tool.

It rides the same signed-send stack as hopper publish-idl (the pure-Rust IDL publisher to the SPL Program Metadata PDA, no JS toolchain, though not yet devnet-battle-tested).

hopper tx explain: named decodes from confirmed transactions

hopper tx explain <signature> decodes what a transaction actually did:

  • Touch maps from the log stream: W slot 1 (vault) [48..56) -> Vault.balance, see Self-describing transactions.
  • Self-CPI events from inner-instruction metadata: the indexer-grade read path, because inner instructions survive the log truncation that motivates event_cpi in the first place. Authenticity is judged honestly: a marker CPI whose target equals the enclosing program in a successful transaction was sink-authenticated on-chain (only that program's invoke_signed can sign its event authority); a marker-shaped CPI to a foreign program is labeled exactly that.
  • With the program's manifest, events render named and field-decoded: little-endian integers by declared wire type, hex fallback for unknown types, manifest/payload mismatches reported, never padded over.

The loop, run live

On 2026-07-10 every instruction of the devnet smoke program was fired with hopper tx send and decoded back with hopper tx explain: five instructions, live CU from 762 to 3,586, including the named event decode event: DepositReceipt (tag 0x02) { balance: 1500000, deposit_count: 3 } whose live cost matches the Mollusk lab exactly. The full table with signatures is on the Benchmarks page.

Manifests and tx tooling | Hopper docs