Skip to content

Contract Development Overview

What is a contract?

A Wasichain application can combine two kinds of components: deterministic contracts and nondeterministic services. This page covers the contract side.

A Wasichain contract is a wasm32-wasip2 component that implements the contract-world WIT interface. Each contract is a standalone Wasm component linked against chain:contract@1.0.0 host imports.

This is a specification, not a custom language runtime. Wasichain runs contracts in Wasmtime and expects them to follow a standard WIT-defined contract shape. Services follow the same component-first philosophy with a different execution role. Rust is the best-supported example in this repository, but the model is intentionally language-agnostic.

Contract exports

The current contract world defines five exports. In practice, reply is only used by contracts that dispatch sub-messages, and service-consensus-query is only used when a service asks this contract to normalize payloads before response consensus.

Export Signature When called
init func(msg: list<u8>) -> result<list<u8>, string> Once, when the contract is instantiated
exec func(msg: list<u8>) -> result<list<u8>, string> On each ExecContract transaction
query func(msg: list<u8>) -> result<list<u8>, string> On read-only queries (no state changes allowed)
service-consensus-query func(payload: list<u8>) -> list<u8> When a service uses this contract to normalize payloads for response consensus
reply func(msg-id: u64, success: bool, data: list<u8>) -> result<list<u8>, string> When a dispatched sub-message completes (if reply-on matches)

Messages are opaque byte slices. By convention, contracts use JSON serialization via serde_json, but any encoding works.

Language support

Any language can target Wasichain if it can:

  • produce a compatible wasm32-wasip2 component
  • bind to the chain:contract@1.0.0 WIT world
  • respect deterministic execution constraints

The examples in this repository use Rust because the tooling is mature, not because Wasichain requires Rust. See Services Overview for the companion model used for network-connected workflows.

Scaffolding a contract

Contracts use wit_bindgen to generate Rust bindings from the WIT file:

wit_bindgen::generate!({
    path: "../../../config/wit",
    world: "contract-world",
});

Then implement the Guest trait and use the export! macro:

struct MyContract;
export!(MyContract);

impl Guest for MyContract {
    fn init(msg: Vec<u8>) -> Result<Vec<u8>, String> { /* ... */ }
    fn exec(msg: Vec<u8>) -> Result<Vec<u8>, String> { /* ... */ }
    fn query(msg: Vec<u8>) -> Result<Vec<u8>, String> { /* ... */ }
    fn service_consensus_query(payload: Vec<u8>) -> Vec<u8> { payload }
    fn reply(_msg_id: u64, _success: bool, _data: Vec<u8>) -> Result<Vec<u8>, String> {
        Err("reply not implemented".into())
    }
}

service_consensus_query should be a pure, deterministic normalization hook. If a contract is never used by services for consensus, returning the payload unchanged is a safe passthrough implementation.

Build target

cargo build --target wasm32-wasip2 --release

Deterministic execution

Contracts run inside a sandboxed Wasmtime instance with no access to:

  • filesystem, sockets, or network
  • clocks or timers
  • random number generators
  • process or environment APIs

The only I/O available is through the host functions defined in the WIT. See WIT Reference for the full list.

Actor model

Contracts communicate through an asynchronous dispatch/reply pattern rather than synchronous function calls. When contract A dispatches a message to contract B, B executes after A returns. A can optionally receive a reply callback with the result. See Cross-Contract Calls for details.

Synchronous read-only queries between contracts are also supported via query-contract.