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:
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;