Skip to main content
Scope: AI-agent-agnostic playbook for building applications with the Stoffel framework. This is not a maintainer guide for compiler, VM, protocol, or release engineering work. Dependency assumption: use public crates.io releases by default, a full pinned revision of the official repository as fallback, and local paths only in explicit nonportable framework-development mode.

Use when

Use this playbook when a Stoffel app uses ClientStore and a Rust client/server wants compile-time input/output structs generated from the exact app bytecode.

Goal

Generate Rust bindings from the exact .stflb program the app will execute, use those bindings for typed client IO, and let the manifest select/validate backend and client-slot IO shape.

Current source of truth

  • crates/stoffel-rust-sdk/src/codegen.rs
  • crates/stoffel-rust-sdk/src/types.rs
  • crates/stoffel-rust-sdk/src/program.rs
  • crates/stoffel-rust-sdk/src/client.rs
  • crates/stoffel-rust-sdk/tests/sdk_usage.rs
  • crates/stoffel-rust-sdk/tests/compile_fail.rs

Cargo dependency roles

The runtime SDK belongs in [dependencies]; the dedicated generator belongs in [build-dependencies]. Use the matching versions from the current Rust SDK installation docs:
If the required API is not published, pin both crates to the same full commit in the official repository:
Do not put stoffel-rust-sdk in [build-dependencies] to generate bindings. Do not emit local path = "../stoffel/..." entries except in explicitly labeled, nonportable framework-development manifests.

Mode A: exact-bytecode bindings

Use this mode for deployment and whenever bindings must describe a specific .stflb artifact. Keep the artifact under the app repository (for example artifacts/program.stflb), and generate only into Cargo’s OUT_DIR:
Include the generated file:
For non-standard crate paths or derives, call stoffel_bindgen::generate_bindings_with_config with the same rooted input/output paths and stoffel_bindgen::BindingsConfig. Runtime code must load the same artifact without assuming the process CWD:

Mode B: source-generated bindings

Use source mode only when the application intentionally compiles source during its Cargo build. It is convenient for development but does not prove that bindings match a separately deployed bytecode file:
Emit one cargo:rerun-if-changed=... line for every imported source or other generator input. If the bytecode is built by a separate command, do not blur the modes: build it first, then use exact-bytecode mode against that output.

Generated shapes

The generator emits:
  • ProgramManifest
  • impl stoffel::GeneratedProgramManifest for ProgramManifest
  • Client{slot}Inputs for each client slot with declared inputs
  • Client{slot}Outputs for each client slot with declared outputs
  • ordered fields such as input_0, input_1, output_0
  • TypedClientInputs / TypedClientOutputs implementations
Current type mapping:
  • integer shares -> i64
  • boolean secret integers -> bool
  • fixed-point shares -> f64
Bindings can be generated for bytecode without ClientStore IO; the file still contains a ProgramManifest and a comment that no client IO was declared.

Use manifest-backed config

The manifest carries the bytecode backend plus per-client input/output types. Prefer it over hand-written backend/curve literals for ClientStore programs.

Typed client call

Advanced explicit manifest call:

Bytecode must be the contract

Treat .stflb as the app/client contract:
  1. Write or update .stfl source.
  2. Build bytecode with the same backend/curve/topology assumptions that will be used at runtime.
  3. Generate Rust bindings from that bytecode.
  4. Compile the Rust client/server code.
  5. At runtime, load the same bytecode and validate manifest/client IO shape before submitting inputs.
If source changes, rebuild bytecode and regenerate bindings. Do not hand-edit generated structs.

Multi-client and ordered-input guidance

If a program has:
Expect generated shapes like:
In the CLI equivalent, repeat a client slot in the same order:

Validation / done criteria

  • Regenerate bindings after bytecode changes.
  • Commit the generated app’s Cargo.lock; run cargo check --locked to catch type mismatches.
  • Run the app’s local smoke with the same bytecode.
  • For network/off-chain submissions, validate the runtime’s program manifest against generated types before submitting.
Audit dependency provenance without relying on the current directory:
Both stoffel-rust-sdk and stoffel-bindgen must resolve from registry+... or the same pinned official git+...#<full-sha>. A null package source is a local path/workspace leak. Final proof is cargo check --locked --manifest-path ... from a clean checkout outside the Stoffel repository, with no adjacent framework checkout. Framework tests:

Common pitfalls

  • Bindings must come from the exact .stflb deployed/executed.
  • Rebuild bytecode and regenerate bindings after any source, backend, or curve change.
  • Do not hand-edit generated binding files.
  • Do not bypass manifest validation when network clients submit real inputs.
  • Do not assume slot order from Rust struct field order alone; it follows ordered ClientStore metadata from bytecode.
  • Wrong: stoffel = { path = "../stoffel/crates/stoffel-rust-sdk" } in a distributable app. Right: crates.io, or the official Git URL plus full rev.
  • Wrong: stoffel::generate_bindings(...) from an SDK build-dependency. Right: stoffel_bindgen in [build-dependencies].
  • Wrong: input/output paths relative to process CWD or generated files written into src/. Right: inputs under CARGO_MANIFEST_DIR, outputs under OUT_DIR, and explicit rerun directives.
  • Wrong: generating from source and claiming an independently built deployment artifact is identical. Use exact-bytecode mode for that claim.

Next playbooks