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