sdk

Radroots SDK and bindings
git clone https://radroots.dev/git/sdk.git
Log | Files | Refs | README

commit b5c7b0d62c8477fc2621a549bd21e762dc25af3f
parent b743b87c31b4b51d82e7148ba4fedbf8d5b35250
Author: triesap <tyson@radroots.org>
Date:   Mon, 15 Jun 2026 17:34:59 -0700

sdk: document runtime publish posture

Diffstat:
Mcrates/sdk/README | 48+++++++++++++++++++++++++++++++++++++++++-------
Mcrates/sdk/examples/runtime_local.rs | 24+++++++++++++++---------
2 files changed, 56 insertions(+), 16 deletions(-)

diff --git a/crates/sdk/README b/crates/sdk/README @@ -3,20 +3,49 @@ Curated Rad Roots Rust SDK for local-first Rad Roots product workflows. The SDK v1 product runtime is centered on `RadrootsSdk::builder()`, -`sdk.listings()`, `sdk.sync()`, and `sdk.orders()`. Product callers use the -runtime clients to prepare listing publishes, enqueue signed local mutations, -push the signed outbox through an explicit relay transport adapter, and read -local order status projections. +`sdk.listings()`, `sdk.sync()`, and `sdk.orders()`. + +`RadrootsSdk::builder()` defaults to memory storage, the system clock, no relay +URLs, and no production network publishing. Directory storage is opt-in and +creates `event_store.sqlite` and `outbox.sqlite` in the selected directory. +Configured relay URLs are also opt-in. Product sync publish requires configured +relay URLs and the `relay-runtime` feature. + +`sdk.listings().prepare_publish(...)` is side-effect-free and returns a typed +`ListingPublishPlan`. `sdk.listings().enqueue_publish(...)` signs the authorized +listing event, ingests it into the local event store, queues signed outbox work, +and returns a typed `ListingEnqueueReceipt`. The enqueue request uses typed relay +target and idempotency inputs; omitted idempotency keys are derived +deterministically. + +`sdk.sync().push_outbox(...)` is the product sync entrypoint. It publishes queued +signed outbox work only when relay runtime support and configured relay URLs are +present. `push_outbox_with_adapter(...)` remains available for tests and +controlled adapter-level substrate checks. + +`sdk.orders().status(...)` reads local order projections and returns typed +source, event count, limit state, event IDs, and reducer issues. It is not a +network fetch. Low-level event-contract and transport helpers are intentionally scoped under -`protocol`: +explicit modules: +- `protocol::events` +- `protocol::wire` - `protocol::profile` - `protocol::farm` - `protocol::listing` - `protocol::order` -- `protocol::client` -- `protocol::config` +- `protocol::identity` when `identity-models` is enabled +- `client` +- `config` +- `adapters` + +Product runtime callers do not need `WireEventParts`. Wire helpers are exposed +only through `protocol::wire`. + +SDK v1 does not claim strict NIP-99 Markdown-content interoperability. It emits +and consumes Rad Roots v1 listing and trade event contracts. Optional advanced substrate is explicitly feature-scoped: @@ -25,3 +54,8 @@ Optional advanced substrate is explicitly feature-scoped: - `signing`: Nostr builder and local signing adapters - `relay-client`: relay client and publish adapters - `signer-adapters`: NIP-46 and signer-session primitives + +The crate is licensed as `MIT OR Apache-2.0`. Its manifest is configured for a +future crates.io release, but a public release still requires the full SDK check +lane, generated artifact reproducibility checks, metadata review, and a publish +dry run. diff --git a/crates/sdk/examples/runtime_local.rs b/crates/sdk/examples/runtime_local.rs @@ -10,14 +10,14 @@ use radroots_events::draft::{ RadrootsFrozenEventDraft, RadrootsSignedNostrEvent, RadrootsSignedNostrEventParts, }; use radroots_events::ids::{RadrootsDTag, RadrootsInventoryBinId}; -use radroots_relay_transport::RadrootsMockRelayPublishAdapter; use radroots_sdk::protocol::farm::RadrootsFarmRef; use radroots_sdk::protocol::listing::{ RadrootsListing, RadrootsListingBin, RadrootsListingProduct, }; use radroots_sdk::{ ListingEnqueuePublishRequest, ListingPreparePublishRequest, OrderStatusRequest, - PushOutboxRequest, RadrootsSdk, RadrootsSdkTimestamp, SdkRelayTargetPolicy, + PushOutboxRequest, RadrootsSdk, RadrootsSdkError, RadrootsSdkTimestamp, SdkRelayTargetPolicy, + SdkRelayUrlPolicy, }; const SELLER: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; @@ -76,7 +76,6 @@ impl RadrootsEventSigner for FixtureSigner { async fn main() -> Result<(), Box<dyn std::error::Error>> { let sdk = RadrootsSdk::builder() .fixed_clock(RadrootsSdkTimestamp::from_unix_seconds(1_700_000_000)) - .relay_url(RELAY) .build() .await?; let actor = RadrootsActorContext::test(SELLER, [RadrootsActorRole::Seller])?; @@ -87,6 +86,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { listing, SdkRelayTargetPolicy::UseConfiguredRelays, ) + .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public)? .try_with_idempotency_key("example-1")?; let prepared = sdk.listings().prepare_publish(prepare_request)?; @@ -96,11 +96,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { .await?; let push = sdk .sync() - .push_outbox_with_adapter( - &RadrootsMockRelayPublishAdapter::new(), - PushOutboxRequest::new().with_limit(1), - ) - .await?; + .push_outbox(PushOutboxRequest::new().with_limit(1)) + .await; let order_status = sdk .orders() .status(OrderStatusRequest::parse("example-order-1")?) @@ -110,7 +107,16 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { prepared.public_listing_addr.as_str(), enqueue.public_listing_addr.as_str() ); - assert_eq!(push.attempted_events, 1); + #[cfg(feature = "relay-runtime")] + assert!(matches!( + push, + Err(RadrootsSdkError::ProductSyncRelaySetupFailure { .. }) + )); + #[cfg(not(feature = "relay-runtime"))] + assert!(matches!( + push, + Err(RadrootsSdkError::ProductSyncUnsupported { .. }) + )); assert!(!order_status.found); Ok(()) }