wasmtime 45.x (latest stable as of Feb 2026).
- Release cadence: new major on the 20th of each month.
- LTS every 12th version (24 months support). Nearest LTS: v36.
- Requires Rust 1.90.0+.
- Repo: https://github.com/bytecodealliance/wasmtime
| Criterion | wasmtime | wasmer | wasm3 |
|---|---|---|---|
| Rust-native embedding | First-class | Yes | C FFI |
| Async host functions | Yes | No | No |
| Component Model / WASI | Full | Partial | No |
| Fuel / epoch metering | Both | Fuel only | Injection |
| Production users | Fastly, Fermyon, Cloudflare, Zed | General | Embedded |
| Sandboxing | Proven | Similar | Similar |
The Component Model is production-viable in wasmtime 45 and gives us critical advantages over raw core modules:
-
Structural sandboxing. A component compiled against a WIT world with no filesystem import literally cannot access the filesystem - enforced at the type level, not just by omission of host functions. This is stronger than core module sandboxing where imports are stringly-typed.
-
Type-safe API contract. The WIT definition is the API spec. Both host and guest get generated bindings (
wasmtime::component::bindgen!on the host,wit_bindgen::generate!on the guest). No manual ABI wrangling, no serialisation disagreements. -
Resource types. Opaque handles with lifecycle management (constructors, methods, destructors via
ResourceTable). Ideal for subscription handles, RPC connections, etc. -
Multi-language guests from day 1. Module authors can use Rust, C/C++, Go, JavaScript (ComponentizeJS), or Python (componentize-py) - all producing valid components against the same WIT world. This dramatically lowers the barrier for community modules.
-
No WASI required. The Component Model and WASI are architecturally separate. We define a pure
nexum:hostworld with exactly our host APIs. Zero WASI imports means zero implicit capabilities. -
Acceptable overhead. The canonical ABI adds marshalling for strings/lists (memory copy across boundary), but for a plugin system with coarse-grained calls this is negligible.
InstancePrefront-loads validation costs.
- Tooling churn.
wit-bindgen(v0.57) andcargo-component(v0.21) are functional but APIs are not yet stable. Pin versions in the SDK. - Native async Component Model (
stream<T>,future<T>) is still evolving. We use basic async host functions (func_wrap_async) which are stable.
| Aspect | Risk |
|---|---|
bindgen! macro, custom worlds, resource types |
Low - stable, well-documented |
wit-bindgen guest bindings |
Medium - API churn between versions |
| Component Model native async (streams/futures) | High - not needed yet, avoid for now |
Global, thread-safe compilation environment. One per process.
let mut config = Config::new();
config.async_support(true);
config.consume_fuel(true);
config.epoch_interruption(true);
let engine = Engine::new(&config)?;Per-module execution context. Holds component instances, host state (NexumHostState), fuel counters, resource limits, and the ResourceTable for handle management.
let mut store = Store::new(&engine, NexumHostState {
table: ResourceTable::new(),
rpc: alloy_provider,
db: redb_handle,
// ...
});
store.set_fuel(10_000)?;
store.epoch_deadline_async_yield_and_update(10); // yield after 10 epochs (~1s at 100ms tick)- Component: compiled from
.wasmcomponent binary (expensive, cacheable, thread-safe). - Linker: binds host implementations of our WIT interfaces.
- InstancePre: pre-validated component + linker (reusable across stores).
- Instance: a live component in a specific store, from which we call exports.
let component = Component::from_file(&engine, "twap_monitor.wasm")?;
let mut linker = Linker::new(&engine);
EventModule::add_to_linker(&mut linker, |state| state)?;
// Pre-validate once, instantiate many times (one per store)
let pre = linker.instantiate_pre(&component)?;
let bindings = EventModule::instantiate_pre(&mut store, &pre)?;Nexum uses a two-layer WIT architecture. The universal package nexum:host defines platform-agnostic interfaces and the event-module world. The CoW-specific package shepherd:cow extends it with CoW Protocol interfaces and the shepherd world.
The nexum:host package is the single source of truth for the universal host-guest contract. It defines a custom world with no WASI imports:
package nexum:host@0.2.0;
interface types {
type chain-id = u64;
record block {
chain-id: chain-id,
number: u64,
hash: list<u8>,
timestamp: u64, // milliseconds since Unix epoch, UTC
}
record chain-log {
address: list<u8>,
topics: list<list<u8>>,
data: list<u8>,
block-hash: option<list<u8>>, // block-scoped fields absent on a pending log
block-number: option<u64>,
block-timestamp: option<u64>,
transaction-hash: option<list<u8>>,
transaction-index: option<u64>,
log-index: option<u64>,
removed: bool,
}
// A batch of logs from one subscription; the alloy log carries no chain
// id, so it sits here once and every log shares the subscription's chain.
record chain-logs {
chain-id: chain-id,
logs: list<chain-log>,
}
record tick {
fired-at: u64, // milliseconds since Unix epoch, UTC
}
record message {
content-topic: string,
payload: list<u8>,
timestamp: u64, // milliseconds since Unix epoch, UTC
sender: option<list<u8>>,
}
variant event {
block(block),
chain-logs(chain-logs),
tick(tick),
message(message),
}
/// Opaque config from module.toml [config] section. All TOML scalars are
/// flattened to their string form by the host. A typed `config-value`
/// variant is on the 0.3 roadmap, bundled with the manifest parser work.
type config = list<tuple<string, string>>;
/// The cross-domain failure vocabulary richer interfaces embed as a
/// case. Each payload-bearing case carries a human-readable detail;
/// `rate-limited` carries structured backoff guidance instead.
variant fault {
unsupported(string), // capability declared but not provisioned
unavailable(string), // capability exists, backend is down/offline
denied(string), // user or policy rejected
rate-limited(rate-limit),
timeout,
invalid-input(string),
internal(string),
}
/// Backoff guidance for a `fault.rate-limited`.
record rate-limit {
retry-after-ms: option<u64>,
}
}
interface chain {
use types.{chain-id, fault};
/// A JSON-RPC error response from the upstream node: `code` is the
/// node-reported numeric (typically -32000 for an `eth_call` revert)
/// and `data` is the decoded revert payload, hex-decoded host-side.
record rpc-error {
code: s32,
message: string,
data: option<list<u8>>,
}
/// Failure of a chain call: either a shared host `fault` (transport
/// down, timed out, denied, ...) or a structured JSON-RPC error.
variant chain-error {
fault(fault),
rpc(rpc-error),
}
/// Execute a JSON-RPC request against the specified chain.
///
/// The host forwards the request to the configured alloy provider for
/// the given chain, applying timeout/retry/rate-limit/fallback middleware
/// transparently. Method includes the namespace prefix (e.g. "eth_call").
///
/// `params` and the success return value are JSON-encoded strings matching
/// the JSON-RPC spec. The host handles id/jsonrpc framing; the guest only
/// provides method + params and receives the `result` field.
///
/// See doc 07 (RPC Namespace Design) for the full design rationale: a
/// single generic function replaces per-method WIT functions, enabling
/// the SDK to implement alloy's Transport trait and expose the full
/// alloy Provider API (80+ methods) to guest modules with zero WIT churn.
///
/// Note: signing RPC methods (eth_sendTransaction, eth_accounts,
/// eth_signTypedData_v4, personal_sign) are intercepted by the host and
/// delegated to the identity backend. The module does not need to handle
/// key material directly when using chain for transactions.
request: func(chain-id: chain-id, method: string, params: string)
-> result<string, chain-error>;
/// A single JSON-RPC request to be executed as part of a batch.
record rpc-request {
method: string,
params: string,
}
/// Result of a single request inside a batch. Each entry is independent;
/// one failing call does not abort the others.
variant rpc-result {
ok(string),
err(chain-error),
}
/// Additive 0.2 method: batched JSON-RPC. The alloy-backed HostTransport
/// routes RequestPacket::Batch through this - `provider.multicall(...)`
/// actually batches on the wire in 0.2. Hosts that cannot batch natively
/// MUST fall back to sequential `request` calls; the returned list is
/// the same length as `requests` and in the same order.
request-batch: func(chain-id: chain-id, requests: list<rpc-request>)
-> result<list<rpc-result>, chain-error>;
}
interface identity {
use types.{fault};
/// Get available signing accounts (20-byte Ethereum addresses).
accounts: func() -> result<list<list<u8>>, fault>;
/// Sign a message with `personal_sign` semantics. The host MUST prepend
/// the EIP-191 prefix (`\x19Ethereum Signed Message:\n<len>`) before
/// hashing and signing. Hosts MUST NOT expose a raw-bytes signing path
/// through this function - a raw signer can be tricked into signing
/// EIP-155 transactions or EIP-712 payloads disguised as plain bytes.
///
/// Returns a 65-byte ECDSA secp256k1 signature (r || s || v).
///
/// A separate raw-bytes signing primitive, gated by an explicit
/// capability, is on the 0.3 roadmap.
sign: func(account: list<u8>, message: list<u8>) -> result<list<u8>, fault>;
/// Sign EIP-712 typed data with the specified account.
/// `typed-data` is the JSON-encoded EIP-712 TypedData structure.
sign-typed-data: func(account: list<u8>, typed-data: string) -> result<list<u8>, fault>;
}
interface local-store {
use types.{fault};
get: func(key: string) -> result<option<list<u8>>, fault>;
set: func(key: string, value: list<u8>) -> result<_, fault>;
delete: func(key: string) -> result<_, fault>;
list-keys: func(prefix: string) -> result<list<string>, fault>;
}
interface logging {
enum level { trace, debug, info, warn, error }
log: func(level: level, message: string);
}
/// The universal event-driven module world. Platform-agnostic: no CoW,
/// no domain-specific imports. Suitable for any web3 automation.
///
/// In 0.2 this imports all six primitives - the identity import was
/// missing from the 0.1 WIT despite being part of the documented primitive
/// taxonomy, and is now present.
world event-module {
import chain;
import identity;
import local-store;
import remote-store;
import messaging;
import logging;
/// Called once on load. Receives typed config from module.toml.
export init: func(config: types.config) -> result<_, fault>;
/// Called for each subscribed event.
export on-event: func(event: types.event) -> result<_, fault>;
}In addition to the six core imports, 0.2 publishes one additive optional capability - http (allowlisted outbound HTTP) - which modules declare in their module.toml [capabilities] section. The declaration is a manifest concern only: the capability is serviced by the standard wasi:http/outgoing-handler interface, not a nexum:host one. The migration guide carries the details. 0.2 also publishes the experimental query-module world for request/response modules; the WIT is stable but no host implementation ships in 0.2, so it's a target for MockHost testing only.
The shepherd:cow package extends the universal world with CoW Protocol interfaces. In 0.2 the two 0.1 interfaces (cow + order) merge into a single cow-api interface to eliminate the cow::cow::request triple-stutter:
package shepherd:cow@0.2.0;
interface cow-api {
use nexum:host/types.{chain-id, fault};
/// A non-2xx reply with no typed rejection envelope; `body` is raw text.
record http-failure { status: u16, body: option<string> }
/// A typed orderbook rejection, parsed host-side from `{errorType, description}`.
record order-rejection { status: u16, error-type: string, description: string, data: option<string> }
/// A cow-api call failure: a shared host `fault`, a raw HTTP failure,
/// or a typed order rejection.
variant cow-api-error {
fault(fault),
http(http-failure),
rejected(order-rejection),
}
/// HTTP-style request to the CoW Protocol API.
///
/// The host routes to the correct CoW API base URL for the given chain.
/// `method`: "GET" | "POST" | "PUT" | "DELETE"
/// `path`: relative API path, e.g. "/api/v1/orders"
/// `body`: optional JSON request body
request: func(
chain-id: chain-id,
method: string,
path: string,
body: option<string>,
) -> result<string, cow-api-error>;
/// Submit a serialised order to the CoW Protocol.
/// (Replaces the 0.1 `order::submit` interface.)
submit-order: func(chain-id: chain-id, order-data: list<u8>)
-> result<string, cow-api-error>;
}
/// CoW Protocol module world. Extends the universal event-module
/// with CoW-specific imports.
world shepherd {
include nexum:host/event-module;
import cow-api;
}- Constrained WASI - the WASI p2 surface linked into every store includes
wasi:clocksandwasi:randomambiently; there is no filesystem grant and no inbound network. The only network path is allowlisted outbound HTTP throughwasi:http/outgoing-handler, available to modules that declare thehttpcapability in the manifest's[capabilities]section. Thewasi:socketsbindings are linked as part of the p2 surface but stay inert because the WASI context grants no network. - All I/O through our interfaces - RPC reads, identity/signing, CoW API, local-store, order submission, logging.
- Generic JSON-RPC passthrough - the
chaininterface exposes a singlerequestfunction (plus an additiverequest-batch). The SDK implements alloy'sTransporttrait on top of it, giving modules the full alloyProviderAPI. See doc 07 for details. - Identity as a first-class primitive - the
identityinterface provides key management and signing. Thechainhost implementation depends onidentityinternally: signing RPC methods (eth_sendTransaction,eth_accounts,eth_signTypedData_v4,personal_sign) are intercepted and delegated to the identity backend. Modules can also importidentitydirectly forpersonal_sign-style message signing, EIP-712 typed data signing, and listing accounts. (Raw-bytes signing, gated by an explicit capability, is on the 0.3 roadmap; the currentsignMUST prepend the EIP-191 prefix.) - Per-interface typed errors over a shared
faultvocabulary - each interface declares its own error type; the cross-domain cases share one payload-bearingfault(unsupported,unavailable,denied,rate-limited,timeout,invalid-input,internal). Interfaces with nothing to add returnfaultdirectly (identity, local-store, remote-store, messaging, the module exports);chain-errorembedsfaultand adds anrpccase,cow-api-erroraddshttpandrejected. The 0.1 per-protocol error types (json-rpc-error,identity-error,msg-error,store-error,api-error) are gone. Modules match on the typed variant for retry/backoff decisions. See ADR-0011. list<u8>for raw bytes - local-store values, order payloads, signatures, accounts, etc. The SDK provides typed wrappers.- Resource types can be added later (e.g. subscription handles, cursor-based log iteration).
- Two worlds in 0.2's reference runtime -
nexum:host/event-modulefor platform-agnostic modules;shepherd:cow/shepherdfor CoW Protocol modules that need thecow-apiimport. The experimentalnexum:host/query-moduleworld is published but not yet hosted.
The host uses wasmtime::component::bindgen! to generate Rust traits from the WIT. For universal interfaces, the generated traits live under nexum::host::. For CoW-specific interfaces, they live under shepherd::cow::.
// Universal event-module world
wasmtime::component::bindgen!({
path: "wit/nexum-host",
world: "event-module",
async: true,
});
// CoW-specific shepherd world (extends event-module)
wasmtime::component::bindgen!({
path: "wit/shepherd-cow",
world: "shepherd",
async: true,
});The Identity trait abstracts key management and signing. Platform implementations vary (server uses keystore/KMS/HSM, mobile uses device keychain, WebView uses wallet extensions), but the trait is uniform:
trait Identity {
fn accounts(&self) -> Result<Vec<Address>>;
fn sign(&self, account: Address, data: &[u8]) -> Result<Signature>;
fn sign_typed_data(&self, account: Address, typed_data: &str) -> Result<Signature>;
}The chain host implementation depends on Identity internally. When a module calls a signing RPC method through chain::request (e.g. eth_sendTransaction, eth_accounts, eth_signTypedData_v4, personal_sign), the host intercepts the call and delegates to the identity backend instead of forwarding to the RPC provider:
impl nexum::host::chain::Host for NexumHostState {
async fn request(
&mut self,
chain_id: u64,
method: String,
params: String,
) -> Result<Result<String, ChainError>> {
// Signing methods are intercepted and delegated to identity.
match method.as_str() {
"eth_accounts" => {
let accounts = self.identity.accounts()?;
let json = serde_json::to_string(&accounts)?;
return Ok(Ok(json));
}
"eth_sendTransaction" => {
// Parse tx, sign via identity, then submit signed tx to provider
let tx = serde_json::from_str(¶ms)?;
let signature = self.identity.sign(tx.from, &tx.signing_hash())?;
let signed = tx.with_signature(signature);
let provider = self.provider_for(chain_id)?;
let hash = provider.send_raw_transaction(&signed.encoded()).await?;
return Ok(Ok(serde_json::to_string(&hash)?));
}
"eth_signTypedData_v4" | "personal_sign" => {
// Delegate to identity for signing
let (account, data) = parse_sign_params(&method, ¶ms)?;
let sig = self.identity.sign(account, &data)?;
return Ok(Ok(serde_json::to_string(&sig)?));
}
_ => {}
}
if !self.is_method_allowed(&method) {
return Ok(Err(ChainError::Fault(Fault::Denied(format!(
"method not allowed: {method}"
)))));
}
let provider = self.provider_for(chain_id)?;
let raw_params: Box<RawValue> = RawValue::from_string(params)?;
// One function handles the entire eth_ namespace - alloy's provider
// stack (timeout, retry, rate-limit, fallback) applies transparently.
// A structured JSON-RPC error folds to ChainError::Rpc (node code +
// decoded revert bytes); a transport failure folds to a Fault.
match provider.raw_request_dyn(method.into(), &raw_params).await {
Ok(result) => Ok(Ok(result.get().to_string())),
Err(e) => Ok(Err(e.into())),
}
}
}The identity::Host implementation delegates to the platform-specific Identity trait. The interface is the failure domain, so errors are a plain Fault:
impl nexum::host::identity::Host for NexumHostState {
async fn accounts(&mut self) -> Result<Result<Vec<Vec<u8>>, Fault>> {
match self.identity.accounts() {
Ok(addrs) => Ok(Ok(addrs.into_iter().map(|a| a.to_vec()).collect())),
Err(e) => Ok(Err(Fault::Internal(e.to_string()))),
}
}
async fn sign(
&mut self,
account: Vec<u8>,
data: Vec<u8>,
) -> Result<Result<Vec<u8>, Fault>> {
let address = Address::from_slice(&account);
match self.identity.sign(address, &data) {
Ok(sig) => Ok(Ok(sig.to_vec())),
Err(IdentityBackendError::UserRejected) => Ok(Err(Fault::Denied("user rejected".into()))),
Err(e) => Ok(Err(Fault::Internal(e.to_string()))),
}
}
// sign_typed_data follows the same pattern.
}impl nexum::host::local_store::Host for NexumHostState {
async fn get(&mut self, key: String) -> Result<Result<Option<Vec<u8>>, Fault>> {
// Read from the in-flight WriteTransaction (not a new ReadTransaction)
// so the module sees its own uncommitted writes within a single on_event.
let table = self.write_txn.open_table(self.local_store_table())?;
Ok(Ok(table.get(key.as_str())?.map(|v| v.value().to_vec())))
}
// ...
}
impl shepherd::cow::cow_api::Host for NexumHostState {
// CoW-specific host implementation
// ...
}See doc 07 for the full chain and cow-api host implementations, method allowlisting, and the HostTransport that bridges this to alloy's Provider API on the guest side.
The two subsections below describe the 0.3+ macro-driven authoring model (
#[nexum::module]/#[shepherd::module], alloyRootProviderinjection,TypedState). It is future direction, not in 0.2 scope. In 0.2, modules ship today using the host-trait seam from ADR-0009: astrategy.rs(pure logic against&impl Host) plus alib.rsWitBindgenHostadapter that bridges towit-bindgen::generate!. Seesdk.mdand the example modules undermodules/examples/for the shipped pattern.
In the future direction, module authors targeting the universal event-module world would add the nexum-sdk crate and use the #[nexum::module] proc macro. Modules can access identity for signing operations - either indirectly through chain (signing RPC methods are handled transparently) or directly via the identity interface for raw signing:
use nexum_sdk::prelude::*;
#[nexum::module]
struct BlockLogger;
impl BlockLogger {
fn init(config: Config) -> Result<()> {
info!("Block logger starting");
Ok(())
}
async fn on_block(block: Block, provider: &RootProvider) -> Result<()> {
let block_num = provider.get_block_number().await?;
info!("New block: {block_num}");
TypedState::set("last_block", &block_num)?;
Ok(())
}
}In the future direction, module authors targeting the CoW-specific shepherd world would add the shepherd-sdk crate and use the #[shepherd::module] proc macro. The macro provides named event handlers (on_block, on_chain_logs, on_tick, on_message) - it generates the on_event match dispatch, WIT export wrapper, and optional provider injection. Handlers can be async fn for natural .await:
use shepherd_sdk::prelude::*;
sol! {
function getTradeableOrderWithSignature(
address owner, bytes32 ctx, bytes32 orderHash
) external view returns (bytes memory order, bytes memory signature);
}
#[shepherd::module]
struct TwapMonitor;
impl TwapMonitor {
fn init(config: Config) -> Result<()> {
info!("TWAP monitor starting");
Ok(())
}
// Named handler - macro generates on_event match dispatch.
// provider is injected from block.chain_id.
// async fn - macro wraps in block_on (single-poll, zero overhead).
async fn on_block(block: Block, provider: &RootProvider) -> Result<()> {
// Full alloy Provider API - natural .await
let block_num = provider.get_block_number().await?;
let balance = provider.get_balance(owner).latest().await?;
// Typed contract calls with sol! + EthCall builder
let tx = TransactionRequest::default()
.to(contract)
.input(getTradeableOrderWithSignatureCall {
owner, ctx, orderHash: order_hash,
}.abi_encode().into());
let result = provider.call(tx).latest().await?;
let decoded = getTradeableOrderWithSignatureCall::abi_decode_returns(&result)?;
// CoW API via typed client
let cow = Cow::new(block.chain_id);
cow.submit_order(&order)?;
// State persistence
TypedState::set("last_block", &block_num)?;
Ok(())
}
// Only define handlers for events you subscribe to.
// No on_chain_logs, on_tick, or on_message → those events are silently ignored.
}Build with cargo component build --release (or cargo build --target wasm32-wasip2 + wasm-tools component new).
See doc 05 for the full macro design (named handlers, provider injection, escape hatch) and doc 07 for the HostTransport implementation and provider() constructor.
| Language | Tooling | Maturity |
|---|---|---|
| Rust | wit-bindgen + cargo-component |
Mature |
| C/C++ | wit-bindgen c + WASI SDK |
Mature |
| Go | wit-bindgen Go generator |
Maturing |
| JavaScript | ComponentizeJS (SpiderMonkey) | Maturing |
| Python | componentize-py (CPython) | Maturing |
| C# | wit-bindgen-csharp |
Emerging |
All produce valid components against the same WIT worlds (nexum:host/event-module for universal, shepherd:cow/shepherd for CoW).
Config::consume_fuel(true)- each WASM op consumes fuel; exhaustion traps.- Use for per-invocation budgets: cap a single
on_eventcallback.
Config::epoch_interruption(true)- background Tokio task callsengine.increment_epoch()on a fixed interval.- Stores yield at epoch boundaries via
epoch_deadline_async_yield_and_update. - Use for wall-clock fairness: prevent one module from starving others.
Both are needed: fuel for correctness, epochs for liveness.
Implement ResourceLimiter to cap per-module:
- Memory growth - target <10 MB default.
- Table growth - max entries.
- Instance count - max concurrent.
Enforced synchronously on every memory.grow / table.grow.
All RPC and CoW API I/O is async (alloy / reqwest on the host). wasmtime bridges this:
Config::async_support(true).- Host functions registered with
func_wrap_async(or viaasync: trueinbindgen!). - Guest exports called with
call_async. - wasmtime runs WASM on a separate native stack;
Future::polldrives execution. - Epoch yielding ensures cooperation with the Tokio scheduler.
Note: We use wasmtime's basic async support (stable), not the Component Model native async (stream<T>, future<T>) which is still evolving.
- WASI 0.2.1 is stable in wasmtime. WASI 0.3 (native async) is in preview.
- The
event-moduleworld imports zero WASI interfaces; the WASI surface a module actually sees is what the engine links into its store, and that surface is deliberately constrained. wasi:clocksandwasi:randomare ambient: every module reads time and draws CSPRNG bytes with no capability declaration.- There is no filesystem grant and no inbound network.
wasi:socketsis linked as part of the p2 surface but inert - the WASI context grants no network, so the HTTP gate below is the only live network path. - Outbound HTTP is the standard
wasi:http/outgoing-handlerinterface, linked for modules that declare thehttpcapability in their manifest. Every outgoing request is checked against the module's[capabilities.http].allowlist - exact host match or*.suffixwildcard, case-insensitive, name-based - and an off-list host is denied with theHTTP-request-deniederror code before any connection is made. The host does not follow redirects, so a redirect a guest chooses to follow re-enters the gate as a fresh request. - Every admitted request is also bounded by the engine's
[limits.http]knobs: ceilings on the guest-settable connect / first-byte / between-bytes timeouts (which double as the effective values when a guest sets none), an unconditional total deadline over the whole exchange, and a cap on the response body size.engine.example.tomldocuments the knobs and their defaults.
| Nexum Concept | wasmtime Primitive |
|---|---|
| Runtime process | Engine (one, shared) |
| Universal API contract | WIT world (nexum:host/event-module) |
| CoW API contract | WIT world (shepherd:cow/shepherd) |
| Compiled module | Component (cached, thread-safe) |
| Pre-validated module | InstancePre (linker + component) |
| Running instance | Store<NexumHostState> + Instance |
| Host API impl | Traits generated by bindgen! |
| Host identity | Identity trait (keystore/KMS/HSM on server) |
| Opaque handles | Resource<T> + ResourceTable |
| Per-call budget | Fuel |
| Wall-clock fairness | Epoch interruption |
| Memory/table caps | ResourceLimiter |
| Async RPC / CoW I/O | func_wrap_async + Tokio |
| Persistent state | redb (per-module database file, via local-store interface host fns) |