Manifests and tx tooling
Manifest generation, versioned ELF interface binding, 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 environment constants. The macro assembles metadata emitted by
Hopper's layout, context, handler, and event macros: field types and offsets,
instruction arguments and account roles, declared strict_writes ranges,
mutation_complete and lamport declarations, receipt expectations, and event
fields.
The scoped guarantee is narrower than "the whole program is proven." For macro-declared fields reached through Hopper-tracked typed paths, the same generated layout IDs and write-range constants feed the manifest and runtime gates. Raw, unchecked, FFI, and dependency mutation paths require separate review, and exporting or publishing a declaration does not prove handler behavior.
The counter regression fixture checks that manifest generation preserves its
compiled layout identity. In Hopper 0.3.0,
hopper::program_manifest! also embeds a versioned SHA-256 commitment to the
declared executable interface. hopper verify --release requires the compiled
ELF to contain that exact commitment. This binds declarations to an artifact;
it does not prove artifact freshness, deployment identity, or behavior.
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 spellingpayerresolves 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-runpreviews 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 uses the same signed-send stack as hopper publish-idl, the pure-Rust
publisher for a validated Solana IDL v0.1.0 projection at the Program Metadata
PDA. The current projection preserves Hopper's wire discriminators and marks
account bodies with custom hopper-zero-copy-v1 serialization; it does not make
them Anchor-Borsh decodable. Publication refuses dynamic account tails, unsupported bounded wire
encodings, remaining-account contracts, unresolved or unsafe PDA seeds,
unresolved fixed addresses, and ambiguous discriminator prefixes. It does not
publish writeRanges or a Grillo effect contract.
One devnet transaction on 2026-07-12 published and read back the then-current payload. That receipt proves the old Program Metadata transport, ownership, compression, and readback path. It does not validate today's Solana IDL v0.1.0 shape or fail-closed projection, and it is not broad production evidence.
hopper tx explain: named decodes from confirmed transactions
hopper tx explain <signature> decodes the artifacts a confirmed transaction
published:
- Touch maps from the log stream, scoped to ranges acquired through
Hopper-tracked APIs:
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_cpiin 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'sinvoke_signedcan 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.
Transaction v1 and metadata records
The 0.3.0 CLI also supports hopper tx send --v1. It encodes
compute and loaded-data limits in the transaction's config mask, with an
optional total-lamport --priority-fee; it rejects those v1-only options on
legacy sends. hopper tx explain accepts legacy, v0, and v1 responses.
See the CLI reference for the exact limits and defaults.
hopper publish-security validates and publishes a Program Metadata security
record. hopper publish-manifest publishes the normalized Hopper manifest
under the custom hopper-manifest seed and can read it back. That seed is a
Hopper convention, not a standard reserved record. The full manifest retains
Hopper declarations omitted by the lossless-only Solana IDL projection.
Publishing declarations does not by itself prove that deployed code enforces
them. The legacy hopper fetch command still reads a different,
application-provisioned PDA.
The 2026-09-21 devnet archive records the security/manifest publication and transaction-v1 checks.
