commit 89a9a563a7d1a6e7a2e72fcc64d2fff9fed2c1cc
parent 233f9fc50f5571d517d07be8564a282faeb959ee
Author: triesap <tyson@radroots.org>
Date: Mon, 13 Apr 2026 07:48:53 +0000
sdk: add radrootsd signer session handles
Diffstat:
5 files changed, 481 insertions(+), 44 deletions(-)
diff --git a/crates/sdk/src/adapters/radrootsd.rs b/crates/sdk/src/adapters/radrootsd.rs
@@ -7,7 +7,7 @@ use crate::listing;
use crate::listing::RadrootsListing;
use radroots_events::kinds::KIND_LISTING;
use reqwest::header::{AUTHORIZATION, CONTENT_TYPE, HeaderMap, HeaderValue};
-use serde::{Deserialize, Serialize};
+use serde::{Deserialize, Serialize, de::DeserializeOwned};
use serde_json::{Value, json};
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
@@ -34,6 +34,59 @@ impl fmt::Debug for SdkRadrootsdSignerAuthority {
}
}
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum SdkRadrootsdSignerSessionMode {
+ #[serde(alias = "bunker")]
+ Bunker,
+ #[serde(alias = "nostrconnect")]
+ Nostrconnect,
+}
+
+#[derive(Clone, PartialEq, Eq, Serialize)]
+pub struct SdkRadrootsdSignerSessionConnectRequest {
+ pub url: String,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub client_secret_key: Option<String>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub signer_authority: Option<SdkRadrootsdSignerAuthority>,
+}
+
+impl SdkRadrootsdSignerSessionConnectRequest {
+ pub fn bunker(url: impl Into<String>) -> Self {
+ Self {
+ url: url.into(),
+ client_secret_key: None,
+ signer_authority: None,
+ }
+ }
+
+ pub fn nostrconnect(url: impl Into<String>, client_secret_key: impl Into<String>) -> Self {
+ Self {
+ url: url.into(),
+ client_secret_key: Some(client_secret_key.into()),
+ signer_authority: None,
+ }
+ }
+
+ pub fn with_signer_authority(mut self, signer_authority: SdkRadrootsdSignerAuthority) -> Self {
+ self.signer_authority = Some(signer_authority);
+ self
+ }
+}
+
+impl fmt::Debug for SdkRadrootsdSignerSessionConnectRequest {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let mut debug = f.debug_struct("SdkRadrootsdSignerSessionConnectRequest");
+ debug.field("url", &self.url);
+ debug.field(
+ "client_secret_key",
+ &self.client_secret_key.as_ref().map(|_| "<redacted>"),
+ );
+ debug.field("signer_authority", &self.signer_authority);
+ debug.finish()
+ }
+}
+
#[derive(Clone, Serialize)]
pub struct SdkRadrootsdListingPublishRequest {
pub listing: RadrootsListing,
@@ -80,6 +133,15 @@ impl SdkRadrootsdListingPublishRequest {
}
}
+#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
+pub(crate) struct SdkRadrootsdSignerSessionConnectResponse {
+ pub session_id: String,
+ pub mode: SdkRadrootsdSignerSessionMode,
+ pub remote_signer_pubkey: String,
+ pub client_pubkey: String,
+ pub relays: Vec<String>,
+}
+
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct SdkRadrootsdBridgePublishResponse {
pub deduplicated: bool,
@@ -168,6 +230,69 @@ pub async fn publish_listing(
request: &SdkRadrootsdListingPublishRequest,
timeout: Duration,
) -> Result<SdkRadrootsdBridgePublishResponse, RadrootsdError> {
+ jsonrpc_call(
+ endpoint,
+ auth,
+ "radroots-sdk-listing-publish",
+ "bridge.listing.publish",
+ request,
+ timeout,
+ )
+ .await
+}
+
+pub(crate) async fn connect_signer_session(
+ endpoint: &str,
+ auth: &RadrootsdAuth,
+ request: &SdkRadrootsdSignerSessionConnectRequest,
+ timeout: Duration,
+) -> Result<SdkRadrootsdSignerSessionConnectResponse, RadrootsdError> {
+ jsonrpc_call(
+ endpoint,
+ auth,
+ "radroots-sdk-nip46-connect",
+ "nip46.connect",
+ request,
+ timeout,
+ )
+ .await
+}
+
+fn auth_headers(auth: &RadrootsdAuth) -> Result<HeaderMap, RadrootsdError> {
+ let mut headers = HeaderMap::new();
+ match auth {
+ RadrootsdAuth::None => Ok(headers),
+ RadrootsdAuth::BearerToken(token) => {
+ let value = HeaderValue::from_str(format!("Bearer {token}").as_str())
+ .map_err(|err| RadrootsdError::InvalidAuthHeader(err.to_string()))?;
+ headers.insert(AUTHORIZATION, value);
+ Ok(headers)
+ }
+ }
+}
+
+pub fn bridge_listing_publish_request_json(
+ request: &SdkRadrootsdListingPublishRequest,
+) -> Result<Value, RadrootsdError> {
+ serde_json::to_value(request).map_err(|err| {
+ RadrootsdError::MalformedResponse(format!(
+ "serialize radrootsd listing publish request: {err}"
+ ))
+ })
+}
+
+async fn jsonrpc_call<P, R>(
+ endpoint: &str,
+ auth: &RadrootsdAuth,
+ request_id: &str,
+ method: &str,
+ params: &P,
+ timeout: Duration,
+) -> Result<R, RadrootsdError>
+where
+ P: Serialize + ?Sized,
+ R: DeserializeOwned,
+{
let client = reqwest::Client::builder()
.timeout(timeout)
.build()
@@ -177,16 +302,17 @@ pub async fn publish_listing(
.headers(auth_headers(auth)?)
.json(&json!({
"jsonrpc": "2.0",
- "id": "radroots-sdk-listing-publish",
- "method": "bridge.listing.publish",
- "params": request,
+ "id": request_id,
+ "method": method,
+ "params": params,
}));
request_builder = request_builder.header(CONTENT_TYPE, "application/json");
- let response = request_builder.send().await.map_err(|err| {
- RadrootsdError::Http(format!("send radrootsd listing publish request: {err}"))
- })?;
+ let response = request_builder
+ .send()
+ .await
+ .map_err(|err| RadrootsdError::Http(format!("send radrootsd {method} request: {err}")))?;
let status = response.status();
let body = response
.text()
@@ -201,47 +327,21 @@ pub async fn publish_listing(
)));
}
- let envelope: JsonRpcEnvelope<SdkRadrootsdBridgePublishResponse> =
- serde_json::from_str(body.as_str()).map_err(|err| {
- RadrootsdError::MalformedResponse(format!(
- "decode radrootsd bridge.listing.publish response: {err}"
- ))
- })?;
+ let envelope: JsonRpcEnvelope<R> = serde_json::from_str(body.as_str()).map_err(|err| {
+ RadrootsdError::MalformedResponse(format!("decode radrootsd {method} response: {err}"))
+ })?;
match (envelope.result, envelope.error) {
(Some(result), None) => Ok(result),
(None, Some(error)) => Err(RadrootsdError::JsonRpc(format!(
- "radrootsd bridge.listing.publish failed {}: {}",
+ "radrootsd {method} failed {}: {}",
error.code, error.message
))),
(Some(_), Some(error)) => Err(RadrootsdError::MalformedResponse(format!(
- "radrootsd bridge.listing.publish returned result and error: {} {}",
+ "radrootsd {method} returned result and error: {} {}",
error.code, error.message
))),
- (None, None) => Err(RadrootsdError::MalformedResponse(
- "radrootsd bridge.listing.publish returned neither result nor error".to_owned(),
- )),
- }
-}
-
-fn auth_headers(auth: &RadrootsdAuth) -> Result<HeaderMap, RadrootsdError> {
- let mut headers = HeaderMap::new();
- match auth {
- RadrootsdAuth::None => Ok(headers),
- RadrootsdAuth::BearerToken(token) => {
- let value = HeaderValue::from_str(format!("Bearer {token}").as_str())
- .map_err(|err| RadrootsdError::InvalidAuthHeader(err.to_string()))?;
- headers.insert(AUTHORIZATION, value);
- Ok(headers)
- }
+ (None, None) => Err(RadrootsdError::MalformedResponse(format!(
+ "radrootsd {method} returned neither result nor error"
+ ))),
}
}
-
-pub fn bridge_listing_publish_request_json(
- request: &SdkRadrootsdListingPublishRequest,
-) -> Result<Value, RadrootsdError> {
- serde_json::to_value(request).map_err(|err| {
- RadrootsdError::MalformedResponse(format!(
- "serialize radrootsd listing publish request: {err}"
- ))
- })
-}
diff --git a/crates/sdk/src/client.rs b/crates/sdk/src/client.rs
@@ -177,6 +177,105 @@ impl core::fmt::Display for SdkPublishError {
impl std::error::Error for SdkPublishError {}
#[cfg(feature = "radrootsd-client")]
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum SdkRadrootsdSessionError {
+ Config(SdkConfigError),
+ UnsupportedTransport {
+ transport: SdkTransportMode,
+ operation: &'static str,
+ },
+ Radrootsd(String),
+}
+
+#[cfg(feature = "radrootsd-client")]
+impl From<SdkConfigError> for SdkRadrootsdSessionError {
+ fn from(value: SdkConfigError) -> Self {
+ Self::Config(value)
+ }
+}
+
+#[cfg(feature = "radrootsd-client")]
+impl fmt::Display for SdkRadrootsdSessionError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Config(err) => write!(f, "{err}"),
+ Self::UnsupportedTransport {
+ transport,
+ operation,
+ } => {
+ write!(
+ f,
+ "{operation} requires a different sdk transport mode than {transport:?}"
+ )
+ }
+ Self::Radrootsd(message) => write!(f, "{message}"),
+ }
+ }
+}
+
+#[cfg(all(feature = "radrootsd-client", feature = "std"))]
+impl std::error::Error for SdkRadrootsdSessionError {}
+
+#[cfg(feature = "radrootsd-client")]
+#[derive(Clone, PartialEq, Eq)]
+pub struct SdkRadrootsdSignerSessionHandle {
+ session_id: String,
+ mode: radrootsd::SdkRadrootsdSignerSessionMode,
+ remote_signer_pubkey: String,
+ client_pubkey: String,
+ relays: Vec<String>,
+}
+
+#[cfg(feature = "radrootsd-client")]
+impl fmt::Debug for SdkRadrootsdSignerSessionHandle {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let mut debug = f.debug_struct("SdkRadrootsdSignerSessionHandle");
+ debug.field("session_id", &"<redacted>");
+ debug.field("mode", &self.mode);
+ debug.field("remote_signer_pubkey", &self.remote_signer_pubkey);
+ debug.field("client_pubkey", &self.client_pubkey);
+ debug.field("relays", &self.relays);
+ debug.finish()
+ }
+}
+
+#[cfg(feature = "radrootsd-client")]
+impl SdkRadrootsdSignerSessionHandle {
+ pub fn mode(&self) -> radrootsd::SdkRadrootsdSignerSessionMode {
+ self.mode
+ }
+
+ pub fn remote_signer_pubkey(&self) -> &str {
+ self.remote_signer_pubkey.as_str()
+ }
+
+ pub fn client_pubkey(&self) -> &str {
+ self.client_pubkey.as_str()
+ }
+
+ pub fn relays(&self) -> &[String] {
+ self.relays.as_slice()
+ }
+
+ pub(crate) fn session_id(&self) -> &str {
+ self.session_id.as_str()
+ }
+}
+
+#[cfg(feature = "radrootsd-client")]
+impl From<radrootsd::SdkRadrootsdSignerSessionConnectResponse> for SdkRadrootsdSignerSessionHandle {
+ fn from(value: radrootsd::SdkRadrootsdSignerSessionConnectResponse) -> Self {
+ Self {
+ session_id: value.session_id,
+ mode: value.mode,
+ remote_signer_pubkey: value.remote_signer_pubkey,
+ client_pubkey: value.client_pubkey,
+ relays: value.relays,
+ }
+ }
+}
+
+#[cfg(feature = "radrootsd-client")]
#[derive(Clone, PartialEq, Eq)]
pub struct SdkRadrootsdListingPublishOptions {
pub signer_session_id: String,
@@ -191,6 +290,10 @@ impl SdkRadrootsdListingPublishOptions {
idempotency_key: None,
}
}
+
+ pub fn from_signer_session(session: &SdkRadrootsdSignerSessionHandle) -> Self {
+ Self::new(session.session_id())
+ }
}
#[cfg(feature = "radrootsd-client")]
@@ -263,6 +366,11 @@ impl RadrootsSdkClient {
TradeClient { client: self }
}
+ #[cfg(feature = "radrootsd-client")]
+ pub fn radrootsd(&self) -> RadrootsdClient<'_> {
+ RadrootsdClient { client: self }
+ }
+
#[cfg(any(
feature = "radrootsd-client",
all(
@@ -364,6 +472,113 @@ impl RadrootsSdkClient {
response,
))
}
+
+ #[cfg(feature = "radrootsd-client")]
+ async fn connect_radrootsd_signer_session(
+ &self,
+ request: &radrootsd::SdkRadrootsdSignerSessionConnectRequest,
+ ) -> Result<SdkRadrootsdSignerSessionHandle, SdkRadrootsdSessionError> {
+ if self.transport() != SdkTransportMode::Radrootsd {
+ return Err(SdkRadrootsdSessionError::UnsupportedTransport {
+ transport: self.transport(),
+ operation: "radrootsd.signer_sessions.connect",
+ });
+ }
+
+ let endpoint = match &self.resolved_transport_target {
+ SdkResolvedTransportTarget::Radrootsd { endpoint } => endpoint.as_str(),
+ SdkResolvedTransportTarget::RelayDirect { .. } => {
+ return Err(SdkRadrootsdSessionError::UnsupportedTransport {
+ transport: self.transport(),
+ operation: "radrootsd.signer_sessions.connect",
+ });
+ }
+ };
+ let response = radrootsd::connect_signer_session(
+ endpoint,
+ &self.config.radrootsd.auth,
+ request,
+ Duration::from_millis(self.config.network.timeout_ms),
+ )
+ .await
+ .map_err(|err| SdkRadrootsdSessionError::Radrootsd(err.to_string()))?;
+ Ok(response.into())
+ }
+}
+
+#[cfg(feature = "radrootsd-client")]
+#[derive(Debug, Clone, Copy)]
+pub struct RadrootsdClient<'a> {
+ client: &'a RadrootsSdkClient,
+}
+
+#[cfg(feature = "radrootsd-client")]
+impl<'a> RadrootsdClient<'a> {
+ pub fn sdk(&self) -> &'a RadrootsSdkClient {
+ self.client
+ }
+
+ pub fn transport(&self) -> SdkTransportMode {
+ self.client.transport()
+ }
+
+ pub fn signer(&self) -> SignerConfig {
+ self.client.signer()
+ }
+
+ pub fn signer_sessions(&self) -> RadrootsdSignerSessionClient<'a> {
+ RadrootsdSignerSessionClient {
+ client: self.client,
+ }
+ }
+}
+
+#[cfg(feature = "radrootsd-client")]
+#[derive(Debug, Clone, Copy)]
+pub struct RadrootsdSignerSessionClient<'a> {
+ client: &'a RadrootsSdkClient,
+}
+
+#[cfg(feature = "radrootsd-client")]
+impl<'a> RadrootsdSignerSessionClient<'a> {
+ pub fn sdk(&self) -> &'a RadrootsSdkClient {
+ self.client
+ }
+
+ pub fn transport(&self) -> SdkTransportMode {
+ self.client.transport()
+ }
+
+ pub fn signer(&self) -> SignerConfig {
+ self.client.signer()
+ }
+
+ pub async fn connect(
+ &self,
+ request: &radrootsd::SdkRadrootsdSignerSessionConnectRequest,
+ ) -> Result<SdkRadrootsdSignerSessionHandle, SdkRadrootsdSessionError> {
+ self.client.connect_radrootsd_signer_session(request).await
+ }
+
+ pub async fn connect_bunker(
+ &self,
+ url: impl Into<String>,
+ ) -> Result<SdkRadrootsdSignerSessionHandle, SdkRadrootsdSessionError> {
+ let request = radrootsd::SdkRadrootsdSignerSessionConnectRequest::bunker(url);
+ self.connect(&request).await
+ }
+
+ pub async fn connect_nostrconnect(
+ &self,
+ url: impl Into<String>,
+ client_secret_key: impl Into<String>,
+ ) -> Result<SdkRadrootsdSignerSessionHandle, SdkRadrootsdSessionError> {
+ let request = radrootsd::SdkRadrootsdSignerSessionConnectRequest::nostrconnect(
+ url,
+ client_secret_key,
+ );
+ self.connect(&request).await
+ }
}
#[derive(Debug, Clone, Copy)]
diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs
@@ -26,12 +26,20 @@ pub mod profile;
pub mod trade;
#[cfg(feature = "radrootsd-client")]
-pub use crate::client::SdkRadrootsdListingPublishOptions;
+pub use crate::adapters::radrootsd::{
+ SdkRadrootsdSignerAuthority, SdkRadrootsdSignerSessionConnectRequest,
+ SdkRadrootsdSignerSessionMode,
+};
pub use crate::client::{
FarmClient, ListingClient, ProfileClient, RadrootsSdkClient, SdkPublishError,
SdkPublishReceipt, SdkRadrootsdPublishReceipt, SdkRelayFailure, SdkRelayPublishReceipt,
SdkResolvedTransportTarget, SdkTransportReceipt, TradeClient,
};
+#[cfg(feature = "radrootsd-client")]
+pub use crate::client::{
+ RadrootsdClient, RadrootsdSignerSessionClient, SdkRadrootsdListingPublishOptions,
+ SdkRadrootsdSessionError, SdkRadrootsdSignerSessionHandle,
+};
pub use crate::config::{
NetworkConfig, RADROOTS_SDK_LOCAL_RADROOTSD_ENDPOINT, RADROOTS_SDK_LOCAL_RELAY_URL,
RADROOTS_SDK_PRODUCTION_RADROOTSD_ENDPOINT, RADROOTS_SDK_PRODUCTION_RELAY_URL,
diff --git a/crates/sdk/tests/client.rs b/crates/sdk/tests/client.rs
@@ -165,11 +165,15 @@ fn namespace_clients_reflect_explicit_transport_mode() {
assert_eq!(client.farm().transport(), SdkTransportMode::Radrootsd);
assert_eq!(client.listing().transport(), SdkTransportMode::Radrootsd);
assert_eq!(client.trade().transport(), SdkTransportMode::Radrootsd);
+ #[cfg(feature = "radrootsd-client")]
+ assert_eq!(client.radrootsd().transport(), SdkTransportMode::Radrootsd);
assert_eq!(client.signer(), SignerConfig::LocalIdentity);
assert_eq!(client.profile().signer(), SignerConfig::LocalIdentity);
assert_eq!(client.farm().signer(), SignerConfig::LocalIdentity);
assert_eq!(client.listing().signer(), SignerConfig::LocalIdentity);
assert_eq!(client.trade().signer(), SignerConfig::LocalIdentity);
+ #[cfg(feature = "radrootsd-client")]
+ assert_eq!(client.radrootsd().signer(), SignerConfig::LocalIdentity);
}
#[test]
diff --git a/crates/sdk/tests/radrootsd.rs b/crates/sdk/tests/radrootsd.rs
@@ -7,7 +7,8 @@ use radroots_core::{
use radroots_events::kinds::KIND_LISTING_DRAFT;
use radroots_sdk::adapters::radrootsd::{
SdkRadrootsdBridgeJob, SdkRadrootsdBridgePublishResponse, SdkRadrootsdListingPublishRequest,
- SdkRadrootsdSignerAuthority,
+ SdkRadrootsdSignerAuthority, SdkRadrootsdSignerSessionConnectRequest,
+ SdkRadrootsdSignerSessionMode,
};
use radroots_sdk::listing::{
RadrootsListing, RadrootsListingAvailability, RadrootsListingBin,
@@ -17,7 +18,7 @@ use radroots_sdk::listing::{
use radroots_sdk::{
RadrootsNostrEvent, RadrootsSdkClient, RadrootsSdkConfig, RadrootsdAuth, RadrootsdConfig,
SdkEnvironment, SdkPublishError, SdkRadrootsdListingPublishOptions, SdkRadrootsdPublishReceipt,
- SdkTransportMode, SdkTransportReceipt, SignerConfig,
+ SdkRadrootsdSessionError, SdkTransportMode, SdkTransportReceipt, SignerConfig,
};
use serde_json::{Value, json};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
@@ -325,6 +326,115 @@ fn radrootsd_debug_redacts_signer_session_values() {
let options_debug = format!("{options:?}");
assert!(!options_debug.contains("session-123"));
assert!(options_debug.contains("<redacted>"));
+
+ let connect_request = SdkRadrootsdSignerSessionConnectRequest::nostrconnect(
+ "nostrconnect://bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb?relay=wss%3A%2F%2Fradroots.org&secret=shared-secret",
+ "client-secret-key",
+ )
+ .with_signer_authority(SdkRadrootsdSignerAuthority {
+ provider_runtime_id: "runtime-1".to_owned(),
+ account_identity_id: "identity-1".to_owned(),
+ provider_signer_session_id: Some("provider-session-123".to_owned()),
+ });
+ let connect_request_debug = format!("{connect_request:?}");
+ assert!(!connect_request_debug.contains("client-secret-key"));
+ assert!(!connect_request_debug.contains("provider-session-123"));
+ assert!(connect_request_debug.contains("<redacted>"));
+}
+
+#[tokio::test]
+async fn radrootsd_signer_session_connect_returns_opaque_handle() -> TestResult<()> {
+ let (server, request_rx) = JsonRpcServer::spawn(
+ Some("Bearer sdk-secret"),
+ json!({
+ "jsonrpc": "2.0",
+ "id": "radroots-sdk-nip46-connect",
+ "result": {
+ "session_id": "session-123",
+ "mode": "Nostrconnect",
+ "remote_signer_pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "client_pubkey": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "relays": ["wss://radroots.org"]
+ }
+ }),
+ )
+ .await?;
+
+ let mut config = RadrootsSdkConfig::for_environment(SdkEnvironment::Production);
+ config.transport = SdkTransportMode::Radrootsd;
+ config.signer = SignerConfig::Nip46;
+ config.radrootsd = RadrootsdConfig {
+ endpoint: Some(server.endpoint().to_owned()),
+ auth: RadrootsdAuth::BearerToken("sdk-secret".to_owned()),
+ };
+ let client = RadrootsSdkClient::from_config(config)?;
+ let request = SdkRadrootsdSignerSessionConnectRequest::nostrconnect(
+ "nostrconnect://bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb?relay=wss%3A%2F%2Fradroots.org&secret=shared-secret",
+ "client-secret-key",
+ );
+
+ let handle = client
+ .radrootsd()
+ .signer_sessions()
+ .connect(&request)
+ .await?;
+ let request_json = request_rx.await?;
+
+ assert_eq!(request_json["method"], "nip46.connect");
+ assert_eq!(
+ request_json["params"]["url"],
+ "nostrconnect://bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb?relay=wss%3A%2F%2Fradroots.org&secret=shared-secret"
+ );
+ assert_eq!(
+ request_json["params"]["client_secret_key"],
+ "client-secret-key"
+ );
+ assert_eq!(handle.mode(), SdkRadrootsdSignerSessionMode::Nostrconnect);
+ assert_eq!(
+ handle.remote_signer_pubkey(),
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ );
+ assert_eq!(
+ handle.client_pubkey(),
+ "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
+ );
+ assert_eq!(handle.relays(), &["wss://radroots.org".to_owned()]);
+
+ let handle_debug = format!("{handle:?}");
+ assert!(!handle_debug.contains("session-123"));
+ assert!(handle_debug.contains("<redacted>"));
+
+ let options = SdkRadrootsdListingPublishOptions::from_signer_session(&handle);
+ let options_debug = format!("{options:?}");
+ assert!(!options_debug.contains("session-123"));
+ assert!(options_debug.contains("<redacted>"));
+
+ Ok(())
+}
+
+#[tokio::test]
+async fn radrootsd_signer_session_connect_rejects_relay_transport_mode() -> TestResult<()> {
+ let client = RadrootsSdkClient::from_config(RadrootsSdkConfig::production())?;
+ let request = SdkRadrootsdSignerSessionConnectRequest::bunker(
+ "bunker://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa?relay=wss%3A%2F%2Fradroots.org&secret=shared-secret",
+ );
+
+ let error = client
+ .radrootsd()
+ .signer_sessions()
+ .connect(&request)
+ .await
+ .expect_err("unsupported transport");
+
+ assert!(matches!(
+ error,
+ SdkRadrootsdSessionError::UnsupportedTransport {
+ transport: SdkTransportMode::RelayDirect,
+ operation: "radrootsd.signer_sessions.connect",
+ }
+ ));
+
+ Ok(())
}
#[tokio::test]