lib

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

commit a0b0c1ae5ddc5ea36885339878f4e562a6c87e93
parent c804f77f4ef827a05a4c4b6d1ba8f75f58902315
Author: triesap <tyson@radroots.org>
Date:   Tue, 26 May 2026 02:07:33 +0000

sdk: publish order decisions via relay

Diffstat:
Mcrates/sdk/src/client.rs | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/sdk/src/trade.rs | 40++++++++++++++++++++++++++++++++++++++++
Mcrates/sdk/tests/relay_direct.rs | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 246 insertions(+), 0 deletions(-)

diff --git a/crates/sdk/src/client.rs b/crates/sdk/src/client.rs @@ -2309,6 +2309,16 @@ impl<'a> TradeClient<'a> { } #[cfg(feature = "serde_json")] + pub fn build_order_decision_draft( + &self, + root_event_id: &str, + prev_event_id: &str, + payload: &trade::RadrootsTradeOrderDecisionEvent, + ) -> Result<trade::RadrootsTradeOrderDecisionDraft, trade::EventEncodeError> { + trade::build_order_decision_draft(root_event_id, prev_event_id, payload) + } + + #[cfg(feature = "serde_json")] pub fn parse_order_request( &self, event: &RadrootsNostrEvent, @@ -2319,6 +2329,17 @@ impl<'a> TradeClient<'a> { trade::parse_order_request(event) } + #[cfg(feature = "serde_json")] + pub fn parse_order_decision( + &self, + event: &RadrootsNostrEvent, + ) -> Result< + trade::RadrootsActiveTradeEnvelope<trade::RadrootsTradeOrderDecisionEvent>, + trade::RadrootsActiveTradeEnvelopeParseError, + > { + trade::parse_order_decision(event) + } + #[cfg(all( feature = "identity-models", feature = "relay-client", @@ -2346,6 +2367,29 @@ impl<'a> TradeClient<'a> { feature = "relay-client", feature = "signing" ))] + pub async fn publish_order_decision_with_identity( + &self, + identity: &RadrootsIdentity, + root_event_id: &str, + prev_event_id: &str, + payload: &trade::RadrootsTradeOrderDecisionEvent, + ) -> Result<SdkPublishReceipt, SdkPublishError> { + let draft = trade::build_order_decision_draft(root_event_id, prev_event_id, payload) + .map_err(|err| SdkPublishError::Encode(err.to_string()))?; + self.client + .publish_parts_via_relay_with_identity( + identity, + draft.into_wire_parts(), + "trade.publish_order_decision_with_identity", + ) + .await + } + + #[cfg(all( + feature = "identity-models", + feature = "relay-client", + feature = "signing" + ))] pub async fn publish_order_request_draft_with_identity( &self, identity: &RadrootsIdentity, @@ -2360,6 +2404,25 @@ impl<'a> TradeClient<'a> { .await } + #[cfg(all( + feature = "identity-models", + feature = "relay-client", + feature = "signing" + ))] + pub async fn publish_order_decision_draft_with_identity( + &self, + identity: &RadrootsIdentity, + draft: trade::RadrootsTradeOrderDecisionDraft, + ) -> Result<SdkPublishReceipt, SdkPublishError> { + self.client + .publish_parts_via_relay_with_identity( + identity, + draft.into_wire_parts(), + "trade.publish_order_decision_draft_with_identity", + ) + .await + } + #[cfg(feature = "radrootsd-client")] pub async fn publish_order_request_via_radrootsd( &self, diff --git a/crates/sdk/src/trade.rs b/crates/sdk/src/trade.rs @@ -15,6 +15,11 @@ pub struct RadrootsTradeOrderRequestDraft { parts: WireEventParts, } +#[derive(Debug, Clone)] +pub struct RadrootsTradeOrderDecisionDraft { + parts: WireEventParts, +} + impl RadrootsTradeOrderRequestDraft { pub fn as_wire_parts(&self) -> &WireEventParts { &self.parts @@ -25,6 +30,16 @@ impl RadrootsTradeOrderRequestDraft { } } +impl RadrootsTradeOrderDecisionDraft { + pub fn as_wire_parts(&self) -> &WireEventParts { + &self.parts + } + + pub fn into_wire_parts(self) -> WireEventParts { + self.parts + } +} + #[cfg(feature = "serde_json")] pub fn build_envelope_draft( recipient_pubkey: impl Into<String>, @@ -62,6 +77,21 @@ pub fn build_order_request_draft( } #[cfg(feature = "serde_json")] +pub fn build_order_decision_draft( + root_event_id: &str, + prev_event_id: &str, + payload: &RadrootsTradeOrderDecisionEvent, +) -> Result<RadrootsTradeOrderDecisionDraft, EventEncodeError> { + Ok(RadrootsTradeOrderDecisionDraft { + parts: radroots_events_codec::trade::active_trade_order_decision_event_build( + root_event_id, + prev_event_id, + payload, + )?, + }) +} + +#[cfg(feature = "serde_json")] pub fn parse_envelope( event: &RadrootsNostrEvent, ) -> Result<SdkTradeEnvelope, RadrootsTradeEnvelopeParseError> { @@ -79,6 +109,16 @@ pub fn parse_order_request( } #[cfg(feature = "serde_json")] +pub fn parse_order_decision( + event: &RadrootsNostrEvent, +) -> Result< + RadrootsActiveTradeEnvelope<RadrootsTradeOrderDecisionEvent>, + RadrootsActiveTradeEnvelopeParseError, +> { + radroots_events_codec::trade::active_trade_order_decision_from_event(event) +} + +#[cfg(feature = "serde_json")] pub fn parse_listing_address( listing_addr: &str, ) -> Result<RadrootsTradeListingAddress, RadrootsTradeListingAddressError> { diff --git a/crates/sdk/tests/relay_direct.rs b/crates/sdk/tests/relay_direct.rs @@ -19,6 +19,7 @@ use radroots_sdk::listing::{ }; use radroots_sdk::profile::{RadrootsProfile, RadrootsProfileType}; use radroots_sdk::trade::{ + RadrootsTradeInventoryCommitment, RadrootsTradeOrderDecision, RadrootsTradeOrderDecisionEvent, RadrootsTradeOrderEconomicItem, RadrootsTradeOrderEconomics, RadrootsTradeOrderItem, RadrootsTradeOrderRequested, RadrootsTradePricingBasis, }; @@ -252,6 +253,24 @@ fn sample_order_request( } } +fn sample_order_decision( + buyer_pubkey: String, + seller_pubkey: String, +) -> RadrootsTradeOrderDecisionEvent { + RadrootsTradeOrderDecisionEvent { + order_id: "order-1".into(), + listing_addr: format!("30402:{seller_pubkey}:AAAAAAAAAAAAAAAAAAAAAg"), + buyer_pubkey, + seller_pubkey, + decision: RadrootsTradeOrderDecision::Accepted { + inventory_commitments: vec![RadrootsTradeInventoryCommitment { + bin_id: "bin-1".into(), + bin_count: 2, + }], + }, + } +} + #[tokio::test] async fn relay_direct_farm_publish_accepts_sdk_built_draft() -> TestResult<()> { let relay = AckRelay::spawn().await?; @@ -390,6 +409,130 @@ async fn relay_direct_order_request_publish_accepts_sdk_built_draft() -> TestRes } #[tokio::test] +async fn relay_direct_order_decision_publish_accepts_sdk_built_draft() -> TestResult<()> { + let relay = AckRelay::spawn().await?; + let buyer_identity = RadrootsIdentity::generate(); + let seller_identity = RadrootsIdentity::generate(); + let root_event_id = "order-request-event-1"; + let payload = sample_order_decision( + buyer_identity.public_key_hex(), + seller_identity.public_key_hex(), + ); + let mut config = RadrootsSdkConfig::for_environment(SdkEnvironment::Custom); + config.transport = SdkTransportMode::RelayDirect; + config.signer = SignerConfig::LocalIdentity; + config.relay = RelayConfig { + urls: vec![relay.url().to_owned()], + }; + let client = RadrootsSdkClient::from_config(config)?; + let draft = + client + .trade() + .build_order_decision_draft(root_event_id, root_event_id, &payload)?; + assert_eq!(draft.as_wire_parts().kind, 3423); + + let receipt = client + .trade() + .publish_order_decision_draft_with_identity(&seller_identity, draft) + .await?; + + assert_eq!(receipt.transport, SdkTransportMode::RelayDirect); + assert_eq!(receipt.event_kind, Some(3423)); + assert!(receipt.event_id.is_some()); + match receipt.transport_receipt { + SdkTransportReceipt::RelayDirect(relay_receipt) => { + assert_eq!( + receipt.event_id.as_deref(), + Some(relay_receipt.event_id.as_str()) + ); + assert_eq!(receipt.event_kind, Some(relay_receipt.event_kind)); + assert_eq!(relay_receipt.event.kind, 3423); + assert_eq!(relay_receipt.event.author, seller_identity.public_key_hex()); + assert!( + relay_receipt + .event + .tags + .contains(&vec!["p".to_owned(), buyer_identity.public_key_hex()]) + ); + assert!( + relay_receipt + .event + .tags + .contains(&vec!["a".to_owned(), payload.listing_addr.clone()]) + ); + assert!( + relay_receipt + .event + .tags + .contains(&vec!["d".to_owned(), payload.order_id.clone()]) + ); + assert!( + relay_receipt + .event + .tags + .contains(&vec!["e_root".to_owned(), root_event_id.to_owned(),]) + ); + assert!( + relay_receipt + .event + .tags + .contains(&vec!["e_prev".to_owned(), root_event_id.to_owned(),]) + ); + assert_eq!(relay_receipt.target_relays, vec![relay.url().to_owned()]); + assert_eq!(relay_receipt.connected_relays, vec![relay.url().to_owned()]); + assert_eq!( + relay_receipt.acknowledged_relays, + vec![relay.url().to_owned()] + ); + assert!(relay_receipt.failed_relays.is_empty()); + let envelope = client + .trade() + .parse_order_decision(&relay_receipt.event) + .expect("active order decision"); + assert_eq!(envelope.order_id, payload.order_id); + assert_eq!(envelope.listing_addr, payload.listing_addr); + assert_eq!(envelope.payload.decision, payload.decision); + } + SdkTransportReceipt::Radrootsd(_) => panic!("unexpected radrootsd receipt"), + } + + Ok(()) +} + +#[tokio::test] +async fn relay_direct_order_decision_publish_builds_and_publishes_payload() -> TestResult<()> { + let relay = AckRelay::spawn().await?; + let buyer_identity = RadrootsIdentity::generate(); + let seller_identity = RadrootsIdentity::generate(); + let payload = sample_order_decision( + buyer_identity.public_key_hex(), + seller_identity.public_key_hex(), + ); + let mut config = RadrootsSdkConfig::for_environment(SdkEnvironment::Custom); + config.transport = SdkTransportMode::RelayDirect; + config.signer = SignerConfig::LocalIdentity; + config.relay = RelayConfig { + urls: vec![relay.url().to_owned()], + }; + let client = RadrootsSdkClient::from_config(config)?; + + let receipt = client + .trade() + .publish_order_decision_with_identity( + &seller_identity, + "order-request-event-1", + "order-request-event-1", + &payload, + ) + .await?; + + assert_eq!(receipt.transport, SdkTransportMode::RelayDirect); + assert_eq!(receipt.event_kind, Some(3423)); + + Ok(()) +} + +#[tokio::test] async fn relay_direct_order_request_publish_builds_and_publishes_payload() -> TestResult<()> { let relay = AckRelay::spawn().await?; let buyer_identity = RadrootsIdentity::generate();