lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit 94ca83e4764520d260c81e2ebee2512f8747ec30
parent 81fb655abd071eac6c6ebe2aa085cfa3eab04423
Author: triesap <tyson@radroots.org>
Date:   Mon,  3 Aug 2026 12:21:45 +0000

sdk: document public api contract

- Document consumers, feature behavior, commit points, serialization, and security.
- Replace dormant legacy examples with compile-checked final API examples.
- Include the package README in rustdoc so its getting-started example is tested.
- Record the reviewed all-features public API baseline and regeneration contract.

Diffstat:
Mcrates/sdk/Cargo.toml | 9+++++++++
Mcrates/sdk/README.md | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dcrates/sdk/examples/runtime_local.rs | 125-------------------------------------------------------------------------------
Acrates/sdk/examples/safe_memory_client.rs | 20++++++++++++++++++++
Dcrates/sdk/examples/sdk_v1_knowledge_prepare.rs | 100-------------------------------------------------------------------------------
Dcrates/sdk/examples/sdk_v1_listing_prepare.rs | 86-------------------------------------------------------------------------------
Dcrates/sdk/examples/sdk_v1_local_enqueue_and_mock_sync.rs | 122-------------------------------------------------------------------------------
Dcrates/sdk/examples/sdk_v1_myc_nip46_signer_setup.rs | 61-------------------------------------------------------------
Acrates/sdk/examples/transport_profile.rs | 11+++++++++++
Mcrates/sdk/src/lib.rs | 1+
10 files changed, 95 insertions(+), 494 deletions(-)

diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml @@ -101,5 +101,14 @@ path = "tests/lifecycle.rs" name = "public_api" path = "tests/public_api.rs" +[[example]] +name = "safe_memory_client" +path = "examples/safe_memory_client.rs" +required-features = ["memory"] + +[[example]] +name = "transport_profile" +path = "examples/transport_profile.rs" + [lints] workspace = true diff --git a/crates/sdk/README.md b/crates/sdk/README.md @@ -10,6 +10,38 @@ and `Result`. Advanced operations live in the `farm`, `listing`, `trade`, `signing`, `transport`, `storage`, `sync`, `diagnostics`, and `capability` modules. +The package charter is the normative [`radroots_sdk` crate +specification](../../docs/specs/radroots_crates_release_v1.md#18-radroots_sdk). +This advanced front door is intended for CLI, Studio, FFI/mobile, and native +applications that need to compose storage, signing, transport, or sync +capabilities directly. Ordinary Rust applications should use the curated +`radroots` facade. + +## Getting started + +The default `memory` feature provides an inert, in-process backend. The caller +supplies its source generation; building the client creates no files, opens no +network connection, installs no runtime, and starts no worker. + +```rust +use radroots_sdk::{ClientBuilder, capability::CapabilityId}; +use radroots_storage::event::SourceGeneration; + +let generation = SourceGeneration::new([1; 32])?; +let client = ClientBuilder::memory(generation).build()?; +let storage = client + .capabilities() + .get(CapabilityId::CANONICAL_STORAGE); +assert!(storage.is_some()); + +# Ok::<(), Box<dyn std::error::Error>>(()) +``` + +The complete executable form, including explicit asynchronous shutdown, lives +in [`examples/safe_memory_client.rs`](examples/safe_memory_client.rs). The +side-effect-free transport-selection example lives in +[`examples/transport_profile.rs`](examples/transport_profile.rs). + ## Feature contract The complete public feature vocabulary is: @@ -70,6 +102,19 @@ cancellation policy, and an explicit transport profile, then return the canonical sync receipt. Repeating the same idempotent request is the supported resume/replay path. +Preparation has no commit point. During commit, durable local acceptance is +the first commit point; a cancellation observed before that point returns +without claiming acceptance. Cancellation after durable acceptance cannot +roll the accepted event back: the returned error/receipt identifies the safe +resume path, and retrying with the same idempotency identity must not create a +second logical operation. Dropping `Client::close` before completion leaves +the shared client in a retry-required closing state; call `close` again. + +The SDK does not define a second wire model. Canonical lower-crate domain and +protocol types own serialization, validation, versioning, and size limits. +SDK request, plan, and receipt structs are constructor-led orchestration types; +their private representation is not a persistence or interchange format. + ## Reliability and privacy Backup, restore, integrity, and status operations delegate to @@ -84,6 +129,12 @@ SPIs. Diagnostics contain only capability and canonical storage status. Public errors and daemon failures use stable, redacted classifications while retaining private source chains for local diagnostics. +Signer material and bearer credentials are caller-owned capabilities. The SDK +does not generate keys, read a keyring, persist secrets, or include credentials +in `Debug`, `Display`, diagnostics, receipts, or public error text. Hosts remain +responsible for protecting source chains and any lower-level logs they choose +to expose. + ## Daemon execution The `radrootsd` feature compiles a private HTTP/RPC adapter using the versioned @@ -97,3 +148,6 @@ surfaced, and the response must match the signed event and requested policies. This package remains `publish = false` until the complete package-realistic release qualification and separately authorized publication step. The crate is licensed under `MIT OR Apache-2.0`. + +The reviewed all-features public API baseline is recorded at +[`docs/api/radroots_sdk-0.1.0-alpha.txt`](../../docs/api/radroots_sdk-0.1.0-alpha.txt). diff --git a/crates/sdk/examples/runtime_local.rs b/crates/sdk/examples/runtime_local.rs @@ -1,125 +0,0 @@ -use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit}; -use radroots_event::contract::AuthorRole; -use radroots_event::farm::FarmRef; -use radroots_event::id::{DTag, InventoryBinId}; -use radroots_event::listing::operational::{ - OperationalListing, OperationalListingAvailability, OperationalListingBin, - OperationalListingDeliveryMethod, OperationalListingProduct, OperationalListingPublicLocation, - OperationalListingStatus, -}; -use radroots_nostr::signing::LocalSigner; -use radroots_sdk::{ - ListingPreparePublishRequest, NostrRelayUrlPolicy, PushOutboxRequest, RadrootsClient, - RadrootsSdkError, RadrootsSdkLocalKeySigner, RadrootsSdkSignerProvider, RadrootsSdkTimestamp, - SdkIdempotencyKey, TargetPolicy, -}; -use radroots_signing::{Actor, actor::ActorSource}; - -const RELAY: &str = "wss://relay.example.com"; - -#[tokio::main] -async fn main() -> Result<(), Box<dyn std::error::Error>> { - let signer = LocalSigner::generate()?; - let seller = signer.public_key().to_hex(); - let signer = RadrootsSdkLocalKeySigner::from_signer(signer, seller.as_str())?; - let sdk = RadrootsClient::builder() - .fixed_clock(RadrootsSdkTimestamp::from_unix_seconds(1_700_000_000)) - .signer_provider(RadrootsSdkSignerProvider::LocalKey(signer)) - .build() - .await?; - let actor = Actor::from_public_key_hex( - seller.as_str(), - ActorSource::ExplicitPublicKey, - [AuthorRole::Seller], - )?; - let listing = sample_listing(seller.as_str()); - let prepare_request = ListingPreparePublishRequest::new(actor.clone(), listing); - let target_policy = TargetPolicy::try_nostr_relays([RELAY], NostrRelayUrlPolicy::Public)?; - let idempotency_key = SdkIdempotencyKey::new("01890f0e-6c00-7000-8000-000000000202")?; - - let prepared = sdk.listings().prepare_publish(prepare_request)?; - let enqueue = sdk - .listings() - .enqueue_prepared_publish( - &actor, - prepared.clone(), - target_policy, - Some(idempotency_key), - ) - .await?; - let push = sdk - .sync() - .push_outbox(PushOutboxRequest::new().with_limit(1)) - .await; - assert_eq!( - prepared.public_listing_addr().as_str(), - enqueue.public_listing_addr.as_str() - ); - #[cfg(feature = "transport-nostr-runtime")] - assert!(matches!( - push, - Err(RadrootsSdkError::ProductSyncTransportSetupFailure { .. }) - )); - #[cfg(not(feature = "transport-nostr-runtime"))] - assert!(matches!( - push, - Err(RadrootsSdkError::ProductSyncUnsupported { .. }) - )); - Ok(()) -} - -fn sample_listing(seller: &str) -> OperationalListing { - OperationalListing { - d_tag: DTag::parse("AAAAAAAAAAAAAAAAAAAAAQ").expect("d tag"), - published_at: None, - farm: FarmRef { - pubkey: seller.to_owned(), - d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_owned(), - }, - product: OperationalListingProduct { - key: "coffee".to_owned(), - title: "Coffee".to_owned(), - category: "coffee".to_owned(), - summary: Some("Single origin coffee".to_owned()), - process: None, - lot: None, - location: None, - profile: None, - year: None, - }, - primary_bin_id: InventoryBinId::parse("bin-1").expect("bin id"), - bins: vec![OperationalListingBin { - bin_id: InventoryBinId::parse("bin-1").expect("bin id"), - quantity: Quantity::try_new(Decimal::from(1000u32), Unit::MassG) - .expect("positive example quantity"), - price_per_canonical_unit: QuantityPrice::try_new( - Money::try_new(Decimal::from(20u32), Currency::USD) - .expect("non-negative example money"), - Quantity::try_new(Decimal::from(1u32), Unit::MassG) - .expect("positive example pricing quantity"), - ) - .expect("non-zero example pricing quantity"), - display_amount: None, - display_unit: None, - display_label: None, - display_price: None, - display_price_unit: None, - }], - resource_area: None, - plot: None, - discounts: None, - inventory_available: Some(Decimal::from(5u32)), - availability: Some(OperationalListingAvailability::Status { - status: OperationalListingStatus::Active, - }), - delivery_method: Some(OperationalListingDeliveryMethod::Pickup), - location: Some(OperationalListingPublicLocation { - primary: "Victoria".to_owned(), - city: Some("Victoria".to_owned()), - region: Some("British Columbia".to_owned()), - country: Some("CA".to_owned()), - geohash: "c287g".to_owned(), - }), - images: None, - } -} diff --git a/crates/sdk/examples/safe_memory_client.rs b/crates/sdk/examples/safe_memory_client.rs @@ -0,0 +1,20 @@ +//! Builds and explicitly closes the safe in-process client. + +use radroots_sdk::{ClientBuilder, capability::CapabilityId}; +use radroots_storage::event::SourceGeneration; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let generation = SourceGeneration::new([1; 32])?; + let client = ClientBuilder::memory(generation).build()?; + + assert!( + client + .capabilities() + .get(CapabilityId::CANONICAL_STORAGE) + .is_some() + ); + + client.close().await?; + Ok(()) +} diff --git a/crates/sdk/examples/sdk_v1_knowledge_prepare.rs b/crates/sdk/examples/sdk_v1_knowledge_prepare.rs @@ -1,100 +0,0 @@ -use nostr::{EventBuilder, Keys, Kind, Tag, Timestamp}; -use radroots_event::envelope::EventEnvelopeParts; -use radroots_identity::PublicKey; -use radroots_sdk::knowledge::prelude::*; - -const CREATED_AT: u32 = 1_800_000_000; -const RELAY: &str = "wss://relay.radroots.example"; - -fn main() -> Result<(), Box<dyn std::error::Error>> { - let keys = Keys::generate(); - let claim = claim_builder().build()?; - let parts = claim_builder().build_event()?; - let draft = claim_builder().build_draft(keys.public_key().to_hex(), CREATED_AT)?; - let signed = sign_parts(parts, &keys)?; - let decoded = KnowledgeCodec::new().verify_and_decode_radroots_event(signed)?; - let manifest = contract_manifest(); - let manifest_hash = contract_manifest_sha256()?; - - assert_eq!(draft.contract_id(), KNOWLEDGE_CLAIM_CONTRACT_ID); - assert_eq!(manifest.contract_count, 11); - assert_eq!(manifest_hash.len(), 64); - - match decoded { - RadrootsDecodedEvent::KnowledgeClaim(parsed) => { - assert_eq!(parsed.data.data.text, claim.text); - } - _ => return Err("expected knowledge claim".into()), - } - - println!( - "prepared knowledge claim draft: {}", - draft.expected_event_id_hex() - ); - println!("knowledge manifest sha256: {manifest_hash}"); - Ok(()) -} - -fn sign_parts( - parts: Nip01EventWireParts, - keys: &Keys, -) -> Result<EventEnvelope, Box<dyn std::error::Error>> { - let tags = parts - .tags - .into_iter() - .map(Tag::parse) - .collect::<Result<Vec<_>, _>>()?; - let event = EventBuilder::new(Kind::Custom(parts.kind as u16), parts.content) - .tags(tags) - .custom_created_at(Timestamp::from_secs(u64::from(CREATED_AT))) - .sign_with_keys(keys)?; - Ok(EventEnvelope::new(EventEnvelopeParts { - id: event.id.to_hex(), - author: event.pubkey.to_hex(), - created_at: event.created_at.as_secs(), - kind: u32::from(event.kind.as_u16()), - tags: event - .tags - .as_slice() - .iter() - .map(|tag| tag.as_slice().to_vec()) - .collect(), - content: event.content, - sig: event.sig.to_string(), - })?) -} - -fn hex_64(character: char) -> String { - character.to_string().repeat(64) -} - -fn event_ref(character: char, kind: u32) -> EventRef { - EventRef { - id: hex_64(character), - author: PublicKey::from_hex( - "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df", - ) - .expect("fixture public key"), - kind, - d_tag: None, - relays: Some(vec![RELAY.to_owned()]), - } -} - -fn claim_builder() -> RadrootsKnowledgeClaimBuilder { - RadrootsKnowledgeClaimBuilder::new() - .claim_type("practice_effect") - .text("Cover crops improve soil structure.") - .citation_span(KnowledgeCitationSpan { - source_ref: event_ref('4', KIND_KNOWLEDGE_SOURCE), - artifact_ref: None, - page_start: Some(12), - page_end: Some(13), - section_path: vec!["chapter-1".to_owned()], - quote_hash: Some(hex_64('5')), - chunk_id: Some("chunk-1".to_owned()), - }) - .topic("cover-crops") - .applies_to("local-food") - .author_asserted_confidence("medium") -} diff --git a/crates/sdk/examples/sdk_v1_listing_prepare.rs b/crates/sdk/examples/sdk_v1_listing_prepare.rs @@ -1,86 +0,0 @@ -use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit}; -use radroots_event::contract::AuthorRole; -use radroots_event::farm::FarmRef; -use radroots_event::id::{DTag, InventoryBinId}; -use radroots_event::listing::operational::{ - OperationalListing, OperationalListingAvailability, OperationalListingBin, - OperationalListingDeliveryMethod, OperationalListingProduct, OperationalListingPublicLocation, - OperationalListingStatus, -}; -use radroots_sdk::{ListingPreparePublishRequest, RadrootsClient, RadrootsSdkTimestamp}; -use radroots_signing::{Actor, actor::ActorSource}; - -const SELLER: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - -#[tokio::main] -async fn main() -> Result<(), Box<dyn std::error::Error>> { - let sdk = RadrootsClient::builder() - .fixed_clock(RadrootsSdkTimestamp::from_unix_seconds(1_700_000_000)) - .build() - .await?; - let actor = - Actor::from_public_key_hex(SELLER, ActorSource::ExplicitPublicKey, [AuthorRole::Seller])?; - let request = ListingPreparePublishRequest::new(actor, sample_listing(SELLER)); - - let plan = sdk.listings().prepare_publish(request)?; - - println!("public listing: {}", plan.public_listing_addr().as_str()); - println!("expected event: {}", plan.expected_event_id().to_hex()); - Ok(()) -} - -fn sample_listing(seller: &str) -> OperationalListing { - OperationalListing { - d_tag: DTag::parse("AAAAAAAAAAAAAAAAAAAAAQ").expect("d tag"), - published_at: None, - farm: FarmRef { - pubkey: seller.to_owned(), - d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_owned(), - }, - product: OperationalListingProduct { - key: "coffee".to_owned(), - title: "Coffee".to_owned(), - category: "coffee".to_owned(), - summary: Some("Single origin coffee".to_owned()), - process: None, - lot: None, - location: None, - profile: None, - year: None, - }, - primary_bin_id: InventoryBinId::parse("bin-1").expect("bin id"), - bins: vec![OperationalListingBin { - bin_id: InventoryBinId::parse("bin-1").expect("bin id"), - quantity: Quantity::try_new(Decimal::from(1000u32), Unit::MassG) - .expect("positive example quantity"), - price_per_canonical_unit: QuantityPrice::try_new( - Money::try_new(Decimal::from(20u32), Currency::USD) - .expect("non-negative example money"), - Quantity::try_new(Decimal::from(1u32), Unit::MassG) - .expect("positive example pricing quantity"), - ) - .expect("non-zero example pricing quantity"), - display_amount: None, - display_unit: None, - display_label: None, - display_price: None, - display_price_unit: None, - }], - resource_area: None, - plot: None, - discounts: None, - inventory_available: Some(Decimal::from(5u32)), - availability: Some(OperationalListingAvailability::Status { - status: OperationalListingStatus::Active, - }), - delivery_method: Some(OperationalListingDeliveryMethod::Pickup), - location: Some(OperationalListingPublicLocation { - primary: "Victoria".to_owned(), - city: Some("Victoria".to_owned()), - region: Some("British Columbia".to_owned()), - country: Some("CA".to_owned()), - geohash: "c287g".to_owned(), - }), - images: None, - } -} diff --git a/crates/sdk/examples/sdk_v1_local_enqueue_and_mock_sync.rs b/crates/sdk/examples/sdk_v1_local_enqueue_and_mock_sync.rs @@ -1,122 +0,0 @@ -use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit}; -use radroots_event::contract::AuthorRole; -use radroots_event::farm::FarmRef; -use radroots_event::id::{DTag, InventoryBinId}; -use radroots_event::listing::operational::{ - OperationalListing, OperationalListingAvailability, OperationalListingBin, - OperationalListingDeliveryMethod, OperationalListingProduct, OperationalListingPublicLocation, - OperationalListingStatus, -}; -use radroots_nostr::signing::LocalSigner; -use radroots_sdk::{ - ListingPreparePublishRequest, NostrRelayUrlPolicy, PushOutboxRequest, RadrootsClient, - RadrootsSdkLocalKeySigner, RadrootsSdkSignerProvider, RadrootsSdkTimestamp, SdkIdempotencyKey, - TargetPolicy, TargetSet, -}; -use radroots_signing::{Actor, actor::ActorSource}; -use radroots_transport_nostr::{RadrootsMockRelayPublishAdapter, RadrootsNostrTransport}; - -const LOCAL_RELAY: &str = "ws://localhost:7777"; - -#[tokio::main] -async fn main() -> Result<(), Box<dyn std::error::Error>> { - let signer = LocalSigner::generate()?; - let seller = signer.public_key().to_hex(); - let signer = RadrootsSdkLocalKeySigner::from_signer(signer, seller.as_str())?; - let sdk = RadrootsClient::builder() - .fixed_clock(RadrootsSdkTimestamp::from_unix_seconds(1_700_000_000)) - .signer_provider(RadrootsSdkSignerProvider::LocalKey(signer)) - .build() - .await?; - let actor = Actor::from_public_key_hex( - seller.as_str(), - ActorSource::ExplicitPublicKey, - [AuthorRole::Seller], - )?; - let targets = TargetSet::nostr_relays([LOCAL_RELAY], NostrRelayUrlPolicy::Localhost)?; - let target_policy = TargetPolicy::explicit(targets); - - let prepared = sdk - .listings() - .prepare_publish(ListingPreparePublishRequest::new( - actor.clone(), - sample_listing(seller.as_str()), - ))?; - let enqueue = sdk - .listings() - .enqueue_prepared_publish( - &actor, - prepared, - target_policy, - Some(SdkIdempotencyKey::new( - "01890f0e-6c00-7000-8000-000000000201", - )?), - ) - .await?; - let adapter = RadrootsMockRelayPublishAdapter::new(); - let transport = RadrootsNostrTransport::new(&adapter); - let push = sdk - .sync() - .push_outbox_with_transport(&transport, PushOutboxRequest::new().with_limit(1)) - .await?; - - println!("queued listing event: {}", enqueue.signed_event_id.to_hex()); - println!("published events: {}", push.published_events); - Ok(()) -} - -fn sample_listing(seller: &str) -> OperationalListing { - OperationalListing { - d_tag: DTag::parse("AAAAAAAAAAAAAAAAAAAAAQ").expect("d tag"), - published_at: None, - farm: FarmRef { - pubkey: seller.to_owned(), - d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_owned(), - }, - product: OperationalListingProduct { - key: "coffee".to_owned(), - title: "Coffee".to_owned(), - category: "coffee".to_owned(), - summary: Some("Single origin coffee".to_owned()), - process: None, - lot: None, - location: None, - profile: None, - year: None, - }, - primary_bin_id: InventoryBinId::parse("bin-1").expect("bin id"), - bins: vec![OperationalListingBin { - bin_id: InventoryBinId::parse("bin-1").expect("bin id"), - quantity: Quantity::try_new(Decimal::from(1000u32), Unit::MassG) - .expect("positive example quantity"), - price_per_canonical_unit: QuantityPrice::try_new( - Money::try_new(Decimal::from(20u32), Currency::USD) - .expect("non-negative example money"), - Quantity::try_new(Decimal::from(1u32), Unit::MassG) - .expect("positive example pricing quantity"), - ) - .expect("non-zero example pricing quantity"), - display_amount: None, - display_unit: None, - display_label: None, - display_price: None, - display_price_unit: None, - }], - resource_area: None, - plot: None, - discounts: None, - inventory_available: Some(Decimal::from(5u32)), - availability: Some(OperationalListingAvailability::Status { - status: OperationalListingStatus::Active, - }), - delivery_method: Some(OperationalListingDeliveryMethod::Pickup), - location: Some(OperationalListingPublicLocation { - primary: "Victoria".to_owned(), - city: Some("Victoria".to_owned()), - region: Some("British Columbia".to_owned()), - country: Some("CA".to_owned()), - geohash: "c287g".to_owned(), - }), - images: None, - } -} diff --git a/crates/sdk/examples/sdk_v1_myc_nip46_signer_setup.rs b/crates/sdk/examples/sdk_v1_myc_nip46_signer_setup.rs @@ -1,61 +0,0 @@ -use nostr::Keys as RadrootsNostrKeys; -use radroots_event::envelope::kind::KIND_TRADE_PROPOSAL; -use radroots_nostr_connect::{ - client::{CancellationToken, Client, ClientEvent, Receive, Target, Transport, TransportFuture}, - uri::RelayUrl as ConnectRelayUrl, -}; -use radroots_sdk::{ - RadrootsClient, RadrootsSdkMycNip46Signer, RadrootsSdkSignerMode, RadrootsSdkSignerProvider, - radroots_sdk_myc_nip46_product_permission_strings, -}; - -struct ExampleNip46Transport; - -impl Transport for ExampleNip46Transport { - fn publish<'a>(&'a mut self, _event: ClientEvent) -> TransportFuture<'a, ()> { - Box::pin(async { Ok(()) }) - } - - fn receive<'a>( - &'a mut self, - _cancellation: &'a CancellationToken, - ) -> TransportFuture<'a, Receive> { - Box::pin(async { Ok(Receive::TimedOut) }) - } -} - -#[tokio::main] -async fn main() -> Result<(), Box<dyn std::error::Error>> { - let remote_signer_keys = RadrootsNostrKeys::generate(); - let user_keys = RadrootsNostrKeys::generate(); - let remote_signer_public_key = - radroots_nostr::key::public_key_from_nostr(remote_signer_keys.public_key())?; - let target = Target::try_new( - remote_signer_public_key, - vec![ConnectRelayUrl::parse("wss://relay.example.com")?], - )?; - let client = Client::generate(target)?; - let signer = RadrootsSdkMycNip46Signer::from_client( - client, - user_keys.public_key().to_hex(), - ExampleNip46Transport, - )?; - let sdk = RadrootsClient::builder() - .signer_provider(RadrootsSdkSignerProvider::MycNip46(Box::new(signer))) - .build() - .await?; - let status = sdk.signer_status().expect("configured signer status"); - let permissions = radroots_sdk_myc_nip46_product_permission_strings(); - - assert_eq!(status.mode, RadrootsSdkSignerMode::MycNip46); - assert!(permissions.iter().any(|value| value == "sign_event:30340")); - assert!( - permissions - .iter() - .any(|value| value == &format!("sign_event:{KIND_TRADE_PROPOSAL}")) - ); - assert!(!permissions.iter().any(|value| value == "sign_event:3422")); - println!("configured signer mode: {}", status.mode.as_str()); - println!("requested permissions: {}", permissions.join(",")); - Ok(()) -} diff --git a/crates/sdk/examples/transport_profile.rs b/crates/sdk/examples/transport_profile.rs @@ -0,0 +1,11 @@ +//! Selects local-only behavior without constructing or probing a transport. + +use radroots_sdk::transport::Profile; + +fn main() { + let profile = Profile::local_only(); + + assert!(profile.is_local_only()); + assert!(profile.targets().is_none()); + assert!(profile.satisfaction().is_none()); +} diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs @@ -24,6 +24,7 @@ //! ``` #![forbid(unsafe_code)] +#![doc = include_str!("../README.md")] mod adapters;