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-wasip2component - bind to the
chain:contract@1.0.0WIT 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:
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¶
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.