lib

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

commit f5e7b656ce9d3dac3bc8281ee09088cc5c7a1a81
parent e0e4f14d2db5e35027235b7f5c4095884bce420d
Author: triesap <tyson@radroots.org>
Date:   Sun,  5 Jul 2026 02:54:22 +0000

status: add root-specific trade selectors

Diffstat:
Mcrates/sdk/src/lib.rs | 12++++++------
Mcrates/sdk/src/orders_runtime.rs | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mcrates/sdk/tests/orders_runtime.rs | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 120 insertions(+), 10 deletions(-)

diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs @@ -104,12 +104,12 @@ pub use crate::market_runtime::{ #[cfg(feature = "runtime")] pub use crate::orders_runtime::{ SdkTradeStatusIssue, SdkTradeStatusIssueKind, SdkTradeStatusSource, TRADE_STATUS_DEFAULT_LIMIT, - TRADE_STATUS_MAX_LIMIT, TradeEvidenceBranchReceipt, TradeEvidenceIngestReceipt, - TradeEvidenceIngestRequest, TradeEvidenceQueryBranch, TradeEvidenceQueryBranchKind, - TradeEvidenceQueryPlan, TradeEvidenceRelayFilter, TradeEvidenceRelayTagFilter, - TradeRequestEvidenceIngestReceipt, TradeRequestEvidenceIngestRequest, - TradeResyncEventImportReceipt, TradeResyncEvidenceReceipt, TradeResyncReceipt, - TradeResyncRelayOutcomeKind, TradeResyncRelayOutcomeReceipt, + TRADE_STATUS_MAX_LIMIT, TRADE_STATUS_ROOT_SELECTOR_SEPARATOR, TradeEvidenceBranchReceipt, + TradeEvidenceIngestReceipt, TradeEvidenceIngestRequest, TradeEvidenceQueryBranch, + TradeEvidenceQueryBranchKind, TradeEvidenceQueryPlan, TradeEvidenceRelayFilter, + TradeEvidenceRelayTagFilter, TradeRequestEvidenceIngestReceipt, + TradeRequestEvidenceIngestRequest, TradeResyncEventImportReceipt, TradeResyncEvidenceReceipt, + TradeResyncReceipt, TradeResyncRelayOutcomeKind, TradeResyncRelayOutcomeReceipt, TradeResyncRelayTransportOutcomeKind, TradeResyncRequest, TradeSellerInboxReceipt, TradeSellerInboxRequest, TradeStatusAmbiguityCandidate, TradeStatusEligibility, TradeStatusEvidenceSummary, TradeStatusKind, TradeStatusNextActionKind, TradeStatusReceipt, diff --git a/crates/sdk/src/orders_runtime.rs b/crates/sdk/src/orders_runtime.rs @@ -114,6 +114,8 @@ use std::collections::{BTreeMap, BTreeSet}; pub const TRADE_STATUS_DEFAULT_LIMIT: u32 = 500; #[cfg(feature = "runtime")] pub const TRADE_STATUS_MAX_LIMIT: u32 = 1_000; +#[cfg(feature = "runtime")] +pub const TRADE_STATUS_ROOT_SELECTOR_SEPARATOR: char = '@'; #[cfg(any(feature = "signer-adapters", test))] pub const TRADE_SUBMIT_OPERATION_KIND: &str = "trade.submit.v1"; #[cfg(any(feature = "signer-adapters", test))] @@ -1965,11 +1967,35 @@ impl TradeStatusRequest { } } - pub fn parse(order_id: &str) -> Result<Self, RadrootsSdkError> { - RadrootsOrderId::parse(order_id) + pub fn parse(selector: &str) -> Result<Self, RadrootsSdkError> { + let (trade_id, root_event_id) = trade_status_selector_parts(selector)?; + let locator = RadrootsOrderId::parse(trade_id) .map(RadrootsTradeLocator::from_order_id) - .map(Self::new) - .map_err(|error| RadrootsSdkError::invalid_trade_id(order_id, error.to_string())) + .map_err(|error| RadrootsSdkError::invalid_trade_id(trade_id, error.to_string()))?; + let locator = match root_event_id { + Some(root_event_id) => { + locator.with_root_event_id(root_event_id.parse().map_err(|error| { + RadrootsSdkError::InvalidRequest { + message: format!( + "invalid trade status root selector `{selector}`: {error}" + ), + } + })?) + } + None => locator, + }; + Ok(Self::new(locator)) + } + + pub fn locator_selector(locator: &RadrootsTradeLocator) -> String { + match locator.root_event_id.as_ref() { + Some(root_event_id) => format!( + "{}{TRADE_STATUS_ROOT_SELECTOR_SEPARATOR}{}", + locator.trade_id.as_str(), + root_event_id.as_str() + ), + None => locator.trade_id.as_str().to_owned(), + } } pub fn with_limit(mut self, limit: u32) -> Self { @@ -2015,6 +2041,30 @@ impl TradeStatusRequest { } #[cfg(feature = "runtime")] +fn trade_status_selector_parts(selector: &str) -> Result<(&str, Option<&str>), RadrootsSdkError> { + let selector = selector.trim(); + if selector.is_empty() { + return Err(RadrootsSdkError::invalid_trade_id( + selector, + "empty trade id", + )); + } + let Some((trade_id, root_event_id)) = selector.split_once(TRADE_STATUS_ROOT_SELECTOR_SEPARATOR) + else { + return Ok((selector, None)); + }; + if trade_id.trim().is_empty() + || root_event_id.trim().is_empty() + || root_event_id.contains(TRADE_STATUS_ROOT_SELECTOR_SEPARATOR) + { + return Err(RadrootsSdkError::InvalidRequest { + message: format!("invalid trade status selector `{selector}`"), + }); + } + Ok((trade_id, Some(root_event_id))) +} + +#[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] pub struct TradeStatusReceipt { pub locator: RadrootsTradeLocator, diff --git a/crates/sdk/tests/orders_runtime.rs b/crates/sdk/tests/orders_runtime.rs @@ -6031,6 +6031,35 @@ fn order_status_parse_rejects_invalid_order_ids() { assert!(matches!(error, RadrootsSdkError::InvalidTradeId { .. })); } +#[test] +fn order_status_parse_accepts_root_specific_selectors() { + let root_event_id = deterministic_event_id("order-status-root-selector"); + let request = TradeStatusRequest::parse(&format!("order-1@{}", root_event_id.as_str())) + .expect("root-specific status request"); + + assert_eq!(request.locator.order_id().as_str(), "order-1"); + assert_eq!( + request + .locator + .root_event_id + .as_ref() + .map(RadrootsEventId::as_str), + Some(root_event_id.as_str()) + ); + assert_eq!( + TradeStatusRequest::locator_selector(&request.locator), + format!("order-1@{}", root_event_id.as_str()) + ); +} + +#[test] +fn order_status_parse_rejects_malformed_root_selectors() { + for selector in ["order-1@", "@aaaaaaaa", "order-1@aaaaaaaa@bbbbbbbb"] { + let error = TradeStatusRequest::parse(selector).expect_err("malformed root selector"); + assert!(matches!(error, RadrootsSdkError::InvalidRequest { .. })); + } +} + #[tokio::test] async fn order_status_contract_dtos_serialize_deterministically() { let (_tempdir, sdk, _store) = directory_sdk_and_store().await; @@ -6662,6 +6691,37 @@ async fn order_status_reports_root_ambiguity_for_reused_trade_ids() { second_request_event.id.as_str() ] ); + assert_eq!( + receipt + .ambiguity_candidates + .iter() + .map(|candidate| TradeStatusRequest::locator_selector(&candidate.locator)) + .collect::<Vec<_>>(), + vec![ + format!("order-1@{}", first_request_event.id.as_str()), + format!("order-1@{}", second_request_event.id.as_str()) + ] + ); + + let root_specific = sdk + .trades() + .status( + TradeStatusRequest::parse(&format!("order-1@{}", second_request_event.id.as_str())) + .expect("root status request"), + ) + .await + .expect("root-specific status"); + + assert!(root_specific.found); + assert_eq!(root_specific.status, TradeStatusKind::Requested); + assert_eq!( + root_specific + .request_event_id + .as_ref() + .map(RadrootsEventId::as_str), + Some(second_request_event.id.as_str()) + ); + assert!(root_specific.ambiguity_candidates.is_empty()); } #[cfg(feature = "signer-adapters")]