Sigil’s contract runtime is WASI-component-based (Wasmtime 40, WASI 0.2 Component Model). Contracts run in a sandboxed VM with explicit host-function permits. Code is content-addressed: you publish the module once via RegisterContractCode, then deploy as many instances as you like via DeployContract.

When to use this

  • Custom logic that the native primitives don’t cover.
  • An on-chain price oracle, escrow with bespoke conditions, or governance contract.
  • Migrating EVM-style logic to Sigil’s WASI runtime.

Prerequisites

  • Rust toolchain with the wasm32-wasip2 target installed.
  • A funded controller DID for the contract.
rustup target add wasm32-wasip2

Recipe

1

Write a minimal contract

Contracts target the Sigil contract ABI exposed by sigil-wasm. The simplest contract has an execute entry point.
hello_contract.rs
// src/lib.rs — replace with the canonical sigil-contract template once it ships.
#[no_mangle]
pub extern "C" fn execute(input_ptr: u32, input_len: u32) -> u64 {
    // Read input bytes provided by the host, return a (offset, len)
    // packed u64 pointing into a result buffer.
    // See node/sigil-wasm/HOST_ABI.md for the canonical ABI.
    0
}
Build it:
cargo build --release --target wasm32-wasip2
ls target/wasm32-wasip2/release/hello_contract.wasm
2

Register the code

RegisterContractCode publishes the bytes on-chain and records the BLAKE3 hash, license, and source pointers.The Rust SDK has a typed builder for this:
use sigil_core::transaction::ContractLicenseMetadata;
use sigil_sdk::{TransactionBuilder, sign_transaction};
use std::fs;

let code = fs::read("target/wasm32-wasip2/release/hello_contract.wasm")?;
let metadata = ContractLicenseMetadata {
    license: "Apache-2.0".into(),
    source_uri: Some("https://github.com/yourorg/hello_contract".into()),
    source_hash: None,
    audit_uri: None,
    metadata: serde_json::json!({ "name": "hello", "version": "0.1.0" }),
};

let tx = TransactionBuilder::new(&chain_id, &sender_did)
    .nonce(current_nonce)
    .register_contract_code(code, metadata);
let signed = sign_transaction(tx, &signing_key)?;
Persist the code_hash from the receipt — you’ll reference it in the next step.
3

Deploy a contract instance

DeployContract instantiates a contract bound to a specific DID, controller, and zone.
let tx = TransactionBuilder::new(&chain_id, &sender_did)
    .nonce(current_nonce + 1)
    .deploy_contract(
        "did:oas:sigil:contract:hello-1",
        &sender_did,           // controller
        &code_hash_hex,
        Some("zone:default".into()),
    );
let signed = sign_transaction(tx, &signing_key)?;
The deployment establishes an immutable code binding plus a mutable ControlPolicy for upgrades and invocations.
4

Invoke the contract

Invocations are InvokeContract transactions whose payload the chain passes to the WASM module’s execute export.
await signAndSend({
  InvokeContract: {
    contract_did: 'did:oas:sigil:contract:hello-1',
    action: 'greet',
    args: { name: 'Sigil' },
    attached_value: null,           // attach native MINT if the contract accepts it
  },
});
Read-only views run via sigil_callView without a transaction:
curl -s "$SIGIL_RPC_URL" -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"sigil_callView",
       "params":{
         "contract_did":"did:oas:sigil:contract:hello-1",
         "action":"current_greeting",
         "args":{}
       }}'

Inspecting a deployment

curl -s "$SIGIL_RPC_URL" -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"sigil_getState",
       "params":{"partition":"contracts","key":"did:oas:sigil:contract:hello-1"}}'
The response includes the deployed code_hash, controller, upgrade policy, call policy, allowed host functions, and the contract’s current storage root.

Common errors

SymptomCauseFix
code_hash not registeredDeployContract before RegisterContractCode confirmedWait for the registration receipt to finalise
wasm validation failedModule uses unsupported importsBuild with the canonical sigil-contract template; remove non-WASI imports
host function not allowedContract calls an import outside allowed_host_functionsAdd it to the deploy policy or remove the call
gas limit exceededExecution took more gas than gas_limitRaise gas_limit or optimise the contract

See also