diff --git a/videre/crates/videre-host/src/bindings.rs b/videre/crates/videre-host/src/bindings.rs index 06e2b887..28f35449 100644 --- a/videre/crates/videre-host/src/bindings.rs +++ b/videre/crates/videre-host/src/bindings.rs @@ -19,7 +19,7 @@ mod venue_adapter { path: [ "../../wit/videre-value-flow", "../../wit/videre-types", - "../../wit/nexum-host", + "../../wit/deps/nexum-host", "../../wit/videre-venue", ], world: "videre:venue/venue-adapter", @@ -54,7 +54,7 @@ mod client_host { path: [ "../../wit/videre-value-flow", "../../wit/videre-types", - "../../wit/nexum-host", + "../../wit/deps/nexum-host", "../../wit/videre-venue", ], imports: { default: async }, @@ -171,7 +171,7 @@ mod client_smoke { path: [ "../../wit/videre-value-flow", "../../wit/videre-types", - "../../wit/nexum-host", + "../../wit/deps/nexum-host", "../../wit/videre-venue", ], }); diff --git a/videre/crates/videre-sdk/src/bindings.rs b/videre/crates/videre-sdk/src/bindings.rs index 9f57dde8..77a561a4 100644 --- a/videre/crates/videre-sdk/src/bindings.rs +++ b/videre/crates/videre-sdk/src/bindings.rs @@ -34,7 +34,7 @@ world sdk-imports { path: [ "../../wit/videre-value-flow", "../../wit/videre-types", - "../../wit/nexum-host", + "../../wit/deps/nexum-host", "../../wit/videre-venue", ], world: "videre:sdk-shims/sdk-imports", diff --git a/videre/wit/deps.lock b/videre/wit/deps.lock new file mode 100644 index 00000000..c9d6d0ce --- /dev/null +++ b/videre/wit/deps.lock @@ -0,0 +1,5 @@ +[nexum-host] +url = "https://github.com/nullislabs/nexum-runtime/archive/refs/tags/nexum-host-v0.1.0.tar.gz" +prefix = "nexum-runtime-nexum-host-v0.1.0/wit/nexum-host" +sha256 = "c0a31ec3ff63a2b903daff7de9658d5caf9122737e388766fd6c380718e05587" +sha512 = "bcdba1c0e1c70c0eb42eb04591b0c45f923efb8de631d2acfbf2c9fecf455d5104af7a75513ac4cfa500a91700fbd8e82070d395939e5dfb50a190a6bb4a0413" diff --git a/videre/wit/deps.toml b/videre/wit/deps.toml new file mode 100644 index 00000000..eed3dd05 --- /dev/null +++ b/videre/wit/deps.toml @@ -0,0 +1,8 @@ +# Cross-repo WIT pins, vendored under wit/deps and verified against +# deps.lock by `wit-deps`. Pins are exact git tags of the owning repo; +# they converge to wkg/OCI (ghcr.io/nullislabs) with per-package semver +# once the packages are published. + +[nexum-host] +url = "https://github.com/nullislabs/nexum-runtime/archive/refs/tags/nexum-host-v0.1.0.tar.gz" +prefix = "nexum-runtime-nexum-host-v0.1.0/wit/nexum-host" diff --git a/videre/wit/deps/nexum-host/chain.wit b/videre/wit/deps/nexum-host/chain.wit new file mode 100644 index 00000000..1d812dc6 --- /dev/null +++ b/videre/wit/deps/nexum-host/chain.wit @@ -0,0 +1,60 @@ +package nexum:host@0.1.0; + +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). `data` is the decoded `error.data` payload: the host + /// hex-decodes the upstream JSON string once, so a guest receives + /// the raw abi-encoded revert bytes without any string handling. + record rpc-error { + code: s32, + message: string, + data: option>, + } + + /// Failure of a chain call: either a shared host `fault` (transport + /// down, timed out, denied, ...) or a structured JSON-RPC error + /// carrying the node code and any decoded revert payload. + variant chain-error { + fault(fault), + rpc(rpc-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), + } + + /// Execute a JSON-RPC request against the specified chain. + /// + /// The host routes to its configured provider for the given chain, + /// applying whatever middleware is appropriate for the platform + /// (timeout, retry, rate-limit, fallback on server; simple HTTP + /// on mobile; window.ethereum or injected provider in WebView). + /// + /// `method` includes the namespace prefix (e.g. "eth_call"). Each + /// host enforces its own permitted method surface; the reference + /// server host forwards only a read-only set and refuses signing + /// or mutating methods with a `denied` fault before they reach the + /// provider. `params` and the success value are JSON-encoded + /// strings. + request: func(chain-id: chain-id, method: string, params: string) + -> result; + + /// Execute several JSON-RPC requests against the same chain in a single + /// round trip where the host transport supports it. 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) + -> result, chain-error>; +} diff --git a/videre/wit/deps/nexum-host/event-module.wit b/videre/wit/deps/nexum-host/event-module.wit new file mode 100644 index 00000000..277134a9 --- /dev/null +++ b/videre/wit/deps/nexum-host/event-module.wit @@ -0,0 +1,24 @@ +package nexum:host@0.1.0; + +/// Event-driven module — automation, background processing. +/// No UI capabilities. Runs on any conforming host. +world event-module { + use types.{config, event, fault}; + + // Six core primitives (always provided by a conforming host). + import chain; + import identity; + import local-store; + import remote-store; + import messaging; + import logging; + + // Time, randomness, and outbound HTTP are WASI concerns, not + // nexum:host interfaces: wasi:clocks and wasi:random are linked + // into every module store, and hosts link + // wasi:http/outgoing-handler, gated per-module by the + // `[capabilities.http].allow` allowlist in module.toml. + + export init: func(config: config) -> result<_, fault>; + export on-event: func(event: event) -> result<_, fault>; +} diff --git a/videre/wit/deps/nexum-host/identity.wit b/videre/wit/deps/nexum-host/identity.wit new file mode 100644 index 00000000..2e82c9fe --- /dev/null +++ b/videre/wit/deps/nexum-host/identity.wit @@ -0,0 +1,24 @@ +package nexum:host@0.1.0; + +/// Identity / signing capability. +/// +/// 0.2 ships a single, minimal interface. A future release (0.4+) is +/// expected to split this into `identity-read` and `identity-sign` and to +/// introduce a richer `signing-result` variant; for 0.2 the simple shape is +/// sufficient because user rejection can already be expressed via +/// `fault.denied`. +interface identity { + use types.{fault}; + + /// Return the list of account addresses (20-byte EVM addresses) the host + /// is willing to sign for. Empty list means no signing capability. + accounts: func() -> result>, fault>; + + /// Sign an arbitrary message with personal_sign semantics (prepends the + /// "\x19Ethereum Signed Message:\n" prefix). Returns a 65-byte signature. + sign: func(account: list, message: list) -> result, fault>; + + /// Sign EIP-712 typed data. `typed-data` is a JSON-encoded EIP-712 payload. + /// Returns a 65-byte signature. + sign-typed-data: func(account: list, typed-data: string) -> result, fault>; +} diff --git a/videre/wit/deps/nexum-host/local-store.wit b/videre/wit/deps/nexum-host/local-store.wit new file mode 100644 index 00000000..8521b37a --- /dev/null +++ b/videre/wit/deps/nexum-host/local-store.wit @@ -0,0 +1,29 @@ +package nexum:host@0.1.0; + +interface local-store { + use types.{fault}; + + /// Get a value by key. Returns none if the key does not exist. + get: func(key: string) -> result>, fault>; + + /// Set a key-value pair. Overwrites any existing value. + /// The host may enforce a size quota; if exceeded, returns err. + set: func(key: string, value: list) -> result<_, fault>; + + /// Delete a key. No-op if the key does not exist. + delete: func(key: string) -> result<_, fault>; + + /// List all keys matching a prefix. Empty prefix returns all keys. + list-keys: func(prefix: string) -> result, fault>; + + /// Whether the key exists, without transferring the value. + contains: func(key: string) -> result; + + /// Value byte length, none if the key is absent, without + /// transferring the value. On some backends this may be a scan. + len: func(key: string) -> result, fault>; + + /// Number of keys matching a prefix, without materialising the key + /// list. On some backends this may be a scan. + count: func(prefix: string) -> result; +} diff --git a/videre/wit/deps/nexum-host/logging.wit b/videre/wit/deps/nexum-host/logging.wit new file mode 100644 index 00000000..8cd98140 --- /dev/null +++ b/videre/wit/deps/nexum-host/logging.wit @@ -0,0 +1,15 @@ +package nexum:host@0.1.0; + +interface logging { + enum level { + trace, + debug, + info, + warn, + error, + } + + /// Emit a structured log message. + /// The host decides how to handle it (stdout, file, discard). + log: func(level: level, message: string); +} diff --git a/videre/wit/deps/nexum-host/messaging.wit b/videre/wit/deps/nexum-host/messaging.wit new file mode 100644 index 00000000..30777ca7 --- /dev/null +++ b/videre/wit/deps/nexum-host/messaging.wit @@ -0,0 +1,19 @@ +package nexum:host@0.1.0; + +interface messaging { + use types.{fault, message}; + + /// Publish a message to a content topic. + /// + /// Content topics follow the format: //// + /// e.g. "/nexum/1/twap-updates/proto" + publish: func(content-topic: string, payload: list) -> result<_, fault>; + + /// Query historical messages from the Waku store protocol. + query: func( + content-topic: string, + start-time: option, + end-time: option, + limit: option, + ) -> result, fault>; +} diff --git a/videre/wit/deps/nexum-host/query-module.wit b/videre/wit/deps/nexum-host/query-module.wit new file mode 100644 index 00000000..ffb29b87 --- /dev/null +++ b/videre/wit/deps/nexum-host/query-module.wit @@ -0,0 +1,25 @@ +package nexum:host@0.1.0; + +/// Query module — synchronous, side-effect-free evaluation. +/// +/// EXPERIMENTAL (0.2): the shape of this world is provisional and may +/// change in a future minor release without a major bump. Hosts and SDKs +/// should expect breakage here until the world is stabilised. +/// +/// A query module exposes a single pure `evaluate` entry point. It is given +/// read-only access to the local store (for cached/derived state) and to +/// logging; everything else (chain access, network, messaging, signing) is +/// deliberately excluded so the host can run queries inside a tight +/// deterministic sandbox. +world query-module { + use types.{config, fault}; + + import local-store; + import logging; + + export init: func(config: config) -> result<_, fault>; + + /// Evaluate the query. `input` and the returned bytes are opaque to the + /// host; the module and its caller agree on the encoding. + export evaluate: func(input: list) -> result, fault>; +} diff --git a/videre/wit/deps/nexum-host/remote-store.wit b/videre/wit/deps/nexum-host/remote-store.wit new file mode 100644 index 00000000..39b36bae --- /dev/null +++ b/videre/wit/deps/nexum-host/remote-store.wit @@ -0,0 +1,33 @@ +package nexum:host@0.1.0; + +interface remote-store { + use types.{fault}; + + /// Upload raw data to the decentralised store. + /// Returns the 32-byte content reference (Swarm address). + upload: func(data: list) -> result, fault>; + + /// Download raw data by 32-byte content reference. + download: func(reference: list) -> result, fault>; + + /// Read the latest value from a mutable feed. + /// + /// Feeds are mutable pointers: (owner, topic) -> latest chunk. + /// `owner`: 20-byte Ethereum address of the feed owner. + /// `topic`: 32-byte topic hash. + read-feed: func( + owner: list, + topic: list, + ) -> result>, fault>; + + /// Update a mutable feed with new data. + /// + /// The host signs the feed update with its configured identity. + /// `topic`: 32-byte topic hash. + /// `data`: the payload to publish. + /// Returns the 32-byte reference of the new chunk. + write-feed: func( + topic: list, + data: list, + ) -> result, fault>; +} diff --git a/videre/wit/deps/nexum-host/types.wit b/videre/wit/deps/nexum-host/types.wit new file mode 100644 index 00000000..8e834263 --- /dev/null +++ b/videre/wit/deps/nexum-host/types.wit @@ -0,0 +1,106 @@ +package nexum:host@0.1.0; + +/// Common types shared across all runtime interfaces. +/// +/// All `u64` timestamps in this package are milliseconds since the Unix +/// epoch, UTC. +interface types { + type chain-id = u64; + + record block { + chain-id: chain-id, + number: u64, + hash: list, + timestamp: u64, + } + + /// One decoded log, mirroring the RPC `eth_getLogs` shape field for + /// field so the guest can rebuild the native alloy log losslessly. + /// Fixed-width byte fields are carried raw: `address` is 20 bytes, + /// each topic and hash is 32. The block-scoped fields are absent on a + /// pending log (mined logs carry them all). + record chain-log { + address: list, + topics: list>, + data: list, + block-hash: option>, + block-number: option, + block-timestamp: option, + transaction-hash: option>, + transaction-index: option, + log-index: option, + removed: bool, + } + + /// A batch of logs delivered from one subscription. The alloy log type + /// carries no chain id, so it sits here once: every log in a delivery + /// shares the chain of the subscription that produced it. + record chain-logs { + chain-id: chain-id, + logs: list, + } + + /// A message delivered over the messaging interface. Defined here (rather + /// than only in `messaging.wit`) so the `event` variant can reference it + /// without a cross-interface use clause. + record message { + content-topic: string, + payload: list, + timestamp: u64, + /// Optional sender identity (protocol-dependent). + sender: option>, + } + + /// Fired by the host on a configured cadence. `fired-at` is the host's + /// wall-clock time (ms since Unix epoch, UTC) at which the tick was + /// generated. + record tick { + fired-at: u64, + } + + /// A host-observed status transition for a previously submitted + /// intent. Transport-blind: the subscriber sees only where the + /// intent is in its life, never how the host learnt it. + record intent-status-update { + /// Venue id the receipt was issued by. + venue: string, + /// The venue-scoped intent identifier, opaque to the host. + receipt: list, + /// Opaque versioned status body: a leading u8 version tag, + /// then that version's borsh payload (v1: lifecycle status, + /// optional settlement proof, optional failure reason). An + /// unknown tag is a decode error and the body is never empty. + /// The host never inspects it. + status: list, + } + + variant event { + block(block), + chain-logs(chain-logs), + tick(tick), + message(message), + intent-status(intent-status-update), + } + + /// Opaque config from module.toml [config] section. + type config = list>; + + /// 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), + unavailable(string), + denied(string), + rate-limited(rate-limit), + timeout, + invalid-input(string), + internal(string), + } + + /// Backoff guidance for a `fault.rate-limited`. `retry-after-ms` is + /// the host's suggested wait before retrying, when known. + record rate-limit { + retry-after-ms: option, + } +} diff --git a/videre/wit/nexum-host b/videre/wit/nexum-host deleted file mode 120000 index 9b9eefce..00000000 --- a/videre/wit/nexum-host +++ /dev/null @@ -1 +0,0 @@ -../../nexum/wit/nexum-host \ No newline at end of file