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: application examples use public crates.io releases by default. A pinned official GitHub revision is the fallback when the required public release is unavailable. A local path is an explicit, nonportable framework-development mode only.

Use when

Use this playbook when a Rust application embeds Stoffel compilation, bytecode loading, local execution, client/server builders, typed client IO bindings, or network/off-chain integration.

Current source of truth

  • crates/stoffel-rust-sdk/README.md
  • crates/stoffel-rust-sdk/src/lib.rs
  • crates/stoffel-rust-sdk/src/prelude.rs
  • crates/stoffel-rust-sdk/src/runtime.rs
  • crates/stoffel-rust-sdk/src/config.rs
  • crates/stoffel-rust-sdk/src/types.rs
  • crates/stoffel-rust-sdk/examples/*

Dependencies

Use the released SDK dependency from the current Rust SDK installation docs. Keep the placeholder below synchronized with that page rather than copying a release number into this skill:
Equivalent manifest shape:
If that release does not contain a required fix, pin the official repository to a full 40-character commit SHA (not a branch, tag, or abbreviated SHA):
Only framework contributors intentionally testing an adjacent checkout should use a path dependency:
Do not use path = "../stoffel/..." as an application default, and do not combine path with version or git to make a locally dependent manifest appear portable. Use use stoffel::prelude::*; for app code.

Path discipline

Rust file APIs resolve relative paths from the process current directory, which may differ under tests, services, IDEs, and CI. Root app-owned paths at the Cargo manifest instead:
Use app_path("src/main.stfl"), app_path("artifacts/program.stflb"), and similar values with compile_file, load_file, and bytecode save/load calls. Accept deployment paths as explicit configuration when artifacts live outside the app; never depend on cd having been run first.

Clear local execution

Production-shaped client integration

For application integration, design toward deployed services and packaged artifacts: build bytecode once, deploy MPC nodes separately, and have each participant-owned client load deployment config plus typed bindings. For client-owned private input, that participant process submits directly to the MPC service; an application backend remains outside the plaintext path. Use local MPC as the development smoke path, not the production topology. Client input ownership rule: private ClientStore values originate in the SDK client. The client validates the generated input shape, encodes its own vector, and submits it through the client/coordinator protocol. Application servers and SDK server/node builders receive deployment configuration and value-blind protocol metadata; they must not receive participant values or proxy plaintext private inputs. Do not add per-client value payloads to ServerBuilder to model client slots or input ranges.
Generated typed bindings should be compiled into the participant client. Production clients should load pinned bytecode/metadata; they should not compile .stfl source dynamically for every request. A backend gateway that accepts participant plaintext is a separate, degraded-trust architecture and requires explicit approval.

Local MPC execution

Use local MPC to verify program semantics before deploying. .execute_local().await? spawns a local MPC test network on the developer machine, and one harness process may see every fixture input. It does not prove that a production application service is outside the plaintext path.
If the program sends outputs to client slots, configure the expected output clients before executing:

Loading and saving bytecode

Builder options to know

Program source:
  • Stoffel::compile(source)
  • Stoffel::compile_file(path)
  • Stoffel::load(bytes)
  • Stoffel::load_file(path)
MPC config:
  • .parties(n)
  • .threshold(t)
  • .instance_id(id)
  • .honeybadger()
  • .avss(Curve::Bls12_381) / .curve(curve)
  • .backend(MpcBackend::...)
  • .manifest::<ProgramManifest>() when using generated bindings
Compiler options:
  • .optimize(bool)
  • .optimization_level(0..=3)
  • .print_ir(bool)
  • .compiler_options(CompilationOptions { ... })
Inputs:
  • .with_input("a", 40_i64)
  • .with_inputs(&[("a", 40_i64), ("b", 2_i64)])
  • .with_client_input(0, &[40_i64, 2_i64])
  • .with_client_inputs(&[(0, vec![...])])
  • .expected_output_clients(n)
Runtime:
  • .build()
  • .summary() / runtime.summary()
  • .to_bytecode() / runtime.to_bytecode()
  • .save_bytecode(path) / runtime.save_bytecode(path)
  • .execute_clear()
  • .execute_local()
  • .execute_local_function("entry") / timeout variants where appropriate
  • runtime.client(), runtime.server(party_id), runtime.offchain_client_config(slot)

SDK value model

Use stoffel::Value at the SDK boundary:
  • Value::I64, Value::U64, Value::Bool, Value::Float, Value::String, Value::Bytes, Value::List, Value::Object, Value::Unit.
  • Convenience accessors: as_i64, as_u64, as_bool, as_f64, as_str, as_bytes, as_list, as_object, is_unit.
Typed client IO maps current manifest types as:
  • integer shares -> i64
  • unsigned integer shares -> i64/integer Rust fields at generated boundary depending on manifest mapping
  • boolean secret integers -> bool
  • fixed-point shares -> f64
See Stoffel Typed Client IO Bindings for generated structs and validation.

Network config builders

For deployment-oriented code, use builders instead of hand-rolled maps:
For full deployment handoff, also capture coordinator address, node RPC addresses, identity material, bytecode hash, generated binding version, persistence/state location, and process supervision. See Stoffel Deployment Runbook.

Validation / done criteria

For a generated or handed-off Rust app, commit Cargo.lock and use the locked graph in verification and CI:
Audit both the manifest text and Cargo’s resolved provenance. Run these from the app manifest explicitly, so the result does not depend on the caller’s current directory:
Inspect the Stoffel packages in cargo-metadata.json: crates.io packages have a registry+... source; the fallback has a git+https://github.com/Stoffel-Labs/stoffel.git?...#<full-sha> source. A null source means a path/workspace package and fails the portable-app audit. The final portability proof must run from a clean checkout outside the Stoffel framework repository and without an adjacent ../stoffel directory:
For repository scripts, derive paths from the script location instead of assuming the current directory:
For local MPC app paths:
Framework validation:

Common pitfalls

  • Do not use path dependencies as the default after crates.io publication.
  • Do not accept git = "...", branch = "main"; use the official URL and a full rev.
  • Do not omit a generated app’s Cargo.lock or silently drop --locked in CI.
  • Do not treat a successful build inside the framework checkout as portability proof; workspace inheritance and nearby paths can hide leaks.
  • Do not simulate protocol behavior in app code; use SDK/runtime execution paths.
  • Do not route ClientStore values through an application server or add participant input payloads to SDK server/node builders. Input ownership and submission belong to the SDK client.
  • Do not present .execute_local().await? as a production deployment path.
  • Do not compile .stfl source dynamically inside production clients; load pinned bytecode and generated metadata.
  • Do not set an explicit backend that conflicts with bytecode metadata. Prefer generated manifests for ClientStore programs.
  • For ClientStore apps, validate client input shapes before network submission.
  • Do not use stoffel-rust-sdk as the binding generator build-dependency. Use the matching public stoffel-bindgen crate under [build-dependencies] as shown in the typed-bindings playbook.

Next playbooks