Skip to content
hopper
Get started
Operations / client-generation

Client generation

Generate TypeScript, Kotlin, Python, Go, C, and off-chain Rust clients from one Hopper manifest.

This reference covers Hopper 0.5.0, published on crates.io. See the release status for package verification, dated benchmarks, and remaining audit work.
  • hopper client gen --ts <manifest.json> -> TypeScript SDK
  • hopper client gen --kt <manifest.json> -> Kotlin SDK (org.sol4k)
  • hopper client gen --py <manifest.json> -> Python SDK (stdlib-only)
  • hopper client gen --go <manifest.json> -> Go SDK (stdlib-only)
  • hopper client gen --c <manifest.json> -> C header-only client
  • hopper compile --emit rust-client <manifest.json> -> Rust off-chain SDK

Hopper 0.3.1 generates TypeScript, Kotlin, Python, Go, C header-only, and off-chain Rust clients, plus Hopper public IDL, Codama JSON, and a lossless-only Solana IDL v0.1.0 projection. compile --emit idl emits Hopper's public subset; Solana IDL is the separate schema export --anchor-idl <manifest> --program-id <pubkey> path. That projection preserves Hopper's wire discriminators and declares custom hopper-zero-copy-v1 account serialization, so account decoding remains Hopper-aware. It refuses bounded encodings, remaining-account contracts, unresolved or unsafe PDA seeds, unresolved fixed addresses, and ambiguous discriminator prefixes that cannot be represented faithfully. Headered account readers in every generated language assert the 8-byte Hopper LAYOUT_ID before decoding fields. Fixed compact readers require exact manifest size plus discriminator; compact-dynamic readers require the manifest's minimum prefix size plus discriminator. Compact bytes do not contain a fingerprint, so the generated LAYOUT_ID remains manifest/IDL identity metadata. Generated readers decode declared fixed fields; application-specific dynamic-tail semantics remain a separate validation step.

Copyable generated-client flows

Write all generated artifacts to files during release verification:

hopper compile --emit ts --package hopper-token-2022-vault --out target/clients/vault.ts --force
hopper compile --emit kt --package hopper-token-2022-vault --out target/clients/Vault.kt --force
hopper compile --emit py --package hopper-token-2022-vault --out target/clients/vault_client.py --force
hopper compile --emit go --package hopper-token-2022-vault --out target/clients/vault_client.go --force
hopper compile --emit c --package hopper-token-2022-vault --out target/clients/vault_client.h --force
hopper compile --emit rust-client --package hopper-token-2022-vault --out target/clients/vault_client.rs --force

Generate user-facing surfaces from the same manifest:

hopper actions gen --program examples/hopper-token-2022-vault/hopper.manifest.json --out target/actions
hopper mobile gen --program examples/hopper-token-2022-vault/hopper.manifest.json --target kotlin --out target/mobile-kotlin
hopper mobile gen --program examples/hopper-token-2022-vault/hopper.manifest.json --target react-native --out target/mobile-rn
hopper test-gen security --program examples/hopper-token-2022-vault/hopper.manifest.json --out target/security_matrix.rs

Every command above consumes the manifest. That keeps account layout identity, instruction tags, client decoders, Actions route scaffolds, mobile binding stubs, and generated security test matrices on the same source of truth.

Motivation

A program needs clients that agree with its instruction and account contracts.

The client generator reads a Hopper ProgramManifest and emits typed SDKs that frontends, bots, scripts, and tests can drop into their project. All generated account decoders fail closed before reading fields: headered layouts compare the 8-byte LAYOUT_ID in account bytes, while compact layouts compare the discriminator and either exact fixed size or a compact-dynamic minimum prefix. They retain the manifest fingerprint as external identity metadata.

TypeScript Generation

What it generates

=== types.ts ===
=== accounts.ts ===
=== instructions.ts ===
=== events.ts ===
=== index.ts ===

The CLI prints a file-marked bundle to stdout. Each section can be split into its own file, or consumed by a generation pipeline.

Type mapping

Hopper canonical type TypeScript type Encoding
u8 number 1 byte LE
u16 number 2 bytes LE
u32 number 4 bytes LE
u64 bigint 8 bytes LE
u128 bigint 16 bytes LE
i8 number 1 byte LE signed
i16 number 2 bytes LE signed
i32 number 4 bytes LE signed
i64 bigint 8 bytes LE signed
bool boolean 1 byte (0/1)
Pubkey PublicKey 32 bytes
[u8; N] Uint8Array N bytes

Account decoders

Each account type gets:

  • A TypeScript interface with typed fields
  • A decode<Name>(data: Buffer): <Name> function
  • A discriminator constant for account identification

Instruction builders

Each instruction gets:

  • An <InstructionName>Args interface for typed arguments
  • An <InstructionName>Accounts interface for required accounts
  • A create<InstructionName>Instruction(args, accounts, programId) function that returns a TransactionInstruction

Events

Each event gets:

  • A TypeScript interface with typed fields
  • A decode<Name>Event(data: Buffer): <Name>Event function

Kotlin Generation

Kotlin generation is implemented today. The generator emits a file-marked bundle targeting org.sol4k primitives:

  • Types.kt with header and discriminator helpers
  • Accounts.kt with data classes and decoders
  • Instructions.kt with typed argument/account classes and builders
  • Events.kt with event data classes and decoders

Encoding and decoding use explicit little-endian ByteBuffer operations, so the generated Kotlin stays close to Hopper's wire model.

Python Generation

Python generation emits a single stdlib-only module:

  • dataclasses for account layouts and events
  • decode classmethods that verify layout identity first
  • segment-aware field readers for partial account inspection
  • build_<instruction> helpers that return raw instruction-data bytes

The generated module intentionally does not pick a Solana transport library. Callers can pass the emitted bytes to solders, solana-py, or their own RPC stack.

Go Generation

Go generation emits a single stdlib-only package with:

  • Pubkey, AccountMeta, and Instruction transport-neutral structs
  • typed account structs and Decode<Name> helpers that verify headered LAYOUT_ID, fixed compact exact size plus discriminator, or compact-dynamic minimum prefix size plus discriminator
  • typed <Instruction>Args / <Instruction>Accounts structs
  • Build<Instruction>Instruction helpers with discriminator-prefixed data
  • event structs and decoders keyed by event tag

The generated package deliberately avoids choosing a Go Solana SDK. Callers can adapt the emitted Instruction into their RPC or transaction-building library.

C Generation

C generation emits a single header-style client with:

  • fixed-width integer and HopperPubkey types
  • hopper_decode_<layout> account decoders that verify headered LAYOUT_ID, fixed compact exact size plus discriminator, or compact-dynamic minimum prefix size plus discriminator
  • hopper_build_<instruction>_data payload builders
  • hopper_<instruction>_account_metas account metadata helpers
  • event decoders and explicit HopperClientError return codes

The header is dependency-light (stdint.h, stdbool.h, stddef.h, string.h) and leaves transaction transport to the embedding application.

Rust Client Generation

hopper compile --emit rust-client emits an off-chain Rust client using Solana SDK types. It is separate from hopper compile --emit rust, which emits the lowered Hopper runtime preview for auditing macro expansion, accessors, and offset paths.

Usage

# Generate from manifest JSON
hopper client gen --ts @my-program.manifest.json

# Generate Kotlin client bundle
hopper client gen --kt @my-program.manifest.json

# Generate Python client module
hopper client gen --py @my-program.manifest.json

# Generate Go client package
hopper client gen --go @my-program.manifest.json

# Generate C client header
hopper client gen --c @my-program.manifest.json

# Generate off-chain Rust client
hopper compile --emit rust-client @my-program.manifest.json

# Normalize a manifest, then generate from the resulting file
hopper schema export --manifest @my-program.manifest.json > normalized.manifest.json
hopper client gen --ts @normalized.manifest.json

Architecture

ProgramManifest
  ├─ hopper-schema::clientgen::TsClientGen
  │    ├─ types.ts
  │    ├─ accounts.ts
  │    ├─ instructions.ts
  │    ├─ events.ts
  │    └─ index.ts
  ├─ hopper-schema::clientgen::KtClientGen
  │    ├─ Types.kt
  │    ├─ Accounts.kt
  │    ├─ Instructions.kt
  │    └─ Events.kt
  ├─ hopper-schema::python_client::PyClientGen
  │    └─ <program>_client.py
  ├─ hopper-schema::go_client::GoClientGen
  │    └─ client.go
  ├─ hopper-schema::c_client::CClientGen
  │    └─ client.h
  └─ hopper-schema::rust_client::RsClientGen
       └─ client.rs

The generator operates on ProgramManifest directly. It does NOT parse JSON. The CLI is responsible for loading the manifest; the generator formats TypeScript, Kotlin, Python, Go, C, and Rust output bundles.