From 80b1ad02214f9028029322d952d817f55024f1e2 Mon Sep 17 00:00:00 2001 From: mfw78 Date: Sat, 18 Jul 2026 00:39:52 +0000 Subject: [PATCH] docs: rewrite the platform docs as the shipped-venue source of truth Doc 05 documents both personas as shipped: the videre-sdk surface, the venue and keeper macros, the typed client, and an author-a-venue walkthrough on the echo pair, with the CoW crates as the production instance. Doc 08 names venue adapters as the domain-extension mechanism, replaces the Layer-3 world-include pattern with the videre:venue contract and the registry route, marks shepherd:cow as the retired legacy read path (cow-events stays as the event-ABI package of record), and squares the WIT layout, server table, and SDK layering with the tree. One inbound sdk.md anchor follows the doc 05 heading. --- docs/05-sdk-design.md | 543 +++++++++++++++++++---------- docs/08-platform-generalisation.md | 141 ++++---- docs/sdk.md | 85 ++--- 3 files changed, 461 insertions(+), 308 deletions(-) diff --git a/docs/05-sdk-design.md b/docs/05-sdk-design.md index eff5eed4..085bbf97 100755 --- a/docs/05-sdk-design.md +++ b/docs/05-sdk-design.md @@ -1,107 +1,101 @@ -# SDK Design: The Two-Persona SDK Plan - -This document describes the guest-side SDK crates and the plan that -shapes them: a **module-author persona** and a **venue-adapter -persona**, each with its own crate pair and attribute macro. The -module-author persona is shipped and is what this document mostly -describes; the venue-adapter persona is design intent tracked by a -separate epic and is called out explicitly as such wherever it -appears below. - -For the architectural decision behind the host-trait seam that the -module-author persona builds on, see [ADR-0009](adr/0009-host-trait-surface.md). -For the rustdoc-level API reference (the source of truth once you are -writing module code), see [`sdk.md`](sdk.md) and the rustdoc under -`crates/nexum-sdk/`, `crates/shepherd-sdk/`, and -`crates/nexum-module-macros/`. +# SDK Design: The Two-Persona SDK -## The two personas +This document describes the guest-side SDK crates. There are two +personas, and both are shipped: the **module author**, served by +`nexum-sdk`, and the **venue persona**, served by `videre-sdk` - which +covers both sides of a venue: the adapter author who speaks one venue's +protocol, and the keeper author who drives venues through the typed +client. + +For the architectural decision behind the host-trait seam both personas +build on, see [ADR-0009](adr/0009-host-trait-surface.md). For the +rustdoc-level API reference, see [`sdk.md`](sdk.md) and the rustdoc +under `crates/nexum-sdk/` and `crates/videre-sdk/`. -The runtime has two kinds of guest authors, and they need different -things from the SDK: +## The two personas 1. **Module author.** Writes an automation module against - `nexum:host/event-module` (or the CoW-extended `shepherd:cow/shepherd` - world): react to blocks, chain logs, ticks, or messages; read and - write local state; submit orders. This persona is served today by - `nexum-sdk` (+ the `#[nexum::module]` macro, spelled - `#[nexum_sdk::module]` in code) and, for CoW-specific modules, - `shepherd-sdk` on top. - -2. **Venue adapter author.** Writes an adapter that exposes a trading - venue (CoW Protocol, a DEX, a lending market, ...) to modules - through a common intent surface, so a module author does not need - to know the venue's wire format. This persona is planned but not - yet shipped: the crate (`videre-sdk`), the per-venue crates - (e.g. a `cow-venue` crate carrying CoW's intent-body codec), the - `#[nexum::venue]` macro, and the `videre-test` conformance kit - are all tracked by the SDK-surfaces epic and have no code in the - tree yet. See [Venue-adapter persona (planned)](#venue-adapter-persona-planned) - below for the shape of the plan. - -Each persona has its own proc-macro crate (`nexum-module-macros` for -modules, `videre-macros` for venue adapters) and both share the + `nexum:host/event-module`: react to blocks, chain logs, ticks, or + messages; read and write local state. Served by `nexum-sdk` plus the + `#[nexum_sdk::module]` attribute macro (from `nexum-module-macros`). + +2. **Venue author.** Writes the component that exposes a trading venue + (CoW Protocol, a DEX, a lending market, ...) to modules through the + `videre:venue` intent surface, so a module author never sees the + venue's wire format. Served by `videre-sdk`: the `VenueAdapter` + trait under `#[videre_sdk::venue]`, the `IntentBody` derive, and the + `videre-test` conformance kit. + +A keeper - a module that drives venues - sits between the two: it is a +module by world, but it authors with `#[videre_sdk::keeper]` and calls +venues through the typed `VenueClient`. The domain itself (CoW, a DEX) +lives in the venue adapter, never in the host: see +[doc 08](08-platform-generalisation.md#layer-3-domain-extensions-venue-adapters). + +Each persona has its own proc-macro crate (`nexum-module-macros`, +`videre-macros`), reached through the SDK re-exports. Both share the same host-trait philosophy: guest code is written against small Rust traits that mirror the WIT interfaces one-for-one, so strategy logic can be unit-tested against an in-memory mock without a `wasm32-wasip2` toolchain or a running wasmtime instance. -## Module-author persona (shipped): `nexum-sdk` + `shepherd-sdk` - -### Crate structure +## Crate layout ``` -nexum-sdk/ -├── Cargo.toml +nexum-sdk/ # universal module SDK (host-neutral, domain-free) └── src/ - ├── lib.rs # crate docs, `pub use nexum_module_macros::module` - ├── prelude.rs # alloy primitive re-exports (Address, B256, Bytes, U256, keccak256) + ├── lib.rs # crate docs, `pub use nexum_module_macros::module` + ├── prelude.rs # alloy primitive re-exports (Address, B256, Bytes, U256, keccak256) ├── host.rs # ChainHost / LocalStoreHost / LoggingHost + supertrait Host; Fault, ChainError, RpcError - ├── wit_bindgen_macro.rs # bind_host_via_wit_bindgen! - generates WitBindgenHost + converters - ├── keeper.rs # WatchSet, Gates, Journal, ConditionalSource, Retrier - ├── chain/ # eth_call_params, parse_eth_call_result, chainlink AggregatorV3 reader + ├── wit_bindgen_macro.rs # bind_host_via_wit_bindgen! - generates WitBindgenHost + converters + ├── keeper.rs # WatchSet, Gates, Journal, ConditionalSource, Retrier + ├── chain/ # eth_call_params, parse_eth_call_result, chainlink AggregatorV3 reader ├── events.rs # native alloy Log assembly from the wire ChainLog record ├── config.rs # (key, value) config-table lookups, decimal scaling ├── address.rs # EVM address parsing with typed errors ├── http.rs # Fetch trait seam, WasiFetch, FetchError (wasi:http) - ├── tracing.rs # guest tracing facade + panic hook over a LogSink seam - └── proptests.rs # cfg(test) property tests (not part of the public surface) + └── tracing.rs # guest tracing facade + panic hook over a LogSink seam -nexum-module-macros/ -├── Cargo.toml # proc-macro = true -└── src/ - └── lib.rs # #[module] attribute macro +nexum-module-macros/ # #[module] attribute macro (proc-macro) -shepherd-sdk/ -├── Cargo.toml +videre-sdk/ # venue SDK: both venue sides └── src/ - ├── lib.rs # crate docs; no re-export of nexum-sdk - ├── prelude.rs # cowprotocol order/signing/orderbook re-exports - ├── wit_bindgen_macro.rs # bind_cow_host_via_wit_bindgen! - layers CowApiHost onto WitBindgenHost - ├── cow/ # CowApiHost trait, gpv2_to_order_data, Verdict, LegacyRevertAdapter, - │ # RetryAction classifiers, run() (poll -> gate/journal/submit) - └── proptests.rs # cfg(test) property tests (not part of the public surface) + ├── lib.rs # crate docs, macro re-exports (venue, keeper, IntentBody) + ├── adapter.rs # VenueAdapter trait (init + the five intent functions) + ├── body.rs # IntentBody trait + BodyError (versioned borsh codec) + ├── client.rs # Venue, VenueId, VenueClient, VenueTransport, HostVenues + ├── keeper.rs # Keeper::sweep - the generic sweep assembler; Sweep, SweepReport + ├── transport.rs # HostChain, HostMessaging, http, BoundedFetch + ├── faults.rs # VenueFault + conversions across wire fault / SDK fault / VenueError + ├── rt.rs # completes async keeper handlers on the sync guest boundary + └── bindings.rs # the shared import-only bindgen the macros remap onto + +videre-macros/ # #[venue], #[keeper], derive(IntentBody) (proc-macro) + +videre-test/ # venue conformance kit +└── src/ # CodecVectors, HeaderGoldens, MockTransport, reference venue + +cow-venue/ # the CoW venue, as feature slices +composable-cow/ # ComposableCoW keeper machinery (body, poll seam, sweep) ``` `nexum-sdk` is host-neutral and domain-free: any module targeting the runtime pulls helpers and canonical primitive types from it regardless -of which world it exports. `shepherd-sdk` depends on `nexum-sdk` and -layers the CoW Protocol domain on top; modules that touch the -orderbook import both crates directly (nothing is re-exported between -them). `shepherd-sdk` has not been retired - the clean break described -in the SDK epic (folding its CoW surface into a future `cow-venue` -crate) is deferred to a follow-on train, sequenced after the -venue-adapter persona lands. +of which world it exports. `videre-sdk` layers the venue platform's +guest surface on top. Domain crates (`cow-venue`, `composable-cow`) +depend on the SDKs; nothing is re-exported between layers. Companion mock crates: `nexum-sdk-test` (in-memory `MockHost` over -`ChainHost` / `LocalStoreHost` / `LoggingHost`) and `shepherd-sdk-test` -(composes those mocks with `MockCowApi`). See -[Testing](#testing-nexum-sdk-test-and-shepherd-sdk-test) below. +`ChainHost` / `LocalStoreHost` / `LoggingHost`) for modules and +keepers, and `videre-test` for adapters. See +[Testing](#testing-nexum-sdk-test-and-videre-test) below. + +## Module-author persona: `nexum-sdk` ### The host-trait seam -Neither crate calls `wit_bindgen`-generated functions directly. -Instead `nexum-sdk::host` exposes small traits that mirror the WIT +`nexum-sdk` never calls `wit_bindgen`-generated functions directly. +Instead `nexum_sdk::host` exposes small traits that mirror the WIT interfaces: ```rust @@ -121,39 +115,33 @@ pub trait Host: ChainHost + LocalStoreHost + LoggingHost {} impl Host for T {} ``` -`shepherd-sdk` adds a fourth trait, `CowApiHost` (`submit_order`, -`cow_api_request`), and -its own supertrait `CowHost: Host + CowApiHost`. Strategy code takes -`&impl Host` (or a narrower `` bound -when it only needs part of the surface) so tests inject -`nexum_sdk_test::MockHost` while the compiled module injects the -wit-bindgen-backed adapter. See [ADR-0009](adr/0009-host-trait-surface.md) -for the full rationale (four traits over one fat trait, the -`strategy.rs` / `lib.rs` split, and the world-neutral `HostError` -predecessor that per-interface typed errors later replaced - see -[ADR-0011](adr/0011-per-interface-typed-errors.md)). +Strategy code takes `&impl Host` (or a narrower +`` bound when it only needs part of the +surface) so tests inject `nexum_sdk_test::MockHost` while the compiled +module injects the wit-bindgen-backed adapter. See +[ADR-0009](adr/0009-host-trait-surface.md) for the full rationale and +[ADR-0011](adr/0011-per-interface-typed-errors.md) for the typed error +model. ### The wit-bindgen adapter: `bind_host_via_wit_bindgen!` -Every module still keeps its own `wit_bindgen::generate!` call (the -macro emits types into the calling crate; re-exporting wit-bindgen -output from a library crate would duplicate symbols and break the +Every module keeps its own `wit_bindgen::generate!` call (the macro +emits types into the calling crate; re-exporting wit-bindgen output +from a library crate would duplicate symbols and break the component-export contract). What the SDK removes is the ~80 lines of -mechanical glue that used to sit next to it: the `nexum_sdk::bind_host_via_wit_bindgen!()` -declarative macro emits a `WitBindgenHost` struct, the `ChainHost` / -`LocalStoreHost` / `LoggingHost` impls over the generated import +mechanical glue that used to sit next to it: the +`nexum_sdk::bind_host_via_wit_bindgen!()` declarative macro emits a +`WitBindgenHost` struct, the trait impls over the generated import shims, the `Fault` / `ChainError` converters in both directions, a -`Level` <-> wit-bindgen `logging::Level` converter, a -`From for nexum_sdk::events::Log` impl, and an -`install_tracing()` helper that routes `tracing::info!(...)` through -the bound host logging call. The adapter is capability-selected: the -zero-argument form emits the full set for blanket-world modules, and -the `caps: [chain, logging]` form (what `#[nexum_sdk::module]` -generates from the manifest) emits only the pieces whose imports the -module's world carries. `shepherd-sdk::bind_cow_host_via_wit_bindgen!` -layers the `CowApiHost` impl on top of the same `WitBindgenHost` type. - -### The `#[nexum::module]` macro +`Level` converter, a `From for nexum_sdk::events::Log` impl, +and an `install_tracing()` helper that routes `tracing::info!(...)` +through the bound host logging call. The adapter is +capability-selected: the zero-argument form emits the full set for +blanket-world modules, and the `caps: [chain, logging]` form (what +`#[nexum_sdk::module]` generates from the manifest) emits only the +pieces whose imports the module's world carries. + +### The `#[nexum_sdk::module]` macro `nexum-module-macros` ships one attribute macro, re-exported as `nexum_sdk::module`. Apply it to an inherent `impl` block whose @@ -190,37 +178,130 @@ impl HttpProbe { } ``` -Two things worth being precise about, since they differ from earlier -drafts of this plan: - -- **One macro, not two.** There is no separate `#[shepherd::module]`. - The macro reads the crate's `module.toml` and generates against a - per-module world whose imports are exactly the - `[capabilities].required`/`optional` declarations (a chain + - local-store module simply has no `cow-api` or `identity` bindings to - call). This retires the import-elision dependency ADR-0009 flagged - for macro-built modules: their imports equal their declarations by - construction, and the runtime's capability check is a backstop - rather than a consumer of toolchain dead-import elision. Declaring - `cow-api` (or `pool`) pulls that import into the world, and the - module layers its own domain adapter (a `CowApiHost` impl over the - generated shims) on top of the emitted core one. +Two things worth being precise about: + +- **The world is derived from the manifest.** The macro generates + against a per-module world whose imports are exactly the + `[capabilities].required`/`optional` declarations: a chain + + local-store module simply has no `identity` bindings to call. Its + imports equal its declarations by construction, and the runtime's + capability check is a backstop rather than a consumer of toolchain + dead-import elision. - **Handlers are synchronous.** `init` and the named handlers are - plain `fn`, called directly with no `block_on` wrapper. There is no - `async fn` handler support and no injected `&RootProvider` - modules + plain `fn`, called directly with no `block_on` wrapper. Modules call `host.request(chain_id, method, params_json)` (or the `chain::eth_call_params` / `parse_eth_call_result` helpers) directly against `ChainHost`, per [doc 07](07-rpc-namespace-design.md). + (Keeper handlers are the exception: see below.) + +The `Guest`/`export!` shape the macro emits follows the `strategy.rs` +(pure logic, tested against `&impl Host`) / `lib.rs` (handlers plus +the macro attribute) split from ADR-0009. The keeper primitives in +`nexum_sdk::keeper` - `WatchSet`, `Gates`, `Journal`, +`ConditionalSource`, `Retrier` - give conditional-commitment modules +a shared set of `LocalStoreHost` conventions instead of hand-rolled +key schemes; `videre_sdk::keeper` assembles them into the generic +sweep. + +## Venue persona: `videre-sdk` + +### The venue contract + +An adapter targets the `videre:venue/venue-adapter` world: it exports +the `adapter` interface (`body-versions`, `derive-header`, `quote`, +`submit`, `status`, `cancel`) plus `init`, and imports scoped +transport only - `chain`, `messaging`, and allowlisted `wasi:http`. +No local-store, remote-store, identity, or logging import: an adapter +structurally cannot touch host key material or persistent state. +Keepers reach venues through the host-implemented +`videre:venue/client` interface, which mirrors `adapter` with a venue +selector per call. The types (`intent-header`, `quotation`, +`submit-outcome`, `receipt`, `intent-status`, `venue-error`) live in +`videre:types`, over the `videre:value-flow` asset vocabulary. + +### Bodies: the `IntentBody` derive + +A venue's intent body is opaque on the wire; typing is a guest-side +agreement between keeper and adapter, spelled as an outer per-venue +version enum under `#[derive(videre_sdk::IntentBody)]`: -The `Guest`/`export!` shape the macro emits still follows the -`strategy.rs` (pure logic, tested against `&impl Host`) / `lib.rs` -(handlers plus the macro attribute) split from ADR-0009. The keeper -helpers in `nexum_sdk::keeper` - `WatchSet`, `Gates`, `Journal`, -`ConditionalSource`, `Retrier` - give conditional-commitment -modules (watchers that poll a set of pending commitments) a shared set -of `LocalStoreHost` conventions instead of hand-rolled key schemes. +```rust +#[derive(videre_sdk::IntentBody)] +enum EchoBody { + V1(u64), +} +``` + +The wire form is the borsh enum layout: a one-byte version tag (the +variant's declaration index) then the borsh payload - so the tag order +is the schema: append new versions, never reorder. Decoding an unknown +tag fails typedly as `BodyError::UnknownVersion` rather than as a +stringly decode error. The versions an adapter decodes are declared in +its manifest `[venue] body_versions` and asserted at install against +its `body-versions` export; a keeper declares the single +`[venue] body_version` it encodes and install refuses it unless every +installed adapter decodes that version. + +### `#[videre_sdk::venue]` + +The single blessed venue authoring path. Apply it to the adapter's +`impl VenueAdapter for MyVenue` block: the macro reads the crate's +`module.toml`, asserts its `[module] kind` is `venue-adapter`, +synthesizes a per-component world exporting the `videre:venue/adapter` +face and importing exactly the manifest's declared scoped transport, +then emits the `wit_bindgen::generate!` call, the untouched trait +impl, and the export glue. An undeclared capability's bindings do not +exist (using one is a compile error), and a capability outside the +venue-permitted set (`chain`, `messaging`, `http`) is rejected at +expansion. The generated world remaps the shared interfaces onto +`videre_sdk::bindings`, so the impl speaks `videre_sdk` types directly +and shares type identity with the conformance kit and the client core. + +### The typed client and `#[videre_sdk::keeper]` + +The wire carries opaque bodies and a stringly venue selector; typing +is recovered in `videre_sdk::client`. A keeper names a venue once, as +a `Venue` marker carrying its `VenueId` and body schema: + +```rust +struct CowVenue; +impl Venue for CowVenue { + const ID: VenueId = VenueId::from_static("cow"); + type Body = CowIntentBody; +} +``` -### Testing: `nexum-sdk-test` and `shepherd-sdk-test` +`VenueClient` then drives the venue with typed bodies - `quote` +returns a `Quoted` typestate whose `submit` sends exactly the priced +bytes - encoding through `IntentBody` before the byte-level, +native-AFIT `VenueTransport` seam. `HostVenues` binds the seam to the +module's own `videre:venue/client` import; tests implement +`VenueTransport` in memory. + +`#[videre_sdk::keeper]` is the keeper mirror of `#[module]`: apply it +to an `impl` block whose associated functions are the event handlers +(`init`, `on_block`, `on_chain_logs`, `on_tick`, `on_message`, +`on_intent_status`). It requires the `client` capability (the +`videre:venue/client` import is what makes a keeper a keeper), wires +that import onto the SDK's shared shims, and lets handlers be `async` +so they can await the typed client directly; `videre_sdk::rt` +completes the futures on the synchronous guest boundary. A +`From` impl onto the wire fault is emitted, so `?` +applies to client calls inside handlers. + +`videre_sdk::keeper::Keeper::sweep` assembles the world-neutral +`nexum_sdk::keeper` stores - `WatchSet` to `Gates` to +`ConditionalSource::poll` to `Retrier` to `Journal` - over the +`VenueTransport` seam, so a conditional-commitment keeper writes one +`poll` producing the shared `Sweep` outcome and inherits the whole +gate/journal/retry pass. + +### Testing: `nexum-sdk-test` and `videre-test` + +Keeper strategy logic tests exactly as module logic does: against the +host traits with `nexum_sdk_test::MockHost`, plus an in-memory +`VenueTransport` for the client seam. Tests run as plain native Rust - +no `wasm32-wasip2` target, no wasmtime instance, no network. ```rust use nexum_sdk::host::*; @@ -233,79 +314,159 @@ assert_eq!(host.request(1, "eth_blockNumber", "[]").unwrap(), "\"0x1\""); assert_eq!(host.chain.calls().len(), 1); ``` -`MockHost` composes one mock per trait (`chain`, `store`, `logging` -in `nexum-sdk-test`; `shepherd-sdk-test` adds a `cow_api` field on the -`shepherd:cow/cow-api` seam, backed by `MockCowApi` by default or the -per-call-scriptable `MockVenue` via `MockHost::with_venue()`), each -recording calls and letting tests program responses. Tests run as -plain native Rust against the traits - no `wasm32-wasip2` target, no -wasmtime instance, no network round-trip. This is the whole SDK-side -testing story today. (The runtime crate separately ships a -feature-gated component-level harness - `nexum-runtime`'s -`test_utils::TestRuntime`, behind the `test-utils` feature - that -loads a compiled `.wasm` plus manifest under real wasmtime and -dispatches events to it; that is runtime-internal tooling, not part -of the module-author SDK contract.) - -## Venue-adapter persona (planned) - -The venue-adapter persona is the other half of the two-persona plan -and is **not shipped**. It is tracked by a set of open issues under -the SDK-surfaces epic and depends on a venue-adapter WIT world that -does not exist yet either. Nothing below this heading describes code -in this repository; it is recorded here so the module-author persona -above is read in the context of where the SDK is going, not as a -competing vision. - -The planned shape: - -- **`videre-sdk`** - a new crate carrying the guest-side - `VenueAdapter` trait over the (also planned) adapter-world bindgen, - a `borsh`-backed `IntentBody` derive that enforces a per-venue - version enum (an adapter rejects an intent body tagged with an - unknown version rather than misinterpreting it), and typed wrappers - over the scoped transport imports (`http`, `messaging`, `chain`) an - adapter is granted. -- **Per-venue crates** - e.g. a `cow-venue` crate that would carry - CoW Protocol's intent-body codec and become the eventual home for - the CoW helpers `shepherd-sdk::cow` carries today, once the clean - break happens. -- **`#[nexum::venue]`** - a second attribute macro, in `videre-macros`, - parallel to `#[nexum::module]`: it would emit the per-cdylib export - glue for an adapter and a per-component world matching the - manifest's declared capabilities (retiring the import-elision - dependency for the venue side from day one, rather than as - follow-on work). -- **`videre-test`** - a conformance kit: published codec - round-trip vectors (so a non-Rust adapter author can prove - byte-exact `IntentBody` encoding without linking Rust), header- - derivation golden fixtures, and a `MockTransport` for adapter unit - tests. - -Once this lands, `shepherd-sdk`'s CoW surface is expected to move into -the `cow-venue` crate as a single clean-break migration - the same -no-deprecation-window reasoning [ADR-0011](adr/0011-per-interface-typed-errors.md) -gives for pre-1.0 wire breaks applies here - and this document should -be revisited to describe the venue-adapter persona as shipped rather -than planned. +Adapters are held to the `videre-test` conformance kit instead: + +- **`CodecVectors`** - the venue's `IntentBody` wire bytes as a JSON + file (bytes as lowercase hex). A Rust adapter checks its derived + enum with `assert_conforms`; a non-Rust author reads the same file + and proves byte-exactness without linking Rust. +- **`HeaderGoldens`** - published bodies paired with the header a + conforming `derive-header` projects from them. +- **`MockTransport`** - the three transports an adapter is granted + (chain, messaging, outbound HTTP) as programmable in-memory mocks + behind the SDK's own seams. + +(The runtime crate separately ships a feature-gated component-level +harness - `nexum-runtime`'s `test_utils::TestRuntime` - that loads a +compiled `.wasm` plus manifest under real wasmtime; that is +runtime-internal tooling, not part of either SDK contract.) + +## Walkthrough: authoring a venue on videre + +The shipped reference pair is `modules/examples/echo-venue` (adapter) +and `modules/examples/echo-keeper` (driver); the production instance +of the same shape is `crates/cow-venue` driven by `modules/twap-monitor`. + +1. **Declare the manifest.** A venue adapter is a component with a + `module.toml` whose kind names it: + + ```toml + [module] + name = "echo-venue" # the venue id the registry installs it under + version = "0.1.0" + kind = "venue-adapter" + component = "sha256:..." + + [capabilities] + required = ["chain"] # scoped transport only: chain, messaging, http + + [capabilities.http] + allow = [] + + [venue] + body_versions = [1] # must equal the body-versions export; install asserts it + ``` + +2. **Implement `VenueAdapter`.** One impl block under the macro; the + five intent functions plus `init` and `body_versions`: + + ```rust + use videre_sdk::{IntentHeader, IntentStatus, Quotation, SubmitOutcome, VenueAdapter, VenueError}; + + struct EchoVenue; + + #[videre_sdk::venue] + impl VenueAdapter for EchoVenue { + fn init(_config: Config) -> Result<(), Fault> { Ok(()) } + fn body_versions() -> Vec { vec![1] } + fn derive_header(body: Vec) -> Result { /* pure, no I/O */ } + fn quote(body: Vec) -> Result { /* ... */ } + fn submit(body: Vec) -> Result { /* ... */ } + fn status(receipt: Vec) -> Result { /* ... */ } + fn cancel(receipt: Vec) -> Result<(), VenueError> { /* ... */ } + } + ``` + + `derive_header` is the policy seam: it projects the guard-facing + `IntentHeader` (`gives`, `wants`, `settlement`, `authorisation`) + from a body, pure and I/O-free, and the host's egress guard runs on + it before every submit. + +3. **Publish the fixtures.** Ship the venue's codec vectors and header + goldens, and hold the adapter to them with `videre-test` in the + crate's tests. Non-Rust keeper authors read the same files. + +4. **Build and install.** Build the cdylib for `wasm32-wasip2` + (`cargo build --target wasm32-wasip2 --release -p echo-venue`) and + install it via the engine config: + + ```toml + [[adapters]] + path = "target/wasm32-wasip2/release/echo_venue.wasm" + manifest = "modules/examples/echo-venue/module.toml" + http_allow = [] # the operator's outbound-HTTP grant + ``` + + Install boots the component, checks the `body-versions` handshake + against the manifest, and registers the venue under its manifest + name in the `VenueRegistry` ([doc 08](08-platform-generalisation.md)). + +5. **Drive it from a keeper.** A keeper declares the `client` + capability plus its `[venue] body_version`, names the venue as a + `Venue` marker, and speaks types end to end: + + ```rust + #[videre_sdk::keeper] + impl EchoKeeper { + async fn on_block(block: types::Block) -> Result<(), Fault> { + let venue = VenueClient::::new(); + let quoted = venue.quote(&EchoBody::V1(block.number)).await?; + if let SubmitOutcome::Accepted(receipt) = quoted.submit().await? { + let _status = venue.status(&receipt).await?; + } + Ok(()) + } + } + ``` + + An accepted submit is watched implicitly: the registry polls the + adapter's `status` and fans transitions back as `intent-status` + events, which the keeper subscribes to in its manifest and handles + in `on_intent_status`. + +## The CoW venue + +CoW ships as the production instance of the persona, in two crates so +the venue stays orderbook-only: + +- **`cow-venue`** - feature slices. `body` (default, `no_std`): the + order intent body types and codec, light enough for any keeper or + adapter to carry. `client`: the typed `CowClient` bound to the CoW + venue, the deterministic `intent_id` journal key, and the + table-driven retry classification generated from the shipped + `data/classification.toml`. `assembly`: the chain-edge order + projections and orderbook submission bodies. `adapter`: the venue + adapter component itself (`CowAdapter` under `#[videre_sdk::venue]`, + manifest at `crates/cow-venue/module.toml`). +- **`composable-cow`** - the ComposableCoW keeper machinery, kept out + of the venue: the conditional-order `ComposableBody`, the structured + poll seam (`Verdict`, with the deployed 1.x reverting wire + quarantined behind `LegacyRevertAdapter`, per + [ADR-0013](adr/0013-composable-cow-structured-poll.md)), and the + `sweep` slice composing the poll loop over the typed `CowClient`. + +The shipped CoW keepers - `modules/twap-monitor`, +`modules/ethflow-watcher`, `modules/examples/stop-loss` - are ordinary +`#[videre_sdk::keeper]` modules on this surface. ## Non-Rust module and adapter authors For **non-Rust** authors (JavaScript, Python, Go, C++), neither SDK is relevant - they generate bindings directly from the WIT package for -their target world with their language's `wit-bindgen`. The WIT is -the universal contract; both Rust SDKs are an ergonomics layer on top -of it, not a requirement. +their target world with their language's `wit-bindgen`, and prove body +conformance against the venue's published `videre-test` fixture files. +The WIT is the universal contract; both Rust SDKs are an ergonomics +layer on top of it, not a requirement. ## Where to go next - [`sdk.md`](sdk.md) - the day-to-day API reference and rustdoc entry - point for module authors. + point. - [ADR-0009](adr/0009-host-trait-surface.md) - the host-trait seam decision this document builds on. - [ADR-0011](adr/0011-per-interface-typed-errors.md) - the typed - error model (`Fault`, `ChainError`, `CowApiError`) the host traits - return. + error model the host traits return. - [doc 07](07-rpc-namespace-design.md) - the `chain` RPC passthrough - design and why module authors call `host.request` directly rather - than through an injected provider. + design and why module authors call `host.request` directly. +- [doc 08](08-platform-generalisation.md) - the layered WIT and why + venue adapters are the domain-extension mechanism. diff --git a/docs/08-platform-generalisation.md b/docs/08-platform-generalisation.md index 110bf3c0..fc9023d9 100755 --- a/docs/08-platform-generalisation.md +++ b/docs/08-platform-generalisation.md @@ -39,22 +39,23 @@ These six primitives are orthogonal: Together they cover the full spectrum: persistent truth (chain), cryptographic agency (identity), local scratch (local-store), shared content (remote-store), real-time coordination (messaging), and diagnostics (logging). -The 0.2 `event-module` world imports all six. (In 0.1 the WIT inadvertently omitted `identity` from the world definition despite the docs claiming six primitives; 0.2 makes the contract match the taxonomy.) One additional **additive** capability - `http` (allowlisted) - is declared via the manifest's `[capabilities]` section but is not part of the six-primitive core; it is serviced by the standard `wasi:http/outgoing-handler` interface rather than a `nexum:host` one. +The 0.2 `event-module` world imports all six. (In 0.1 the WIT inadvertently omitted `identity` from the world definition despite the docs claiming six primitives; 0.2 makes the contract match the taxonomy.) Two additional **additive** capabilities are declared via the manifest's `[capabilities]` section but are not part of the six-primitive core: `http` (allowlisted), serviced by the standard `wasi:http/outgoing-handler` interface rather than a `nexum:host` one, and `client`, the `videre:venue/client` intent surface (see [Layer 3](#layer-3-domain-extensions-venue-adapters)). ## Architectural Principle: Layered WIT Worlds -The current `shepherd` world conflates universal blockchain runtime capabilities with CoW Protocol domain-specific interfaces. To enable reuse across platforms and domains, the WIT is split into layers: +Universal runtime capabilities and domain-specific surfaces live in separate layers: ```mermaid graph TD - subgraph L3["Layer 3: Application-Specific Worlds"] - COW["shepherd:cow - cow + order (CoW Protocol automation)"] - DEFI["myapp:defi - vault + strategy (DeFi yield app)"] - GAME["game:engine - physics + assets (on-chain game)"] + subgraph L3["Layer 3: Domain Venues (adapter components)"] + COW["cow-venue - CoW Protocol orderbook"] + DEX["dex-venue - a DEX (hypothetical)"] + LEND["lend-venue - a lending market (hypothetical)"] end subgraph L2["Layer 2: Capability Extensions (optional, composable)"] - UI["ui - user interface bridge (interactive modules)"] + VC["videre:venue/client - typed intent access to installed venues"] + UI["ui - user interface bridge (planned)"] end subgraph L1["Layer 1: Universal Runtime Interfaces"] @@ -67,11 +68,11 @@ graph TD EXP["Exports: init(config) + on-event(event)"] end - L3 -->|"builds on via WIT include"| L2 - L2 -->|"builds on via WIT include"| L1 + L3 -->|"exports videre:venue/adapter, routed by the host through"| L2 + L2 -->|"adds imports to"| L1 ``` -Each layer builds on the one below via WIT `include`. A module compiled against Layer 1 alone runs on any conforming host. A module compiled against Layer 3 (e.g. `shepherd:cow`) requires a host that implements Layers 1 + the CoW extension. +Layer 1 is the world every module compiles against. A Layer 2 capability adds an import to a module's manifest-derived world: a keeper module declares `client` and gains `videre:venue/client`. Layer 3 is not a world at all: a domain enters the system as a **venue adapter component** installed into the host's venue registry, and modules reach it through the Layer 2 client interface. No module compiles against a domain world - see [Layer 3](#layer-3-domain-extensions-venue-adapters). ## Layer 1: Universal Interfaces @@ -576,54 +577,48 @@ price-dashboard/ The host loads `index.html` into a WebView and injects the bridge JavaScript that connects DOM events to `on-interact` and `ui::render` calls to DOM updates. -## Layer 3: Domain Extensions +## Layer 3: Domain Extensions (Venue Adapters) -Domain-specific interfaces extend the universal layer for particular use cases. The pattern: +A domain (CoW Protocol, a DEX, a lending market) extends the platform as a **venue adapter**: a component authored with `#[videre_sdk::venue]` that exports the `videre:venue/adapter` interface and imports scoped transport only (`chain`, `messaging`, allowlisted `wasi:http`). This is the domain-extension mechanism - the domain's wire protocol, body codec, and error projection live inside the adapter component, and nothing domain-specific enters the host or any module world. ```wit -package shepherd:cow@0.1.0; - -interface cow-api { - use nexum:host/types.{chain-id, fault}; - - record http-failure { status: u16, body: option } - record order-rejection { status: u16, error-type: string, description: string, data: option } - variant cow-api-error { fault(fault), http(http-failure), rejected(order-rejection) } - - request: func( - chain-id: chain-id, - method: string, - path: string, - body: option, - ) -> result; - - submit-order: func(chain-id: chain-id, order-data: list) - -> result; +package videre:venue@0.1.0; + +/// Worker (keeper) face. The host holds the venue registry; the keeper +/// names a venue by string. +interface client { + use videre:types/types.{quotation, receipt, intent-status, submit-outcome, venue-error}; + + quote: func(venue: string, body: list) -> result; + submit: func(venue: string, body: list) -> result; + observe: func(venue: string, receipt: receipt) -> result<_, venue-error>; + status: func(venue: string, receipt: receipt) -> result; + cancel: func(venue: string, receipt: receipt) -> result<_, venue-error>; } -world shepherd { - include nexum:host/event-module; - import cow-api; +/// Provider (venue) face. Mirrors `client` without the venue selector: +/// one installed adapter answers for exactly one venue. +interface adapter { + use videre:types/types.{intent-header, quotation, receipt, intent-status, submit-outcome, venue-error}; + + body-versions: func() -> list; + derive-header: func(body: list) -> result; + quote: func(body: list) -> result; + submit: func(body: list) -> result; + status: func(receipt: receipt) -> result; + cancel: func(receipt: receipt) -> result<_, venue-error>; } ``` -Other domains follow the same pattern: +The two faces meet in the host. The venue platform (`crates/videre-host`, one `nexum-runtime` extension registered at the composition root) holds the `VenueRegistry`, links `videre:venue/client` into keeper worlds, and routes each call: resolve the venue id to its installed adapter, run the advisory egress guard over the adapter's pure `derive-header` projection, then invoke the adapter face. Accepted submits go under a status watch; the platform polls the adapter's `status` and fans transitions back to subscribed modules as `intent-status` events. Intent bodies are opaque on this whole path - typing is a guest-side agreement between keeper and adapter over the venue's published `IntentBody` schema ([doc 05](05-sdk-design.md#bodies-the-intentbody-derive)). -```wit -// Hypothetical DeFi yield module -package defi:yield@0.1.0; +A new domain therefore adds a component and (usually) a body crate, never a WIT package or a host change: write the adapter with `#[videre_sdk::venue]`, publish its codec vectors and header goldens, and install it via the engine's `[[adapters]]` table. The `shepherd` binary is exactly this composition: the core lattice plus the videre platform, with CoW entering only as the bundled `cow-venue` adapter - the engine itself stays venue- and cow-free. -interface vault { /* ... */ } -interface strategy { /* ... */ } +### The legacy read path: `shepherd:cow` -world yield-module { - include nexum:host/event-module; - import vault; - import strategy; -} -``` +The retired predecessor to venue adapters was a Layer-3 *world* extension: the `shepherd:cow/cow-api` interface (orderbook passthrough plus `submit-order`), a `shepherd` world including `event-module` and importing it, and a host-side extension cone implementing the interface over a cached orderbook client. That model put the domain in the module's own world and a backend in every host that ran it; each new domain would have needed its own world, host cone, and SDK glue. -The `include` mechanism ensures that any domain-specific module inherits the full universal interface set. A `shepherd` module can call `chain::request`, `identity::sign`, `local-store::get`, `remote-store::upload`, `messaging::publish`, and `logging::log` - plus the CoW-specific `cow-api::request` and `cow-api::submit-order`. +The path is deleted: the `cow-api` interface, the `shepherd`/`cow-ext` worlds, and the host cone are gone, and orderbook I/O lives in the `cow-venue` adapter behind `videre:venue/client`. The `shepherd:cow` package remains only as `cow-events`, the package of record for the CoW on-chain event ABIs (signatures and topic-0 hashes) that keeper manifests and decoders are parity-tested against. [ADR-0005](adr/0005-cow-api-via-cached-orderbookapi.md) and [ADR-0006](adr/0006-cow-twap-ethflow-host-helpers.md) are superseded accordingly. ## Complete WIT Package Layout @@ -637,23 +632,29 @@ wit/ │ ├── remote-store.wit # remote-store interface (Swarm) │ ├── messaging.wit # messaging interface (Waku) │ ├── logging.wit # logging interface -│ ├── ui.wit # ui interface + host-capabilities (planned hosts only) │ ├── event-module.wit # event-module world (6 imports) -│ ├── query-module.wit # experimental: query-module world (no host impl in 0.2) -│ └── app-module.wit # app-module world (includes ui) - design only +│ └── query-module.wit # experimental: query-module world (no host impl in 0.2) +│ +├── videre-value-flow/ +│ └── types.wit # asset + asset-amount vocabulary +│ +├── videre-types/ +│ └── types.wit # intent-header, quotation, receipt, submit-outcome, intent-status, venue-error +│ +├── videre-venue/ +│ └── venue.wit # client + adapter interfaces, venue-adapter world │ └── shepherd-cow/ - ├── cow-api.wit # merged cow-api interface (request + submit-order) - └── shepherd.wit # shepherd world (includes event-module + cow-api) + └── cow-events.wit # CoW event-ABI package of record (legacy package name) ``` -The `nexum-host` package is domain-agnostic and reusable. The `shepherd-cow` package is the CoW Protocol extension. New domains add new packages without touching the universal layer. +The `nexum-host` package is domain-agnostic and reusable. The `videre` packages are the venue-neutral intent contract. `shepherd-cow` carries only the CoW event ABIs. New domains add adapter components, not packages: the universal and venue layers are closed. (The `ui` interface and `app-module` world are design-only and ship no WIT yet.) ## Platform Targets ### Server Runtime (Reference Implementation - Nexum) -This is the current design (docs 01-07), adapted for the layered WIT. Shepherd is the Nexum distribution with CoW Protocol support. +This is the current design (docs 01-07), adapted for the layered WIT. Shepherd is the Nexum composition root that registers the videre venue platform and bundles the `cow-venue` adapter. | Interface | Implementation | |-----------|---------------| @@ -663,7 +664,7 @@ This is the current design (docs 01-07), adapted for the layered WIT. Shepherd i | `remote-store` | Bee API (`http://localhost:1633`) - operator runs a Bee node | | `messaging` | Waku node (nwaku) via JSON-RPC or REST API | | `logging` | `tracing` crate -> JSON structured logs | -| `cow-api` | reqwest HTTP client -> CoW Protocol API (REST passthrough + typed `submit-order`) | +| `videre:venue/client` | videre-host `VenueRegistry` -> installed venue adapter components (CoW via the bundled `cow-venue` adapter over allowlisted `wasi:http`) | | Event sources | `eth_subscribe` (blocks, logs), cron (Tokio interval), Waku relay (messages) | | WASM engine | wasmtime 45.x (Component Model, fuel, epoch metering) | @@ -867,7 +868,7 @@ The super app adds a capability-grant layer on top of the WIT world. When a modu ✓ remote-store - read/write to Swarm network ✓ messaging - send/receive messages (topics: /nexum/1/twap-*) ✗ ui - (not requested - event-driven module) - ✓ cow-api - interact with CoW Protocol API and submit orders + ✓ client - submit intents to installed venues (cow) [Allow] [Deny] ``` @@ -970,26 +971,15 @@ The content hash is the trust anchor. The transport is interchangeable. ## SDK Layering -The SDK is designed to mirror the WIT layering. **The two-crate split is shipped: `nexum-sdk` carries the universal surface (host-trait seam, bind macro, chain / config / address helpers, `http::fetch`, tracing facade) and `shepherd-sdk` layers the CoW domain on top, with no re-export between them.** The host-trait seam is from [ADR-0009](adr/0009-host-trait-surface.md). The diagram below describes the 0.3+ target for the typed-client layer: +The SDK mirrors the architecture, one crate per layer, with no re-export between them. See [doc 05](05-sdk-design.md) for the full treatment. -```mermaid -graph TD - subgraph ShepherdSDK["shepherd-sdk (Domain-specific: CoW Protocol)"] - COW_ITEMS["Cow client,\n#[shepherd::module] macro\n(imports cow-api)"] - end - - subgraph NexumSDK["nexum-sdk (Universal: any blockchain app)"] - NEXUM_ITEMS["HostTransport, provider(),\nTypedState, RemoteStore,\nMessaging, Signer,\nlogging macros,\nFault / HostFault / ChainError,\n#[nexum::module] macro\n(imports chain + identity\n+ local-store\n+ remote-store + messaging\n+ logging)"] - end - - ShepherdSDK -->|"extends"| NexumSDK -``` +- **`nexum-sdk` (shipped)** - the universal Rust SDK for any module targeting `nexum:host/event-module`. It ships the host-trait seam (`ChainHost`, `LocalStoreHost`, `LoggingHost`, supertrait `Host`), `Fault` / `ChainError`, the `bind_host_via_wit_bindgen!` adapter macro, the `#[nexum_sdk::module]` attribute macro, chain / config / address helpers, the `http` fetch seam over wasi:http, the keeper store primitives, and the guest tracing facade. Would additionally provide `HostTransport` (alloy `Transport` trait over `chain::request` / `chain::request-batch`), `provider(chain_id)`, `TypedState` (serde over `local-store`), `RemoteStore`, `Messaging`, and `Signer` typed wrappers as future direction. Any module author - CoW, DeFi, gaming, whatever - uses this. -- **`nexum-sdk` (shipped)** - the universal Rust SDK for any module targeting `nexum:host/event-module`. It ships the host-trait seam (`ChainHost`, `LocalStoreHost`, `LoggingHost`, supertrait `Host`), `Fault` / `HostFault` / `ChainError`, the `bind_host_via_wit_bindgen!` adapter macro, chain / config / address helpers, the `http::fetch` helper over wasi:http, and the guest tracing facade. Would additionally provide `HostTransport` (alloy `Transport` trait over `chain::request` / `chain::request-batch`), `provider(chain_id)`, `TypedState` (serde over `local-store`), `RemoteStore` (typed wrapper over `remote-store`), `Messaging` (typed wrapper over `messaging`), `Signer` (typed wrapper over `identity`). Any module author - CoW, DeFi, gaming, whatever - uses this. +- **`videre-sdk` (shipped)** - the venue layer, serving both venue sides: the `VenueAdapter` trait under `#[videre_sdk::venue]` for adapter authors, and the `IntentBody` codec, typed `VenueClient` and `#[videre_sdk::keeper]` for keeper authors, plus the generic sweep assembler and the `videre-test` conformance kit alongside. -- **`shepherd-sdk` (shipped)** - the CoW-domain layer: the `CowApiHost` trait and `CowHost` bound, CoW helpers (`Verdict`, `RetryAction`, `gpv2_to_order_data`, `LegacyRevertAdapter`, …), and the `bind_cow_host_via_wit_bindgen!` macro layering the generic adapter. In the 0.3+ target, it would extend `nexum-sdk` with the typed `Cow` client and the `#[shepherd::module]` proc macro. +- **Per-venue crates (shipped for CoW)** - each domain ships as crates on the venue layer, not as an SDK layer: `cow-venue` (body codec, typed client, adapter component) and `composable-cow` (conditional-order keeper machinery). A new domain adds its own. -A module author building a generic blockchain automation module depends only on `nexum-sdk`; a CoW Protocol module depends on both `nexum-sdk` and `shepherd-sdk` and imports each directly. +A generic automation module depends only on `nexum-sdk`; a keeper adds `videre-sdk` and the venue's body crate; a venue adapter depends on `videre-sdk` and its own crate. For **non-Rust** module authors (JavaScript, Python, Go, C++), the SDK is unnecessary - they use `wit-bindgen` directly against the WIT package for their target world. The WIT is the universal contract; the SDK is a Rust ergonomics layer on top. @@ -1014,10 +1004,11 @@ For **non-Rust** module authors (JavaScript, Python, Go, C++), the SDK is unnece | `event-module` world (0.2, shipping) | Event-driven modules - server today, mobile/background planned | | `query-module` world (0.2 experimental) | Request/response modules - WIT published, no host impl in 0.2 | | `app-module` world | Interactive modules - design only; planned hosts | -| `shepherd:cow` WIT package | CoW Protocol domain extension | -| `shepherd` world | CoW automation modules (includes event-module + cow-api) | -| `nexum-sdk` crate (shipped) | Universal Rust SDK: host-trait seam (ADR-0009), Fault / HostFault / ChainError, bind macro, chain / config / address helpers, guest `http` helper, tracing facade. HostTransport, TypedState, RemoteStore, Messaging, Signer remain future direction | -| `shepherd-sdk` crate (shipped) | CoW-domain Rust SDK: cow-api trait + CoW helpers on top of `nexum-sdk`, no re-export between the layers. | +| `videre:types` / `videre:value-flow` / `videre:venue` WIT packages | Venue-neutral intent contract: types, asset vocabulary, client + adapter faces, venue-adapter world | +| `shepherd:cow` WIT package | CoW event-ABI package of record (`cow-events` only; the legacy `cow-api` read path and `shepherd` world are retired) | +| Venue adapter components | The domain-extension mechanism: `#[videre_sdk::venue]` components installed into the videre platform (`cow-venue` shipped) | +| `nexum-sdk` crate (shipped) | Universal Rust SDK: host-trait seam (ADR-0009), Fault / ChainError, bind macro, module macro, chain / config / address helpers, keeper store primitives, guest `http` helper, tracing facade | +| `videre-sdk` crate (shipped) | Venue Rust SDK: VenueAdapter + venue macro, IntentBody codec, typed VenueClient + keeper macro, sweep assembler; `videre-test` conformance kit alongside | | Content-addressed distribution | Platform-agnostic (Swarm/IPFS, ENS discovery, hash verification) | | Host Adapter | Platform-specific implementation of universal interfaces | diff --git a/docs/sdk.md b/docs/sdk.md index 4d48a7fa..1e93cd36 100644 --- a/docs/sdk.md +++ b/docs/sdk.md @@ -1,19 +1,20 @@ -# The module SDK: nexum-sdk + shepherd-sdk +# The module SDK: nexum-sdk + videre-sdk `nexum-sdk` is the guest-side library every module consumes: typed primitives, ABI helpers, an effect-trait seam for testing, the `#[nexum_sdk::module]` attribute macro and per-module adapter macro, and a `prelude` that keeps boilerplate out of module crates. -`shepherd-sdk` layers the CoW Protocol surface on top; modules that -touch the orderbook depend on both crates and import each directly -(nothing is re-exported between them). +`videre-sdk` layers the venue surface on top: the typed venue client, +the intent-body codec, the `VenueAdapter` seam and the keeper sweep. +Modules that talk to a venue depend on both crates and import each +directly (nothing is re-exported between them). This page is the entry point. The full API reference is the rustdoc -site under `target/doc/nexum_sdk/` and `target/doc/shepherd_sdk/`, +site under `target/doc/nexum_sdk/` and `target/doc/videre_sdk/`, generated by: ```sh -RUSTDOCFLAGS="-D warnings -D missing-docs" cargo doc -p shepherd-sdk -p nexum-sdk -p nexum-module-macros -p videre-macros --no-deps --open +RUSTDOCFLAGS="-D warnings -D missing-docs" cargo doc -p nexum-sdk -p videre-sdk -p nexum-module-macros -p videre-macros --no-deps --open ``` ## Authoring a module @@ -27,7 +28,7 @@ The macro generates the rest of the per-cdylib glue: the `wit_bindgen::generate!` call, the `bind_host_via_wit_bindgen!()` adapter, a `Guest` impl whose `on_event` dispatches to whichever handlers are present (absent handlers no-op), and `export!`. See -[doc 05](05-sdk-design.md#the-nexummodule-macro) for a worked +[doc 05](05-sdk-design.md#the-nexum_sdkmodule-macro) for a worked example and the `nexum-module-macros` rustdoc for the fine print. ## Supported host capabilities @@ -36,9 +37,9 @@ The SDK is host-neutral - it does not call wit-bindgen-generated functions directly. Instead, it exposes traits that mirror the on-the-wire host interfaces, and modules adapt their wit-bindgen imports to the traits at the cdylib boundary (the -`bind_host_via_wit_bindgen!` / `bind_cow_host_via_wit_bindgen!` -macros generate that adapter). The traits in -[`nexum_sdk::host`][host-doc] and [`shepherd_sdk::cow`][cow-doc] are: +`bind_host_via_wit_bindgen!` macro generates that adapter). The traits +in [`nexum_sdk::host`][host-doc] and the venue seam in +[`videre_sdk::client`][client-doc] are: | Trait | Mirrors | What it does | |---|---|---| @@ -46,36 +47,35 @@ macros generate that adapter). The traits in | `LocalStoreHost` | `nexum:host/local-store@0.1.0` | Per-module key-value store | | `LoggingHost` | `nexum:host/logging@0.1.0` | Structured log lines tagged by module | | `Host` | supertrait | Bundles the three core traits; blanket impl | -| `CowApiHost` (shepherd-sdk) | `shepherd:cow/cow-api@0.1.0` | Orderbook submission (`POST /api/v1/orders`) | -| `CowHost` (shepherd-sdk) | supertrait | `Host` + `CowApiHost` for orderbook strategies | +| `VenueTransport` (videre-sdk) | `videre:venue/client@0.1.0` | Quote, submit, observe, status and cancel against an installed venue adapter | A module declaring `[capabilities].required = ["chain", "local-store", -"cow-api", "logging"]` in its `module.toml` matches the host trait -seam one-for-one. +"client", "logging"]` in its `module.toml` matches the host trait seam +one-for-one. [host-doc]: ../target/doc/nexum_sdk/host/index.html -[cow-doc]: ../target/doc/shepherd_sdk/cow/index.html +[client-doc]: ../target/doc/videre_sdk/client/index.html ## Modules - [`nexum_sdk::prelude`](../target/doc/nexum_sdk/prelude/index.html) - and [`shepherd_sdk::prelude`](../target/doc/shepherd_sdk/prelude/index.html) - - bulk re-exports. The nexum prelude covers the alloy primitives - (`Address`, `B256`, `Bytes`, `U256`, `keccak256`); the shepherd - prelude covers cowprotocol's order / signing / orderbook surface. - -- [`cow`](../target/doc/shepherd_sdk/cow/index.html) - CoW Protocol - bridging: - - `cow::order::gpv2_to_order_data` - convert the on-chain - `GPv2OrderData` (12-field Solidity tuple with bytes32 markers) - into the typed `OrderData` shape the orderbook signs against. - - `cow::composable::Verdict` + `cow::composable::LegacyRevertAdapter` - - typed dispatch over the five `IConditionalOrder` custom errors - (`OrderNotValid`, `PollTryNextBlock`, `PollTryAtBlock`, - `PollTryAtEpoch`, `PollNever`). - - `cow::error::RetryAction` + `cow::error::classify_api_error` - - map `cow_api::submit_order` failures into `TryNextBlock` / - `Backoff(s)` / `Drop`. + - bulk re-exports covering the alloy primitives (`Address`, `B256`, + `Bytes`, `U256`, `keccak256`). `videre-sdk` has no prelude; it + re-exports its surface from the crate root. + +- [`client`](../target/doc/videre_sdk/client/index.html) - the typed + venue seam (in `videre-sdk`): a `Venue` marker drives `VenueClient`, + which encodes through `IntentBody` before the byte-level + `VenueTransport` seam. `keeper::retry_action` folds a `VenueFault` + into a `RetryAction`. + +- CoW-specific pieces live in their own L3 crates rather than the SDK: + `cow_venue::assembly::gpv2_to_order_data` converts the on-chain + `GPv2OrderData` into the typed `OrderData` the orderbook signs + against, and `composable_cow::{Verdict, LegacyRevertAdapter}` give + typed dispatch over the five `IConditionalOrder` custom errors + (`OrderNotValid`, `PollTryNextBlock`, `PollTryAtBlock`, + `PollTryAtEpoch`, `PollNever`). - [`chain`](../target/doc/nexum_sdk/chain/index.html) - `eth_call` JSON plumbing (in `nexum-sdk`): @@ -85,8 +85,8 @@ seam one-for-one. response into bytes. - `chain::chainlink::read_latest_answer` - Chainlink AggregatorV3 reader over the two helpers above. - (The CoW-specific `cow::LegacyRevertAdapter(s)` - the `chain-error` - rpc revert bytes -> typed `Verdict` - lives in `shepherd-sdk`.) + (The CoW-specific `LegacyRevertAdapter` - the `chain-error` rpc + revert bytes to a typed `Verdict` - lives in `composable-cow`.) - [`host`](../target/doc/nexum_sdk/host/index.html) - host trait seam plus the SDK's host-neutral `Fault` vocabulary (same cases @@ -108,20 +108,21 @@ seam one-for-one. failures. See `modules/examples/http-probe` for a complete module. -## Companions: nexum-sdk-test and shepherd-sdk-test +## Companions: nexum-sdk-test and videre-test -Add `nexum-sdk-test` as a dev-dep on the module crate to write -strategy tests against in-memory mocks; CoW modules use -`shepherd-sdk-test`, whose `MockHost` composes the generic mocks with -`MockCowApi`. See the crate docs +Add `nexum-sdk-test` as a dev-dep on the module crate to write strategy +tests against in-memory mocks; its `MockHost` covers the chain, local +store and logging seams. `videre-test` is the venue-side kit: codec +vectors and header goldens for conformance runs, plus transport mocks +such as `MockFetch`. See the crate docs ([nexum-sdk-test](../crates/nexum-sdk-test/src/lib.rs), -[shepherd-sdk-test](../crates/shepherd-sdk-test/src/lib.rs)) for the -usage pattern. +[videre-test](../crates/videre-test/src/lib.rs)) for the usage +pattern. ## Versioning The SDK crates are currently `0.1.0` and live at `crates/nexum-sdk/` -and `crates/shepherd-sdk/` in the shepherd monorepo. They are not yet +and `crates/videre-sdk/` in the shepherd monorepo. They are not yet published to crates.io; modules depend on them via workspace paths. The `cowprotocol` crate is published to crates.io; the workspace