cli

Command-line interface for Radroots
git clone https://radroots.dev/git/cli.git
Log | Files | Refs | README | LICENSE

commit 64c52fbc0a0eac0169f285ec508e46192069bcb6
parent 4ddb3e43d5fc258de2403b8dd8c35f8fe6d737e8
Author: triesap <tyson@radroots.org>
Date:   Mon, 13 Apr 2026 14:52:03 +0000

listing: route cli publish through radroots_sdk

Diffstat:
MCargo.lock | 15+++++++++++++++
MCargo.toml | 2++
Msrc/runtime/daemon.rs | 139++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
3 files changed, 130 insertions(+), 26 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1738,6 +1738,7 @@ dependencies = [ "radroots_runtime_distribution", "radroots_runtime_manager", "radroots_runtime_paths", + "radroots_sdk", "radroots_secret_vault", "radroots_sql_core", "radroots_trade", @@ -1747,6 +1748,7 @@ dependencies = [ "tar", "tempfile", "thiserror 2.0.18", + "tokio", "toml", "url", "zeroize", @@ -1776,6 +1778,7 @@ dependencies = [ name = "radroots_events_codec" version = "0.1.0-alpha.2" dependencies = [ + "nostr", "radroots_core", "radroots_events", "serde", @@ -1967,6 +1970,18 @@ dependencies = [ ] [[package]] +name = "radroots_sdk" +version = "0.1.0-alpha.2" +dependencies = [ + "radroots_events", + "radroots_events_codec", + "radroots_trade", + "reqwest", + "serde", + "serde_json", +] + +[[package]] name = "radroots_secret_vault" version = "0.1.0-alpha.2" dependencies = [ diff --git a/Cargo.toml b/Cargo.toml @@ -35,12 +35,14 @@ radroots_runtime_distribution = { path = "../lib/crates/runtime_distribution" } radroots_runtime_manager = { path = "../lib/crates/runtime_manager" } radroots_runtime_paths = { path = "../lib/crates/runtime_paths" } radroots_secret_vault = { path = "../lib/crates/secret_vault", features = ["std", "os-keyring"] } +radroots_sdk = { path = "../lib/crates/sdk", default-features = false, features = ["std", "serde", "serde_json", "radrootsd-client"] } radroots_sql_core = { path = "../lib/crates/sql_core", features = ["native"] } radroots_trade = { path = "../lib/crates/trade" } reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "2.0" +tokio = { version = "1", features = ["net", "rt-multi-thread", "time"] } toml = "0.8" url = "2.5" zeroize = "1.8" diff --git a/src/runtime/daemon.rs b/src/runtime/daemon.rs @@ -2,6 +2,10 @@ use std::time::Duration; use radroots_events::listing::RadrootsListing; use radroots_events::trade::RadrootsTradeOrder; +use radroots_sdk::{ + RadrootsSdkConfig, RadrootsdAuth, SdkPublishError, SdkRadrootsdListingPublishOptions, + SdkRadrootsdSignerAuthority, SdkRadrootsdSignerSessionRef, SdkTransportMode, SignerConfig, +}; use reqwest::blocking::Client; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; @@ -98,8 +102,6 @@ struct BridgeJobRemote { signer_mode: String, #[serde(default)] signer_session_id: Option<String>, - #[serde(default)] - event_kind: Option<u32>, event_id: Option<String>, event_addr: Option<String>, delivery_policy: String, @@ -391,30 +393,35 @@ pub fn bridge_listing_publish( signer_session_id: Option<&str>, signer_authority: Option<&ActorWriteSignerAuthority>, ) -> Result<BridgeListingPublishResult, DaemonRpcError> { - let target = actor_write_target(config)?; - let response: BridgePublishResponseRemote = call( - &target, - "bridge.listing.publish", - Some(serde_json::json!({ - "listing": listing, - "kind": kind, - "idempotency_key": idempotency_key, - "signer_session_id": signer_session_id, - "signer_authority": signer_authority, - })), - RpcAuthMode::BridgeBearer, - )?; - Ok(BridgeListingPublishResult { - deduplicated: response.deduplicated, - job_id: response.job.job_id, - idempotency_key: response.job.idempotency_key, - status: response.job.status, - signer_mode: response.job.signer_mode, - signer_session_id: response.job.signer_session_id, - event_kind: response.job.event_kind, - event_id: response.job.event_id, - event_addr: response.job.event_addr, - }) + if kind != 30402 { + return Err(DaemonRpcError::External(format!( + "sdk listing publish only supports kind 30402, got {kind}" + ))); + } + + let Some(signer_session_id) = signer_session_id else { + return Err(DaemonRpcError::Unconfigured( + "listing publish requires a signer session id".to_owned(), + )); + }; + + let sdk = actor_write_sdk_client(config)?; + let session = SdkRadrootsdSignerSessionRef::from_session_id(signer_session_id.to_owned()); + let mut options = SdkRadrootsdListingPublishOptions::from_signer_session_ref(&session); + if let Some(idempotency_key) = idempotency_key { + options = options.with_idempotency_key(idempotency_key.to_owned()); + } + if let Some(signer_authority) = signer_authority { + options = options.with_signer_authority(sdk_signer_authority(signer_authority)); + } + + let receipt = block_on_sdk(sdk.listing().publish_listing_via_radrootsd_with_options( + listing, + &options, + ))? + .map_err(map_sdk_publish_error)?; + + map_listing_publish_receipt(receipt, idempotency_key) } pub fn bridge_order_request( @@ -504,6 +511,86 @@ fn default_target(config: &RuntimeConfig) -> RpcTarget { } } +fn actor_write_sdk_client(config: &RuntimeConfig) -> Result<radroots_sdk::RadrootsSdkClient, DaemonRpcError> { + let target = actor_write_target(config)?; + let mut sdk_config = RadrootsSdkConfig::custom(); + sdk_config.transport = SdkTransportMode::Radrootsd; + sdk_config.signer = SignerConfig::Nip46; + sdk_config.radrootsd.endpoint = Some(target.url); + let Some(bridge_bearer_token) = target.bridge_bearer_token else { + return Err(DaemonRpcError::Unconfigured( + "actor write plane target is missing a bridge bearer token".to_owned(), + )); + }; + sdk_config.radrootsd.auth = RadrootsdAuth::BearerToken(bridge_bearer_token); + radroots_sdk::RadrootsSdkClient::from_config(sdk_config) + .map_err(|err| DaemonRpcError::Unconfigured(err.to_string())) +} + +fn sdk_signer_authority(value: &ActorWriteSignerAuthority) -> SdkRadrootsdSignerAuthority { + SdkRadrootsdSignerAuthority { + provider_runtime_id: value.provider_runtime_id.clone(), + account_identity_id: value.account_identity_id.clone(), + provider_signer_session_id: value.provider_signer_session_id.clone(), + } +} + +fn map_sdk_publish_error(error: SdkPublishError) -> DaemonRpcError { + match error { + SdkPublishError::Config(err) => DaemonRpcError::Unconfigured(err.to_string()), + SdkPublishError::Radrootsd(message) => DaemonRpcError::Remote(message), + other => DaemonRpcError::External(other.to_string()), + } +} + +fn map_listing_publish_receipt( + receipt: radroots_sdk::SdkPublishReceipt, + idempotency_key: Option<&str>, +) -> Result<BridgeListingPublishResult, DaemonRpcError> { + let radroots_sdk::SdkTransportReceipt::Radrootsd(transport_receipt) = receipt.transport_receipt else { + return Err(DaemonRpcError::InvalidResponse( + "sdk listing publish returned a non-radrootsd transport receipt".to_owned(), + )); + }; + let Some(job_id) = transport_receipt.job_id else { + return Err(DaemonRpcError::InvalidResponse( + "sdk listing publish did not return a job id".to_owned(), + )); + }; + let Some(status) = transport_receipt.status else { + return Err(DaemonRpcError::InvalidResponse( + "sdk listing publish did not return a job status".to_owned(), + )); + }; + let Some(signer_mode) = transport_receipt.signer_mode else { + return Err(DaemonRpcError::InvalidResponse( + "sdk listing publish did not return a signer mode".to_owned(), + )); + }; + Ok(BridgeListingPublishResult { + deduplicated: transport_receipt.deduplicated, + job_id, + idempotency_key: idempotency_key.map(str::to_owned), + status, + signer_mode, + signer_session_id: transport_receipt.signer_session_id, + event_kind: receipt.event_kind, + event_id: receipt.event_id, + event_addr: transport_receipt.event_addr, + }) +} + +fn block_on_sdk<F, T>(future: F) -> Result<T, DaemonRpcError> +where + F: std::future::Future<Output = T>, +{ + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .map_err(|err| DaemonRpcError::External(format!("build sdk runtime: {err}")))?; + Ok(runtime.block_on(future)) +} + pub fn resolve_signer_session_id( config: &RuntimeConfig, actor_role: &str,