lib

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

commit 9eee1823f01dbc1579643d457804706bd8403264
parent 4931e0a17b37dac553c0a268d179caa063c36d9b
Author: triesap <tyson@radroots.org>
Date:   Tue, 30 Jun 2026 03:04:09 +0000

sdk: adopt trade product facade

Diffstat:
Mcrates/sdk/Cargo.toml | 2--
Mcrates/sdk/examples/runtime_local.rs | 8++++----
Mcrates/sdk/examples/sdk_v1_local_enqueue_and_mock_sync.rs | 8++++----
Mcrates/sdk/src/dvm_runtime.rs | 47+++++++++++++++++++++++++----------------------
Mcrates/sdk/src/error.rs | 32++++++++++++++++----------------
Mcrates/sdk/src/idempotency.rs | 27+++++++++++++++++++++++++++
Mcrates/sdk/src/lib.rs | 61+++++++++++++++++++++++++++++++++++++++++--------------------
Mcrates/sdk/src/orders_runtime.rs | 1079+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Acrates/sdk/src/privacy.rs | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/sdk/src/product_clients.rs | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/sdk/src/relay_targets.rs | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/sdk/src/trade_storage.rs | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/sdk/tests/dvm_runtime.rs | 29++++++++++++++++-------------
Mcrates/sdk/tests/orders_runtime.rs | 452+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mcrates/sdk/tests/runtime_foundation.rs | 10+++++-----
Mcrates/sdk/tests/source_boundary.rs | 188+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mcrates/sdk/tests/unit/dvm_runtime_tests.rs | 58+++++++++++++++++++++++++++++++---------------------------
Mcrates/sdk/tests/unit/error_tests.rs | 4++--
Mcrates/sdk/tests/unit/idempotency_tests.rs | 42+++++++++++++++++++++++++++++++++++++++++-
Mcrates/sdk/tests/unit/orders_runtime_tests.rs | 583+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mcrates/sdk/tests/unit/relay_targets_tests.rs | 64+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
21 files changed, 2177 insertions(+), 927 deletions(-)

diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml @@ -62,7 +62,6 @@ runtime = [ "dep:radroots_outbox", "dep:radroots_relay_transport", "dep:radroots_runtime_paths", - "dep:radroots_sp1_guest_trade", "dep:sha2", "dep:sqlx", "dep:uuid", @@ -116,7 +115,6 @@ radroots_outbox = { workspace = true, optional = true, default-features = false radroots_publish_proxy_protocol = { workspace = true, optional = true, default-features = false } radroots_relay_transport = { workspace = true, optional = true, default-features = false } radroots_runtime_paths = { workspace = true, optional = true, default-features = false } -radroots_sp1_guest_trade = { workspace = true, optional = true } radroots_trade = { workspace = true, default-features = false } radroots_identity = { workspace = true, optional = true, default-features = false } radroots_nostr = { workspace = true, optional = true, default-features = false } diff --git a/crates/sdk/examples/runtime_local.rs b/crates/sdk/examples/runtime_local.rs @@ -11,9 +11,9 @@ use radroots_sdk::protocol::listing::{ RadrootsListing, RadrootsListingBin, RadrootsListingProduct, }; use radroots_sdk::{ - ListingPreparePublishRequest, OrderStatusRequest, PushOutboxRequest, RadrootsClient, - RadrootsSdkError, RadrootsSdkLocalKeySigner, RadrootsSdkSignerProvider, RadrootsSdkTimestamp, - SdkIdempotencyKey, SdkRelayTargetPolicy, SdkRelayUrlPolicy, + ListingPreparePublishRequest, PushOutboxRequest, RadrootsClient, RadrootsSdkError, + RadrootsSdkLocalKeySigner, RadrootsSdkSignerProvider, RadrootsSdkTimestamp, SdkIdempotencyKey, + SdkRelayTargetPolicy, SdkRelayUrlPolicy, TradeStatusRequest, }; const RELAY: &str = "wss://relay.example.com"; @@ -50,7 +50,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { .await; let order_status = sdk .trades() - .status(OrderStatusRequest::parse("example-order-1")?) + .status(TradeStatusRequest::parse("example-order-1")?) .await?; assert_eq!( diff --git a/crates/sdk/examples/sdk_v1_local_enqueue_and_mock_sync.rs b/crates/sdk/examples/sdk_v1_local_enqueue_and_mock_sync.rs @@ -12,9 +12,9 @@ use radroots_sdk::protocol::listing::{ RadrootsListing, RadrootsListingBin, RadrootsListingProduct, }; use radroots_sdk::{ - ListingPreparePublishRequest, OrderStatusRequest, PushOutboxRequest, RadrootsClient, - RadrootsSdkLocalKeySigner, RadrootsSdkSignerProvider, RadrootsSdkTimestamp, SdkIdempotencyKey, - SdkRelayTargetPolicy, SdkRelayTargetSet, SdkRelayUrlPolicy, + ListingPreparePublishRequest, PushOutboxRequest, RadrootsClient, RadrootsSdkLocalKeySigner, + RadrootsSdkSignerProvider, RadrootsSdkTimestamp, SdkIdempotencyKey, SdkRelayTargetPolicy, + SdkRelayTargetSet, SdkRelayUrlPolicy, TradeStatusRequest, }; const LOCAL_RELAY: &str = "ws://localhost:7777"; @@ -55,7 +55,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { .await?; let order_status = sdk .trades() - .status(OrderStatusRequest::parse("example-order-1")?) + .status(TradeStatusRequest::parse("example-order-1")?) .await?; println!("queued listing event: {}", enqueue.signed_event_id.as_str()); diff --git a/crates/sdk/src/dvm_runtime.rs b/crates/sdk/src/dvm_runtime.rs @@ -23,11 +23,7 @@ use radroots_events::{ #[cfg(feature = "runtime")] use radroots_events_codec::wire::{WireEventParts, canonicalize_tags, to_frozen_draft}; #[cfg(feature = "runtime")] -use radroots_sp1_guest_trade::{ - RADROOTS_SP1_TRADE_ORDER_ACCEPTANCE_PROOF_TARGET, RADROOTS_SP1_TRADE_PROTOCOL_VERSION, - RADROOTS_SP1_TRADE_REDUCER_PROGRAM_HASH, RADROOTS_SP1_TRADE_WITNESS_VERSION, - RadrootsSp1TradeInventoryBinWitness, -}; +use radroots_trade::dvm::RadrootsTradeInventoryBinWitnessDto; #[cfg(feature = "runtime")] use radroots_trade::validation_receipt::{ RadrootsValidationReceiptExpectedBinding, RadrootsValidationReceiptProofSystem, @@ -36,6 +32,16 @@ use radroots_trade::validation_receipt::{ }; #[cfg(feature = "runtime")] +const RADROOTS_TRADE_TRANSITION_WITNESS_VERSION: u32 = 1; +#[cfg(feature = "runtime")] +const RADROOTS_TRADE_TRANSITION_PROTOCOL_VERSION: &str = "radroots.trade.v1"; +#[cfg(feature = "runtime")] +const RADROOTS_TRADE_TRANSITION_REDUCER_PROGRAM_HASH: &str = + "0x3d8f7f463904d71f2d0d14b1551450756697e51c7b658e10c6d5c20a7bc61f08"; +#[cfg(feature = "runtime")] +const RADROOTS_TRADE_TRANSITION_PROOF_TARGET: &str = "trade.order_acceptance.v1"; + +#[cfg(feature = "runtime")] pub const DVM_TRADE_TRANSITION_PROOF_REQUEST_CONTRACT_ID: &str = "radroots.trade.transition_proof.request.v1"; #[cfg(feature = "runtime")] @@ -43,9 +49,6 @@ pub const DVM_TRADE_TRANSITION_PROOF_REQUEST_OPERATION_KIND: &str = "dvm.trade_transition_proof.request.v1"; #[cfg(feature = "runtime")] -pub type SdkDvmInventoryBinWitness = RadrootsSp1TradeInventoryBinWitness; - -#[cfg(feature = "runtime")] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "snake_case")] #[non_exhaustive] @@ -96,7 +99,7 @@ pub struct DvmTradeTransitionProofPrepareRequest { pub listing_event_id: RadrootsEventId, pub request_event_id: RadrootsEventId, pub decision_event_id: RadrootsEventId, - pub inventory_bins: Vec<SdkDvmInventoryBinWitness>, + pub inventory_bins: Vec<RadrootsTradeInventoryBinWitnessDto>, pub inventory_sequence: u128, pub previous_state_root: Option<String>, pub proof_mode: DvmProofMode, @@ -114,7 +117,7 @@ impl DvmTradeTransitionProofPrepareRequest { listing_event_id: RadrootsEventId, request_event_id: RadrootsEventId, decision_event_id: RadrootsEventId, - inventory_bins: Vec<SdkDvmInventoryBinWitness>, + inventory_bins: Vec<RadrootsTradeInventoryBinWitnessDto>, ) -> Self { Self { actor, @@ -183,7 +186,7 @@ impl DvmTradeTransitionProofEnqueueRequest { listing_event_id: RadrootsEventId, request_event_id: RadrootsEventId, decision_event_id: RadrootsEventId, - inventory_bins: Vec<SdkDvmInventoryBinWitness>, + inventory_bins: Vec<RadrootsTradeInventoryBinWitnessDto>, target_relays: SdkRelayTargetPolicy, ) -> Self { Self::from_prepare( @@ -276,7 +279,7 @@ pub struct DvmTradeTransitionProofRequestPayload { pub listing_event_id: String, pub request_event_id: String, pub decision_event_id: String, - pub inventory_bins: Vec<SdkDvmInventoryBinWitness>, + pub inventory_bins: Vec<RadrootsTradeInventoryBinWitnessDto>, pub inventory_sequence: u128, pub previous_state_root: Option<String>, pub proof_mode: DvmProofMode, @@ -530,8 +533,8 @@ fn dvm_trade_transition_proof_plan( validate_inventory_bins(&request.inventory_bins)?; validate_sp1_identity(&request)?; let payload = DvmTradeTransitionProofRequestPayload { - witness_version: RADROOTS_SP1_TRADE_WITNESS_VERSION, - proof_target: RADROOTS_SP1_TRADE_ORDER_ACCEPTANCE_PROOF_TARGET.to_owned(), + witness_version: RADROOTS_TRADE_TRANSITION_WITNESS_VERSION, + proof_target: RADROOTS_TRADE_TRANSITION_PROOF_TARGET.to_owned(), listing_event_id: request.listing_event_id.as_str().to_owned(), request_event_id: request.request_event_id.as_str().to_owned(), decision_event_id: request.decision_event_id.as_str().to_owned(), @@ -539,8 +542,8 @@ fn dvm_trade_transition_proof_plan( inventory_sequence: request.inventory_sequence, previous_state_root: request.previous_state_root.clone(), proof_mode: request.proof_mode, - reducer_program_hash: RADROOTS_SP1_TRADE_REDUCER_PROGRAM_HASH.to_owned(), - radroots_protocol_version: RADROOTS_SP1_TRADE_PROTOCOL_VERSION.to_owned(), + reducer_program_hash: RADROOTS_TRADE_TRANSITION_REDUCER_PROGRAM_HASH.to_owned(), + radroots_protocol_version: RADROOTS_TRADE_TRANSITION_PROTOCOL_VERSION.to_owned(), sp1_program_hash: request.sp1_program_hash.clone(), sp1_verifying_key_hash: request.sp1_verifying_key_hash.clone(), }; @@ -552,19 +555,19 @@ fn dvm_trade_transition_proof_plan( "i".to_owned(), request.listing_event_id.as_str().to_owned(), "event".to_owned(), - "listing".to_owned(), + "radroots:listing_event".to_owned(), ], vec![ "i".to_owned(), request.request_event_id.as_str().to_owned(), "event".to_owned(), - "order_request".to_owned(), + "radroots:order_request_event".to_owned(), ], vec![ "i".to_owned(), request.decision_event_id.as_str().to_owned(), "event".to_owned(), - "order_decision".to_owned(), + "radroots:order_decision_event".to_owned(), ], ]; canonicalize_tags(&mut tags); @@ -599,7 +602,7 @@ fn dvm_trade_transition_proof_plan( #[cfg(feature = "runtime")] fn validate_inventory_bins( - inventory_bins: &[SdkDvmInventoryBinWitness], + inventory_bins: &[RadrootsTradeInventoryBinWitnessDto], ) -> Result<(), RadrootsSdkError> { if inventory_bins.is_empty() { return Err(RadrootsSdkError::InvalidRequest { @@ -607,7 +610,7 @@ fn validate_inventory_bins( }); } for bin in inventory_bins { - if bin.bin_id.trim().is_empty() { + if bin.bin_id.as_str().trim().is_empty() { return Err(RadrootsSdkError::InvalidRequest { message: "DVM proof request inventory bin id cannot be empty".to_owned(), }); @@ -616,7 +619,7 @@ fn validate_inventory_bins( return Err(RadrootsSdkError::InvalidRequest { message: format!( "DVM proof request inventory bin `{}` previous_reserved exceeds listing_capacity", - bin.bin_id + bin.bin_id.as_str() ), }); } diff --git a/crates/sdk/src/error.rs b/crates/sdk/src/error.rs @@ -138,12 +138,12 @@ pub enum RadrootsSdkError { existing_digest_prefix: String, new_digest_prefix: String, }, - OrderStatusLimitInvalid { + TradeStatusLimitInvalid { limit: u32, min: u32, max: u32, }, - InvalidOrderId { + InvalidTradeId { value: String, message: String, }, @@ -208,8 +208,8 @@ impl RadrootsSdkError { Self::RelayTargetLimitExceeded { .. } => "relay_target_limit_exceeded", Self::InvalidRelayUrl { .. } => "invalid_relay_url", Self::IdempotencyConflict { .. } => "idempotency_conflict", - Self::OrderStatusLimitInvalid { .. } => "order_status_limit_invalid", - Self::InvalidOrderId { .. } => "invalid_order_id", + Self::TradeStatusLimitInvalid { .. } => "trade_status_limit_invalid", + Self::InvalidTradeId { .. } => "invalid_trade_id", Self::ProductSyncUnsupported { .. } => "product_sync_unsupported", Self::ProductSyncRelaySetupFailure { .. } => "product_sync_relay_setup_failure", Self::Authority { .. } => "authority", @@ -261,8 +261,8 @@ impl RadrootsSdkError { | Self::RelayTargetLimitExceeded { .. } | Self::InvalidRelayUrl { .. } => RadrootsSdkErrorClass::Configuration, Self::IdempotencyConflict { .. } - | Self::OrderStatusLimitInvalid { .. } - | Self::InvalidOrderId { .. } + | Self::TradeStatusLimitInvalid { .. } + | Self::InvalidTradeId { .. } | Self::SignerProtocol { .. } | Self::SignerAuthChallengePending { .. } | Self::InvalidRequest { .. } @@ -348,8 +348,8 @@ impl RadrootsSdkError { Self::PartialLocalMutation(error) => vec![error.recovery], Self::ClockBeforeUnixEpoch | Self::TimestampOutOfRange { .. } - | Self::OrderStatusLimitInvalid { .. } - | Self::InvalidOrderId { .. } + | Self::TradeStatusLimitInvalid { .. } + | Self::InvalidTradeId { .. } | Self::SignerProtocol { .. } | Self::InvalidRequest { .. } | Self::ListingDraft { .. } @@ -405,10 +405,10 @@ impl RadrootsSdkError { "existing_digest_prefix": existing_digest_prefix, "new_digest_prefix": new_digest_prefix }), - Self::OrderStatusLimitInvalid { limit, min, max } => { + Self::TradeStatusLimitInvalid { limit, min, max } => { json!({ "limit": limit, "min": min, "max": max }) } - Self::InvalidOrderId { value, message } => { + Self::InvalidTradeId { value, message } => { json!({ "value": value, "message": message }) } Self::ProductSyncUnsupported { @@ -491,12 +491,12 @@ impl RadrootsSdkError { } } - pub(crate) fn order_status_limit_invalid(limit: u32, min: u32, max: u32) -> Self { - Self::OrderStatusLimitInvalid { limit, min, max } + pub(crate) fn trade_status_limit_invalid(limit: u32, min: u32, max: u32) -> Self { + Self::TradeStatusLimitInvalid { limit, min, max } } - pub(crate) fn invalid_order_id(value: impl Into<String>, message: impl Into<String>) -> Self { - Self::InvalidOrderId { + pub(crate) fn invalid_trade_id(value: impl Into<String>, message: impl Into<String>) -> Self { + Self::InvalidTradeId { value: value.into(), message: message.into(), } @@ -583,11 +583,11 @@ impl fmt::Display for RadrootsSdkError { f, "sdk idempotency conflict for {operation_kind}: expected_pubkey_prefix={expected_pubkey_prefix}, existing_digest_prefix={existing_digest_prefix}, new_digest_prefix={new_digest_prefix}" ), - Self::OrderStatusLimitInvalid { limit, min, max } => write!( + Self::TradeStatusLimitInvalid { limit, min, max } => write!( f, "sdk order status limit invalid: limit={limit}, min={min}, max={max}" ), - Self::InvalidOrderId { value, message } => { + Self::InvalidTradeId { value, message } => { write!(f, "sdk invalid order id `{value}`: {message}") } Self::ProductSyncUnsupported { diff --git a/crates/sdk/src/idempotency.rs b/crates/sdk/src/idempotency.rs @@ -1,5 +1,6 @@ use crate::RadrootsSdkError; use core::fmt; +use radroots_events::ids::{RadrootsEventId, RadrootsPublicKey}; use serde::ser::SerializeStruct; use sha2::{Digest, Sha256}; @@ -88,6 +89,32 @@ struct SdkIdempotencyDerivationInput<'a> { target_relays: &'a [String], } +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] +pub struct SdkTradeIdempotencyRecord { + pub idempotency_key: SdkIdempotencyKey, + pub operation_kind: String, + pub actor_pubkey: RadrootsPublicKey, + pub digest: String, + pub canonical_payload_hash: String, + pub expected_event_id: RadrootsEventId, + pub outbox_operation_id: i64, +} + +impl SdkTradeIdempotencyRecord { + pub fn matches_payload(&self, canonical_payload_hash: &str) -> bool { + self.canonical_payload_hash == canonical_payload_hash + } + + pub fn conflict_error(&self, new_digest: impl Into<String>) -> RadrootsSdkError { + RadrootsSdkError::IdempotencyConflict { + operation_kind: self.operation_kind.clone(), + expected_pubkey_prefix: self.actor_pubkey.as_str().chars().take(12).collect(), + existing_digest_prefix: self.digest.chars().take(12).collect(), + new_digest_prefix: new_digest.into().chars().take(12).collect(), + } + } +} + fn invalid_request(message: impl Into<String>) -> RadrootsSdkError { RadrootsSdkError::InvalidRequest { message: message.into(), diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs @@ -35,6 +35,8 @@ mod order; #[cfg(feature = "runtime")] mod orders_runtime; #[cfg(feature = "runtime")] +mod privacy; +#[cfg(feature = "runtime")] mod private_store; #[cfg(feature = "runtime")] mod product_clients; @@ -49,6 +51,8 @@ mod signer_provider; #[cfg(feature = "runtime")] mod sync_runtime; #[cfg(feature = "runtime")] +mod trade_storage; +#[cfg(feature = "runtime")] mod workflow_runtime; #[cfg(feature = "runtime")] @@ -58,7 +62,7 @@ pub use crate::dvm_runtime::{ DvmTradeTransitionProofEnqueueRequest, DvmTradeTransitionProofPlan, DvmTradeTransitionProofPrepareRequest, DvmTradeTransitionProofReceipt, DvmTradeTransitionProofRequestPayload, DvmValidationReceiptIngestReceipt, - DvmValidationReceiptIngestRequest, SdkDvmInventoryBinWitness, + DvmValidationReceiptIngestRequest, }; #[cfg(feature = "runtime")] pub use crate::error::{ @@ -85,7 +89,9 @@ pub use crate::geonames::{ RadrootsGeoNamesConfig, }; #[cfg(feature = "runtime")] -pub use crate::idempotency::{SDK_IDEMPOTENCY_KEY_MAX_LEN, SdkIdempotencyKey}; +pub use crate::idempotency::{ + SDK_IDEMPOTENCY_KEY_MAX_LEN, SdkIdempotencyKey, SdkTradeIdempotencyRecord, +}; #[cfg(feature = "runtime")] pub use crate::listings_runtime::{ LISTING_PUBLISH_OPERATION_KIND, ListingEnqueuePublishRequest, ListingEnqueueReceipt, @@ -98,30 +104,38 @@ pub use crate::market_runtime::{ }; #[cfg(feature = "runtime")] pub use crate::orders_runtime::{ - ORDER_CANCELLATION_OPERATION_KIND, ORDER_DECISION_OPERATION_KIND, - ORDER_REVISION_DECISION_OPERATION_KIND, ORDER_REVISION_PROPOSAL_OPERATION_KIND, - ORDER_STATUS_DEFAULT_LIMIT, ORDER_STATUS_MAX_LIMIT, ORDER_SUBMIT_OPERATION_KIND, - OrderCancellationEnqueueRequest, OrderCancellationPlan, OrderCancellationPrepareRequest, - OrderCancellationReceipt, OrderDecisionEnqueueRequest, OrderDecisionPlan, - OrderDecisionPrepareRequest, OrderDecisionReceipt, OrderEvidenceIngestReceipt, - OrderEvidenceIngestRequest, OrderRequestEvidenceIngestReceipt, - OrderRequestEvidenceIngestRequest, OrderRevisionDecisionEnqueueRequest, - OrderRevisionDecisionPlan, OrderRevisionDecisionPrepareRequest, OrderRevisionDecisionReceipt, - OrderRevisionProposalEnqueueRequest, OrderRevisionProposalPlan, - OrderRevisionProposalPrepareRequest, OrderRevisionProposalReceipt, OrderStatusEligibility, - OrderStatusEvidenceSummary, OrderStatusKind, OrderStatusNextActionKind, OrderStatusReceipt, - OrderStatusRequest, OrderSubmitEnqueueRequest, OrderSubmitPlan, OrderSubmitPrepareRequest, - OrderSubmitReceipt, OrderWorkflowEnqueueReceipt, OrderWorkflowIdempotencyReceipt, - OrderWorkflowKind, OrderWorkflowPlan, OrderWorkflowRetryAdvice, SdkOrderStatusIssue, - SdkOrderStatusIssueKind, SdkOrderStatusSource, + SdkTradeStatusIssue, SdkTradeStatusIssueKind, SdkTradeStatusSource, + TRADE_CANCELLATION_OPERATION_KIND, TRADE_DECISION_OPERATION_KIND, + TRADE_REVISION_DECISION_OPERATION_KIND, TRADE_REVISION_PROPOSAL_OPERATION_KIND, + TRADE_STATUS_DEFAULT_LIMIT, TRADE_STATUS_MAX_LIMIT, TRADE_SUBMIT_OPERATION_KIND, + TradeCancellationEnqueueRequest, TradeCancellationPlan, TradeCancellationPrepareRequest, + TradeCancellationReceipt, TradeDecisionEnqueueRequest, TradeDecisionPlan, + TradeDecisionPrepareRequest, TradeDecisionReceipt, TradeEvidenceIngestReceipt, + TradeEvidenceIngestRequest, TradeRequestEvidenceIngestReceipt, + TradeRequestEvidenceIngestRequest, TradeRevisionDecisionEnqueueRequest, + TradeRevisionDecisionPlan, TradeRevisionDecisionPrepareRequest, TradeRevisionDecisionReceipt, + TradeRevisionProposalEnqueueRequest, TradeRevisionProposalPlan, + TradeRevisionProposalPrepareRequest, TradeRevisionProposalReceipt, + TradeStatusAmbiguityCandidate, TradeStatusEligibility, TradeStatusEvidenceSummary, + TradeStatusKind, TradeStatusNextActionKind, TradeStatusReceipt, TradeStatusRequest, + TradeSubmitEnqueueRequest, TradeSubmitPlan, TradeSubmitPrepareRequest, TradeSubmitReceipt, + TradeWorkflowEnqueueReceipt, TradeWorkflowIdempotencyReceipt, TradeWorkflowKind, + TradeWorkflowPlan, TradeWorkflowRetryAdvice, +}; +#[cfg(feature = "runtime")] +pub use crate::privacy::{ + PrivacyPreflightReceipt, PrivacyPreflightStatus, ProductSensitivityField, }; #[cfg(feature = "runtime")] pub use crate::product_clients::{ - DvmClient, FarmsClient, GeoNamesClient, ListingsClient, MarketClient, SyncClient, TradesClient, + DvmClient, FarmsClient, GeoNamesClient, ListingsClient, MarketClient, SyncClient, + TradeBuyerClient, TradeResyncClient, TradeSellerClient, TradeStatusClient, + TradeValidationClient, TradesClient, }; #[cfg(feature = "runtime")] pub use crate::relay_targets::{ - SDK_RELAY_TARGET_MAX_COUNT, SdkRelayTargetPolicy, SdkRelayTargetSet, SdkRelayUrlPolicy, + AckPolicy, PublishMode, RelayResolutionPolicy, SDK_RELAY_TARGET_MAX_COUNT, + SdkRelayTargetPolicy, SdkRelayTargetSet, SdkRelayUrlPolicy, }; #[cfg(feature = "runtime")] pub use crate::runtime::{ @@ -153,3 +167,10 @@ pub use crate::sync_runtime::{ SyncProjectionRefreshRequest, SyncRelayTargetSummary, SyncStatusReceipt, SyncStatusRequest, SyncStatusSource, }; +#[cfg(feature = "runtime")] +pub use crate::trade_storage::{ + SDK_TRADE_PROJECTION_CACHE_VERSION, SdkTradeProjectionCache, SdkTradeProjectionCacheKey, + SdkTradeProjectionCacheRecord, +}; +#[cfg(feature = "runtime")] +pub use radroots_trade::dvm::RadrootsTradeInventoryBinWitnessDto; diff --git a/crates/sdk/src/orders_runtime.rs b/crates/sdk/src/orders_runtime.rs @@ -2,8 +2,9 @@ use crate::workflow_runtime::enqueue_configured_signed_workflow; #[cfg(feature = "runtime")] use crate::{ - RadrootsSdkError, RadrootsSdkRecoveryAction, RadrootsSdkTimestamp, SdkIdempotencyKey, - SdkMutationState, SdkRelayTargetPolicy, SdkRelayUrlPolicy, TradesClient, order, + AckPolicy, PublishMode, RadrootsSdkError, RadrootsSdkRecoveryAction, RadrootsSdkTimestamp, + RelayResolutionPolicy, SdkIdempotencyKey, SdkMutationState, SdkRelayUrlPolicy, TradesClient, + order, workflow_runtime::{SdkWorkflowEnqueueRequest, enqueue_signed_workflow}, }; #[cfg(feature = "runtime")] @@ -33,47 +34,50 @@ use radroots_events_codec::order::{ #[cfg(feature = "runtime")] use radroots_events_codec::wire::{WireEventParts, to_frozen_draft}; #[cfg(feature = "runtime")] +use radroots_trade::identity::{RadrootsTradeLocator, RadrootsTradeLocatorCandidate}; +#[cfg(feature = "runtime")] use radroots_trade::order::{ RadrootsOrderCanonicalizationError, RadrootsOrderIssue, RadrootsOrderProjection, RadrootsOrderProjectionQueryResult, RadrootsOrderStoreQueryError, + RadrootsTradeLocatorProjectionQueryResult, RadrootsTradeLocatorProjectionResolution, canonicalize_order_decision_for_signer, canonicalize_order_request_for_signer, - order_projection_query_for_order_id, + order_projection_query_for_order_id, order_projection_query_for_trade_locator, }; #[cfg(feature = "runtime")] use radroots_trade::workflow::RadrootsTradeWorkflowState; #[cfg(feature = "runtime")] use serde::ser::SerializeStruct; #[cfg(feature = "runtime")] -pub const ORDER_STATUS_DEFAULT_LIMIT: u32 = 500; +pub const TRADE_STATUS_DEFAULT_LIMIT: u32 = 500; #[cfg(feature = "runtime")] -pub const ORDER_STATUS_MAX_LIMIT: u32 = 1_000; +pub const TRADE_STATUS_MAX_LIMIT: u32 = 1_000; #[cfg(feature = "runtime")] -pub const ORDER_SUBMIT_OPERATION_KIND: &str = "order.submit.v1"; +pub const TRADE_SUBMIT_OPERATION_KIND: &str = "trade.submit.v1"; #[cfg(feature = "runtime")] -pub const ORDER_DECISION_OPERATION_KIND: &str = "order.decision.v1"; +pub const TRADE_DECISION_OPERATION_KIND: &str = "trade.decision.v1"; #[cfg(feature = "runtime")] -pub const ORDER_REVISION_PROPOSAL_OPERATION_KIND: &str = "order.revision.proposal.v1"; +pub const TRADE_REVISION_PROPOSAL_OPERATION_KIND: &str = "trade.revision.proposal.v1"; #[cfg(feature = "runtime")] -pub const ORDER_REVISION_DECISION_OPERATION_KIND: &str = "order.revision.decision.v1"; +pub const TRADE_REVISION_DECISION_OPERATION_KIND: &str = "trade.revision.decision.v1"; #[cfg(feature = "runtime")] -pub const ORDER_CANCELLATION_OPERATION_KIND: &str = "order.cancellation.v1"; +pub const TRADE_CANCELLATION_OPERATION_KIND: &str = "trade.cancellation.v1"; #[cfg(feature = "runtime")] -const ORDER_REQUEST_CONTRACT_ID: &str = "radroots.order.request.v1"; +const TRADE_SUBMIT_CONTRACT_ID: &str = "radroots.order.request.v1"; #[cfg(feature = "runtime")] -const ORDER_DECISION_CONTRACT_ID: &str = "radroots.order.decision.v1"; +const TRADE_DECISION_CONTRACT_ID: &str = "radroots.order.decision.v1"; #[cfg(feature = "runtime")] -const ORDER_REVISION_PROPOSAL_CONTRACT_ID: &str = "radroots.order.revision_proposal.v1"; +const TRADE_REVISION_PROPOSAL_CONTRACT_ID: &str = "radroots.order.revision_proposal.v1"; #[cfg(feature = "runtime")] -const ORDER_REVISION_DECISION_CONTRACT_ID: &str = "radroots.order.revision_decision.v1"; +const TRADE_REVISION_DECISION_CONTRACT_ID: &str = "radroots.order.revision_decision.v1"; #[cfg(feature = "runtime")] -const ORDER_CANCELLATION_CONTRACT_ID: &str = "radroots.order.cancellation.v1"; +const TRADE_CANCELLATION_CONTRACT_ID: &str = "radroots.order.cancellation.v1"; #[cfg(feature = "runtime")] #[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)] #[serde(rename_all = "snake_case")] #[non_exhaustive] -pub enum OrderWorkflowKind { +pub enum TradeWorkflowKind { Submit, Decision, RevisionProposal, @@ -82,32 +86,32 @@ pub enum OrderWorkflowKind { } #[cfg(feature = "runtime")] -impl OrderWorkflowKind { +impl TradeWorkflowKind { pub fn operation_kind(self) -> &'static str { match self { - Self::Submit => ORDER_SUBMIT_OPERATION_KIND, - Self::Decision => ORDER_DECISION_OPERATION_KIND, - Self::RevisionProposal => ORDER_REVISION_PROPOSAL_OPERATION_KIND, - Self::RevisionDecision => ORDER_REVISION_DECISION_OPERATION_KIND, - Self::Cancellation => ORDER_CANCELLATION_OPERATION_KIND, + Self::Submit => TRADE_SUBMIT_OPERATION_KIND, + Self::Decision => TRADE_DECISION_OPERATION_KIND, + Self::RevisionProposal => TRADE_REVISION_PROPOSAL_OPERATION_KIND, + Self::RevisionDecision => TRADE_REVISION_DECISION_OPERATION_KIND, + Self::Cancellation => TRADE_CANCELLATION_OPERATION_KIND, } } pub fn contract_id(self) -> &'static str { match self { - Self::Submit => ORDER_REQUEST_CONTRACT_ID, - Self::Decision => ORDER_DECISION_CONTRACT_ID, - Self::RevisionProposal => ORDER_REVISION_PROPOSAL_CONTRACT_ID, - Self::RevisionDecision => ORDER_REVISION_DECISION_CONTRACT_ID, - Self::Cancellation => ORDER_CANCELLATION_CONTRACT_ID, + Self::Submit => TRADE_SUBMIT_CONTRACT_ID, + Self::Decision => TRADE_DECISION_CONTRACT_ID, + Self::RevisionProposal => TRADE_REVISION_PROPOSAL_CONTRACT_ID, + Self::RevisionDecision => TRADE_REVISION_DECISION_CONTRACT_ID, + Self::Cancellation => TRADE_CANCELLATION_CONTRACT_ID, } } } #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct OrderWorkflowPlan { - pub kind: OrderWorkflowKind, +pub struct TradeWorkflowPlan { + pub kind: TradeWorkflowKind, pub operation_kind: &'static str, pub contract_id: &'static str, pub expected_event_id: RadrootsEventId, @@ -116,8 +120,8 @@ pub struct OrderWorkflowPlan { #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct OrderWorkflowEnqueueReceipt { - pub kind: OrderWorkflowKind, +pub struct TradeWorkflowEnqueueReceipt { + pub kind: TradeWorkflowKind, pub operation_kind: &'static str, pub expected_event_id: RadrootsEventId, pub signed_event_id: RadrootsEventId, @@ -126,13 +130,13 @@ pub struct OrderWorkflowEnqueueReceipt { pub outbox_event_id: i64, pub state: SdkMutationState, pub idempotency_digest_prefix: Option<String>, - pub idempotency: OrderWorkflowIdempotencyReceipt, - pub retry: OrderWorkflowRetryAdvice, + pub idempotency: TradeWorkflowIdempotencyReceipt, + pub retry: TradeWorkflowRetryAdvice, } #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct OrderWorkflowIdempotencyReceipt { +pub struct TradeWorkflowIdempotencyReceipt { pub digest_prefix: Option<String>, pub replayed_existing_operation: bool, pub safe_to_retry_with_same_idempotency_key: bool, @@ -140,7 +144,7 @@ pub struct OrderWorkflowIdempotencyReceipt { #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct OrderWorkflowRetryAdvice { +pub struct TradeWorkflowRetryAdvice { pub retryable_after_error: bool, pub safe_to_retry_enqueue_with_same_idempotency_key: bool, pub recovery_actions: Vec<RadrootsSdkRecoveryAction>, @@ -149,7 +153,7 @@ pub struct OrderWorkflowRetryAdvice { #[cfg(feature = "runtime")] #[derive(Clone, Debug, serde::Serialize)] #[non_exhaustive] -pub struct OrderSubmitPrepareRequest { +pub struct TradeSubmitPrepareRequest { #[serde(serialize_with = "crate::actor_json::serialize_actor_context")] pub actor: RadrootsActorContext, pub listing_event: RadrootsNostrEventPtr, @@ -158,7 +162,7 @@ pub struct OrderSubmitPrepareRequest { } #[cfg(feature = "runtime")] -impl OrderSubmitPrepareRequest { +impl TradeSubmitPrepareRequest { pub fn new( actor: RadrootsActorContext, listing_event: RadrootsNostrEventPtr, @@ -181,29 +185,35 @@ impl OrderSubmitPrepareRequest { #[cfg(feature = "runtime")] #[derive(Clone, Debug, serde::Serialize)] #[non_exhaustive] -pub struct OrderSubmitEnqueueRequest { +pub struct TradeSubmitEnqueueRequest { #[serde(serialize_with = "crate::actor_json::serialize_actor_context")] pub actor: RadrootsActorContext, pub listing_event: RadrootsNostrEventPtr, pub order: RadrootsOrderRequest, - pub target_relays: SdkRelayTargetPolicy, + pub target_relays: RelayResolutionPolicy, + pub publish_mode: PublishMode, + pub ack_policy: AckPolicy, pub idempotency_key: Option<SdkIdempotencyKey>, pub created_at: Option<RadrootsSdkTimestamp>, } #[cfg(feature = "runtime")] -impl OrderSubmitEnqueueRequest { +impl TradeSubmitEnqueueRequest { pub fn new( actor: RadrootsActorContext, listing_event: RadrootsNostrEventPtr, order: RadrootsOrderRequest, - target_relays: SdkRelayTargetPolicy, + target_relays: RelayResolutionPolicy, + publish_mode: PublishMode, + ack_policy: AckPolicy, ) -> Self { Self { actor, listing_event, order, target_relays, + publish_mode, + ack_policy, idempotency_key: None, created_at: None, } @@ -218,7 +228,7 @@ impl OrderSubmitEnqueueRequest { I: IntoIterator<Item = S>, S: AsRef<str>, { - self.target_relays = SdkRelayTargetPolicy::try_explicit(target_relays, policy)?; + self.target_relays = RelayResolutionPolicy::try_explicit(target_relays, policy)?; Ok(self) } @@ -243,8 +253,8 @@ impl OrderSubmitEnqueueRequest { #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct OrderSubmitPlan { - pub workflow: OrderWorkflowPlan, +pub struct TradeSubmitPlan { + pub workflow: TradeWorkflowPlan, pub order_id: RadrootsOrderId, pub listing_addr: RadrootsListingAddress, pub listing_event_id: RadrootsEventId, @@ -255,8 +265,8 @@ pub struct OrderSubmitPlan { #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct OrderSubmitReceipt { - pub workflow: OrderWorkflowEnqueueReceipt, +pub struct TradeSubmitReceipt { + pub workflow: TradeWorkflowEnqueueReceipt, pub order_id: RadrootsOrderId, pub listing_addr: RadrootsListingAddress, pub listing_event_id: RadrootsEventId, @@ -272,13 +282,13 @@ pub struct OrderSubmitReceipt { #[cfg(feature = "runtime")] #[derive(Clone, Debug, serde::Serialize)] #[non_exhaustive] -pub struct OrderRequestEvidenceIngestRequest { +pub struct TradeRequestEvidenceIngestRequest { pub event: RadrootsNostrEvent, pub observed_at: Option<RadrootsSdkTimestamp>, } #[cfg(feature = "runtime")] -impl OrderRequestEvidenceIngestRequest { +impl TradeRequestEvidenceIngestRequest { pub fn new(event: RadrootsNostrEvent) -> Self { Self { event, @@ -294,7 +304,7 @@ impl OrderRequestEvidenceIngestRequest { #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct OrderRequestEvidenceIngestReceipt { +pub struct TradeRequestEvidenceIngestReceipt { pub order_id: RadrootsOrderId, pub listing_addr: RadrootsListingAddress, pub buyer_pubkey: RadrootsPublicKey, @@ -307,13 +317,13 @@ pub struct OrderRequestEvidenceIngestReceipt { #[cfg(feature = "runtime")] #[derive(Clone, Debug, serde::Serialize)] #[non_exhaustive] -pub struct OrderEvidenceIngestRequest { +pub struct TradeEvidenceIngestRequest { pub event: RadrootsNostrEvent, pub observed_at: Option<RadrootsSdkTimestamp>, } #[cfg(feature = "runtime")] -impl OrderEvidenceIngestRequest { +impl TradeEvidenceIngestRequest { pub fn new(event: RadrootsNostrEvent) -> Self { Self { event, @@ -329,7 +339,7 @@ impl OrderEvidenceIngestRequest { #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct OrderEvidenceIngestReceipt { +pub struct TradeEvidenceIngestReceipt { pub order_id: RadrootsOrderId, pub listing_addr: RadrootsListingAddress, pub event_id: RadrootsEventId, @@ -341,7 +351,7 @@ pub struct OrderEvidenceIngestReceipt { #[cfg(feature = "runtime")] #[derive(Clone, Debug, serde::Serialize)] #[non_exhaustive] -pub struct OrderDecisionPrepareRequest { +pub struct TradeDecisionPrepareRequest { #[serde(serialize_with = "crate::actor_json::serialize_actor_context")] pub actor: RadrootsActorContext, pub request_event: RadrootsNostrEventPtr, @@ -350,7 +360,7 @@ pub struct OrderDecisionPrepareRequest { } #[cfg(feature = "runtime")] -impl OrderDecisionPrepareRequest { +impl TradeDecisionPrepareRequest { pub fn new( actor: RadrootsActorContext, request_event: RadrootsNostrEventPtr, @@ -373,29 +383,35 @@ impl OrderDecisionPrepareRequest { #[cfg(feature = "runtime")] #[derive(Clone, Debug, serde::Serialize)] #[non_exhaustive] -pub struct OrderDecisionEnqueueRequest { +pub struct TradeDecisionEnqueueRequest { #[serde(serialize_with = "crate::actor_json::serialize_actor_context")] pub actor: RadrootsActorContext, pub request_event: RadrootsNostrEventPtr, pub decision: RadrootsOrderDecision, - pub target_relays: SdkRelayTargetPolicy, + pub target_relays: RelayResolutionPolicy, + pub publish_mode: PublishMode, + pub ack_policy: AckPolicy, pub idempotency_key: Option<SdkIdempotencyKey>, pub created_at: Option<RadrootsSdkTimestamp>, } #[cfg(feature = "runtime")] -impl OrderDecisionEnqueueRequest { +impl TradeDecisionEnqueueRequest { pub fn new( actor: RadrootsActorContext, request_event: RadrootsNostrEventPtr, decision: RadrootsOrderDecision, - target_relays: SdkRelayTargetPolicy, + target_relays: RelayResolutionPolicy, + publish_mode: PublishMode, + ack_policy: AckPolicy, ) -> Self { Self { actor, request_event, decision, target_relays, + publish_mode, + ack_policy, idempotency_key: None, created_at: None, } @@ -410,7 +426,7 @@ impl OrderDecisionEnqueueRequest { I: IntoIterator<Item = S>, S: AsRef<str>, { - self.target_relays = SdkRelayTargetPolicy::try_explicit(target_relays, policy)?; + self.target_relays = RelayResolutionPolicy::try_explicit(target_relays, policy)?; Ok(self) } @@ -435,8 +451,8 @@ impl OrderDecisionEnqueueRequest { #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct OrderDecisionPlan { - pub workflow: OrderWorkflowPlan, +pub struct TradeDecisionPlan { + pub workflow: TradeWorkflowPlan, pub order_id: RadrootsOrderId, pub listing_addr: RadrootsListingAddress, pub buyer_pubkey: RadrootsPublicKey, @@ -449,8 +465,8 @@ pub struct OrderDecisionPlan { #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct OrderDecisionReceipt { - pub workflow: OrderWorkflowEnqueueReceipt, +pub struct TradeDecisionReceipt { + pub workflow: TradeWorkflowEnqueueReceipt, pub order_id: RadrootsOrderId, pub listing_addr: RadrootsListingAddress, pub buyer_pubkey: RadrootsPublicKey, @@ -468,7 +484,7 @@ pub struct OrderDecisionReceipt { #[cfg(feature = "runtime")] #[derive(Clone, Debug, serde::Serialize)] #[non_exhaustive] -pub struct OrderRevisionProposalPrepareRequest { +pub struct TradeRevisionProposalPrepareRequest { #[serde(serialize_with = "crate::actor_json::serialize_actor_context")] pub actor: RadrootsActorContext, pub root_event: RadrootsNostrEventPtr, @@ -478,7 +494,7 @@ pub struct OrderRevisionProposalPrepareRequest { } #[cfg(feature = "runtime")] -impl OrderRevisionProposalPrepareRequest { +impl TradeRevisionProposalPrepareRequest { pub fn new( actor: RadrootsActorContext, root_event: RadrootsNostrEventPtr, @@ -503,25 +519,29 @@ impl OrderRevisionProposalPrepareRequest { #[cfg(feature = "runtime")] #[derive(Clone, Debug, serde::Serialize)] #[non_exhaustive] -pub struct OrderRevisionProposalEnqueueRequest { +pub struct TradeRevisionProposalEnqueueRequest { #[serde(serialize_with = "crate::actor_json::serialize_actor_context")] pub actor: RadrootsActorContext, pub root_event: RadrootsNostrEventPtr, pub previous_event: RadrootsNostrEventPtr, pub proposal: RadrootsOrderRevisionProposal, - pub target_relays: SdkRelayTargetPolicy, + pub target_relays: RelayResolutionPolicy, + pub publish_mode: PublishMode, + pub ack_policy: AckPolicy, pub idempotency_key: Option<SdkIdempotencyKey>, pub created_at: Option<RadrootsSdkTimestamp>, } #[cfg(feature = "runtime")] -impl OrderRevisionProposalEnqueueRequest { +impl TradeRevisionProposalEnqueueRequest { pub fn new( actor: RadrootsActorContext, root_event: RadrootsNostrEventPtr, previous_event: RadrootsNostrEventPtr, proposal: RadrootsOrderRevisionProposal, - target_relays: SdkRelayTargetPolicy, + target_relays: RelayResolutionPolicy, + publish_mode: PublishMode, + ack_policy: AckPolicy, ) -> Self { Self { actor, @@ -529,6 +549,8 @@ impl OrderRevisionProposalEnqueueRequest { previous_event, proposal, target_relays, + publish_mode, + ack_policy, idempotency_key: None, created_at: None, } @@ -543,7 +565,7 @@ impl OrderRevisionProposalEnqueueRequest { I: IntoIterator<Item = S>, S: AsRef<str>, { - self.target_relays = SdkRelayTargetPolicy::try_explicit(target_relays, policy)?; + self.target_relays = RelayResolutionPolicy::try_explicit(target_relays, policy)?; Ok(self) } @@ -568,8 +590,8 @@ impl OrderRevisionProposalEnqueueRequest { #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct OrderRevisionProposalPlan { - pub workflow: OrderWorkflowPlan, +pub struct TradeRevisionProposalPlan { + pub workflow: TradeWorkflowPlan, pub order_id: RadrootsOrderId, pub listing_addr: RadrootsListingAddress, pub buyer_pubkey: RadrootsPublicKey, @@ -583,8 +605,8 @@ pub struct OrderRevisionProposalPlan { #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct OrderRevisionProposalReceipt { - pub workflow: OrderWorkflowEnqueueReceipt, +pub struct TradeRevisionProposalReceipt { + pub workflow: TradeWorkflowEnqueueReceipt, pub order_id: RadrootsOrderId, pub listing_addr: RadrootsListingAddress, pub buyer_pubkey: RadrootsPublicKey, @@ -603,7 +625,7 @@ pub struct OrderRevisionProposalReceipt { #[cfg(feature = "runtime")] #[derive(Clone, Debug, serde::Serialize)] #[non_exhaustive] -pub struct OrderRevisionDecisionPrepareRequest { +pub struct TradeRevisionDecisionPrepareRequest { #[serde(serialize_with = "crate::actor_json::serialize_actor_context")] pub actor: RadrootsActorContext, pub root_event: RadrootsNostrEventPtr, @@ -613,7 +635,7 @@ pub struct OrderRevisionDecisionPrepareRequest { } #[cfg(feature = "runtime")] -impl OrderRevisionDecisionPrepareRequest { +impl TradeRevisionDecisionPrepareRequest { pub fn new( actor: RadrootsActorContext, root_event: RadrootsNostrEventPtr, @@ -638,25 +660,29 @@ impl OrderRevisionDecisionPrepareRequest { #[cfg(feature = "runtime")] #[derive(Clone, Debug, serde::Serialize)] #[non_exhaustive] -pub struct OrderRevisionDecisionEnqueueRequest { +pub struct TradeRevisionDecisionEnqueueRequest { #[serde(serialize_with = "crate::actor_json::serialize_actor_context")] pub actor: RadrootsActorContext, pub root_event: RadrootsNostrEventPtr, pub previous_event: RadrootsNostrEventPtr, pub decision: RadrootsOrderRevisionDecision, - pub target_relays: SdkRelayTargetPolicy, + pub target_relays: RelayResolutionPolicy, + pub publish_mode: PublishMode, + pub ack_policy: AckPolicy, pub idempotency_key: Option<SdkIdempotencyKey>, pub created_at: Option<RadrootsSdkTimestamp>, } #[cfg(feature = "runtime")] -impl OrderRevisionDecisionEnqueueRequest { +impl TradeRevisionDecisionEnqueueRequest { pub fn new( actor: RadrootsActorContext, root_event: RadrootsNostrEventPtr, previous_event: RadrootsNostrEventPtr, decision: RadrootsOrderRevisionDecision, - target_relays: SdkRelayTargetPolicy, + target_relays: RelayResolutionPolicy, + publish_mode: PublishMode, + ack_policy: AckPolicy, ) -> Self { Self { actor, @@ -664,6 +690,8 @@ impl OrderRevisionDecisionEnqueueRequest { previous_event, decision, target_relays, + publish_mode, + ack_policy, idempotency_key: None, created_at: None, } @@ -678,7 +706,7 @@ impl OrderRevisionDecisionEnqueueRequest { I: IntoIterator<Item = S>, S: AsRef<str>, { - self.target_relays = SdkRelayTargetPolicy::try_explicit(target_relays, policy)?; + self.target_relays = RelayResolutionPolicy::try_explicit(target_relays, policy)?; Ok(self) } @@ -703,8 +731,8 @@ impl OrderRevisionDecisionEnqueueRequest { #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct OrderRevisionDecisionPlan { - pub workflow: OrderWorkflowPlan, +pub struct TradeRevisionDecisionPlan { + pub workflow: TradeWorkflowPlan, pub order_id: RadrootsOrderId, pub listing_addr: RadrootsListingAddress, pub buyer_pubkey: RadrootsPublicKey, @@ -718,8 +746,8 @@ pub struct OrderRevisionDecisionPlan { #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct OrderRevisionDecisionReceipt { - pub workflow: OrderWorkflowEnqueueReceipt, +pub struct TradeRevisionDecisionReceipt { + pub workflow: TradeWorkflowEnqueueReceipt, pub order_id: RadrootsOrderId, pub listing_addr: RadrootsListingAddress, pub buyer_pubkey: RadrootsPublicKey, @@ -738,7 +766,7 @@ pub struct OrderRevisionDecisionReceipt { #[cfg(feature = "runtime")] #[derive(Clone, Debug, serde::Serialize)] #[non_exhaustive] -pub struct OrderCancellationPrepareRequest { +pub struct TradeCancellationPrepareRequest { #[serde(serialize_with = "crate::actor_json::serialize_actor_context")] pub actor: RadrootsActorContext, pub root_event: RadrootsNostrEventPtr, @@ -748,7 +776,7 @@ pub struct OrderCancellationPrepareRequest { } #[cfg(feature = "runtime")] -impl OrderCancellationPrepareRequest { +impl TradeCancellationPrepareRequest { pub fn new( actor: RadrootsActorContext, root_event: RadrootsNostrEventPtr, @@ -773,25 +801,29 @@ impl OrderCancellationPrepareRequest { #[cfg(feature = "runtime")] #[derive(Clone, Debug, serde::Serialize)] #[non_exhaustive] -pub struct OrderCancellationEnqueueRequest { +pub struct TradeCancellationEnqueueRequest { #[serde(serialize_with = "crate::actor_json::serialize_actor_context")] pub actor: RadrootsActorContext, pub root_event: RadrootsNostrEventPtr, pub previous_event: RadrootsNostrEventPtr, pub cancellation: RadrootsOrderCancellation, - pub target_relays: SdkRelayTargetPolicy, + pub target_relays: RelayResolutionPolicy, + pub publish_mode: PublishMode, + pub ack_policy: AckPolicy, pub idempotency_key: Option<SdkIdempotencyKey>, pub created_at: Option<RadrootsSdkTimestamp>, } #[cfg(feature = "runtime")] -impl OrderCancellationEnqueueRequest { +impl TradeCancellationEnqueueRequest { pub fn new( actor: RadrootsActorContext, root_event: RadrootsNostrEventPtr, previous_event: RadrootsNostrEventPtr, cancellation: RadrootsOrderCancellation, - target_relays: SdkRelayTargetPolicy, + target_relays: RelayResolutionPolicy, + publish_mode: PublishMode, + ack_policy: AckPolicy, ) -> Self { Self { actor, @@ -799,6 +831,8 @@ impl OrderCancellationEnqueueRequest { previous_event, cancellation, target_relays, + publish_mode, + ack_policy, idempotency_key: None, created_at: None, } @@ -813,7 +847,7 @@ impl OrderCancellationEnqueueRequest { I: IntoIterator<Item = S>, S: AsRef<str>, { - self.target_relays = SdkRelayTargetPolicy::try_explicit(target_relays, policy)?; + self.target_relays = RelayResolutionPolicy::try_explicit(target_relays, policy)?; Ok(self) } @@ -838,8 +872,8 @@ impl OrderCancellationEnqueueRequest { #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct OrderCancellationPlan { - pub workflow: OrderWorkflowPlan, +pub struct TradeCancellationPlan { + pub workflow: TradeWorkflowPlan, pub order_id: RadrootsOrderId, pub listing_addr: RadrootsListingAddress, pub buyer_pubkey: RadrootsPublicKey, @@ -853,8 +887,8 @@ pub struct OrderCancellationPlan { #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct OrderCancellationReceipt { - pub workflow: OrderWorkflowEnqueueReceipt, +pub struct TradeCancellationReceipt { + pub workflow: TradeWorkflowEnqueueReceipt, pub order_id: RadrootsOrderId, pub listing_addr: RadrootsListingAddress, pub buyer_pubkey: RadrootsPublicKey, @@ -873,24 +907,25 @@ pub struct OrderCancellationReceipt { #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] #[non_exhaustive] -pub struct OrderStatusRequest { - pub order_id: RadrootsOrderId, +pub struct TradeStatusRequest { + pub locator: RadrootsTradeLocator, pub limit: u32, } #[cfg(feature = "runtime")] -impl OrderStatusRequest { - pub fn new(order_id: RadrootsOrderId) -> Self { +impl TradeStatusRequest { + pub fn new(locator: RadrootsTradeLocator) -> Self { Self { - order_id, - limit: ORDER_STATUS_DEFAULT_LIMIT, + locator, + limit: TRADE_STATUS_DEFAULT_LIMIT, } } pub fn parse(order_id: &str) -> Result<Self, RadrootsSdkError> { RadrootsOrderId::parse(order_id) + .map(RadrootsTradeLocator::from_order_id) .map(Self::new) - .map_err(|error| RadrootsSdkError::invalid_order_id(order_id, error.to_string())) + .map_err(|error| RadrootsSdkError::invalid_trade_id(order_id, error.to_string())) } pub fn with_limit(mut self, limit: u32) -> Self { @@ -899,11 +934,11 @@ impl OrderStatusRequest { } fn validate(&self) -> Result<(), RadrootsSdkError> { - if self.limit == 0 || self.limit > ORDER_STATUS_MAX_LIMIT { - return Err(RadrootsSdkError::order_status_limit_invalid( + if self.limit == 0 || self.limit > TRADE_STATUS_MAX_LIMIT { + return Err(RadrootsSdkError::trade_status_limit_invalid( self.limit, 1, - ORDER_STATUS_MAX_LIMIT, + TRADE_STATUS_MAX_LIMIT, )); } Ok(()) @@ -912,21 +947,24 @@ impl OrderStatusRequest { #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct OrderStatusReceipt { +pub struct TradeStatusReceipt { + pub locator: RadrootsTradeLocator, pub order_id: RadrootsOrderId, - pub source: SdkOrderStatusSource, + pub root_event_id: Option<RadrootsEventId>, + pub ambiguity_candidates: Vec<TradeStatusAmbiguityCandidate>, + pub source: SdkTradeStatusSource, pub found: bool, pub event_count: usize, pub limit_applied: u32, - pub status: OrderStatusKind, + pub status: TradeStatusKind, pub lifecycle_terminal: bool, pub listing_addr: Option<RadrootsListingAddress>, pub buyer_pubkey: Option<RadrootsPublicKey>, pub seller_pubkey: Option<RadrootsPublicKey>, pub economics: Option<RadrootsOrderEconomics>, - pub evidence: OrderStatusEvidenceSummary, - pub eligibility: OrderStatusEligibility, - pub next_action: OrderStatusNextActionKind, + pub evidence: TradeStatusEvidenceSummary, + pub eligibility: TradeStatusEligibility, + pub next_action: TradeStatusNextActionKind, pub event_ids: Vec<RadrootsEventId>, pub request_event_id: Option<RadrootsEventId>, pub decision_event_id: Option<RadrootsEventId>, @@ -935,12 +973,18 @@ pub struct OrderStatusReceipt { pub pending_revision_event_id: Option<RadrootsEventId>, pub cancellation_event_id: Option<RadrootsEventId>, pub last_event_id: Option<RadrootsEventId>, - pub issues: Vec<SdkOrderStatusIssue>, + pub issues: Vec<SdkTradeStatusIssue>, } #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct OrderStatusEvidenceSummary { +pub struct TradeStatusAmbiguityCandidate { + pub locator: RadrootsTradeLocator, +} + +#[cfg(feature = "runtime")] +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] +pub struct TradeStatusEvidenceSummary { pub event_count: usize, pub limit_applied: u32, pub has_request: bool, @@ -953,7 +997,7 @@ pub struct OrderStatusEvidenceSummary { #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct OrderStatusEligibility { +pub struct TradeStatusEligibility { pub can_decide: bool, pub can_propose_revision: bool, pub can_decide_revision: bool, @@ -964,7 +1008,7 @@ pub struct OrderStatusEligibility { #[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)] #[serde(rename_all = "snake_case")] #[non_exhaustive] -pub enum OrderStatusNextActionKind { +pub enum TradeStatusNextActionKind { NoLocalOrder, InspectEvidenceIssues, AwaitSellerDecision, @@ -977,7 +1021,7 @@ pub enum OrderStatusNextActionKind { #[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)] #[serde(rename_all = "snake_case")] #[non_exhaustive] -pub enum SdkOrderStatusSource { +pub enum SdkTradeStatusSource { LocalEventStore, } @@ -985,8 +1029,9 @@ pub enum SdkOrderStatusSource { #[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)] #[serde(rename_all = "snake_case")] #[non_exhaustive] -pub enum OrderStatusKind { +pub enum TradeStatusKind { Missing, + Ambiguous, Requested, RevisionProposed, AgreedPendingRhi, @@ -998,18 +1043,18 @@ pub enum OrderStatusKind { #[cfg(feature = "runtime")] #[derive(Clone, Debug, PartialEq, Eq)] -pub struct SdkOrderStatusIssue { - pub kind: SdkOrderStatusIssueKind, +pub struct SdkTradeStatusIssue { + pub kind: SdkTradeStatusIssueKind, pub event_ids: Vec<RadrootsEventId>, } #[cfg(feature = "runtime")] -impl SdkOrderStatusIssue { - fn new(kind: SdkOrderStatusIssueKind, event_ids: Vec<RadrootsEventId>) -> Self { +impl SdkTradeStatusIssue { + fn new(kind: SdkTradeStatusIssueKind, event_ids: Vec<RadrootsEventId>) -> Self { Self { kind, event_ids } } - fn single(kind: SdkOrderStatusIssueKind, event_id: RadrootsEventId) -> Self { + fn single(kind: SdkTradeStatusIssueKind, event_id: RadrootsEventId) -> Self { Self::new(kind, vec![event_id]) } @@ -1019,12 +1064,12 @@ impl SdkOrderStatusIssue { } #[cfg(feature = "runtime")] -impl serde::Serialize for SdkOrderStatusIssue { +impl serde::Serialize for SdkTradeStatusIssue { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer, { - let mut state = serializer.serialize_struct("SdkOrderStatusIssue", 3)?; + let mut state = serializer.serialize_struct("SdkTradeStatusIssue", 3)?; state.serialize_field("code", &self.code())?; state.serialize_field("kind", &self.kind)?; state.serialize_field("event_ids", &self.event_ids)?; @@ -1036,7 +1081,7 @@ impl serde::Serialize for SdkOrderStatusIssue { #[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)] #[serde(rename_all = "snake_case")] #[non_exhaustive] -pub enum SdkOrderStatusIssueKind { +pub enum SdkTradeStatusIssueKind { MissingRequest, MultipleRequests, RequestPayloadInvalid, @@ -1104,7 +1149,7 @@ pub enum SdkOrderStatusIssueKind { } #[cfg(feature = "runtime")] -impl SdkOrderStatusIssueKind { +impl SdkTradeStatusIssueKind { pub fn code(self) -> String { camel_to_snake(format!("{self:?}").as_str()) } @@ -1114,8 +1159,8 @@ impl SdkOrderStatusIssueKind { impl<'sdk> TradesClient<'sdk> { pub async fn ingest_evidence( &self, - request: OrderEvidenceIngestRequest, - ) -> Result<OrderEvidenceIngestReceipt, RadrootsSdkError> { + request: TradeEvidenceIngestRequest, + ) -> Result<TradeEvidenceIngestReceipt, RadrootsSdkError> { let evidence = parse_order_evidence(&request.event)?; let observed_at = self.resolved_created_at(request.observed_at)?; let observed_at_ms = sdk_timestamp_ms(observed_at)?; @@ -1127,7 +1172,7 @@ impl<'sdk> TradesClient<'sdk> { .map_err(|error| RadrootsSdkError::EventStore { message: error.to_string(), })?; - Ok(OrderEvidenceIngestReceipt { + Ok(TradeEvidenceIngestReceipt { order_id: evidence.order_id, listing_addr: evidence.listing_addr, event_id: evidence.event_id, @@ -1139,8 +1184,8 @@ impl<'sdk> TradesClient<'sdk> { pub async fn ingest_request_evidence( &self, - request: OrderRequestEvidenceIngestRequest, - ) -> Result<OrderRequestEvidenceIngestReceipt, RadrootsSdkError> { + request: TradeRequestEvidenceIngestRequest, + ) -> Result<TradeRequestEvidenceIngestReceipt, RadrootsSdkError> { let evidence = parse_order_request_evidence(&request.event)?; let observed_at = self.resolved_created_at(request.observed_at)?; let observed_at_ms = sdk_timestamp_ms(observed_at)?; @@ -1152,7 +1197,7 @@ impl<'sdk> TradesClient<'sdk> { .map_err(|error| RadrootsSdkError::EventStore { message: error.to_string(), })?; - Ok(OrderRequestEvidenceIngestReceipt { + Ok(TradeRequestEvidenceIngestReceipt { order_id: evidence.order_id, listing_addr: evidence.listing_addr, buyer_pubkey: evidence.buyer_pubkey, @@ -1165,8 +1210,8 @@ impl<'sdk> TradesClient<'sdk> { pub fn prepare_submit( &self, - request: OrderSubmitPrepareRequest, - ) -> Result<OrderSubmitPlan, RadrootsSdkError> { + request: TradeSubmitPrepareRequest, + ) -> Result<TradeSubmitPlan, RadrootsSdkError> { let created_at = self.resolved_created_at(request.created_at)?; order_submit_plan( &request.actor, @@ -1179,41 +1224,54 @@ impl<'sdk> TradesClient<'sdk> { #[cfg(feature = "signer-adapters")] pub async fn enqueue_submit( &self, - request: OrderSubmitEnqueueRequest, - ) -> Result<OrderSubmitReceipt, RadrootsSdkError> { - let OrderSubmitEnqueueRequest { + request: TradeSubmitEnqueueRequest, + ) -> Result<TradeSubmitReceipt, RadrootsSdkError> { + let TradeSubmitEnqueueRequest { actor, listing_event, order, target_relays, + publish_mode, + ack_policy, idempotency_key, created_at, } = request; - let prepare_request = OrderSubmitPrepareRequest { + validate_trade_enqueue_policy(publish_mode, ack_policy)?; + let prepare_request = TradeSubmitPrepareRequest { actor: actor.clone(), listing_event, order, created_at, }; let plan = self.prepare_submit(prepare_request)?; - self.enqueue_prepared_submit(&actor, plan, target_relays, idempotency_key) - .await + self.enqueue_prepared_submit( + &actor, + plan, + target_relays, + publish_mode, + ack_policy, + idempotency_key, + ) + .await } pub async fn enqueue_submit_with_explicit_signer( &self, - request: OrderSubmitEnqueueRequest, + request: TradeSubmitEnqueueRequest, signer: &dyn RadrootsEventSigner, - ) -> Result<OrderSubmitReceipt, RadrootsSdkError> { - let OrderSubmitEnqueueRequest { + ) -> Result<TradeSubmitReceipt, RadrootsSdkError> { + let TradeSubmitEnqueueRequest { actor, listing_event, order, target_relays, + publish_mode, + ack_policy, idempotency_key, created_at, } = request; - let prepare_request = OrderSubmitPrepareRequest { + validate_trade_enqueue_policy(publish_mode, ack_policy)?; + let prepare_request = TradeSubmitPrepareRequest { actor: actor.clone(), listing_event, order, @@ -1224,6 +1282,8 @@ impl<'sdk> TradesClient<'sdk> { &actor, plan, target_relays, + publish_mode, + ack_policy, idempotency_key, signer, ) @@ -1234,17 +1294,20 @@ impl<'sdk> TradesClient<'sdk> { pub async fn enqueue_prepared_submit( &self, actor: &RadrootsActorContext, - plan: OrderSubmitPlan, - target_relays: SdkRelayTargetPolicy, + plan: TradeSubmitPlan, + target_relays: RelayResolutionPolicy, + publish_mode: PublishMode, + ack_policy: AckPolicy, idempotency_key: Option<SdkIdempotencyKey>, - ) -> Result<OrderSubmitReceipt, RadrootsSdkError> { + ) -> Result<TradeSubmitReceipt, RadrootsSdkError> { + validate_trade_enqueue_policy(publish_mode, ack_policy)?; let enqueue = enqueue_configured_signed_workflow( self.sdk, SdkWorkflowEnqueueRequest { - operation_kind: OrderWorkflowKind::Submit.operation_kind(), + operation_kind: TradeWorkflowKind::Submit.operation_kind(), actor, frozen_draft: &plan.frozen_draft, - target_relays, + target_relays: target_relays.workflow_target_policy(), idempotency_key, }, ) @@ -1255,18 +1318,21 @@ impl<'sdk> TradesClient<'sdk> { pub async fn enqueue_prepared_submit_with_explicit_signer( &self, actor: &RadrootsActorContext, - plan: OrderSubmitPlan, - target_relays: SdkRelayTargetPolicy, + plan: TradeSubmitPlan, + target_relays: RelayResolutionPolicy, + publish_mode: PublishMode, + ack_policy: AckPolicy, idempotency_key: Option<SdkIdempotencyKey>, signer: &dyn RadrootsEventSigner, - ) -> Result<OrderSubmitReceipt, RadrootsSdkError> { + ) -> Result<TradeSubmitReceipt, RadrootsSdkError> { + validate_trade_enqueue_policy(publish_mode, ack_policy)?; let enqueue = enqueue_signed_workflow( self.sdk, SdkWorkflowEnqueueRequest { - operation_kind: OrderWorkflowKind::Submit.operation_kind(), + operation_kind: TradeWorkflowKind::Submit.operation_kind(), actor, frozen_draft: &plan.frozen_draft, - target_relays, + target_relays: target_relays.workflow_target_policy(), idempotency_key, }, signer, @@ -1277,8 +1343,8 @@ impl<'sdk> TradesClient<'sdk> { pub fn prepare_decision( &self, - request: OrderDecisionPrepareRequest, - ) -> Result<OrderDecisionPlan, RadrootsSdkError> { + request: TradeDecisionPrepareRequest, + ) -> Result<TradeDecisionPlan, RadrootsSdkError> { let created_at = self.resolved_created_at(request.created_at)?; order_decision_plan( &request.actor, @@ -1291,41 +1357,54 @@ impl<'sdk> TradesClient<'sdk> { #[cfg(feature = "signer-adapters")] pub async fn enqueue_decision( &self, - request: OrderDecisionEnqueueRequest, - ) -> Result<OrderDecisionReceipt, RadrootsSdkError> { - let OrderDecisionEnqueueRequest { + request: TradeDecisionEnqueueRequest, + ) -> Result<TradeDecisionReceipt, RadrootsSdkError> { + let TradeDecisionEnqueueRequest { actor, request_event, decision, target_relays, + publish_mode, + ack_policy, idempotency_key, created_at, } = request; - let prepare_request = OrderDecisionPrepareRequest { + validate_trade_enqueue_policy(publish_mode, ack_policy)?; + let prepare_request = TradeDecisionPrepareRequest { actor: actor.clone(), request_event, decision, created_at, }; let plan = self.prepare_decision(prepare_request)?; - self.enqueue_prepared_decision(&actor, plan, target_relays, idempotency_key) - .await + self.enqueue_prepared_decision( + &actor, + plan, + target_relays, + publish_mode, + ack_policy, + idempotency_key, + ) + .await } pub async fn enqueue_decision_with_explicit_signer( &self, - request: OrderDecisionEnqueueRequest, + request: TradeDecisionEnqueueRequest, signer: &dyn RadrootsEventSigner, - ) -> Result<OrderDecisionReceipt, RadrootsSdkError> { - let OrderDecisionEnqueueRequest { + ) -> Result<TradeDecisionReceipt, RadrootsSdkError> { + let TradeDecisionEnqueueRequest { actor, request_event, decision, target_relays, + publish_mode, + ack_policy, idempotency_key, created_at, } = request; - let prepare_request = OrderDecisionPrepareRequest { + validate_trade_enqueue_policy(publish_mode, ack_policy)?; + let prepare_request = TradeDecisionPrepareRequest { actor: actor.clone(), request_event, decision, @@ -1336,6 +1415,8 @@ impl<'sdk> TradesClient<'sdk> { &actor, plan, target_relays, + publish_mode, + ack_policy, idempotency_key, signer, ) @@ -1346,10 +1427,13 @@ impl<'sdk> TradesClient<'sdk> { pub async fn enqueue_prepared_decision( &self, actor: &RadrootsActorContext, - plan: OrderDecisionPlan, - target_relays: SdkRelayTargetPolicy, + plan: TradeDecisionPlan, + target_relays: RelayResolutionPolicy, + publish_mode: PublishMode, + ack_policy: AckPolicy, idempotency_key: Option<SdkIdempotencyKey>, - ) -> Result<OrderDecisionReceipt, RadrootsSdkError> { + ) -> Result<TradeDecisionReceipt, RadrootsSdkError> { + validate_trade_enqueue_policy(publish_mode, ack_policy)?; if !self .prepared_order_event_exists(&plan.expected_event_id) .await? @@ -1359,10 +1443,10 @@ impl<'sdk> TradesClient<'sdk> { let enqueue = enqueue_configured_signed_workflow( self.sdk, SdkWorkflowEnqueueRequest { - operation_kind: OrderWorkflowKind::Decision.operation_kind(), + operation_kind: TradeWorkflowKind::Decision.operation_kind(), actor, frozen_draft: &plan.frozen_draft, - target_relays, + target_relays: target_relays.workflow_target_policy(), idempotency_key, }, ) @@ -1373,11 +1457,14 @@ impl<'sdk> TradesClient<'sdk> { pub async fn enqueue_prepared_decision_with_explicit_signer( &self, actor: &RadrootsActorContext, - plan: OrderDecisionPlan, - target_relays: SdkRelayTargetPolicy, + plan: TradeDecisionPlan, + target_relays: RelayResolutionPolicy, + publish_mode: PublishMode, + ack_policy: AckPolicy, idempotency_key: Option<SdkIdempotencyKey>, signer: &dyn RadrootsEventSigner, - ) -> Result<OrderDecisionReceipt, RadrootsSdkError> { + ) -> Result<TradeDecisionReceipt, RadrootsSdkError> { + validate_trade_enqueue_policy(publish_mode, ack_policy)?; if !self .prepared_order_event_exists(&plan.expected_event_id) .await? @@ -1387,10 +1474,10 @@ impl<'sdk> TradesClient<'sdk> { let enqueue = enqueue_signed_workflow( self.sdk, SdkWorkflowEnqueueRequest { - operation_kind: OrderWorkflowKind::Decision.operation_kind(), + operation_kind: TradeWorkflowKind::Decision.operation_kind(), actor, frozen_draft: &plan.frozen_draft, - target_relays, + target_relays: target_relays.workflow_target_policy(), idempotency_key, }, signer, @@ -1401,8 +1488,8 @@ impl<'sdk> TradesClient<'sdk> { pub fn prepare_revision_proposal( &self, - request: OrderRevisionProposalPrepareRequest, - ) -> Result<OrderRevisionProposalPlan, RadrootsSdkError> { + request: TradeRevisionProposalPrepareRequest, + ) -> Result<TradeRevisionProposalPlan, RadrootsSdkError> { let created_at = self.resolved_created_at(request.created_at)?; order_revision_proposal_plan( &request.actor, @@ -1416,18 +1503,21 @@ impl<'sdk> TradesClient<'sdk> { #[cfg(feature = "signer-adapters")] pub async fn enqueue_revision_proposal( &self, - request: OrderRevisionProposalEnqueueRequest, - ) -> Result<OrderRevisionProposalReceipt, RadrootsSdkError> { - let OrderRevisionProposalEnqueueRequest { + request: TradeRevisionProposalEnqueueRequest, + ) -> Result<TradeRevisionProposalReceipt, RadrootsSdkError> { + let TradeRevisionProposalEnqueueRequest { actor, root_event, previous_event, proposal, target_relays, + publish_mode, + ack_policy, idempotency_key, created_at, } = request; - let prepare_request = OrderRevisionProposalPrepareRequest { + validate_trade_enqueue_policy(publish_mode, ack_policy)?; + let prepare_request = TradeRevisionProposalPrepareRequest { actor: actor.clone(), root_event, previous_event, @@ -1435,25 +1525,35 @@ impl<'sdk> TradesClient<'sdk> { created_at, }; let plan = self.prepare_revision_proposal(prepare_request)?; - self.enqueue_prepared_revision_proposal(&actor, plan, target_relays, idempotency_key) - .await + self.enqueue_prepared_revision_proposal( + &actor, + plan, + target_relays, + publish_mode, + ack_policy, + idempotency_key, + ) + .await } pub async fn enqueue_revision_proposal_with_explicit_signer( &self, - request: OrderRevisionProposalEnqueueRequest, + request: TradeRevisionProposalEnqueueRequest, signer: &dyn RadrootsEventSigner, - ) -> Result<OrderRevisionProposalReceipt, RadrootsSdkError> { - let OrderRevisionProposalEnqueueRequest { + ) -> Result<TradeRevisionProposalReceipt, RadrootsSdkError> { + let TradeRevisionProposalEnqueueRequest { actor, root_event, previous_event, proposal, target_relays, + publish_mode, + ack_policy, idempotency_key, created_at, } = request; - let prepare_request = OrderRevisionProposalPrepareRequest { + validate_trade_enqueue_policy(publish_mode, ack_policy)?; + let prepare_request = TradeRevisionProposalPrepareRequest { actor: actor.clone(), root_event, previous_event, @@ -1465,6 +1565,8 @@ impl<'sdk> TradesClient<'sdk> { &actor, plan, target_relays, + publish_mode, + ack_policy, idempotency_key, signer, ) @@ -1475,10 +1577,13 @@ impl<'sdk> TradesClient<'sdk> { pub async fn enqueue_prepared_revision_proposal( &self, actor: &RadrootsActorContext, - plan: OrderRevisionProposalPlan, - target_relays: SdkRelayTargetPolicy, + plan: TradeRevisionProposalPlan, + target_relays: RelayResolutionPolicy, + publish_mode: PublishMode, + ack_policy: AckPolicy, idempotency_key: Option<SdkIdempotencyKey>, - ) -> Result<OrderRevisionProposalReceipt, RadrootsSdkError> { + ) -> Result<TradeRevisionProposalReceipt, RadrootsSdkError> { + validate_trade_enqueue_policy(publish_mode, ack_policy)?; if !self .prepared_order_event_exists(&plan.expected_event_id) .await? @@ -1488,10 +1593,10 @@ impl<'sdk> TradesClient<'sdk> { let enqueue = enqueue_configured_signed_workflow( self.sdk, SdkWorkflowEnqueueRequest { - operation_kind: OrderWorkflowKind::RevisionProposal.operation_kind(), + operation_kind: TradeWorkflowKind::RevisionProposal.operation_kind(), actor, frozen_draft: &plan.frozen_draft, - target_relays, + target_relays: target_relays.workflow_target_policy(), idempotency_key, }, ) @@ -1502,11 +1607,14 @@ impl<'sdk> TradesClient<'sdk> { pub async fn enqueue_prepared_revision_proposal_with_explicit_signer( &self, actor: &RadrootsActorContext, - plan: OrderRevisionProposalPlan, - target_relays: SdkRelayTargetPolicy, + plan: TradeRevisionProposalPlan, + target_relays: RelayResolutionPolicy, + publish_mode: PublishMode, + ack_policy: AckPolicy, idempotency_key: Option<SdkIdempotencyKey>, signer: &dyn RadrootsEventSigner, - ) -> Result<OrderRevisionProposalReceipt, RadrootsSdkError> { + ) -> Result<TradeRevisionProposalReceipt, RadrootsSdkError> { + validate_trade_enqueue_policy(publish_mode, ack_policy)?; if !self .prepared_order_event_exists(&plan.expected_event_id) .await? @@ -1516,10 +1624,10 @@ impl<'sdk> TradesClient<'sdk> { let enqueue = enqueue_signed_workflow( self.sdk, SdkWorkflowEnqueueRequest { - operation_kind: OrderWorkflowKind::RevisionProposal.operation_kind(), + operation_kind: TradeWorkflowKind::RevisionProposal.operation_kind(), actor, frozen_draft: &plan.frozen_draft, - target_relays, + target_relays: target_relays.workflow_target_policy(), idempotency_key, }, signer, @@ -1530,8 +1638,8 @@ impl<'sdk> TradesClient<'sdk> { pub fn prepare_revision_decision( &self, - request: OrderRevisionDecisionPrepareRequest, - ) -> Result<OrderRevisionDecisionPlan, RadrootsSdkError> { + request: TradeRevisionDecisionPrepareRequest, + ) -> Result<TradeRevisionDecisionPlan, RadrootsSdkError> { let created_at = self.resolved_created_at(request.created_at)?; order_revision_decision_plan( &request.actor, @@ -1545,18 +1653,21 @@ impl<'sdk> TradesClient<'sdk> { #[cfg(feature = "signer-adapters")] pub async fn enqueue_revision_decision( &self, - request: OrderRevisionDecisionEnqueueRequest, - ) -> Result<OrderRevisionDecisionReceipt, RadrootsSdkError> { - let OrderRevisionDecisionEnqueueRequest { + request: TradeRevisionDecisionEnqueueRequest, + ) -> Result<TradeRevisionDecisionReceipt, RadrootsSdkError> { + let TradeRevisionDecisionEnqueueRequest { actor, root_event, previous_event, decision, target_relays, + publish_mode, + ack_policy, idempotency_key, created_at, } = request; - let prepare_request = OrderRevisionDecisionPrepareRequest { + validate_trade_enqueue_policy(publish_mode, ack_policy)?; + let prepare_request = TradeRevisionDecisionPrepareRequest { actor: actor.clone(), root_event, previous_event, @@ -1564,25 +1675,35 @@ impl<'sdk> TradesClient<'sdk> { created_at, }; let plan = self.prepare_revision_decision(prepare_request)?; - self.enqueue_prepared_revision_decision(&actor, plan, target_relays, idempotency_key) - .await + self.enqueue_prepared_revision_decision( + &actor, + plan, + target_relays, + publish_mode, + ack_policy, + idempotency_key, + ) + .await } pub async fn enqueue_revision_decision_with_explicit_signer( &self, - request: OrderRevisionDecisionEnqueueRequest, + request: TradeRevisionDecisionEnqueueRequest, signer: &dyn RadrootsEventSigner, - ) -> Result<OrderRevisionDecisionReceipt, RadrootsSdkError> { - let OrderRevisionDecisionEnqueueRequest { + ) -> Result<TradeRevisionDecisionReceipt, RadrootsSdkError> { + let TradeRevisionDecisionEnqueueRequest { actor, root_event, previous_event, decision, target_relays, + publish_mode, + ack_policy, idempotency_key, created_at, } = request; - let prepare_request = OrderRevisionDecisionPrepareRequest { + validate_trade_enqueue_policy(publish_mode, ack_policy)?; + let prepare_request = TradeRevisionDecisionPrepareRequest { actor: actor.clone(), root_event, previous_event, @@ -1594,6 +1715,8 @@ impl<'sdk> TradesClient<'sdk> { &actor, plan, target_relays, + publish_mode, + ack_policy, idempotency_key, signer, ) @@ -1604,10 +1727,13 @@ impl<'sdk> TradesClient<'sdk> { pub async fn enqueue_prepared_revision_decision( &self, actor: &RadrootsActorContext, - plan: OrderRevisionDecisionPlan, - target_relays: SdkRelayTargetPolicy, + plan: TradeRevisionDecisionPlan, + target_relays: RelayResolutionPolicy, + publish_mode: PublishMode, + ack_policy: AckPolicy, idempotency_key: Option<SdkIdempotencyKey>, - ) -> Result<OrderRevisionDecisionReceipt, RadrootsSdkError> { + ) -> Result<TradeRevisionDecisionReceipt, RadrootsSdkError> { + validate_trade_enqueue_policy(publish_mode, ack_policy)?; if !self .prepared_order_event_exists(&plan.expected_event_id) .await? @@ -1617,10 +1743,10 @@ impl<'sdk> TradesClient<'sdk> { let enqueue = enqueue_configured_signed_workflow( self.sdk, SdkWorkflowEnqueueRequest { - operation_kind: OrderWorkflowKind::RevisionDecision.operation_kind(), + operation_kind: TradeWorkflowKind::RevisionDecision.operation_kind(), actor, frozen_draft: &plan.frozen_draft, - target_relays, + target_relays: target_relays.workflow_target_policy(), idempotency_key, }, ) @@ -1631,11 +1757,14 @@ impl<'sdk> TradesClient<'sdk> { pub async fn enqueue_prepared_revision_decision_with_explicit_signer( &self, actor: &RadrootsActorContext, - plan: OrderRevisionDecisionPlan, - target_relays: SdkRelayTargetPolicy, + plan: TradeRevisionDecisionPlan, + target_relays: RelayResolutionPolicy, + publish_mode: PublishMode, + ack_policy: AckPolicy, idempotency_key: Option<SdkIdempotencyKey>, signer: &dyn RadrootsEventSigner, - ) -> Result<OrderRevisionDecisionReceipt, RadrootsSdkError> { + ) -> Result<TradeRevisionDecisionReceipt, RadrootsSdkError> { + validate_trade_enqueue_policy(publish_mode, ack_policy)?; if !self .prepared_order_event_exists(&plan.expected_event_id) .await? @@ -1645,10 +1774,10 @@ impl<'sdk> TradesClient<'sdk> { let enqueue = enqueue_signed_workflow( self.sdk, SdkWorkflowEnqueueRequest { - operation_kind: OrderWorkflowKind::RevisionDecision.operation_kind(), + operation_kind: TradeWorkflowKind::RevisionDecision.operation_kind(), actor, frozen_draft: &plan.frozen_draft, - target_relays, + target_relays: target_relays.workflow_target_policy(), idempotency_key, }, signer, @@ -1659,8 +1788,8 @@ impl<'sdk> TradesClient<'sdk> { pub fn prepare_cancellation( &self, - request: OrderCancellationPrepareRequest, - ) -> Result<OrderCancellationPlan, RadrootsSdkError> { + request: TradeCancellationPrepareRequest, + ) -> Result<TradeCancellationPlan, RadrootsSdkError> { let created_at = self.resolved_created_at(request.created_at)?; order_cancellation_plan( &request.actor, @@ -1674,18 +1803,21 @@ impl<'sdk> TradesClient<'sdk> { #[cfg(feature = "signer-adapters")] pub async fn enqueue_cancellation( &self, - request: OrderCancellationEnqueueRequest, - ) -> Result<OrderCancellationReceipt, RadrootsSdkError> { - let OrderCancellationEnqueueRequest { + request: TradeCancellationEnqueueRequest, + ) -> Result<TradeCancellationReceipt, RadrootsSdkError> { + let TradeCancellationEnqueueRequest { actor, root_event, previous_event, cancellation, target_relays, + publish_mode, + ack_policy, idempotency_key, created_at, } = request; - let prepare_request = OrderCancellationPrepareRequest { + validate_trade_enqueue_policy(publish_mode, ack_policy)?; + let prepare_request = TradeCancellationPrepareRequest { actor: actor.clone(), root_event, previous_event, @@ -1693,25 +1825,35 @@ impl<'sdk> TradesClient<'sdk> { created_at, }; let plan = self.prepare_cancellation(prepare_request)?; - self.enqueue_prepared_cancellation(&actor, plan, target_relays, idempotency_key) - .await + self.enqueue_prepared_cancellation( + &actor, + plan, + target_relays, + publish_mode, + ack_policy, + idempotency_key, + ) + .await } pub async fn enqueue_cancellation_with_explicit_signer( &self, - request: OrderCancellationEnqueueRequest, + request: TradeCancellationEnqueueRequest, signer: &dyn RadrootsEventSigner, - ) -> Result<OrderCancellationReceipt, RadrootsSdkError> { - let OrderCancellationEnqueueRequest { + ) -> Result<TradeCancellationReceipt, RadrootsSdkError> { + let TradeCancellationEnqueueRequest { actor, root_event, previous_event, cancellation, target_relays, + publish_mode, + ack_policy, idempotency_key, created_at, } = request; - let prepare_request = OrderCancellationPrepareRequest { + validate_trade_enqueue_policy(publish_mode, ack_policy)?; + let prepare_request = TradeCancellationPrepareRequest { actor: actor.clone(), root_event, previous_event, @@ -1723,6 +1865,8 @@ impl<'sdk> TradesClient<'sdk> { &actor, plan, target_relays, + publish_mode, + ack_policy, idempotency_key, signer, ) @@ -1733,10 +1877,13 @@ impl<'sdk> TradesClient<'sdk> { pub async fn enqueue_prepared_cancellation( &self, actor: &RadrootsActorContext, - plan: OrderCancellationPlan, - target_relays: SdkRelayTargetPolicy, + plan: TradeCancellationPlan, + target_relays: RelayResolutionPolicy, + publish_mode: PublishMode, + ack_policy: AckPolicy, idempotency_key: Option<SdkIdempotencyKey>, - ) -> Result<OrderCancellationReceipt, RadrootsSdkError> { + ) -> Result<TradeCancellationReceipt, RadrootsSdkError> { + validate_trade_enqueue_policy(publish_mode, ack_policy)?; if !self .prepared_order_event_exists(&plan.expected_event_id) .await? @@ -1746,10 +1893,10 @@ impl<'sdk> TradesClient<'sdk> { let enqueue = enqueue_configured_signed_workflow( self.sdk, SdkWorkflowEnqueueRequest { - operation_kind: OrderWorkflowKind::Cancellation.operation_kind(), + operation_kind: TradeWorkflowKind::Cancellation.operation_kind(), actor, frozen_draft: &plan.frozen_draft, - target_relays, + target_relays: target_relays.workflow_target_policy(), idempotency_key, }, ) @@ -1760,11 +1907,14 @@ impl<'sdk> TradesClient<'sdk> { pub async fn enqueue_prepared_cancellation_with_explicit_signer( &self, actor: &RadrootsActorContext, - plan: OrderCancellationPlan, - target_relays: SdkRelayTargetPolicy, + plan: TradeCancellationPlan, + target_relays: RelayResolutionPolicy, + publish_mode: PublishMode, + ack_policy: AckPolicy, idempotency_key: Option<SdkIdempotencyKey>, signer: &dyn RadrootsEventSigner, - ) -> Result<OrderCancellationReceipt, RadrootsSdkError> { + ) -> Result<TradeCancellationReceipt, RadrootsSdkError> { + validate_trade_enqueue_policy(publish_mode, ack_policy)?; if !self .prepared_order_event_exists(&plan.expected_event_id) .await? @@ -1774,10 +1924,10 @@ impl<'sdk> TradesClient<'sdk> { let enqueue = enqueue_signed_workflow( self.sdk, SdkWorkflowEnqueueRequest { - operation_kind: OrderWorkflowKind::Cancellation.operation_kind(), + operation_kind: TradeWorkflowKind::Cancellation.operation_kind(), actor, frozen_draft: &plan.frozen_draft, - target_relays, + target_relays: target_relays.workflow_target_policy(), idempotency_key, }, signer, @@ -1788,17 +1938,20 @@ impl<'sdk> TradesClient<'sdk> { pub async fn status( &self, - request: OrderStatusRequest, - ) -> Result<OrderStatusReceipt, RadrootsSdkError> { + request: TradeStatusRequest, + ) -> Result<TradeStatusReceipt, RadrootsSdkError> { request.validate()?; - let query_result = order_projection_query_for_order_id( + let query_result = order_projection_query_for_trade_locator( &self.sdk._event_store, - &request.order_id, + &request.locator, request.limit, ) .await .map_err(projection_error)?; - Ok(OrderStatusReceipt::from_query_result(query_result)) + Ok(TradeStatusReceipt::from_locator_query_result( + request.locator, + query_result, + )) } fn resolved_created_at( @@ -1813,7 +1966,7 @@ impl<'sdk> TradesClient<'sdk> { async fn require_decision_preflight( &self, - plan: &OrderDecisionPlan, + plan: &TradeDecisionPlan, ) -> Result<(), RadrootsSdkError> { let query_result = self.query_order_projection(&plan.order_id).await?; require_decision_request_evidence(plan, &query_result.projection) @@ -1821,7 +1974,7 @@ impl<'sdk> TradesClient<'sdk> { async fn require_revision_proposal_preflight( &self, - plan: &OrderRevisionProposalPlan, + plan: &TradeRevisionProposalPlan, ) -> Result<(), RadrootsSdkError> { let query_result = self.query_order_projection(&plan.order_id).await?; require_revision_proposal_state(plan, &query_result.projection) @@ -1829,7 +1982,7 @@ impl<'sdk> TradesClient<'sdk> { async fn require_revision_decision_preflight( &self, - plan: &OrderRevisionDecisionPlan, + plan: &TradeRevisionDecisionPlan, ) -> Result<(), RadrootsSdkError> { let query_result = self.query_order_projection(&plan.order_id).await?; require_revision_decision_state(plan, &query_result.projection) @@ -1837,7 +1990,7 @@ impl<'sdk> TradesClient<'sdk> { async fn require_cancellation_preflight( &self, - plan: &OrderCancellationPlan, + plan: &TradeCancellationPlan, ) -> Result<(), RadrootsSdkError> { let query_result = self.query_order_projection(&plan.order_id).await?; require_cancellation_state(plan, &query_result.projection) @@ -1850,7 +2003,7 @@ impl<'sdk> TradesClient<'sdk> { order_projection_query_for_order_id( &self.sdk._event_store, order_id, - ORDER_STATUS_MAX_LIMIT, + TRADE_STATUS_MAX_LIMIT, ) .await .map_err(projection_error) @@ -1872,6 +2025,31 @@ impl<'sdk> TradesClient<'sdk> { } #[cfg(feature = "runtime")] +fn validate_trade_enqueue_policy( + publish_mode: PublishMode, + ack_policy: AckPolicy, +) -> Result<(), RadrootsSdkError> { + if publish_mode == PublishMode::DryRun { + return Err(RadrootsSdkError::InvalidRequest { + message: "trade dry-run publish mode must use a prepare request".to_owned(), + }); + } + if publish_mode == PublishMode::EnqueueOnly && ack_policy != AckPolicy::NoWait { + return Err(RadrootsSdkError::InvalidRequest { + message: "trade enqueue-only publish mode only supports no-wait acknowledgement" + .to_owned(), + }); + } + if publish_mode == PublishMode::EnqueueAndPublish { + return Err(RadrootsSdkError::InvalidRequest { + message: "trade enqueue-and-publish mode requires publish receipt orchestration" + .to_owned(), + }); + } + Ok(()) +} + +#[cfg(feature = "runtime")] struct ParsedOrderEvidence { order_id: RadrootsOrderId, listing_addr: RadrootsListingAddress, @@ -1945,23 +2123,65 @@ fn order_evidence_parse_error( } #[cfg(feature = "runtime")] -impl OrderStatusReceipt { - fn from_query_result(query_result: RadrootsOrderProjectionQueryResult) -> Self { - let projection = query_result.projection; +impl TradeStatusReceipt { + fn from_locator_query_result( + locator: RadrootsTradeLocator, + query_result: RadrootsTradeLocatorProjectionQueryResult, + ) -> Self { + match query_result.resolution { + RadrootsTradeLocatorProjectionResolution::Projected { + locator, + projection, + } => Self::from_projection( + locator.clone(), + locator.root_event_id, + Vec::new(), + projection, + query_result.event_count, + query_result.limit_applied, + query_result.event_ids, + ), + RadrootsTradeLocatorProjectionResolution::Ambiguous { candidates, .. } => { + Self::ambiguous( + locator, + candidates, + query_result.event_count, + query_result.limit_applied, + query_result.event_ids, + ) + } + RadrootsTradeLocatorProjectionResolution::Missing { .. } => Self::missing( + locator, + query_result.event_count, + query_result.limit_applied, + query_result.event_ids, + ), + } + } + + fn from_projection( + locator: RadrootsTradeLocator, + root_event_id: Option<RadrootsEventId>, + ambiguity_candidates: Vec<TradeStatusAmbiguityCandidate>, + projection: RadrootsOrderProjection, + event_count: usize, + limit_applied: u32, + event_ids: Vec<RadrootsEventId>, + ) -> Self { let found = projection.status != RadrootsTradeWorkflowState::Missing; - let evidence = OrderStatusEvidenceSummary::from_projection( - &projection, - query_result.event_count, - query_result.limit_applied, - ); - let eligibility = OrderStatusEligibility::from_projection(&projection); - let next_action = OrderStatusNextActionKind::from_projection(&projection, &eligibility); + let evidence = + TradeStatusEvidenceSummary::from_projection(&projection, event_count, limit_applied); + let eligibility = TradeStatusEligibility::from_projection(&projection); + let next_action = TradeStatusNextActionKind::from_projection(&projection, &eligibility); Self { + locator, order_id: projection.order_id, - source: SdkOrderStatusSource::LocalEventStore, + root_event_id, + ambiguity_candidates, + source: SdkTradeStatusSource::LocalEventStore, found, - event_count: query_result.event_count, - limit_applied: query_result.limit_applied, + event_count, + limit_applied, status: projection.status.into(), lifecycle_terminal: projection.lifecycle_terminal, listing_addr: projection.listing_addr, @@ -1971,7 +2191,7 @@ impl OrderStatusReceipt { evidence, eligibility, next_action, - event_ids: query_result.event_ids, + event_ids, request_event_id: projection.request_event_id, decision_event_id: projection.decision_event_id, agreement_event_id: projection.agreement_event_id, @@ -1982,10 +2202,65 @@ impl OrderStatusReceipt { issues: projection.issues.into_iter().map(Into::into).collect(), } } + + fn missing( + locator: RadrootsTradeLocator, + event_count: usize, + limit_applied: u32, + event_ids: Vec<RadrootsEventId>, + ) -> Self { + Self::from_projection( + locator.clone(), + locator.root_event_id.clone(), + Vec::new(), + RadrootsOrderProjection { + order_id: locator.order_id().clone(), + status: RadrootsTradeWorkflowState::Missing, + request_event_id: None, + decision_event_id: None, + cancellation_event_id: None, + validation_receipt_event_id: None, + lifecycle_terminal: false, + economics: None, + agreement_event_id: None, + pending_revision_event_id: None, + pending_inventory_reservations: Vec::new(), + committed_inventory_reservations: Vec::new(), + listing_addr: locator.listing_addr.clone(), + buyer_pubkey: locator.buyer_pubkey.clone(), + seller_pubkey: locator.seller_pubkey.clone(), + last_event_id: None, + issues: Vec::new(), + }, + event_count, + limit_applied, + event_ids, + ) + } + + fn ambiguous( + locator: RadrootsTradeLocator, + candidates: Vec<RadrootsTradeLocatorCandidate>, + event_count: usize, + limit_applied: u32, + event_ids: Vec<RadrootsEventId>, + ) -> Self { + let ambiguity_candidates = candidates + .into_iter() + .map(|candidate| TradeStatusAmbiguityCandidate { + locator: candidate.locator(), + }) + .collect::<Vec<_>>(); + let mut receipt = Self::missing(locator, event_count, limit_applied, event_ids); + receipt.status = TradeStatusKind::Ambiguous; + receipt.next_action = TradeStatusNextActionKind::InspectEvidenceIssues; + receipt.ambiguity_candidates = ambiguity_candidates; + receipt + } } #[cfg(feature = "runtime")] -impl OrderStatusEvidenceSummary { +impl TradeStatusEvidenceSummary { fn from_projection( projection: &RadrootsOrderProjection, event_count: usize, @@ -2005,7 +2280,7 @@ impl OrderStatusEvidenceSummary { } #[cfg(feature = "runtime")] -impl OrderStatusEligibility { +impl TradeStatusEligibility { fn from_projection(projection: &RadrootsOrderProjection) -> Self { let clean = projection.issues.is_empty(); let open = clean && !projection.lifecycle_terminal; @@ -2026,10 +2301,10 @@ impl OrderStatusEligibility { } #[cfg(feature = "runtime")] -impl OrderStatusNextActionKind { +impl TradeStatusNextActionKind { fn from_projection( projection: &RadrootsOrderProjection, - eligibility: &OrderStatusEligibility, + eligibility: &TradeStatusEligibility, ) -> Self { if projection.status == RadrootsTradeWorkflowState::Missing { return Self::NoLocalOrder; @@ -2060,8 +2335,8 @@ fn order_submit_plan( listing_event: RadrootsNostrEventPtr, order_request: RadrootsOrderRequest, created_at: RadrootsSdkTimestamp, -) -> Result<OrderSubmitPlan, RadrootsSdkError> { - require_buyer_actor(actor, "order.prepare_submit")?; +) -> Result<TradeSubmitPlan, RadrootsSdkError> { + require_buyer_actor(actor, "trade.prepare_submit")?; let listing_event_id = listing_event_id(&listing_event)?; let order_request = canonicalize_order_request_for_signer(order_request, actor.pubkey().as_str()) @@ -2077,16 +2352,16 @@ fn order_submit_plan( })?; let frozen_draft = to_frozen_draft( draft.into_wire_parts(), - ORDER_REQUEST_CONTRACT_ID, + TRADE_SUBMIT_CONTRACT_ID, order_request.buyer_pubkey.as_str(), created_at_nostr, ) .expect("validated order submit draft freezes"); let expected_event_id = RadrootsEventId::parse(frozen_draft.expected_event_id.as_str()) .expect("frozen order submit draft produces a valid event id"); - Ok(OrderSubmitPlan { + Ok(TradeSubmitPlan { workflow: order_workflow_plan( - OrderWorkflowKind::Submit, + TradeWorkflowKind::Submit, expected_event_id.clone(), created_at, ), @@ -2105,12 +2380,12 @@ fn order_decision_plan( request_event: RadrootsNostrEventPtr, decision: RadrootsOrderDecision, created_at: RadrootsSdkTimestamp, -) -> Result<OrderDecisionPlan, RadrootsSdkError> { - require_seller_actor(actor, "order.prepare_decision")?; +) -> Result<TradeDecisionPlan, RadrootsSdkError> { + require_seller_actor(actor, "trade.prepare_decision")?; let request_event_id = request_event_id(&request_event)?; if decision.seller_pubkey.as_str() != actor.pubkey().as_str() { return Err(RadrootsSdkError::UnauthorizedActor { - operation: "order.prepare_decision".to_owned(), + operation: "trade.prepare_decision".to_owned(), reason: "actor pubkey must match order seller_pubkey".to_owned(), }); } @@ -2127,16 +2402,16 @@ fn order_decision_plan( .expect("validated order decision draft encodes"); let frozen_draft = to_frozen_draft( draft.into_wire_parts(), - ORDER_DECISION_CONTRACT_ID, + TRADE_DECISION_CONTRACT_ID, decision.seller_pubkey.as_str(), created_at_nostr, ) .expect("validated order decision draft freezes"); let expected_event_id = RadrootsEventId::parse(frozen_draft.expected_event_id.as_str()) .expect("frozen order decision draft produces a valid event id"); - Ok(OrderDecisionPlan { + Ok(TradeDecisionPlan { workflow: order_workflow_plan( - OrderWorkflowKind::Decision, + TradeWorkflowKind::Decision, expected_event_id.clone(), created_at, ), @@ -2158,13 +2433,13 @@ fn order_revision_proposal_plan( previous_event: RadrootsNostrEventPtr, proposal: RadrootsOrderRevisionProposal, created_at: RadrootsSdkTimestamp, -) -> Result<OrderRevisionProposalPlan, RadrootsSdkError> { - require_seller_actor(actor, "order.prepare_revision_proposal")?; +) -> Result<TradeRevisionProposalPlan, RadrootsSdkError> { + require_seller_actor(actor, "trade.prepare_revision_proposal")?; let root_event_id = order_reference_event_id(&root_event, "root")?; let previous_event_id = order_reference_event_id(&previous_event, "previous")?; if proposal.seller_pubkey.as_str() != actor.pubkey().as_str() { return Err(RadrootsSdkError::UnauthorizedActor { - operation: "order.prepare_revision_proposal".to_owned(), + operation: "trade.prepare_revision_proposal".to_owned(), reason: "actor pubkey must match order seller_pubkey".to_owned(), }); } @@ -2186,14 +2461,14 @@ fn order_revision_proposal_plan( .expect("validated order revision proposal draft encodes"); let (frozen_draft, expected_event_id) = freeze_order_workflow_draft( draft.into_wire_parts(), - ORDER_REVISION_PROPOSAL_CONTRACT_ID, + TRADE_REVISION_PROPOSAL_CONTRACT_ID, seller_pubkey.as_str(), created_at_nostr, "order revision proposal", ); - Ok(OrderRevisionProposalPlan { + Ok(TradeRevisionProposalPlan { workflow: order_workflow_plan( - OrderWorkflowKind::RevisionProposal, + TradeWorkflowKind::RevisionProposal, expected_event_id.clone(), created_at, ), @@ -2216,13 +2491,13 @@ fn order_revision_decision_plan( previous_event: RadrootsNostrEventPtr, decision: RadrootsOrderRevisionDecision, created_at: RadrootsSdkTimestamp, -) -> Result<OrderRevisionDecisionPlan, RadrootsSdkError> { - require_buyer_actor(actor, "order.prepare_revision_decision")?; +) -> Result<TradeRevisionDecisionPlan, RadrootsSdkError> { + require_buyer_actor(actor, "trade.prepare_revision_decision")?; let root_event_id = order_reference_event_id(&root_event, "root")?; let previous_event_id = order_reference_event_id(&previous_event, "previous")?; if decision.buyer_pubkey.as_str() != actor.pubkey().as_str() { return Err(RadrootsSdkError::UnauthorizedActor { - operation: "order.prepare_revision_decision".to_owned(), + operation: "trade.prepare_revision_decision".to_owned(), reason: "actor pubkey must match order buyer_pubkey".to_owned(), }); } @@ -2244,14 +2519,14 @@ fn order_revision_decision_plan( .expect("validated order revision decision draft encodes"); let (frozen_draft, expected_event_id) = freeze_order_workflow_draft( draft.into_wire_parts(), - ORDER_REVISION_DECISION_CONTRACT_ID, + TRADE_REVISION_DECISION_CONTRACT_ID, buyer_pubkey.as_str(), created_at_nostr, "order revision decision", ); - Ok(OrderRevisionDecisionPlan { + Ok(TradeRevisionDecisionPlan { workflow: order_workflow_plan( - OrderWorkflowKind::RevisionDecision, + TradeWorkflowKind::RevisionDecision, expected_event_id.clone(), created_at, ), @@ -2274,13 +2549,13 @@ fn order_cancellation_plan( previous_event: RadrootsNostrEventPtr, cancellation: RadrootsOrderCancellation, created_at: RadrootsSdkTimestamp, -) -> Result<OrderCancellationPlan, RadrootsSdkError> { - require_buyer_actor(actor, "order.prepare_cancellation")?; +) -> Result<TradeCancellationPlan, RadrootsSdkError> { + require_buyer_actor(actor, "trade.prepare_cancellation")?; let root_event_id = order_reference_event_id(&root_event, "root")?; let previous_event_id = order_reference_event_id(&previous_event, "previous")?; if cancellation.buyer_pubkey.as_str() != actor.pubkey().as_str() { return Err(RadrootsSdkError::UnauthorizedActor { - operation: "order.prepare_cancellation".to_owned(), + operation: "trade.prepare_cancellation".to_owned(), reason: "actor pubkey must match order buyer_pubkey".to_owned(), }); } @@ -2295,14 +2570,14 @@ fn order_cancellation_plan( .expect("validated order cancellation draft encodes"); let (frozen_draft, expected_event_id) = freeze_order_workflow_draft( draft.into_wire_parts(), - ORDER_CANCELLATION_CONTRACT_ID, + TRADE_CANCELLATION_CONTRACT_ID, buyer_pubkey.as_str(), created_at_nostr, "order cancellation", ); - Ok(OrderCancellationPlan { + Ok(TradeCancellationPlan { workflow: order_workflow_plan( - OrderWorkflowKind::Cancellation, + TradeWorkflowKind::Cancellation, expected_event_id.clone(), created_at, ), @@ -2320,11 +2595,11 @@ fn order_cancellation_plan( #[cfg(feature = "runtime")] fn order_workflow_plan( - kind: OrderWorkflowKind, + kind: TradeWorkflowKind, expected_event_id: RadrootsEventId, created_at: RadrootsSdkTimestamp, -) -> OrderWorkflowPlan { - OrderWorkflowPlan { +) -> TradeWorkflowPlan { + TradeWorkflowPlan { kind, operation_kind: kind.operation_kind(), contract_id: kind.contract_id(), @@ -2335,12 +2610,12 @@ fn order_workflow_plan( #[cfg(feature = "runtime")] fn order_submit_receipt( - plan: OrderSubmitPlan, + plan: TradeSubmitPlan, enqueue: crate::workflow_runtime::SdkWorkflowEnqueueReceipt, -) -> OrderSubmitReceipt { - OrderSubmitReceipt { +) -> TradeSubmitReceipt { + TradeSubmitReceipt { workflow: order_workflow_enqueue_receipt( - OrderWorkflowKind::Submit, + TradeWorkflowKind::Submit, plan.expected_event_id.clone(), &enqueue, ), @@ -2359,12 +2634,12 @@ fn order_submit_receipt( #[cfg(feature = "runtime")] fn order_decision_receipt( - plan: OrderDecisionPlan, + plan: TradeDecisionPlan, enqueue: crate::workflow_runtime::SdkWorkflowEnqueueReceipt, -) -> OrderDecisionReceipt { - OrderDecisionReceipt { +) -> TradeDecisionReceipt { + TradeDecisionReceipt { workflow: order_workflow_enqueue_receipt( - OrderWorkflowKind::Decision, + TradeWorkflowKind::Decision, plan.expected_event_id.clone(), &enqueue, ), @@ -2385,12 +2660,12 @@ fn order_decision_receipt( #[cfg(feature = "runtime")] fn order_revision_proposal_receipt( - plan: OrderRevisionProposalPlan, + plan: TradeRevisionProposalPlan, enqueue: crate::workflow_runtime::SdkWorkflowEnqueueReceipt, -) -> OrderRevisionProposalReceipt { - OrderRevisionProposalReceipt { +) -> TradeRevisionProposalReceipt { + TradeRevisionProposalReceipt { workflow: order_workflow_enqueue_receipt( - OrderWorkflowKind::RevisionProposal, + TradeWorkflowKind::RevisionProposal, plan.expected_event_id.clone(), &enqueue, ), @@ -2412,12 +2687,12 @@ fn order_revision_proposal_receipt( #[cfg(feature = "runtime")] fn order_revision_decision_receipt( - plan: OrderRevisionDecisionPlan, + plan: TradeRevisionDecisionPlan, enqueue: crate::workflow_runtime::SdkWorkflowEnqueueReceipt, -) -> OrderRevisionDecisionReceipt { - OrderRevisionDecisionReceipt { +) -> TradeRevisionDecisionReceipt { + TradeRevisionDecisionReceipt { workflow: order_workflow_enqueue_receipt( - OrderWorkflowKind::RevisionDecision, + TradeWorkflowKind::RevisionDecision, plan.expected_event_id.clone(), &enqueue, ), @@ -2439,12 +2714,12 @@ fn order_revision_decision_receipt( #[cfg(feature = "runtime")] fn order_cancellation_receipt( - plan: OrderCancellationPlan, + plan: TradeCancellationPlan, enqueue: crate::workflow_runtime::SdkWorkflowEnqueueReceipt, -) -> OrderCancellationReceipt { - OrderCancellationReceipt { +) -> TradeCancellationReceipt { + TradeCancellationReceipt { workflow: order_workflow_enqueue_receipt( - OrderWorkflowKind::Cancellation, + TradeWorkflowKind::Cancellation, plan.expected_event_id.clone(), &enqueue, ), @@ -2466,15 +2741,15 @@ fn order_cancellation_receipt( #[cfg(feature = "runtime")] fn order_workflow_enqueue_receipt( - kind: OrderWorkflowKind, + kind: TradeWorkflowKind, expected_event_id: RadrootsEventId, enqueue: &crate::workflow_runtime::SdkWorkflowEnqueueReceipt, -) -> OrderWorkflowEnqueueReceipt { +) -> TradeWorkflowEnqueueReceipt { let state = SdkMutationState::from(enqueue.state); let digest_prefix = Some(enqueue.idempotency_digest_prefix.clone()); let safe_retry_same_key = true; let replayed_existing_operation = state == SdkMutationState::AlreadyQueued; - OrderWorkflowEnqueueReceipt { + TradeWorkflowEnqueueReceipt { kind, operation_kind: kind.operation_kind(), expected_event_id, @@ -2484,12 +2759,12 @@ fn order_workflow_enqueue_receipt( outbox_event_id: enqueue.outbox_event_id, state, idempotency_digest_prefix: digest_prefix.clone(), - idempotency: OrderWorkflowIdempotencyReceipt { + idempotency: TradeWorkflowIdempotencyReceipt { digest_prefix, replayed_existing_operation, safe_to_retry_with_same_idempotency_key: safe_retry_same_key, }, - retry: OrderWorkflowRetryAdvice { + retry: TradeWorkflowRetryAdvice { retryable_after_error: false, safe_to_retry_enqueue_with_same_idempotency_key: safe_retry_same_key, recovery_actions: Vec::new(), @@ -2568,7 +2843,7 @@ impl OrderPayloadValidate for RadrootsOrderCancellation { } #[cfg(feature = "runtime")] -struct OrderRequestEvidence { +struct TradeRequestEvidence { order_id: RadrootsOrderId, listing_addr: RadrootsListingAddress, buyer_pubkey: RadrootsPublicKey, @@ -2579,7 +2854,7 @@ struct OrderRequestEvidence { #[cfg(feature = "runtime")] fn parse_order_request_evidence( event: &RadrootsNostrEvent, -) -> Result<OrderRequestEvidence, RadrootsSdkError> { +) -> Result<TradeRequestEvidence, RadrootsSdkError> { let request_event_id = RadrootsEventId::parse(event.id.as_str()).map_err(|error| { RadrootsSdkError::InvalidRequest { message: format!("order request evidence event id is invalid: {error}"), @@ -2590,7 +2865,7 @@ fn parse_order_request_evidence( message: format!("order request evidence decode failed: {error}"), })?; let payload = envelope.payload; - Ok(OrderRequestEvidence { + Ok(TradeRequestEvidence { order_id: payload.order_id, listing_addr: payload.listing_addr, buyer_pubkey: payload.buyer_pubkey, @@ -2610,7 +2885,7 @@ fn sdk_timestamp_ms(timestamp: RadrootsSdkTimestamp) -> Result<i64, RadrootsSdkE #[cfg(feature = "runtime")] fn require_decision_request_evidence( - plan: &OrderDecisionPlan, + plan: &TradeDecisionPlan, projection: &RadrootsOrderProjection, ) -> Result<(), RadrootsSdkError> { let Some(request_event_id) = &projection.request_event_id else { @@ -2692,7 +2967,7 @@ struct OrderLifecycleReferences<'a> { #[cfg(feature = "runtime")] fn require_revision_proposal_state( - plan: &OrderRevisionProposalPlan, + plan: &TradeRevisionProposalPlan, projection: &RadrootsOrderProjection, ) -> Result<(), RadrootsSdkError> { let refs = OrderLifecycleReferences { @@ -2713,7 +2988,7 @@ fn require_revision_proposal_state( #[cfg(feature = "runtime")] fn require_revision_decision_state( - plan: &OrderRevisionDecisionPlan, + plan: &TradeRevisionDecisionPlan, projection: &RadrootsOrderProjection, ) -> Result<(), RadrootsSdkError> { let refs = OrderLifecycleReferences { @@ -2738,7 +3013,7 @@ fn require_revision_decision_state( #[cfg(feature = "runtime")] fn require_cancellation_state( - plan: &OrderCancellationPlan, + plan: &TradeCancellationPlan, projection: &RadrootsOrderProjection, ) -> Result<(), RadrootsSdkError> { let refs = OrderLifecycleReferences { @@ -3052,7 +3327,7 @@ fn order_canonicalization_error(error: RadrootsOrderCanonicalizationError) -> Ra match error { RadrootsOrderCanonicalizationError::InvalidBuyerSigner => { RadrootsSdkError::UnauthorizedActor { - operation: "order.prepare_submit".to_owned(), + operation: "trade.prepare_submit".to_owned(), reason: "actor pubkey must match order buyer_pubkey".to_owned(), } } @@ -3072,7 +3347,7 @@ fn order_decision_canonicalization_error( } #[cfg(feature = "runtime")] -impl From<RadrootsTradeWorkflowState> for OrderStatusKind { +impl From<RadrootsTradeWorkflowState> for TradeStatusKind { fn from(status: RadrootsTradeWorkflowState) -> Self { match status { RadrootsTradeWorkflowState::Missing => Self::Missing, @@ -3088,250 +3363,250 @@ impl From<RadrootsTradeWorkflowState> for OrderStatusKind { } #[cfg(feature = "runtime")] -impl From<RadrootsOrderIssue> for SdkOrderStatusIssue { +impl From<RadrootsOrderIssue> for SdkTradeStatusIssue { fn from(issue: RadrootsOrderIssue) -> Self { match issue { RadrootsOrderIssue::MissingRequest => { - Self::new(SdkOrderStatusIssueKind::MissingRequest, Vec::new()) + Self::new(SdkTradeStatusIssueKind::MissingRequest, Vec::new()) } RadrootsOrderIssue::MultipleRequests { event_ids } => { - Self::new(SdkOrderStatusIssueKind::MultipleRequests, event_ids) + Self::new(SdkTradeStatusIssueKind::MultipleRequests, event_ids) } RadrootsOrderIssue::RequestPayloadInvalid { event_id } => { - Self::single(SdkOrderStatusIssueKind::RequestPayloadInvalid, event_id) + Self::single(SdkTradeStatusIssueKind::RequestPayloadInvalid, event_id) } RadrootsOrderIssue::RequestOrderIdMismatch { event_id } => { - Self::single(SdkOrderStatusIssueKind::RequestOrderIdMismatch, event_id) + Self::single(SdkTradeStatusIssueKind::RequestOrderIdMismatch, event_id) } RadrootsOrderIssue::RequestAuthorMismatch { event_id } => { - Self::single(SdkOrderStatusIssueKind::RequestAuthorMismatch, event_id) + Self::single(SdkTradeStatusIssueKind::RequestAuthorMismatch, event_id) } RadrootsOrderIssue::RequestListingAddressInvalid { event_id } => Self::single( - SdkOrderStatusIssueKind::RequestListingAddressInvalid, + SdkTradeStatusIssueKind::RequestListingAddressInvalid, event_id, ), RadrootsOrderIssue::RequestSellerListingMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::RequestSellerListingMismatch, + SdkTradeStatusIssueKind::RequestSellerListingMismatch, event_id, ), RadrootsOrderIssue::DecisionPayloadInvalid { event_id } => { - Self::single(SdkOrderStatusIssueKind::DecisionPayloadInvalid, event_id) + Self::single(SdkTradeStatusIssueKind::DecisionPayloadInvalid, event_id) } RadrootsOrderIssue::DecisionOrderIdMismatch { event_id } => { - Self::single(SdkOrderStatusIssueKind::DecisionOrderIdMismatch, event_id) + Self::single(SdkTradeStatusIssueKind::DecisionOrderIdMismatch, event_id) } RadrootsOrderIssue::DecisionAuthorMismatch { event_id } => { - Self::single(SdkOrderStatusIssueKind::DecisionAuthorMismatch, event_id) + Self::single(SdkTradeStatusIssueKind::DecisionAuthorMismatch, event_id) } RadrootsOrderIssue::DecisionCounterpartyMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::DecisionCounterpartyMismatch, + SdkTradeStatusIssueKind::DecisionCounterpartyMismatch, event_id, ), RadrootsOrderIssue::DecisionBuyerMismatch { event_id } => { - Self::single(SdkOrderStatusIssueKind::DecisionBuyerMismatch, event_id) + Self::single(SdkTradeStatusIssueKind::DecisionBuyerMismatch, event_id) } RadrootsOrderIssue::DecisionSellerMismatch { event_id } => { - Self::single(SdkOrderStatusIssueKind::DecisionSellerMismatch, event_id) + Self::single(SdkTradeStatusIssueKind::DecisionSellerMismatch, event_id) } RadrootsOrderIssue::DecisionListingAddressInvalid { event_id } => Self::single( - SdkOrderStatusIssueKind::DecisionListingAddressInvalid, + SdkTradeStatusIssueKind::DecisionListingAddressInvalid, event_id, ), RadrootsOrderIssue::DecisionListingMismatch { event_id } => { - Self::single(SdkOrderStatusIssueKind::DecisionListingMismatch, event_id) + Self::single(SdkTradeStatusIssueKind::DecisionListingMismatch, event_id) } RadrootsOrderIssue::DecisionRootMismatch { event_id } => { - Self::single(SdkOrderStatusIssueKind::DecisionRootMismatch, event_id) + Self::single(SdkTradeStatusIssueKind::DecisionRootMismatch, event_id) } RadrootsOrderIssue::DecisionPreviousMismatch { event_id } => { - Self::single(SdkOrderStatusIssueKind::DecisionPreviousMismatch, event_id) + Self::single(SdkTradeStatusIssueKind::DecisionPreviousMismatch, event_id) } RadrootsOrderIssue::DecisionMissingInventoryCommitments { event_id } => Self::single( - SdkOrderStatusIssueKind::DecisionMissingInventoryCommitments, + SdkTradeStatusIssueKind::DecisionMissingInventoryCommitments, event_id, ), RadrootsOrderIssue::DecisionInventoryCommitmentMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::DecisionInventoryCommitmentMismatch, + SdkTradeStatusIssueKind::DecisionInventoryCommitmentMismatch, event_id, ), RadrootsOrderIssue::DecisionMissingReason { event_id } => { - Self::single(SdkOrderStatusIssueKind::DecisionMissingReason, event_id) + Self::single(SdkTradeStatusIssueKind::DecisionMissingReason, event_id) } RadrootsOrderIssue::ConflictingDecisions { event_ids } => { - Self::new(SdkOrderStatusIssueKind::ConflictingDecisions, event_ids) + Self::new(SdkTradeStatusIssueKind::ConflictingDecisions, event_ids) } RadrootsOrderIssue::RevisionProposalPayloadInvalid { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionProposalPayloadInvalid, + SdkTradeStatusIssueKind::RevisionProposalPayloadInvalid, event_id, ), RadrootsOrderIssue::RevisionProposalOrderIdMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionProposalOrderIdMismatch, + SdkTradeStatusIssueKind::RevisionProposalOrderIdMismatch, event_id, ), RadrootsOrderIssue::RevisionProposalAuthorMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionProposalAuthorMismatch, + SdkTradeStatusIssueKind::RevisionProposalAuthorMismatch, event_id, ), RadrootsOrderIssue::RevisionProposalCounterpartyMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionProposalCounterpartyMismatch, + SdkTradeStatusIssueKind::RevisionProposalCounterpartyMismatch, event_id, ), RadrootsOrderIssue::RevisionProposalBuyerMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionProposalBuyerMismatch, + SdkTradeStatusIssueKind::RevisionProposalBuyerMismatch, event_id, ), RadrootsOrderIssue::RevisionProposalSellerMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionProposalSellerMismatch, + SdkTradeStatusIssueKind::RevisionProposalSellerMismatch, event_id, ), RadrootsOrderIssue::RevisionProposalListingAddressInvalid { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionProposalListingAddressInvalid, + SdkTradeStatusIssueKind::RevisionProposalListingAddressInvalid, event_id, ), RadrootsOrderIssue::RevisionProposalListingMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionProposalListingMismatch, + SdkTradeStatusIssueKind::RevisionProposalListingMismatch, event_id, ), RadrootsOrderIssue::RevisionProposalRootMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionProposalRootMismatch, + SdkTradeStatusIssueKind::RevisionProposalRootMismatch, event_id, ), RadrootsOrderIssue::RevisionProposalPreviousMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionProposalPreviousMismatch, + SdkTradeStatusIssueKind::RevisionProposalPreviousMismatch, event_id, ), RadrootsOrderIssue::RevisionDecisionWithoutProposal { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionDecisionWithoutProposal, + SdkTradeStatusIssueKind::RevisionDecisionWithoutProposal, event_id, ), RadrootsOrderIssue::RevisionDecisionPayloadInvalid { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionDecisionPayloadInvalid, + SdkTradeStatusIssueKind::RevisionDecisionPayloadInvalid, event_id, ), RadrootsOrderIssue::RevisionDecisionOrderIdMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionDecisionOrderIdMismatch, + SdkTradeStatusIssueKind::RevisionDecisionOrderIdMismatch, event_id, ), RadrootsOrderIssue::RevisionDecisionAuthorMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionDecisionAuthorMismatch, + SdkTradeStatusIssueKind::RevisionDecisionAuthorMismatch, event_id, ), RadrootsOrderIssue::RevisionDecisionCounterpartyMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionDecisionCounterpartyMismatch, + SdkTradeStatusIssueKind::RevisionDecisionCounterpartyMismatch, event_id, ), RadrootsOrderIssue::RevisionDecisionBuyerMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionDecisionBuyerMismatch, + SdkTradeStatusIssueKind::RevisionDecisionBuyerMismatch, event_id, ), RadrootsOrderIssue::RevisionDecisionSellerMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionDecisionSellerMismatch, + SdkTradeStatusIssueKind::RevisionDecisionSellerMismatch, event_id, ), RadrootsOrderIssue::RevisionDecisionListingAddressInvalid { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionDecisionListingAddressInvalid, + SdkTradeStatusIssueKind::RevisionDecisionListingAddressInvalid, event_id, ), RadrootsOrderIssue::RevisionDecisionListingMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionDecisionListingMismatch, + SdkTradeStatusIssueKind::RevisionDecisionListingMismatch, event_id, ), RadrootsOrderIssue::RevisionDecisionRootMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionDecisionRootMismatch, + SdkTradeStatusIssueKind::RevisionDecisionRootMismatch, event_id, ), RadrootsOrderIssue::RevisionDecisionPreviousMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionDecisionPreviousMismatch, + SdkTradeStatusIssueKind::RevisionDecisionPreviousMismatch, event_id, ), RadrootsOrderIssue::RevisionDecisionRevisionIdMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::RevisionDecisionRevisionIdMismatch, + SdkTradeStatusIssueKind::RevisionDecisionRevisionIdMismatch, event_id, ), RadrootsOrderIssue::CancellationWithoutCancellableOrder { event_id } => Self::single( - SdkOrderStatusIssueKind::CancellationWithoutCancellableOrder, + SdkTradeStatusIssueKind::CancellationWithoutCancellableOrder, event_id, ), RadrootsOrderIssue::CancellationPayloadInvalid { event_id } => Self::single( - SdkOrderStatusIssueKind::CancellationPayloadInvalid, + SdkTradeStatusIssueKind::CancellationPayloadInvalid, event_id, ), RadrootsOrderIssue::CancellationOrderIdMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::CancellationOrderIdMismatch, + SdkTradeStatusIssueKind::CancellationOrderIdMismatch, event_id, ), RadrootsOrderIssue::CancellationAuthorMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::CancellationAuthorMismatch, + SdkTradeStatusIssueKind::CancellationAuthorMismatch, event_id, ), RadrootsOrderIssue::CancellationCounterpartyMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::CancellationCounterpartyMismatch, + SdkTradeStatusIssueKind::CancellationCounterpartyMismatch, event_id, ), RadrootsOrderIssue::CancellationBuyerMismatch { event_id } => { - Self::single(SdkOrderStatusIssueKind::CancellationBuyerMismatch, event_id) + Self::single(SdkTradeStatusIssueKind::CancellationBuyerMismatch, event_id) } RadrootsOrderIssue::CancellationSellerMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::CancellationSellerMismatch, + SdkTradeStatusIssueKind::CancellationSellerMismatch, event_id, ), RadrootsOrderIssue::CancellationListingAddressInvalid { event_id } => Self::single( - SdkOrderStatusIssueKind::CancellationListingAddressInvalid, + SdkTradeStatusIssueKind::CancellationListingAddressInvalid, event_id, ), RadrootsOrderIssue::CancellationListingMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::CancellationListingMismatch, + SdkTradeStatusIssueKind::CancellationListingMismatch, event_id, ), RadrootsOrderIssue::CancellationRootMismatch { event_id } => { - Self::single(SdkOrderStatusIssueKind::CancellationRootMismatch, event_id) + Self::single(SdkTradeStatusIssueKind::CancellationRootMismatch, event_id) } RadrootsOrderIssue::CancellationPreviousMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::CancellationPreviousMismatch, + SdkTradeStatusIssueKind::CancellationPreviousMismatch, event_id, ), RadrootsOrderIssue::ForkedLifecycle { event_ids } => { - Self::new(SdkOrderStatusIssueKind::ForkedLifecycle, event_ids) + Self::new(SdkTradeStatusIssueKind::ForkedLifecycle, event_ids) } RadrootsOrderIssue::ValidationReceiptWithoutPendingAgreement { event_id } => { Self::single( - SdkOrderStatusIssueKind::ValidationReceiptWithoutPendingAgreement, + SdkTradeStatusIssueKind::ValidationReceiptWithoutPendingAgreement, event_id, ) } RadrootsOrderIssue::ValidationReceiptOrderIdMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::ValidationReceiptOrderIdMismatch, + SdkTradeStatusIssueKind::ValidationReceiptOrderIdMismatch, event_id, ), RadrootsOrderIssue::ValidationReceiptTypeMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::ValidationReceiptTypeMismatch, + SdkTradeStatusIssueKind::ValidationReceiptTypeMismatch, event_id, ), RadrootsOrderIssue::ValidationReceiptRootMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::ValidationReceiptRootMismatch, + SdkTradeStatusIssueKind::ValidationReceiptRootMismatch, event_id, ), RadrootsOrderIssue::ValidationReceiptTargetMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::ValidationReceiptTargetMismatch, + SdkTradeStatusIssueKind::ValidationReceiptTargetMismatch, event_id, ), RadrootsOrderIssue::ValidationReceiptListingMismatch { event_id } => Self::single( - SdkOrderStatusIssueKind::ValidationReceiptListingMismatch, + SdkTradeStatusIssueKind::ValidationReceiptListingMismatch, event_id, ), RadrootsOrderIssue::ConflictingValidationReceipts { event_ids } => Self::new( - SdkOrderStatusIssueKind::ConflictingValidationReceipts, + SdkTradeStatusIssueKind::ConflictingValidationReceipts, event_ids, ), RadrootsOrderIssue::DeterministicValidationFailure { event_id, .. } => Self::single( - SdkOrderStatusIssueKind::DeterministicValidationFailure, + SdkTradeStatusIssueKind::DeterministicValidationFailure, event_id, ), RadrootsOrderIssue::StaleListingEvent { expected_event_id, current_event_id, } => Self::new( - SdkOrderStatusIssueKind::StaleListingEvent, + SdkTradeStatusIssueKind::StaleListingEvent, vec![expected_event_id, current_event_id], ), } diff --git a/crates/sdk/src/privacy.rs b/crates/sdk/src/privacy.rs @@ -0,0 +1,86 @@ +#[cfg(feature = "runtime")] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum ProductSensitivityField { + ExactLocation, + SensitiveFulfillmentDetails, + PublicButSensitiveNotes, + ProtocolMinimizedInventoryFields, +} + +#[cfg(feature = "runtime")] +#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum PrivacyPreflightStatus { + Ok, + ExplicitConfirmationRequired, + ForbiddenPublicFields, +} + +#[cfg(feature = "runtime")] +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] +pub struct PrivacyPreflightReceipt { + pub status: PrivacyPreflightStatus, + pub fields: Vec<ProductSensitivityField>, +} + +#[cfg(feature = "runtime")] +impl PrivacyPreflightReceipt { + pub fn evaluate<I>(fields: I) -> Self + where + I: IntoIterator<Item = ProductSensitivityField>, + { + let mut fields = fields.into_iter().collect::<Vec<_>>(); + fields.sort(); + fields.dedup(); + let status = if fields.iter().any(|field| { + matches!( + field, + ProductSensitivityField::ExactLocation + | ProductSensitivityField::SensitiveFulfillmentDetails + ) + }) { + PrivacyPreflightStatus::ForbiddenPublicFields + } else if fields + .iter() + .any(|field| matches!(field, ProductSensitivityField::PublicButSensitiveNotes)) + { + PrivacyPreflightStatus::ExplicitConfirmationRequired + } else { + PrivacyPreflightStatus::Ok + }; + Self { status, fields } + } +} + +#[cfg(test)] +#[cfg(feature = "runtime")] +mod tests { + use super::{PrivacyPreflightReceipt, PrivacyPreflightStatus, ProductSensitivityField}; + + #[test] + fn privacy_preflight_classifies_public_sensitivity() { + let ok = PrivacyPreflightReceipt::evaluate([ + ProductSensitivityField::ProtocolMinimizedInventoryFields, + ]); + assert_eq!(ok.status, PrivacyPreflightStatus::Ok); + + let confirm = + PrivacyPreflightReceipt::evaluate([ProductSensitivityField::PublicButSensitiveNotes]); + assert_eq!( + confirm.status, + PrivacyPreflightStatus::ExplicitConfirmationRequired + ); + + let forbidden = PrivacyPreflightReceipt::evaluate([ + ProductSensitivityField::ExactLocation, + ProductSensitivityField::SensitiveFulfillmentDetails, + ]); + assert_eq!( + forbidden.status, + PrivacyPreflightStatus::ForbiddenPublicFields + ); + } +} diff --git a/crates/sdk/src/product_clients.rs b/crates/sdk/src/product_clients.rs @@ -64,6 +64,91 @@ impl<'client> TradesClient<'client> { pub(crate) fn new(sdk: &'client RadrootsClient) -> Self { Self { sdk } } + + pub fn buyer(&self) -> TradeBuyerClient<'client> { + TradeBuyerClient { sdk: self.sdk } + } + + pub fn seller(&self) -> TradeSellerClient<'client> { + TradeSellerClient { sdk: self.sdk } + } + + pub fn validation(&self) -> TradeValidationClient<'client> { + TradeValidationClient { sdk: self.sdk } + } + + pub fn status_client(&self) -> TradeStatusClient<'client> { + TradeStatusClient { sdk: self.sdk } + } + + pub fn resync(&self) -> TradeResyncClient<'client> { + TradeResyncClient { sdk: self.sdk } + } +} + +#[cfg(feature = "runtime")] +#[derive(Clone, Copy)] +pub struct TradeBuyerClient<'client> { + pub(crate) sdk: &'client RadrootsClient, +} + +#[cfg(feature = "runtime")] +impl<'client> TradeBuyerClient<'client> { + pub fn root(&self) -> &'client RadrootsClient { + self.sdk + } +} + +#[cfg(feature = "runtime")] +#[derive(Clone, Copy)] +pub struct TradeSellerClient<'client> { + pub(crate) sdk: &'client RadrootsClient, +} + +#[cfg(feature = "runtime")] +impl<'client> TradeSellerClient<'client> { + pub fn root(&self) -> &'client RadrootsClient { + self.sdk + } +} + +#[cfg(feature = "runtime")] +#[derive(Clone, Copy)] +pub struct TradeValidationClient<'client> { + pub(crate) sdk: &'client RadrootsClient, +} + +#[cfg(feature = "runtime")] +impl<'client> TradeValidationClient<'client> { + pub fn root(&self) -> &'client RadrootsClient { + self.sdk + } +} + +#[cfg(feature = "runtime")] +#[derive(Clone, Copy)] +pub struct TradeStatusClient<'client> { + pub(crate) sdk: &'client RadrootsClient, +} + +#[cfg(feature = "runtime")] +impl<'client> TradeStatusClient<'client> { + pub fn root(&self) -> &'client RadrootsClient { + self.sdk + } +} + +#[cfg(feature = "runtime")] +#[derive(Clone, Copy)] +pub struct TradeResyncClient<'client> { + pub(crate) sdk: &'client RadrootsClient, +} + +#[cfg(feature = "runtime")] +impl<'client> TradeResyncClient<'client> { + pub fn root(&self) -> &'client RadrootsClient { + self.sdk + } } #[cfg(feature = "runtime")] diff --git a/crates/sdk/src/relay_targets.rs b/crates/sdk/src/relay_targets.rs @@ -8,6 +8,93 @@ pub const SDK_RELAY_TARGET_MAX_COUNT: usize = 20; #[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)] #[serde(rename_all = "snake_case")] #[non_exhaustive] +pub enum PublishMode { + DryRun, + EnqueueOnly, + EnqueueAndPublish, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum AckPolicy { + NoWait, + AtLeastOneRelay, + AllRelays, + Quorum { required: u16 }, +} + +impl AckPolicy { + pub fn quorum(required: u16) -> Result<Self, RadrootsSdkError> { + if required == 0 { + return Err(RadrootsSdkError::InvalidRequest { + message: "ack policy quorum must require at least one relay".to_owned(), + }); + } + Ok(Self::Quorum { required }) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum RelayResolutionPolicy { + ConfiguredRelays, + Explicit(SdkRelayTargetSet), +} + +impl RelayResolutionPolicy { + pub fn configured_relays() -> Self { + Self::ConfiguredRelays + } + + pub fn explicit(targets: SdkRelayTargetSet) -> Self { + Self::Explicit(targets) + } + + pub fn try_explicit<I, S>( + relays: I, + url_policy: SdkRelayUrlPolicy, + ) -> Result<Self, RadrootsSdkError> + where + I: IntoIterator<Item = S>, + S: AsRef<str>, + { + Ok(Self::Explicit(SdkRelayTargetSet::new(relays, url_policy)?)) + } + + pub(crate) fn workflow_target_policy(self) -> SdkRelayTargetPolicy { + match self { + Self::ConfiguredRelays => SdkRelayTargetPolicy::UseConfiguredRelays, + Self::Explicit(targets) => SdkRelayTargetPolicy::Explicit(targets), + } + } +} + +impl serde::Serialize for RelayResolutionPolicy { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + match self { + Self::ConfiguredRelays => { + let mut state = serializer.serialize_struct("RelayResolutionPolicy", 1)?; + state.serialize_field("kind", "configured_relays")?; + state.end() + } + Self::Explicit(targets) => { + let mut state = serializer.serialize_struct("RelayResolutionPolicy", 3)?; + state.serialize_field("kind", "explicit")?; + state.serialize_field("relays", targets.relays())?; + state.serialize_field("canonical_relays", targets.canonical_relays())?; + state.end() + } + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] pub enum SdkRelayUrlPolicy { Public, Localhost, diff --git a/crates/sdk/src/trade_storage.rs b/crates/sdk/src/trade_storage.rs @@ -0,0 +1,152 @@ +#[cfg(feature = "runtime")] +use std::collections::BTreeMap; + +#[cfg(feature = "runtime")] +use radroots_events::ids::{RadrootsEventId, RadrootsOrderId}; +#[cfg(feature = "runtime")] +use radroots_trade::{ + identity::{RadrootsTradeId, RadrootsTradeLocator, RadrootsTradeLocatorCandidate}, + workflow::RadrootsTradeWorkflowState, +}; + +#[cfg(feature = "runtime")] +pub const SDK_TRADE_PROJECTION_CACHE_VERSION: u32 = 1; + +#[cfg(feature = "runtime")] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)] +pub struct SdkTradeProjectionCacheKey { + pub order_id: RadrootsOrderId, + pub root_event_id: RadrootsEventId, + pub projection_version: u32, +} + +#[cfg(feature = "runtime")] +impl SdkTradeProjectionCacheKey { + pub fn new(order_id: RadrootsOrderId, root_event_id: RadrootsEventId) -> Self { + Self { + order_id, + root_event_id, + projection_version: SDK_TRADE_PROJECTION_CACHE_VERSION, + } + } +} + +#[cfg(feature = "runtime")] +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] +pub struct SdkTradeProjectionCacheRecord { + pub key: SdkTradeProjectionCacheKey, + pub locator: RadrootsTradeLocator, + pub status: RadrootsTradeWorkflowState, + pub updated_at_ms: i64, +} + +#[cfg(feature = "runtime")] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct SdkTradeProjectionCache { + records: BTreeMap<SdkTradeProjectionCacheKey, SdkTradeProjectionCacheRecord>, +} + +#[cfg(feature = "runtime")] +impl SdkTradeProjectionCache { + pub fn upsert(&mut self, record: SdkTradeProjectionCacheRecord) { + self.records.insert(record.key.clone(), record); + } + + pub fn get(&self, key: &SdkTradeProjectionCacheKey) -> Option<&SdkTradeProjectionCacheRecord> { + self.records.get(key) + } + + pub fn ambiguity_candidates( + &self, + order_id: &RadrootsOrderId, + ) -> Vec<RadrootsTradeLocatorCandidate> { + self.records + .values() + .filter(|record| record.key.order_id == *order_id) + .filter_map(|record| { + Some(RadrootsTradeLocatorCandidate { + trade_id: RadrootsTradeId::from(record.key.order_id.clone()), + root_event_id: record.key.root_event_id.clone(), + listing_addr: record.locator.listing_addr.clone()?, + buyer_pubkey: record.locator.buyer_pubkey.clone()?, + seller_pubkey: record.locator.seller_pubkey.clone()?, + }) + }) + .collect() + } +} + +#[cfg(test)] +#[cfg(feature = "runtime")] +mod tests { + use super::{ + SDK_TRADE_PROJECTION_CACHE_VERSION, SdkTradeProjectionCache, SdkTradeProjectionCacheKey, + SdkTradeProjectionCacheRecord, + }; + use radroots_events::ids::{ + RadrootsEventId, RadrootsListingAddress, RadrootsOrderId, RadrootsPublicKey, + }; + use radroots_events::kinds::KIND_LISTING; + use radroots_trade::{identity::RadrootsTradeLocator, workflow::RadrootsTradeWorkflowState}; + + const SELLER: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + const BUYER: &str = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; + + fn order_id() -> RadrootsOrderId { + RadrootsOrderId::parse("order-1").expect("order id") + } + + fn event_id(raw: u8) -> RadrootsEventId { + RadrootsEventId::parse(format!("{raw:064x}")).expect("event id") + } + + fn listing_addr() -> RadrootsListingAddress { + RadrootsListingAddress::parse(format!("{KIND_LISTING}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg")) + .expect("listing address") + } + + fn locator(root: RadrootsEventId) -> RadrootsTradeLocator { + RadrootsTradeLocator::from_order_id(order_id()) + .with_root_event_id(root) + .with_listing_addr(listing_addr()) + .with_buyer_pubkey(RadrootsPublicKey::parse(BUYER).expect("buyer")) + .with_seller_pubkey(RadrootsPublicKey::parse(SELLER).expect("seller")) + } + + #[test] + fn projection_cache_key_includes_root_and_version() { + let first = SdkTradeProjectionCacheKey::new(order_id(), event_id(1)); + let second = SdkTradeProjectionCacheKey::new(order_id(), event_id(2)); + + assert_ne!(first, second); + assert_eq!(first.projection_version, SDK_TRADE_PROJECTION_CACHE_VERSION); + } + + #[test] + fn projection_cache_returns_root_ambiguity_candidates() { + let mut cache = SdkTradeProjectionCache::default(); + for root in [event_id(1), event_id(2)] { + let record = SdkTradeProjectionCacheRecord { + key: SdkTradeProjectionCacheKey::new(order_id(), root.clone()), + locator: locator(root), + status: RadrootsTradeWorkflowState::Requested, + updated_at_ms: 1, + }; + cache.upsert(record); + } + + let candidates = cache.ambiguity_candidates(&order_id()); + + assert_eq!(candidates.len(), 2); + assert!( + candidates + .iter() + .any(|candidate| candidate.root_event_id == event_id(1)) + ); + assert!( + candidates + .iter() + .any(|candidate| candidate.root_event_id == event_id(2)) + ); + } +} diff --git a/crates/sdk/tests/dvm_runtime.rs b/crates/sdk/tests/dvm_runtime.rs @@ -11,7 +11,10 @@ use radroots_events::{ RadrootsNostrEvent, contract::RadrootsActorRole, draft::{RadrootsFrozenEventDraft, RadrootsSignedNostrEvent}, - ids::{RadrootsEventId, RadrootsListingAddress, RadrootsOrderId, RadrootsPublicKey}, + ids::{ + RadrootsEventId, RadrootsInventoryBinId, RadrootsListingAddress, RadrootsOrderId, + RadrootsPublicKey, + }, kinds::{KIND_LISTING, KIND_TRADE_TRANSITION_PROOF_REQUEST}, order::{ RadrootsOrderDecision, RadrootsOrderDecisionOutcome, RadrootsOrderEconomicItem, @@ -27,10 +30,10 @@ use radroots_outbox::RadrootsOutbox; use radroots_sdk::protocol::events::RadrootsNostrEventPtr; use radroots_sdk::{ DVM_TRADE_TRANSITION_PROOF_REQUEST_OPERATION_KIND, DvmTradeTransitionProofEnqueueRequest, - DvmTradeTransitionProofPrepareRequest, DvmValidationReceiptIngestRequest, OrderStatusKind, - OrderStatusNextActionKind, OrderStatusRequest, RadrootsClient, RadrootsSdkError, - RadrootsSdkStorageConfig, RadrootsSdkTimestamp, SdkDvmInventoryBinWitness, SdkMutationState, - SdkRelayTargetPolicy, SdkRelayUrlPolicy, + DvmTradeTransitionProofPrepareRequest, DvmValidationReceiptIngestRequest, RadrootsClient, + RadrootsSdkError, RadrootsSdkStorageConfig, RadrootsSdkTimestamp, + RadrootsTradeInventoryBinWitnessDto, SdkMutationState, SdkRelayTargetPolicy, SdkRelayUrlPolicy, + TradeStatusKind, TradeStatusNextActionKind, TradeStatusRequest, }; #[cfg(feature = "signer-adapters")] use radroots_sdk::{RadrootsSdkLocalKeySigner, RadrootsSdkSignerProvider}; @@ -272,7 +275,7 @@ async fn dvm_validation_receipt_ingest_commits_pending_trade_status() { .status(status_request("order-dvm-ingest")) .await .expect("pending status"); - assert_eq!(pending.status, OrderStatusKind::AgreedPendingRhi); + assert_eq!(pending.status, TradeStatusKind::AgreedPendingRhi); assert!(pending.rhi_receipt_event_id.is_none()); let listing_event_id = deterministic_event_id("listing-event"); @@ -321,9 +324,9 @@ async fn dvm_validation_receipt_ingest_commits_pending_trade_status() { .status(status_request("order-dvm-ingest")) .await .expect("committed status"); - assert_eq!(committed.status, OrderStatusKind::Committed); + assert_eq!(committed.status, TradeStatusKind::Committed); assert!(committed.lifecycle_terminal); - assert_eq!(committed.next_action, OrderStatusNextActionKind::Terminal); + assert_eq!(committed.next_action, TradeStatusNextActionKind::Terminal); assert_eq!( committed.rhi_receipt_event_id, Some(receipt_event_id.clone()) @@ -648,9 +651,9 @@ fn explicit_relays() -> SdkRelayTargetPolicy { SdkRelayTargetPolicy::try_explicit([RELAY], SdkRelayUrlPolicy::Public).expect("relay targets") } -fn inventory_bins() -> Vec<SdkDvmInventoryBinWitness> { - vec![SdkDvmInventoryBinWitness { - bin_id: "bin-1".to_owned(), +fn inventory_bins() -> Vec<RadrootsTradeInventoryBinWitnessDto> { + vec![RadrootsTradeInventoryBinWitnessDto { + bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), listing_capacity: 5, previous_reserved: 1, }] @@ -660,8 +663,8 @@ fn order_id(raw: &str) -> RadrootsOrderId { RadrootsOrderId::parse(raw).expect("order id") } -fn status_request(raw: &str) -> OrderStatusRequest { - OrderStatusRequest::parse(raw).expect("status request") +fn status_request(raw: &str) -> TradeStatusRequest { + TradeStatusRequest::parse(raw).expect("status request") } fn listing_address() -> RadrootsListingAddress { diff --git a/crates/sdk/tests/orders_runtime.rs b/crates/sdk/tests/orders_runtime.rs @@ -33,19 +33,19 @@ use radroots_sdk::protocol::order::{ }; use radroots_sdk::protocol::wire::WireEventParts; use radroots_sdk::{ - ORDER_CANCELLATION_OPERATION_KIND, ORDER_DECISION_OPERATION_KIND, - ORDER_REVISION_DECISION_OPERATION_KIND, ORDER_REVISION_PROPOSAL_OPERATION_KIND, - ORDER_STATUS_DEFAULT_LIMIT, ORDER_STATUS_MAX_LIMIT, ORDER_SUBMIT_OPERATION_KIND, - OrderCancellationEnqueueRequest, OrderCancellationPrepareRequest, OrderDecisionEnqueueRequest, - OrderDecisionPrepareRequest, OrderEvidenceIngestRequest, OrderRequestEvidenceIngestRequest, - OrderRevisionDecisionEnqueueRequest, OrderRevisionDecisionPrepareRequest, - OrderRevisionProposalEnqueueRequest, OrderRevisionProposalPrepareRequest, OrderStatusKind, - OrderStatusNextActionKind, OrderStatusRequest, OrderSubmitEnqueueRequest, - OrderSubmitPrepareRequest, OrderWorkflowKind, PushOutboxEventState, PushOutboxRelayOutcomeKind, - PushOutboxRequest, RadrootsClient, RadrootsSdkError, RadrootsSdkPartialLocalMutationFailure, - RadrootsSdkRecoveryAction, RadrootsSdkTimestamp, SdkIdempotencyKey, SdkMutationState, - SdkOrderStatusIssue, SdkOrderStatusIssueKind, SdkOrderStatusSource, SdkRelayTargetPolicy, - SdkRelayTargetSet, SdkRelayUrlPolicy, + AckPolicy, PublishMode, PushOutboxEventState, PushOutboxRelayOutcomeKind, PushOutboxRequest, + RadrootsClient, RadrootsSdkError, RadrootsSdkPartialLocalMutationFailure, + RadrootsSdkRecoveryAction, RadrootsSdkTimestamp, RelayResolutionPolicy, SdkIdempotencyKey, + SdkMutationState, SdkRelayTargetSet, SdkRelayUrlPolicy, SdkTradeStatusIssue, + SdkTradeStatusIssueKind, SdkTradeStatusSource, TRADE_CANCELLATION_OPERATION_KIND, + TRADE_DECISION_OPERATION_KIND, TRADE_REVISION_DECISION_OPERATION_KIND, + TRADE_REVISION_PROPOSAL_OPERATION_KIND, TRADE_STATUS_DEFAULT_LIMIT, TRADE_STATUS_MAX_LIMIT, + TRADE_SUBMIT_OPERATION_KIND, TradeCancellationEnqueueRequest, TradeCancellationPrepareRequest, + TradeDecisionEnqueueRequest, TradeDecisionPrepareRequest, TradeEvidenceIngestRequest, + TradeRequestEvidenceIngestRequest, TradeRevisionDecisionEnqueueRequest, + TradeRevisionDecisionPrepareRequest, TradeRevisionProposalEnqueueRequest, + TradeRevisionProposalPrepareRequest, TradeStatusKind, TradeStatusNextActionKind, + TradeStatusRequest, TradeSubmitEnqueueRequest, TradeSubmitPrepareRequest, TradeWorkflowKind, }; use radroots_trade::order::RadrootsOrderIssue; use serde::Serialize; @@ -390,8 +390,8 @@ fn order_id(raw: &str) -> RadrootsOrderId { RadrootsOrderId::parse(raw).expect("order id") } -fn status_request(raw: &str) -> OrderStatusRequest { - OrderStatusRequest::parse(raw).expect("order status request") +fn status_request(raw: &str) -> TradeStatusRequest { + TradeStatusRequest::parse(raw).expect("order status request") } fn buyer_actor() -> RadrootsActorContext { @@ -507,7 +507,7 @@ fn invalid_listing_event_ptr() -> RadrootsNostrEventPtr { async fn order_submit_prepare_is_side_effect_free() { let (_tempdir, sdk, store) = directory_sdk_and_store().await; let listing_event = listing_event_ptr(); - let request = OrderSubmitPrepareRequest::new( + let request = TradeSubmitPrepareRequest::new( buyer_actor(), listing_event.clone(), order_request("order-submit-prepare"), @@ -559,7 +559,7 @@ async fn order_submit_prepare_is_side_effect_free() { #[tokio::test] async fn order_submit_prepare_rejects_missing_listing_evidence() { let (_tempdir, sdk, _store) = directory_sdk_and_store().await; - let request = OrderSubmitPrepareRequest::new( + let request = TradeSubmitPrepareRequest::new( buyer_actor(), invalid_listing_event_ptr(), order_request("order-submit-missing-listing"), @@ -579,7 +579,7 @@ async fn order_submit_prepare_rejects_invalid_actor_or_payload() { let non_buyer = sdk .trades() - .prepare_submit(OrderSubmitPrepareRequest::new( + .prepare_submit(TradeSubmitPrepareRequest::new( non_buyer_actor(), listing_event_ptr(), order_request("order-submit-non-buyer"), @@ -592,7 +592,7 @@ async fn order_submit_prepare_rejects_invalid_actor_or_payload() { let wrong_actor = sdk .trades() - .prepare_submit(OrderSubmitPrepareRequest::new( + .prepare_submit(TradeSubmitPrepareRequest::new( other_buyer_actor(), listing_event_ptr(), order_request("order-submit-wrong-actor"), @@ -607,7 +607,7 @@ async fn order_submit_prepare_rejects_invalid_actor_or_payload() { seller_mismatch.seller_pubkey = OTHER_PUBLIC_KEY_HEX.parse().expect("seller pubkey"); let seller_error = sdk .trades() - .prepare_submit(OrderSubmitPrepareRequest::new( + .prepare_submit(TradeSubmitPrepareRequest::new( buyer_actor(), listing_event_ptr(), seller_mismatch, @@ -622,7 +622,7 @@ async fn order_submit_prepare_rejects_invalid_actor_or_payload() { empty_items.items.clear(); let empty_items_error = sdk .trades() - .prepare_submit(OrderSubmitPrepareRequest::new( + .prepare_submit(TradeSubmitPrepareRequest::new( buyer_actor(), listing_event_ptr(), empty_items, @@ -637,7 +637,7 @@ async fn order_submit_prepare_rejects_invalid_actor_or_payload() { empty_economics.economics.items.clear(); let empty_economics_error = sdk .trades() - .prepare_submit(OrderSubmitPrepareRequest::new( + .prepare_submit(TradeSubmitPrepareRequest::new( buyer_actor(), listing_event_ptr(), empty_economics, @@ -655,16 +655,16 @@ async fn order_submit_enqueue_stores_event_queues_outbox_and_status_sees_request let order = order_request("order-submit-enqueue"); let prepared = sdk .trades() - .prepare_submit(OrderSubmitPrepareRequest::new( + .prepare_submit(TradeSubmitPrepareRequest::new( buyer_actor(), listing_event_ptr(), order.clone(), )) .expect("prepared"); - assert_eq!(prepared.workflow.kind, OrderWorkflowKind::Submit); + assert_eq!(prepared.workflow.kind, TradeWorkflowKind::Submit); assert_eq!( prepared.workflow.operation_kind, - ORDER_SUBMIT_OPERATION_KIND + TRADE_SUBMIT_OPERATION_KIND ); assert_eq!(prepared.workflow.contract_id, "radroots.order.request.v1"); assert_eq!( @@ -672,11 +672,13 @@ async fn order_submit_enqueue_stores_event_queues_outbox_and_status_sees_request prepared.expected_event_id ); assert_eq!(prepared.workflow.created_at, prepared.created_at); - let request = OrderSubmitEnqueueRequest::new( + let request = TradeSubmitEnqueueRequest::new( buyer_actor(), listing_event_ptr(), order, - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public) .expect("target relays") @@ -692,8 +694,8 @@ async fn order_submit_enqueue_stores_event_queues_outbox_and_status_sees_request assert_eq!(receipt.order_id, prepared.order_id); assert_eq!(receipt.listing_addr, prepared.listing_addr); assert_eq!(receipt.listing_event_id, prepared.listing_event_id); - assert_eq!(receipt.workflow.kind, OrderWorkflowKind::Submit); - assert_eq!(receipt.workflow.operation_kind, ORDER_SUBMIT_OPERATION_KIND); + assert_eq!(receipt.workflow.kind, TradeWorkflowKind::Submit); + assert_eq!(receipt.workflow.operation_kind, TRADE_SUBMIT_OPERATION_KIND); assert_eq!( receipt.workflow.expected_event_id, prepared.expected_event_id @@ -775,7 +777,7 @@ async fn order_submit_enqueue_stores_event_queues_outbox_and_status_sees_request .await .expect("status"); assert!(status.found); - assert_eq!(status.status, OrderStatusKind::Requested); + assert_eq!(status.status, TradeStatusKind::Requested); assert_eq!(status.event_count, 1); assert_eq!( status @@ -789,11 +791,13 @@ async fn order_submit_enqueue_stores_event_queues_outbox_and_status_sees_request #[tokio::test] async fn order_submit_enqueue_returns_sanitized_signer_errors_before_mutation() { let (_tempdir, sdk, store) = directory_sdk_and_store().await; - let request = OrderSubmitEnqueueRequest::new( + let request = TradeSubmitEnqueueRequest::new( buyer_actor(), listing_event_ptr(), order_request("order-submit-wrong-signer"), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public) .expect("target relays"); @@ -836,22 +840,26 @@ async fn order_submit_enqueue_returns_sanitized_signer_errors_before_mutation() #[tokio::test] async fn order_submit_enqueue_derives_order_independent_idempotency_key() { let (_tempdir, sdk, _store) = directory_sdk_and_store().await; - let first = OrderSubmitEnqueueRequest::new( + let first = TradeSubmitEnqueueRequest::new( buyer_actor(), listing_event_ptr(), order_request("order-submit-idempotent"), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY_B, RELAY, RELAY], SdkRelayUrlPolicy::Public) .expect("first target relays"); - let second = OrderSubmitEnqueueRequest::new( + let second = TradeSubmitEnqueueRequest::new( buyer_actor(), listing_event_ptr(), order_request("order-submit-idempotent"), - SdkRelayTargetPolicy::explicit( + RelayResolutionPolicy::explicit( SdkRelayTargetSet::new([RELAY, RELAY_B], SdkRelayUrlPolicy::Public) .expect("second target relays"), ), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ); let first_receipt = sdk @@ -918,11 +926,13 @@ async fn order_submit_enqueue_derives_order_independent_idempotency_key() { #[tokio::test] async fn order_submit_enqueue_pushes_queued_event_with_mock_relay_sync() { let (_tempdir, sdk, _store) = directory_sdk_and_store().await; - let enqueue_request = OrderSubmitEnqueueRequest::new( + let enqueue_request = TradeSubmitEnqueueRequest::new( buyer_actor(), listing_event_ptr(), order_request("order-submit-sync"), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public) .expect("target relays"); @@ -979,11 +989,13 @@ async fn order_submit_enqueue_pushes_queued_event_with_mock_relay_sync() { #[tokio::test] async fn order_submit_enqueue_reports_partial_local_mutation_after_outbox_conflict() { let (_tempdir, sdk, _store) = directory_sdk_and_store().await; - let first = OrderSubmitEnqueueRequest::new( + let first = TradeSubmitEnqueueRequest::new( buyer_actor(), listing_event_ptr(), order_request("order-submit-conflict-a"), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public) .expect("first target relays") @@ -994,11 +1006,13 @@ async fn order_submit_enqueue_reports_partial_local_mutation_after_outbox_confli .await .expect("first enqueue"); - let second = OrderSubmitEnqueueRequest::new( + let second = TradeSubmitEnqueueRequest::new( buyer_actor(), listing_event_ptr(), order_request("order-submit-conflict-b"), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public) .expect("second target relays") @@ -1016,7 +1030,7 @@ async fn order_submit_enqueue_reports_partial_local_mutation_after_outbox_confli if partial.stored && !partial.queued && partial.event_id.is_some() - && partial.operation_kind == ORDER_SUBMIT_OPERATION_KIND + && partial.operation_kind == TRADE_SUBMIT_OPERATION_KIND && partial.idempotency_digest_prefix.is_some() && partial.failure == RadrootsSdkPartialLocalMutationFailure::OutboxIdempotencyConflict && partial.recovery == RadrootsSdkRecoveryAction::RetryOperationWithSameIdempotencyKey @@ -1044,7 +1058,7 @@ async fn order_submit_enqueue_reports_partial_local_mutation_after_outbox_confli async fn order_submit_runtime_dtos_serialize_deterministically() { let (_tempdir, sdk, _store) = directory_sdk_and_store().await; let created_at = RadrootsSdkTimestamp::from_unix_seconds(1_700_000_123); - let prepare_request = OrderSubmitPrepareRequest::new( + let prepare_request = TradeSubmitPrepareRequest::new( buyer_actor(), listing_event_ptr(), order_request("order-submit-serialized"), @@ -1083,11 +1097,13 @@ async fn order_submit_runtime_dtos_serialize_deterministically() { assert_eq!(prepare_json["order"]["items"][0]["bin_count"], 2); assert_eq!(prepare_json["created_at"], 1_700_000_123); - let enqueue_request = OrderSubmitEnqueueRequest::new( + let enqueue_request = TradeSubmitEnqueueRequest::new( buyer_actor(), listing_event_ptr(), order_request("order-submit-serialized-enqueue"), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY, RELAY_B], SdkRelayUrlPolicy::Public) .expect("relay targets") @@ -1117,11 +1133,13 @@ async fn order_submit_runtime_dtos_serialize_deterministically() { .contains("order-serialized-idempotency") ); - let try_key_enqueue = OrderSubmitEnqueueRequest::new( + let try_key_enqueue = TradeSubmitEnqueueRequest::new( buyer_actor(), listing_event_ptr(), order_request("order-submit-try-idempotency"), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_idempotency_key("order-submit-try-key") .expect("try idempotency key"); @@ -1145,7 +1163,7 @@ async fn order_submit_runtime_dtos_serialize_deterministically() { serde_json::json!({ "workflow": { "kind": "submit", - "operation_kind": ORDER_SUBMIT_OPERATION_KIND, + "operation_kind": TRADE_SUBMIT_OPERATION_KIND, "expected_event_id": receipt.workflow.expected_event_id.as_str(), "signed_event_id": receipt.workflow.signed_event_id.as_str(), "local_event_seq": 1, @@ -1350,7 +1368,7 @@ async fn order_request_evidence_ingest_stores_request_and_enables_decision_enque let (_tempdir, sdk, store) = directory_sdk_and_store().await; let request_event = signed_order_request_event("order-decision-ingested", 39); let request_event_id = RadrootsEventId::parse(request_event.id.as_str()).expect("request id"); - let ingest_request = OrderRequestEvidenceIngestRequest::new(request_event.clone()) + let ingest_request = TradeRequestEvidenceIngestRequest::new(request_event.clone()) .with_observed_at(RadrootsSdkTimestamp::from_unix_seconds(1_700_000_039)); let ingest_receipt = sdk @@ -1370,7 +1388,7 @@ async fn order_request_evidence_ingest_stores_request_and_enables_decision_enque let actor = seller_actor(); let plan = sdk .trades() - .prepare_decision(OrderDecisionPrepareRequest::new( + .prepare_decision(TradeDecisionPrepareRequest::new( actor.clone(), request_event_ptr(&request_event), order_decision("order-decision-ingested"), @@ -1381,8 +1399,10 @@ async fn order_request_evidence_ingest_stores_request_and_enables_decision_enque .enqueue_prepared_decision_with_explicit_signer( &actor, plan, - SdkRelayTargetPolicy::try_explicit([RELAY], SdkRelayUrlPolicy::Public) + RelayResolutionPolicy::try_explicit([RELAY], SdkRelayUrlPolicy::Public) .expect("target relays"), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &FixtureSigner::new(SELLER_SECRET_KEY_HEX), ) @@ -1392,7 +1412,7 @@ async fn order_request_evidence_ingest_stores_request_and_enables_decision_enque assert_eq!(receipt.local_event_seq, 2); let duplicate_receipt = sdk .trades() - .ingest_request_evidence(OrderRequestEvidenceIngestRequest::new( + .ingest_request_evidence(TradeRequestEvidenceIngestRequest::new( request_event.clone(), )) .await @@ -1419,7 +1439,7 @@ async fn order_evidence_ingest_stores_lifecycle_evidence_for_projection() { let request_receipt = sdk .trades() - .ingest_evidence(OrderEvidenceIngestRequest::new(request_event.clone())) + .ingest_evidence(TradeEvidenceIngestRequest::new(request_event.clone())) .await .expect("request evidence"); assert_eq!(request_receipt.order_id.as_str(), "order-evidence-ingest"); @@ -1429,7 +1449,7 @@ async fn order_evidence_ingest_stores_lifecycle_evidence_for_projection() { let decision_receipt = sdk .trades() - .ingest_evidence(OrderEvidenceIngestRequest::new(decision_event.clone())) + .ingest_evidence(TradeEvidenceIngestRequest::new(decision_event.clone())) .await .expect("decision evidence"); assert_eq!(decision_receipt.order_id.as_str(), "order-evidence-ingest"); @@ -1439,7 +1459,7 @@ async fn order_evidence_ingest_stores_lifecycle_evidence_for_projection() { let duplicate_receipt = sdk .trades() - .ingest_evidence(OrderEvidenceIngestRequest::new(decision_event)) + .ingest_evidence(TradeEvidenceIngestRequest::new(decision_event)) .await .expect("duplicate decision evidence"); assert_eq!(duplicate_receipt.local_event_seq, 2); @@ -1458,7 +1478,7 @@ async fn order_evidence_ingest_stores_lifecycle_evidence_for_projection() { .status(status_request("order-evidence-ingest")) .await .expect("status"); - assert_eq!(status.status, OrderStatusKind::AgreedPendingRhi); + assert_eq!(status.status, TradeStatusKind::AgreedPendingRhi); assert_eq!(status.event_count, 2); assert_eq!( status @@ -1474,7 +1494,7 @@ async fn order_evidence_ingest_rejects_non_order_events() { let (_tempdir, sdk, store) = directory_sdk_and_store().await; let error = sdk .trades() - .ingest_evidence(OrderEvidenceIngestRequest::new(signed_non_order_event(41))) + .ingest_evidence(TradeEvidenceIngestRequest::new(signed_non_order_event(41))) .await .expect_err("non order event"); @@ -1497,7 +1517,7 @@ async fn order_request_evidence_ingest_rejects_non_request_events() { let error = sdk .trades() - .ingest_request_evidence(OrderRequestEvidenceIngestRequest::new(decision_event)) + .ingest_request_evidence(TradeRequestEvidenceIngestRequest::new(decision_event)) .await .expect_err("non request event"); @@ -1520,7 +1540,7 @@ async fn order_decision_prepare_accept_and_decline_are_side_effect_free() { id: request_event_id.as_str().to_owned(), relays: Some(RELAY.to_owned()), }; - let accepted_request = OrderDecisionPrepareRequest::new( + let accepted_request = TradeDecisionPrepareRequest::new( seller_actor(), request_event.clone(), order_decision("order-decision-prepare-accept"), @@ -1549,7 +1569,7 @@ async fn order_decision_prepare_accept_and_decline_are_side_effect_free() { }; let declined = sdk .trades() - .prepare_decision(OrderDecisionPrepareRequest::new( + .prepare_decision(TradeDecisionPrepareRequest::new( seller_actor(), request_event, declined_payload, @@ -1592,7 +1612,7 @@ async fn order_decision_prepare_rejects_invalid_actor_evidence_and_payload() { let non_seller = sdk .trades() - .prepare_decision(OrderDecisionPrepareRequest::new( + .prepare_decision(TradeDecisionPrepareRequest::new( non_seller_actor(), request_event.clone(), order_decision("order-decision-non-seller"), @@ -1605,7 +1625,7 @@ async fn order_decision_prepare_rejects_invalid_actor_evidence_and_payload() { let wrong_actor = sdk .trades() - .prepare_decision(OrderDecisionPrepareRequest::new( + .prepare_decision(TradeDecisionPrepareRequest::new( other_seller_actor(), request_event.clone(), order_decision("order-decision-wrong-seller"), @@ -1618,7 +1638,7 @@ async fn order_decision_prepare_rejects_invalid_actor_evidence_and_payload() { let invalid_evidence = sdk .trades() - .prepare_decision(OrderDecisionPrepareRequest::new( + .prepare_decision(TradeDecisionPrepareRequest::new( seller_actor(), RadrootsNostrEventPtr { id: String::new(), @@ -1638,7 +1658,7 @@ async fn order_decision_prepare_rejects_invalid_actor_evidence_and_payload() { }; let commitment_error = sdk .trades() - .prepare_decision(OrderDecisionPrepareRequest::new( + .prepare_decision(TradeDecisionPrepareRequest::new( seller_actor(), request_event.clone(), empty_commitments, @@ -1655,7 +1675,7 @@ async fn order_decision_prepare_rejects_invalid_actor_evidence_and_payload() { }; let reason_error = sdk .trades() - .prepare_decision(OrderDecisionPrepareRequest::new( + .prepare_decision(TradeDecisionPrepareRequest::new( seller_actor(), request_event, missing_reason, @@ -1672,7 +1692,7 @@ async fn order_decision_runtime_dtos_serialize_deterministically() { let (_tempdir, sdk, store) = directory_sdk_and_store().await; let created_at = RadrootsSdkTimestamp::from_unix_seconds(1_700_000_321); let prepare_event_id = deterministic_event_id("order-decision-serialized-request"); - let prepare_request = OrderDecisionPrepareRequest::new( + let prepare_request = TradeDecisionPrepareRequest::new( seller_actor(), RadrootsNostrEventPtr { id: prepare_event_id.as_str().to_owned(), @@ -1715,11 +1735,13 @@ async fn order_decision_runtime_dtos_serialize_deterministically() { .ingest_event(RadrootsEventIngest::new(request_event.clone(), 4_500)) .await .expect("ingest request"); - let enqueue_request = OrderDecisionEnqueueRequest::new( + let enqueue_request = TradeDecisionEnqueueRequest::new( seller_actor(), request_event_ptr(&request_event), order_decision("order-decision-serialized-enqueue"), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY, RELAY_B], SdkRelayUrlPolicy::Public) .expect("target relays") @@ -1749,11 +1771,13 @@ async fn order_decision_runtime_dtos_serialize_deterministically() { .contains("order-decision-serialized-idempotency") ); - let try_key_enqueue = OrderDecisionEnqueueRequest::new( + let try_key_enqueue = TradeDecisionEnqueueRequest::new( seller_actor(), request_event_ptr(&request_event), order_decision("order-decision-try-idempotency"), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_idempotency_key("order-decision-try-key") .expect("try idempotency key"); @@ -1770,10 +1794,10 @@ async fn order_decision_runtime_dtos_serialize_deterministically() { ) .await .expect("enqueue"); - assert_eq!(receipt.workflow.kind, OrderWorkflowKind::Decision); + assert_eq!(receipt.workflow.kind, TradeWorkflowKind::Decision); assert_eq!( receipt.workflow.operation_kind, - ORDER_DECISION_OPERATION_KIND + TRADE_DECISION_OPERATION_KIND ); assert_eq!( receipt.workflow.expected_event_id, @@ -1806,7 +1830,7 @@ async fn order_decision_runtime_dtos_serialize_deterministically() { serde_json::json!({ "workflow": { "kind": "decision", - "operation_kind": ORDER_DECISION_OPERATION_KIND, + "operation_kind": TRADE_DECISION_OPERATION_KIND, "expected_event_id": receipt.workflow.expected_event_id.as_str(), "signed_event_id": receipt.workflow.signed_event_id.as_str(), "local_event_seq": 2, @@ -1859,7 +1883,7 @@ async fn order_revision_and_cancellation_dtos_serialize_deterministically() { ); let cancellation = order_cancellation("order-revision-dto"); - let proposal_prepare = OrderRevisionProposalPrepareRequest::new( + let proposal_prepare = TradeRevisionProposalPrepareRequest::new( seller_actor(), root_event.clone(), previous_event.clone(), @@ -1891,12 +1915,14 @@ async fn order_revision_and_cancellation_dtos_serialize_deterministically() { ); assert_eq!(proposal_prepare_json["created_at"], 1_700_000_654); - let proposal_enqueue = OrderRevisionProposalEnqueueRequest::new( + let proposal_enqueue = TradeRevisionProposalEnqueueRequest::new( seller_actor(), root_event.clone(), previous_event.clone(), proposal.clone(), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY, RELAY_B], SdkRelayUrlPolicy::Public) .expect("proposal relays") @@ -1919,12 +1945,14 @@ async fn order_revision_and_cancellation_dtos_serialize_deterministically() { ); assert!(!proposal_enqueue_json.to_string().contains("proposal-dto")); - let proposal_try_key = OrderRevisionProposalEnqueueRequest::new( + let proposal_try_key = TradeRevisionProposalEnqueueRequest::new( seller_actor(), root_event.clone(), previous_event.clone(), proposal.clone(), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_idempotency_key("order-revision-proposal-try") .expect("proposal try key"); @@ -1933,7 +1961,7 @@ async fn order_revision_and_cancellation_dtos_serialize_deterministically() { serde_json::json!({ "value": "<redacted>", "len": 27 }) ); - let decision_prepare = OrderRevisionDecisionPrepareRequest::new( + let decision_prepare = TradeRevisionDecisionPrepareRequest::new( buyer_actor(), root_event.clone(), previous_event.clone(), @@ -1960,12 +1988,14 @@ async fn order_revision_and_cancellation_dtos_serialize_deterministically() { ); assert_eq!(decision_prepare_json["created_at"], 1_700_000_654); - let decision_enqueue = OrderRevisionDecisionEnqueueRequest::new( + let decision_enqueue = TradeRevisionDecisionEnqueueRequest::new( buyer_actor(), root_event.clone(), previous_event.clone(), revision_decision, - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY, RELAY_B], SdkRelayUrlPolicy::Public) .expect("decision relays") @@ -1983,7 +2013,7 @@ async fn order_revision_and_cancellation_dtos_serialize_deterministically() { assert_eq!(decision_enqueue_json["created_at"], 1_700_000_654); assert!(!decision_enqueue_json.to_string().contains("decision-dto")); - let decision_try_key = OrderRevisionDecisionEnqueueRequest::new( + let decision_try_key = TradeRevisionDecisionEnqueueRequest::new( buyer_actor(), root_event.clone(), previous_event.clone(), @@ -1992,7 +2022,9 @@ async fn order_revision_and_cancellation_dtos_serialize_deterministically() { &previous_event_id, RadrootsOrderRevisionOutcome::Accepted, ), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_idempotency_key("order-revision-decision-try") .expect("decision try key"); @@ -2001,7 +2033,7 @@ async fn order_revision_and_cancellation_dtos_serialize_deterministically() { serde_json::json!({ "value": "<redacted>", "len": 27 }) ); - let cancellation_prepare = OrderCancellationPrepareRequest::new( + let cancellation_prepare = TradeCancellationPrepareRequest::new( buyer_actor(), root_event.clone(), previous_event.clone(), @@ -2017,12 +2049,14 @@ async fn order_revision_and_cancellation_dtos_serialize_deterministically() { ); assert_eq!(cancellation_prepare_json["created_at"], 1_700_000_654); - let cancellation_enqueue = OrderCancellationEnqueueRequest::new( + let cancellation_enqueue = TradeCancellationEnqueueRequest::new( buyer_actor(), root_event.clone(), previous_event.clone(), cancellation, - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY, RELAY_B], SdkRelayUrlPolicy::Public) .expect("cancellation relays") @@ -2044,12 +2078,14 @@ async fn order_revision_and_cancellation_dtos_serialize_deterministically() { .contains("cancellation-dto") ); - let cancellation_try_key = OrderCancellationEnqueueRequest::new( + let cancellation_try_key = TradeCancellationEnqueueRequest::new( buyer_actor(), root_event.clone(), previous_event.clone(), order_cancellation("order-revision-dto"), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_idempotency_key("order-cancellation-try") .expect("cancellation try key"); @@ -2060,7 +2096,7 @@ async fn order_revision_and_cancellation_dtos_serialize_deterministically() { let event = signed_order_request_event("order-evidence-dto", 77); let request_evidence = - OrderRequestEvidenceIngestRequest::new(event.clone()).with_observed_at(created_at); + TradeRequestEvidenceIngestRequest::new(event.clone()).with_observed_at(created_at); let request_evidence_json = serde_json::to_value(&request_evidence).expect("request evidence json"); assert_struct_serialize_error_paths(&request_evidence, 2); @@ -2068,7 +2104,7 @@ async fn order_revision_and_cancellation_dtos_serialize_deterministically() { assert_eq!(request_evidence_json["observed_at"], 1_700_000_654); let order_evidence = - OrderEvidenceIngestRequest::new(event.clone()).with_observed_at(created_at); + TradeEvidenceIngestRequest::new(event.clone()).with_observed_at(created_at); let order_evidence_json = serde_json::to_value(&order_evidence).expect("order evidence json"); assert_struct_serialize_error_paths(&order_evidence, 2); assert_eq!(order_evidence_json["event"]["id"], event.id.as_str()); @@ -2084,11 +2120,13 @@ async fn order_decision_enqueue_accept_stores_event_queues_outbox_and_updates_st .ingest_event(RadrootsEventIngest::new(request_event.clone(), 4_000)) .await .expect("ingest request"); - let request = OrderDecisionEnqueueRequest::new( + let request = TradeDecisionEnqueueRequest::new( seller_actor(), request_event_ptr(&request_event), order_decision("order-decision-accept"), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public) .expect("target relays") @@ -2133,7 +2171,7 @@ async fn order_decision_enqueue_accept_stores_event_queues_outbox_and_updates_st .await .expect("outbox operation") .expect("outbox operation"); - assert_eq!(operation.operation_kind, ORDER_DECISION_OPERATION_KIND); + assert_eq!(operation.operation_kind, TRADE_DECISION_OPERATION_KIND); let outbox_event = outbox .get_event(receipt.outbox_event_id) .await @@ -2149,7 +2187,7 @@ async fn order_decision_enqueue_accept_stores_event_queues_outbox_and_updates_st .await .expect("status"); assert!(status.found); - assert_eq!(status.status, OrderStatusKind::AgreedPendingRhi); + assert_eq!(status.status, TradeStatusKind::AgreedPendingRhi); assert_eq!(status.event_count, 2); assert_eq!( status @@ -2188,11 +2226,13 @@ async fn order_decision_enqueue_decline_stores_event_and_status_sees_declined() decision.decision = RadrootsOrderDecisionOutcome::Declined { reason: " unavailable ".to_owned(), }; - let request = OrderDecisionEnqueueRequest::new( + let request = TradeDecisionEnqueueRequest::new( seller_actor(), request_event_ptr(&request_event), decision, - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public) .expect("target relays"); @@ -2209,7 +2249,7 @@ async fn order_decision_enqueue_decline_stores_event_and_status_sees_declined() .status(status_request("order-decision-decline")) .await .expect("status"); - assert_eq!(status.status, OrderStatusKind::Declined); + assert_eq!(status.status, TradeStatusKind::Declined); assert_eq!( status .decision_event_id @@ -2229,11 +2269,13 @@ async fn order_decision_enqueue_rejects_missing_request_evidence_before_mutation .to_owned(), relays: Some(RELAY.to_owned()), }; - let request = OrderDecisionEnqueueRequest::new( + let request = TradeDecisionEnqueueRequest::new( seller_actor(), missing_request, order_decision("order-decision-missing-request"), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public) .expect("target relays"); @@ -2274,11 +2316,13 @@ async fn order_decision_enqueue_returns_sanitized_signer_errors_before_decision_ .ingest_event(RadrootsEventIngest::new(request_event.clone(), 4_200)) .await .expect("ingest request"); - let request = OrderDecisionEnqueueRequest::new( + let request = TradeDecisionEnqueueRequest::new( seller_actor(), request_event_ptr(&request_event), order_decision("order-decision-wrong-signer"), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public) .expect("target relays"); @@ -2326,11 +2370,13 @@ async fn order_decision_enqueue_rejects_existing_decision_state_before_mutation( decline.decision = RadrootsOrderDecisionOutcome::Declined { reason: "too late".to_owned(), }; - let request = OrderDecisionEnqueueRequest::new( + let request = TradeDecisionEnqueueRequest::new( seller_actor(), request_event_ptr(&request_event), decline, - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public) .expect("target relays"); @@ -2355,7 +2401,7 @@ async fn order_decision_enqueue_rejects_existing_decision_state_before_mutation( .status(status_request("order-decision-conflict")) .await .expect("status"); - assert_eq!(status.status, OrderStatusKind::AgreedPendingRhi); + assert_eq!(status.status, TradeStatusKind::AgreedPendingRhi); assert_eq!( status .decision_event_id @@ -2383,7 +2429,7 @@ async fn order_revision_lifecycle_accepts_proposal_and_waits_for_rhi() { let proposal_actor = seller_actor(); let proposal_plan = sdk .trades() - .prepare_revision_proposal(OrderRevisionProposalPrepareRequest::new( + .prepare_revision_proposal(TradeRevisionProposalPrepareRequest::new( proposal_actor.clone(), request_event_ptr(&request_event), request_event_ptr(&request_event), @@ -2395,8 +2441,10 @@ async fn order_revision_lifecycle_accepts_proposal_and_waits_for_rhi() { .enqueue_prepared_revision_proposal_with_explicit_signer( &proposal_actor, proposal_plan, - SdkRelayTargetPolicy::try_explicit([RELAY], SdkRelayUrlPolicy::Public) + RelayResolutionPolicy::try_explicit([RELAY], SdkRelayUrlPolicy::Public) .expect("proposal target relays"), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, Some( SdkIdempotencyKey::new("order-lifecycle-revision-proposal") .expect("proposal idempotency"), @@ -2411,7 +2459,7 @@ async fn order_revision_lifecycle_accepts_proposal_and_waits_for_rhi() { ); assert_eq!( outbox_operation_kind(&sdk, proposal_receipt.outbox_operation_id).await, - ORDER_REVISION_PROPOSAL_OPERATION_KIND + TRADE_REVISION_PROPOSAL_OPERATION_KIND ); let stored_proposal = store .get_event(proposal_receipt.signed_event_id.as_str()) @@ -2432,7 +2480,7 @@ async fn order_revision_lifecycle_accepts_proposal_and_waits_for_rhi() { let revision_decision_actor = buyer_actor(); let revision_decision_plan = sdk .trades() - .prepare_revision_decision(OrderRevisionDecisionPrepareRequest::new( + .prepare_revision_decision(TradeRevisionDecisionPrepareRequest::new( revision_decision_actor.clone(), request_event_ptr(&request_event), order_event_ptr(&proposal_receipt.signed_event_id), @@ -2444,8 +2492,10 @@ async fn order_revision_lifecycle_accepts_proposal_and_waits_for_rhi() { .enqueue_prepared_revision_decision_with_explicit_signer( &revision_decision_actor, revision_decision_plan, - SdkRelayTargetPolicy::try_explicit([RELAY], SdkRelayUrlPolicy::Public) + RelayResolutionPolicy::try_explicit([RELAY], SdkRelayUrlPolicy::Public) .expect("revision decision target relays"), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &FixtureSigner::new(BUYER_SECRET_KEY_HEX), ) @@ -2453,7 +2503,7 @@ async fn order_revision_lifecycle_accepts_proposal_and_waits_for_rhi() { .expect("enqueue revision decision"); assert_eq!( outbox_operation_kind(&sdk, revision_decision_receipt.outbox_operation_id).await, - ORDER_REVISION_DECISION_OPERATION_KIND + TRADE_REVISION_DECISION_OPERATION_KIND ); assert_eq!( store @@ -2470,7 +2520,7 @@ async fn order_revision_lifecycle_accepts_proposal_and_waits_for_rhi() { .status(status_request("order-lifecycle-agreement")) .await .expect("status"); - assert_eq!(status.status, OrderStatusKind::AgreedPendingRhi); + assert_eq!(status.status, TradeStatusKind::AgreedPendingRhi); assert_eq!(status.event_count, 3); assert_eq!( status @@ -2495,7 +2545,7 @@ async fn order_revision_lifecycle_accepts_proposal_and_waits_for_rhi() { assert!(!status.lifecycle_terminal); assert_eq!( status.next_action, - OrderStatusNextActionKind::AwaitRhiValidation + TradeStatusNextActionKind::AwaitRhiValidation ); assert!(status.evidence.has_request); assert!(!status.evidence.has_decision); @@ -2527,12 +2577,14 @@ async fn order_revision_proposal_status_exposes_pending_and_blocks_follow_on_lif let proposal_receipt = sdk .trades() .enqueue_revision_proposal_with_explicit_signer( - OrderRevisionProposalEnqueueRequest::new( + TradeRevisionProposalEnqueueRequest::new( seller_actor(), request_event_ptr(&request_event), request_event_ptr(&request_event), proposal, - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public) .expect("proposal target relays"), @@ -2546,7 +2598,7 @@ async fn order_revision_proposal_status_exposes_pending_and_blocks_follow_on_lif .status(status_request("order-lifecycle-pending-revision")) .await .expect("status"); - assert_eq!(status.status, OrderStatusKind::RevisionProposed); + assert_eq!(status.status, TradeStatusKind::RevisionProposed); assert_eq!(status.event_count, 2); assert!(status.agreement_event_id.is_none()); assert_eq!( @@ -2569,11 +2621,13 @@ async fn order_revision_proposal_status_exposes_pending_and_blocks_follow_on_lif let decision_error = sdk .trades() .enqueue_decision_with_explicit_signer( - OrderDecisionEnqueueRequest::new( + TradeDecisionEnqueueRequest::new( seller_actor(), request_event_ptr(&request_event), order_decision("order-lifecycle-pending-revision"), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public) .expect("decision target relays"), @@ -2594,12 +2648,14 @@ async fn order_revision_proposal_status_exposes_pending_and_blocks_follow_on_lif let proposal_error = sdk .trades() .enqueue_revision_proposal_with_explicit_signer( - OrderRevisionProposalEnqueueRequest::new( + TradeRevisionProposalEnqueueRequest::new( seller_actor(), request_event_ptr(&request_event), order_event_ptr(&proposal_receipt.signed_event_id), blocked_proposal, - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public) .expect("blocked proposal target relays"), @@ -2638,12 +2694,14 @@ async fn order_declined_revision_finalizes_declined_negotiation() { let proposal_receipt = sdk .trades() .enqueue_revision_proposal_with_explicit_signer( - OrderRevisionProposalEnqueueRequest::new( + TradeRevisionProposalEnqueueRequest::new( seller_actor(), request_event_ptr(&request_event), request_event_ptr(&request_event), proposal.clone(), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public) .expect("proposal target relays"), @@ -2661,12 +2719,14 @@ async fn order_declined_revision_finalizes_declined_negotiation() { let declined_revision_receipt = sdk .trades() .enqueue_revision_decision_with_explicit_signer( - OrderRevisionDecisionEnqueueRequest::new( + TradeRevisionDecisionEnqueueRequest::new( buyer_actor(), request_event_ptr(&request_event), order_event_ptr(&proposal_receipt.signed_event_id), declined_revision, - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public) .expect("declined revision target relays"), @@ -2680,7 +2740,7 @@ async fn order_declined_revision_finalizes_declined_negotiation() { .status(status_request("order-lifecycle-declined-revision")) .await .expect("status"); - assert_eq!(status.status, OrderStatusKind::Declined); + assert_eq!(status.status, TradeStatusKind::Declined); assert_eq!(status.event_count, 3); assert!(status.agreement_event_id.is_none()); assert_eq!( @@ -2695,7 +2755,7 @@ async fn order_declined_revision_finalizes_declined_negotiation() { Some(declined_revision_receipt.signed_event_id.as_str()) ); assert!(status.lifecycle_terminal); - assert_eq!(status.next_action, OrderStatusNextActionKind::Terminal); + assert_eq!(status.next_action, TradeStatusNextActionKind::Terminal); assert!(!status.eligibility.can_decide); assert!(!status.eligibility.can_propose_revision); assert!(!status.eligibility.can_decide_revision); @@ -2709,12 +2769,14 @@ async fn order_declined_revision_finalizes_declined_negotiation() { let second_decision_error = sdk .trades() .enqueue_revision_decision_with_explicit_signer( - OrderRevisionDecisionEnqueueRequest::new( + TradeRevisionDecisionEnqueueRequest::new( buyer_actor(), request_event_ptr(&request_event), order_event_ptr(&proposal_receipt.signed_event_id), second_decision, - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public) .expect("second decision target relays"), @@ -2748,7 +2810,7 @@ async fn order_cancel_lifecycle_enqueue_updates_status() { let cancellation_actor = buyer_actor(); let cancellation_plan = sdk .trades() - .prepare_cancellation(OrderCancellationPrepareRequest::new( + .prepare_cancellation(TradeCancellationPrepareRequest::new( cancellation_actor.clone(), request_event_ptr(&request_event), request_event_ptr(&request_event), @@ -2760,8 +2822,10 @@ async fn order_cancel_lifecycle_enqueue_updates_status() { .enqueue_prepared_cancellation_with_explicit_signer( &cancellation_actor, cancellation_plan, - SdkRelayTargetPolicy::try_explicit([RELAY], SdkRelayUrlPolicy::Public) + RelayResolutionPolicy::try_explicit([RELAY], SdkRelayUrlPolicy::Public) .expect("cancellation target relays"), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, Some( SdkIdempotencyKey::new("order-lifecycle-cancel").expect("cancellation idempotency"), ), @@ -2774,7 +2838,7 @@ async fn order_cancel_lifecycle_enqueue_updates_status() { assert_eq!(cancellation.previous_event_id, request_event_id); assert_eq!( outbox_operation_kind(&sdk, cancellation.outbox_operation_id).await, - ORDER_CANCELLATION_OPERATION_KIND + TRADE_CANCELLATION_OPERATION_KIND ); assert_eq!( store @@ -2788,12 +2852,14 @@ async fn order_cancel_lifecycle_enqueue_updates_status() { let replay = sdk .trades() .enqueue_cancellation_with_explicit_signer( - OrderCancellationEnqueueRequest::new( + TradeCancellationEnqueueRequest::new( buyer_actor(), request_event_ptr(&request_event), request_event_ptr(&request_event), order_cancellation("order-lifecycle-cancel"), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public) .expect("replay target relays") @@ -2811,7 +2877,7 @@ async fn order_cancel_lifecycle_enqueue_updates_status() { .status(status_request("order-lifecycle-cancel")) .await .expect("status"); - assert_eq!(status.status, OrderStatusKind::Cancelled); + assert_eq!(status.status, TradeStatusKind::Cancelled); assert_eq!( status .cancellation_event_id @@ -2820,7 +2886,7 @@ async fn order_cancel_lifecycle_enqueue_updates_status() { Some(cancellation.signed_event_id.as_str()) ); assert!(status.lifecycle_terminal); - assert_eq!(status.next_action, OrderStatusNextActionKind::Terminal); + assert_eq!(status.next_action, TradeStatusNextActionKind::Terminal); assert!(status.evidence.has_request); assert!(!status.evidence.has_decision); assert!(status.evidence.has_cancellation); @@ -2837,7 +2903,7 @@ async fn order_lifecycle_enqueue_rejects_invalid_state_before_mutation() { let missing = sdk .trades() .enqueue_revision_proposal_with_explicit_signer( - OrderRevisionProposalEnqueueRequest::new( + TradeRevisionProposalEnqueueRequest::new( seller_actor(), request_event_ptr(&request_event), request_event_ptr(&request_event), @@ -2846,7 +2912,9 @@ async fn order_lifecycle_enqueue_rejects_invalid_state_before_mutation() { &request_event_id, &request_event_id, ), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public) .expect("missing target relays"), @@ -2871,11 +2939,13 @@ async fn order_lifecycle_enqueue_rejects_invalid_state_before_mutation() { let decision_receipt = sdk .trades() .enqueue_decision_with_explicit_signer( - OrderDecisionEnqueueRequest::new( + TradeDecisionEnqueueRequest::new( seller_actor(), request_event_ptr(&request_event), order_decision("order-lifecycle-invalid"), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public) .expect("decision target relays"), @@ -2895,12 +2965,14 @@ async fn order_lifecycle_enqueue_rejects_invalid_state_before_mutation() { let revision_error = sdk .trades() .enqueue_revision_decision_with_explicit_signer( - OrderRevisionDecisionEnqueueRequest::new( + TradeRevisionDecisionEnqueueRequest::new( buyer_actor(), request_event_ptr(&request_event), order_event_ptr(&decision_receipt.signed_event_id), revision_without_proposal, - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public) .expect("revision decision target relays"), @@ -2916,12 +2988,14 @@ async fn order_lifecycle_enqueue_rejects_invalid_state_before_mutation() { let cancellation_error = sdk .trades() .enqueue_cancellation_with_explicit_signer( - OrderCancellationEnqueueRequest::new( + TradeCancellationEnqueueRequest::new( buyer_actor(), request_event_ptr(&request_event), order_event_ptr(&decision_receipt.signed_event_id), order_cancellation("order-lifecycle-invalid"), - SdkRelayTargetPolicy::UseConfiguredRelays, + RelayResolutionPolicy::ConfiguredRelays, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays([RELAY], SdkRelayUrlPolicy::Public) .expect("cancellation target relays"), @@ -2948,24 +3022,24 @@ async fn order_status_returns_not_found_for_missing_local_order() { let (_tempdir, sdk, _store) = directory_sdk_and_store().await; let request = status_request("order-1"); - assert_eq!(request.limit, ORDER_STATUS_DEFAULT_LIMIT); + assert_eq!(request.limit, TRADE_STATUS_DEFAULT_LIMIT); let receipt = sdk.trades().status(request).await.expect("status"); assert!(!receipt.found); assert_eq!(receipt.order_id.as_str(), "order-1"); - assert_eq!(receipt.source, SdkOrderStatusSource::LocalEventStore); + assert_eq!(receipt.source, SdkTradeStatusSource::LocalEventStore); assert_eq!(receipt.event_count, 0); - assert_eq!(receipt.limit_applied, ORDER_STATUS_DEFAULT_LIMIT); + assert_eq!(receipt.limit_applied, TRADE_STATUS_DEFAULT_LIMIT); assert!(receipt.event_ids.is_empty()); - assert_eq!(receipt.status, OrderStatusKind::Missing); + assert_eq!(receipt.status, TradeStatusKind::Missing); assert!(receipt.listing_addr.is_none()); assert!(receipt.buyer_pubkey.is_none()); assert!(receipt.seller_pubkey.is_none()); assert!(receipt.economics.is_none()); - assert_eq!(receipt.next_action, OrderStatusNextActionKind::NoLocalOrder); + assert_eq!(receipt.next_action, TradeStatusNextActionKind::NoLocalOrder); assert_eq!(receipt.evidence.event_count, 0); - assert_eq!(receipt.evidence.limit_applied, ORDER_STATUS_DEFAULT_LIMIT); + assert_eq!(receipt.evidence.limit_applied, TRADE_STATUS_DEFAULT_LIMIT); assert!(!receipt.evidence.has_request); assert!(!receipt.evidence.has_issues); assert!(!receipt.eligibility.can_decide); @@ -2986,33 +3060,33 @@ async fn order_status_rejects_invalid_limits_before_querying() { .expect_err("zero limit"); let too_large = sdk .trades() - .status(status_request("order-1").with_limit(ORDER_STATUS_MAX_LIMIT + 1)) + .status(status_request("order-1").with_limit(TRADE_STATUS_MAX_LIMIT + 1)) .await .expect_err("too large"); assert!(matches!( zero, - RadrootsSdkError::OrderStatusLimitInvalid { + RadrootsSdkError::TradeStatusLimitInvalid { limit: 0, min: 1, - max: ORDER_STATUS_MAX_LIMIT + max: TRADE_STATUS_MAX_LIMIT } )); assert!(matches!( too_large, - RadrootsSdkError::OrderStatusLimitInvalid { + RadrootsSdkError::TradeStatusLimitInvalid { limit, min: 1, - max: ORDER_STATUS_MAX_LIMIT - } if limit == ORDER_STATUS_MAX_LIMIT + 1 + max: TRADE_STATUS_MAX_LIMIT + } if limit == TRADE_STATUS_MAX_LIMIT + 1 )); } #[test] fn order_status_parse_rejects_invalid_order_ids() { - let error = OrderStatusRequest::parse("bad order id").expect_err("invalid order id"); + let error = TradeStatusRequest::parse("bad order id").expect_err("invalid order id"); - assert!(matches!(error, RadrootsSdkError::InvalidOrderId { .. })); + assert!(matches!(error, RadrootsSdkError::InvalidTradeId { .. })); } #[tokio::test] @@ -3025,7 +3099,13 @@ async fn order_status_contract_dtos_serialize_deterministically() { assert_eq!( request_json, serde_json::json!({ - "order_id": "order-1", + "locator": { + "trade_id": "order-1", + "root_event_id": null, + "listing_addr": null, + "buyer_pubkey": null, + "seller_pubkey": null + }, "limit": 25 }) ); @@ -3035,6 +3115,10 @@ async fn order_status_contract_dtos_serialize_deterministically() { assert_eq!(receipt_json["source"], "local_event_store"); assert_eq!(receipt_json["status"], "missing"); + assert_eq!(receipt_json["locator"], request_json["locator"]); + assert_eq!(receipt_json["order_id"], "order-1"); + assert_eq!(receipt_json["root_event_id"], serde_json::Value::Null); + assert_eq!(receipt_json["ambiguity_candidates"], serde_json::json!([])); assert_eq!(receipt_json["listing_addr"], serde_json::Value::Null); assert_eq!(receipt_json["buyer_pubkey"], serde_json::Value::Null); assert_eq!(receipt_json["seller_pubkey"], serde_json::Value::Null); @@ -3052,8 +3136,8 @@ async fn order_status_contract_dtos_serialize_deterministically() { assert_eq!(receipt_json["eligibility"]["can_propose_revision"], false); assert_eq!(receipt_json["eligibility"]["can_decide_revision"], false); - let issue = SdkOrderStatusIssue { - kind: SdkOrderStatusIssueKind::DecisionPayloadInvalid, + let issue = SdkTradeStatusIssue { + kind: SdkTradeStatusIssueKind::DecisionPayloadInvalid, event_ids: vec![deterministic_event_id("issue-event")], }; assert_eq!(issue.code(), "decision_payload_invalid"); @@ -3077,7 +3161,7 @@ fn order_status_issue_mapping_preserves_kind_codes_and_event_ids() { RadrootsOrderIssue::$variant { event_id: event_id.clone(), }, - SdkOrderStatusIssueKind::$kind, + SdkTradeStatusIssueKind::$kind, $code, vec![event_id], ) @@ -3094,7 +3178,7 @@ fn order_status_issue_mapping_preserves_kind_codes_and_event_ids() { RadrootsOrderIssue::$variant { event_ids: event_ids.clone(), }, - SdkOrderStatusIssueKind::$kind, + SdkTradeStatusIssueKind::$kind, $code, event_ids, ) @@ -3104,7 +3188,7 @@ fn order_status_issue_mapping_preserves_kind_codes_and_event_ids() { let cases = vec![ ( RadrootsOrderIssue::MissingRequest, - SdkOrderStatusIssueKind::MissingRequest, + SdkTradeStatusIssueKind::MissingRequest, "missing_request", Vec::new(), ), @@ -3412,7 +3496,7 @@ fn order_status_issue_mapping_preserves_kind_codes_and_event_ids() { event_id: event_id.clone(), reason: "fixture validation failed".to_owned(), }, - SdkOrderStatusIssueKind::DeterministicValidationFailure, + SdkTradeStatusIssueKind::DeterministicValidationFailure, "deterministic_validation_failure", vec![event_id], ) @@ -3425,7 +3509,7 @@ fn order_status_issue_mapping_preserves_kind_codes_and_event_ids() { expected_event_id: expected_event_id.clone(), current_event_id: current_event_id.clone(), }, - SdkOrderStatusIssueKind::StaleListingEvent, + SdkTradeStatusIssueKind::StaleListingEvent, "stale_listing_event", vec![expected_event_id, current_event_id], ) @@ -3433,7 +3517,7 @@ fn order_status_issue_mapping_preserves_kind_codes_and_event_ids() { ]; for (issue, expected_kind, expected_code, expected_event_ids) in cases { - let sdk_issue = SdkOrderStatusIssue::from(issue); + let sdk_issue = SdkTradeStatusIssue::from(issue); assert_eq!(sdk_issue.kind, expected_kind); assert_eq!(sdk_issue.code(), expected_code); @@ -3466,7 +3550,7 @@ async fn order_status_projects_local_request_and_decision_events() { assert!(receipt.found); assert_eq!(receipt.order_id.as_str(), "order-1"); - assert_eq!(receipt.source, SdkOrderStatusSource::LocalEventStore); + assert_eq!(receipt.source, SdkTradeStatusSource::LocalEventStore); assert_eq!(receipt.event_count, 2); assert_eq!(receipt.limit_applied, 1_000); assert_eq!( @@ -3477,7 +3561,7 @@ async fn order_status_projects_local_request_and_decision_events() { .collect::<Vec<_>>(), vec![request_event.id.as_str(), decision_event.id.as_str()] ); - assert_eq!(receipt.status, OrderStatusKind::AgreedPendingRhi); + assert_eq!(receipt.status, TradeStatusKind::AgreedPendingRhi); assert_eq!( receipt .request_event_id @@ -3510,7 +3594,7 @@ async fn order_status_projects_local_request_and_decision_events() { assert!(!receipt.lifecycle_terminal); assert_eq!( receipt.next_action, - OrderStatusNextActionKind::AwaitRhiValidation + TradeStatusNextActionKind::AwaitRhiValidation ); assert_eq!(receipt.evidence.event_count, 2); assert!(receipt.evidence.has_request); @@ -3545,7 +3629,7 @@ async fn order_status_reports_limited_local_results() { .expect("status"); assert!(receipt.found); - assert_eq!(receipt.status, OrderStatusKind::Requested); + assert_eq!(receipt.status, TradeStatusKind::Requested); assert_eq!(receipt.event_count, 1); assert_eq!(receipt.limit_applied, 1); assert_eq!( @@ -3572,7 +3656,7 @@ async fn order_status_reports_limited_local_results() { } #[tokio::test] -async fn order_status_reports_typed_reducer_issues() { +async fn order_status_reports_root_ambiguity_for_reused_trade_ids() { let (_tempdir, sdk, store) = directory_sdk_and_store().await; let first_request_event = signed_order_request_event("order-1", 27); let second_request_event = signed_order_request_event("order-1", 28); @@ -3593,8 +3677,8 @@ async fn order_status_reports_typed_reducer_issues() { .await .expect("status"); - assert!(receipt.found); - assert_eq!(receipt.status, OrderStatusKind::Invalid); + assert!(!receipt.found); + assert_eq!(receipt.status, TradeStatusKind::Ambiguous); assert_eq!(receipt.event_count, 2); assert_eq!( receipt @@ -3607,17 +3691,21 @@ async fn order_status_reports_typed_reducer_issues() { second_request_event.id.as_str() ] ); - let issue = receipt - .issues + assert!(receipt.issues.is_empty()); + let candidate_roots = receipt + .ambiguity_candidates .iter() - .find(|issue| issue.kind == SdkOrderStatusIssueKind::MultipleRequests) - .expect("multiple request issue"); + .map(|candidate| { + candidate + .locator + .root_event_id + .as_ref() + .map(RadrootsEventId::as_str) + .expect("root event id") + }) + .collect::<Vec<_>>(); assert_eq!( - issue - .event_ids - .iter() - .map(RadrootsEventId::as_str) - .collect::<Vec<_>>(), + candidate_roots, vec![ first_request_event.id.as_str(), second_request_event.id.as_str() diff --git a/crates/sdk/tests/runtime_foundation.rs b/crates/sdk/tests/runtime_foundation.rs @@ -408,22 +408,22 @@ fn sdk_error_contract_methods_cover_all_variants() { vec![RadrootsSdkRecoveryAction::RetryOperationWithSameIdempotencyKey], ), ( - RadrootsSdkError::OrderStatusLimitInvalid { + RadrootsSdkError::TradeStatusLimitInvalid { limit: 0, min: 1, max: 1000, }, - "order_status_limit_invalid", + "trade_status_limit_invalid", RadrootsSdkErrorClass::Request, false, vec![RadrootsSdkRecoveryAction::FixRequest], ), ( - RadrootsSdkError::InvalidOrderId { + RadrootsSdkError::InvalidTradeId { value: "bad".to_owned(), message: "invalid".to_owned(), }, - "invalid_order_id", + "invalid_trade_id", RadrootsSdkErrorClass::Request, false, vec![RadrootsSdkRecoveryAction::FixRequest], @@ -892,7 +892,7 @@ fn sdk_examples_stay_on_product_api_boundary() { assert!(local_enqueue.contains("enqueue_prepared_publish")); assert!(!local_enqueue.contains("enqueue_prepared_publish_with_explicit_signer")); assert!(local_enqueue.contains("push_outbox_with_adapter")); - assert!(local_enqueue.contains("OrderStatusRequest")); + assert!(local_enqueue.contains("TradeStatusRequest")); let myc_setup = include_str!("../examples/sdk_v1_myc_nip46_signer_setup.rs"); assert!(myc_setup.contains("RadrootsSdkMycNip46Signer")); diff --git a/crates/sdk/tests/source_boundary.rs b/crates/sdk/tests/source_boundary.rs @@ -39,52 +39,53 @@ const FORBIDDEN_SDK_SOURCE_CONCEPTS: &[ForbiddenSdkConcept] = &[ }, ]; -const REQUIRED_ORDER_RUNTIME_EXPORTS: &[&str] = &[ - "ORDER_CANCELLATION_OPERATION_KIND", - "ORDER_DECISION_OPERATION_KIND", - "ORDER_REVISION_DECISION_OPERATION_KIND", - "ORDER_REVISION_PROPOSAL_OPERATION_KIND", - "ORDER_STATUS_DEFAULT_LIMIT", - "ORDER_STATUS_MAX_LIMIT", - "ORDER_SUBMIT_OPERATION_KIND", - "OrderCancellationEnqueueRequest", - "OrderCancellationPlan", - "OrderCancellationPrepareRequest", - "OrderCancellationReceipt", - "OrderDecisionEnqueueRequest", - "OrderDecisionPlan", - "OrderDecisionPrepareRequest", - "OrderDecisionReceipt", - "OrderEvidenceIngestReceipt", - "OrderEvidenceIngestRequest", - "OrderRequestEvidenceIngestReceipt", - "OrderRequestEvidenceIngestRequest", - "OrderRevisionDecisionEnqueueRequest", - "OrderRevisionDecisionPlan", - "OrderRevisionDecisionPrepareRequest", - "OrderRevisionDecisionReceipt", - "OrderRevisionProposalEnqueueRequest", - "OrderRevisionProposalPlan", - "OrderRevisionProposalPrepareRequest", - "OrderRevisionProposalReceipt", - "OrderStatusEligibility", - "OrderStatusEvidenceSummary", - "OrderStatusKind", - "OrderStatusNextActionKind", - "OrderStatusReceipt", - "OrderStatusRequest", - "OrderSubmitEnqueueRequest", - "OrderSubmitPlan", - "OrderSubmitPrepareRequest", - "OrderSubmitReceipt", - "OrderWorkflowEnqueueReceipt", - "OrderWorkflowIdempotencyReceipt", - "OrderWorkflowKind", - "OrderWorkflowPlan", - "OrderWorkflowRetryAdvice", - "SdkOrderStatusIssue", - "SdkOrderStatusIssueKind", - "SdkOrderStatusSource", +const REQUIRED_TRADE_RUNTIME_EXPORTS: &[&str] = &[ + "TRADE_CANCELLATION_OPERATION_KIND", + "TRADE_DECISION_OPERATION_KIND", + "TRADE_REVISION_DECISION_OPERATION_KIND", + "TRADE_REVISION_PROPOSAL_OPERATION_KIND", + "TRADE_STATUS_DEFAULT_LIMIT", + "TRADE_STATUS_MAX_LIMIT", + "TRADE_SUBMIT_OPERATION_KIND", + "TradeCancellationEnqueueRequest", + "TradeCancellationPlan", + "TradeCancellationPrepareRequest", + "TradeCancellationReceipt", + "TradeDecisionEnqueueRequest", + "TradeDecisionPlan", + "TradeDecisionPrepareRequest", + "TradeDecisionReceipt", + "TradeEvidenceIngestReceipt", + "TradeEvidenceIngestRequest", + "TradeRequestEvidenceIngestReceipt", + "TradeRequestEvidenceIngestRequest", + "TradeRevisionDecisionEnqueueRequest", + "TradeRevisionDecisionPlan", + "TradeRevisionDecisionPrepareRequest", + "TradeRevisionDecisionReceipt", + "TradeRevisionProposalEnqueueRequest", + "TradeRevisionProposalPlan", + "TradeRevisionProposalPrepareRequest", + "TradeRevisionProposalReceipt", + "TradeStatusAmbiguityCandidate", + "TradeStatusEligibility", + "TradeStatusEvidenceSummary", + "TradeStatusKind", + "TradeStatusNextActionKind", + "TradeStatusReceipt", + "TradeStatusRequest", + "TradeSubmitEnqueueRequest", + "TradeSubmitPlan", + "TradeSubmitPrepareRequest", + "TradeSubmitReceipt", + "TradeWorkflowEnqueueReceipt", + "TradeWorkflowIdempotencyReceipt", + "TradeWorkflowKind", + "TradeWorkflowPlan", + "TradeWorkflowRetryAdvice", + "SdkTradeStatusIssue", + "SdkTradeStatusIssueKind", + "SdkTradeStatusSource", ]; const REQUIRED_DVM_RUNTIME_EXPORTS: &[&str] = &[ @@ -98,10 +99,24 @@ const REQUIRED_DVM_RUNTIME_EXPORTS: &[&str] = &[ "DvmTradeTransitionProofRequestPayload", "DvmValidationReceiptIngestReceipt", "DvmValidationReceiptIngestRequest", - "SdkDvmInventoryBinWitness", + "RadrootsTradeInventoryBinWitnessDto", +]; + +const REQUIRED_TRADE_POLICY_EXPORTS: &[&str] = &[ + "AckPolicy", + "PublishMode", + "RelayResolutionPolicy", + "SdkTradeIdempotencyRecord", + "PrivacyPreflightReceipt", + "PrivacyPreflightStatus", + "ProductSensitivityField", + "SDK_TRADE_PROJECTION_CACHE_VERSION", + "SdkTradeProjectionCache", + "SdkTradeProjectionCacheKey", + "SdkTradeProjectionCacheRecord", ]; -const REQUIRED_ORDERS_CLIENT_METHODS: &[&str] = &[ +const REQUIRED_TRADES_CLIENT_METHODS: &[&str] = &[ "pub async fn ingest_evidence(", "pub async fn ingest_request_evidence(", "pub fn prepare_submit(", @@ -131,7 +146,7 @@ const REQUIRED_DVM_CLIENT_METHODS: &[&str] = &[ const REQUIRED_DVM_CLIENT_CONFIGURED_SIGNER_METHODS: &[&str] = &["pub async fn enqueue_trade_transition_proof_request("]; -const REQUIRED_ORDERS_CLIENT_ADVANCED_SIGNER_METHODS: &[&str] = &[ +const REQUIRED_TRADES_CLIENT_ADVANCED_SIGNER_METHODS: &[&str] = &[ "pub async fn enqueue_submit_with_explicit_signer(", "pub async fn enqueue_prepared_submit_with_explicit_signer(", "pub async fn enqueue_decision_with_explicit_signer(", @@ -176,6 +191,54 @@ const FORBIDDEN_ORDER_RUNTIME_PUBLIC_EXPORTS: &[&str] = &[ "ORDER_SETTLEMENT_DECISION_OPERATION_KIND", ]; +const FORBIDDEN_LEGACY_ORDER_PRODUCT_EXPORTS: &[&str] = &[ + "ORDER_CANCELLATION_OPERATION_KIND", + "ORDER_DECISION_OPERATION_KIND", + "ORDER_REVISION_DECISION_OPERATION_KIND", + "ORDER_REVISION_PROPOSAL_OPERATION_KIND", + "ORDER_STATUS_DEFAULT_LIMIT", + "ORDER_STATUS_MAX_LIMIT", + "ORDER_SUBMIT_OPERATION_KIND", + "OrderCancellationEnqueueRequest", + "OrderCancellationPlan", + "OrderCancellationPrepareRequest", + "OrderCancellationReceipt", + "OrderDecisionEnqueueRequest", + "OrderDecisionPlan", + "OrderDecisionPrepareRequest", + "OrderDecisionReceipt", + "OrderEvidenceIngestReceipt", + "OrderEvidenceIngestRequest", + "OrderRequestEvidenceIngestReceipt", + "OrderRequestEvidenceIngestRequest", + "OrderRevisionDecisionEnqueueRequest", + "OrderRevisionDecisionPlan", + "OrderRevisionDecisionPrepareRequest", + "OrderRevisionDecisionReceipt", + "OrderRevisionProposalEnqueueRequest", + "OrderRevisionProposalPlan", + "OrderRevisionProposalPrepareRequest", + "OrderRevisionProposalReceipt", + "OrderStatusEligibility", + "OrderStatusEvidenceSummary", + "OrderStatusReceipt", + "OrderStatusRequest", + "OrderSubmitEnqueueRequest", + "OrderSubmitPlan", + "OrderSubmitPrepareRequest", + "OrderSubmitReceipt", + "OrderWorkflowEnqueueReceipt", + "OrderWorkflowIdempotencyReceipt", + "OrderWorkflowKind", + "OrderWorkflowPlan", + "OrderWorkflowRetryAdvice", + "SdkDvmInventoryBinWitness", + "SdkOrderStatusIssue", + "SdkOrderStatusIssueKind", + "SdkOrderStatusSource", + "TradeProtocolClient", +]; + const FORBIDDEN_ORDER_RUNTIME_METHODS: &[&str] = &[ "accept_settlement", "checkout", @@ -298,10 +361,24 @@ fn order_runtime_public_exports_are_explicit() { "src/lib.rs must not wildcard-export the order runtime" ); - for export in REQUIRED_ORDER_RUNTIME_EXPORTS { + for export in REQUIRED_TRADE_RUNTIME_EXPORTS { + assert!( + source.contains(export), + "src/lib.rs must explicitly expose trade SDK runtime export `{export}`" + ); + } + + for export in REQUIRED_TRADE_POLICY_EXPORTS { assert!( source.contains(export), - "src/lib.rs must explicitly expose order SDK runtime export `{export}`" + "src/lib.rs must explicitly expose trade policy export `{export}`" + ); + } + + for forbidden in FORBIDDEN_LEGACY_ORDER_PRODUCT_EXPORTS { + assert!( + !source.contains(forbidden), + "src/lib.rs must not expose legacy order SDK product export `{forbidden}`" ); } } @@ -352,14 +429,14 @@ fn orders_client_surface_is_inventory_guarded() { "src/orders_runtime.rs must own TradesClient runtime methods" ); - for method in REQUIRED_ORDERS_CLIENT_METHODS { + for method in REQUIRED_TRADES_CLIENT_METHODS { assert!( source.contains(method), "TradesClient must expose inventory-guarded method `{method}`" ); } - for method in REQUIRED_ORDERS_CLIENT_ADVANCED_SIGNER_METHODS { + for method in REQUIRED_TRADES_CLIENT_ADVANCED_SIGNER_METHODS { assert!( source.contains(method), "TradesClient must expose explicit-signer advanced method `{method}`" @@ -427,6 +504,11 @@ fn product_clients_remain_thin_sdk_handles() { "ListingsClient", "MarketClient", "SyncClient", + "TradeBuyerClient", + "TradeResyncClient", + "TradeSellerClient", + "TradeStatusClient", + "TradeValidationClient", "TradesClient", ] { assert!( diff --git a/crates/sdk/tests/unit/dvm_runtime_tests.rs b/crates/sdk/tests/unit/dvm_runtime_tests.rs @@ -1,7 +1,7 @@ use super::{ DvmProofMode, DvmTradeTransitionProofEnqueueRequest, DvmTradeTransitionProofPrepareRequest, - DvmValidationReceiptIngestRequest, SdkDvmInventoryBinWitness, dvm_trade_transition_proof_plan, - sdk_timestamp_ms, + DvmValidationReceiptIngestRequest, RadrootsTradeInventoryBinWitnessDto, + dvm_trade_transition_proof_plan, sdk_timestamp_ms, }; use crate::{ RadrootsSdkError, RadrootsSdkTimestamp, SdkIdempotencyKey, SdkRelayTargetPolicy, @@ -11,7 +11,7 @@ use radroots_authority::RadrootsActorContext; use radroots_events::{ RadrootsNostrEvent, contract::RadrootsActorRole, - ids::{RadrootsEventId, RadrootsListingAddress, RadrootsPublicKey}, + ids::{RadrootsEventId, RadrootsInventoryBinId, RadrootsListingAddress, RadrootsPublicKey}, kinds::KIND_TRADE_TRANSITION_PROOF_REQUEST, }; use radroots_trade::validation_receipt::RadrootsValidationReceiptProofSystem; @@ -60,15 +60,30 @@ fn trade_transition_proof_plan_builds_microstandard_wire_payload() { ); assert_eq!( plan.frozen_draft.tags[1], - vec!["i", event_id('1').as_str(), "event", "listing"] + vec![ + "i", + event_id('1').as_str(), + "event", + "radroots:listing_event" + ] ); assert_eq!( plan.frozen_draft.tags[2], - vec!["i", event_id('2').as_str(), "event", "order_request"] + vec![ + "i", + event_id('2').as_str(), + "event", + "radroots:order_request_event" + ] ); assert_eq!( plan.frozen_draft.tags[3], - vec!["i", event_id('3').as_str(), "event", "order_decision"] + vec![ + "i", + event_id('3').as_str(), + "event", + "radroots:order_decision_event" + ] ); assert_eq!(plan.frozen_draft.tags[4], vec!["p", WORKER]); } @@ -110,8 +125,8 @@ fn trade_transition_proof_plan_rejects_inventory_and_sp1_identity_edges() { event_id('1'), event_id('2'), event_id('3'), - vec![SdkDvmInventoryBinWitness { - bin_id: "bin-1".to_owned(), + vec![RadrootsTradeInventoryBinWitnessDto { + bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), listing_capacity: 2, previous_reserved: 3, }], @@ -124,22 +139,11 @@ fn trade_transition_proof_plan_rejects_inventory_and_sp1_identity_edges() { Err(RadrootsSdkError::InvalidRequest { .. }) )); - let empty_bin_id = DvmTradeTransitionProofPrepareRequest::new( - service_actor(), - worker_pubkey(), - listing_addr(), - event_id('1'), - event_id('2'), - event_id('3'), - vec![SdkDvmInventoryBinWitness { - bin_id: " ".to_owned(), - listing_capacity: 2, - previous_reserved: 1, - }], - ); + let invalid_previous_state_root = + proof_request(service_actor()).with_previous_state_root("not-a-hash"); assert!(matches!( dvm_trade_transition_proof_plan( - empty_bin_id, + invalid_previous_state_root, RadrootsSdkTimestamp::from_unix_seconds(1_700_000_000), ), Err(RadrootsSdkError::InvalidRequest { .. }) @@ -344,17 +348,17 @@ fn proof_request(actor: RadrootsActorContext) -> DvmTradeTransitionProofPrepareR event_id('1'), event_id('2'), event_id('3'), - vec![SdkDvmInventoryBinWitness { - bin_id: "bin-1".to_owned(), + vec![RadrootsTradeInventoryBinWitnessDto { + bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), listing_capacity: 5, previous_reserved: 1, }], ) } -fn inventory_bins() -> Vec<SdkDvmInventoryBinWitness> { - vec![SdkDvmInventoryBinWitness { - bin_id: "bin-1".to_owned(), +fn inventory_bins() -> Vec<RadrootsTradeInventoryBinWitnessDto> { + vec![RadrootsTradeInventoryBinWitnessDto { + bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), listing_capacity: 5, previous_reserved: 1, }] diff --git a/crates/sdk/tests/unit/error_tests.rs b/crates/sdk/tests/unit/error_tests.rs @@ -354,8 +354,8 @@ fn sdk_error_contract_methods_cover_representative_classes_and_details() { existing_digest_prefix: "existing".to_owned(), new_digest_prefix: "new".to_owned(), }, - RadrootsSdkError::order_status_limit_invalid(0, 1, 100), - RadrootsSdkError::invalid_order_id("bad order", "bad id"), + RadrootsSdkError::trade_status_limit_invalid(0, 1, 100), + RadrootsSdkError::invalid_trade_id("bad order", "bad id"), RadrootsSdkError::ProductSyncUnsupported { operation: "sync.push_outbox", required_feature: "relay-runtime", diff --git a/crates/sdk/tests/unit/idempotency_tests.rs b/crates/sdk/tests/unit/idempotency_tests.rs @@ -1,5 +1,6 @@ -use super::SdkIdempotencyKey; +use super::{SdkIdempotencyKey, SdkTradeIdempotencyRecord}; use crate::RadrootsSdkError; +use radroots_events::ids::{RadrootsEventId, RadrootsPublicKey}; #[path = "../support/serializer_failure.rs"] mod serializer_failure; @@ -49,3 +50,42 @@ fn idempotency_key_reports_serializer_failures() { assert_struct_serialize_error_paths(&key, 2); } + +#[test] +fn trade_idempotency_record_binds_payload_and_reports_conflicts() { + let record = SdkTradeIdempotencyRecord { + idempotency_key: SdkIdempotencyKey::new("trade-idempotent").expect("key"), + operation_kind: "trade.submit.v1".to_owned(), + actor_pubkey: RadrootsPublicKey::parse( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + ) + .expect("actor pubkey"), + digest: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_owned(), + canonical_payload_hash: "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + .to_owned(), + expected_event_id: RadrootsEventId::parse( + "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + ) + .expect("event id"), + outbox_operation_id: 42, + }; + + assert!( + record.matches_payload("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc") + ); + assert!( + !record.matches_payload("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee") + ); + assert!(matches!( + record.conflict_error("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + RadrootsSdkError::IdempotencyConflict { + ref operation_kind, + ref expected_pubkey_prefix, + ref existing_digest_prefix, + ref new_digest_prefix, + } if operation_kind == "trade.submit.v1" + && expected_pubkey_prefix == "aaaaaaaaaaaa" + && existing_digest_prefix == "bbbbbbbbbbbb" + && new_digest_prefix == "ffffffffffff" + )); +} diff --git a/crates/sdk/tests/unit/orders_runtime_tests.rs b/crates/sdk/tests/unit/orders_runtime_tests.rs @@ -18,6 +18,7 @@ use radroots_nostr::prelude::{ RadrootsNostrKeys, RadrootsNostrSecretKey, radroots_nostr_sign_frozen_draft, }; use radroots_trade::{ + identity::RadrootsTradeLocator, order::{RadrootsOrderEventDecodeError, RadrootsOrderIssue}, projection::RadrootsTradeProjectionError, workflow::RadrootsTradeWorkflowState, @@ -410,8 +411,8 @@ fn fixture_cancellation(raw_order_id: &str) -> RadrootsOrderCancellation { } } -fn fixture_target_relays() -> SdkRelayTargetPolicy { - SdkRelayTargetPolicy::try_explicit([RELAY], SdkRelayUrlPolicy::Public).expect("target relays") +fn fixture_target_relays() -> RelayResolutionPolicy { + RelayResolutionPolicy::try_explicit([RELAY], SdkRelayUrlPolicy::Public).expect("target relays") } async fn prepared_order_sdk() -> RadrootsClient { @@ -439,11 +440,13 @@ async fn order_configured_local_signer_enqueues_submit_without_explicit_signer() let receipt = sdk .trades() - .enqueue_submit(OrderSubmitEnqueueRequest::new( + .enqueue_submit(TradeSubmitEnqueueRequest::new( fixture_buyer_actor(), fixture_event_ptr('a'), fixture_order_request("order-configured-local-1"), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, )) .await .expect("enqueue submit"); @@ -458,11 +461,13 @@ async fn order_configured_local_signer_enqueues_lifecycle_wrappers_without_expli let decision_submit = enqueue_fixture_submit(&seller_sdk, "order-configured-decision").await; let decision = seller_sdk .trades() - .enqueue_decision(OrderDecisionEnqueueRequest::new( + .enqueue_decision(TradeDecisionEnqueueRequest::new( fixture_seller_actor(), fixture_order_event_ptr(&decision_submit.signed_event_id), fixture_order_decision("order-configured-decision"), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, )) .await .expect("configured decision"); @@ -477,12 +482,14 @@ async fn order_configured_local_signer_enqueues_lifecycle_wrappers_without_expli ); let proposal = seller_sdk .trades() - .enqueue_revision_proposal(OrderRevisionProposalEnqueueRequest::new( + .enqueue_revision_proposal(TradeRevisionProposalEnqueueRequest::new( fixture_seller_actor(), fixture_order_event_ptr(&proposal_submit.signed_event_id), fixture_order_event_ptr(&proposal_submit.signed_event_id), proposal_payload, fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, )) .await .expect("configured revision proposal"); @@ -500,12 +507,14 @@ async fn order_configured_local_signer_enqueues_lifecycle_wrappers_without_expli let revision_proposal = buyer_sdk .trades() .enqueue_revision_proposal_with_explicit_signer( - OrderRevisionProposalEnqueueRequest::new( + TradeRevisionProposalEnqueueRequest::new( fixture_seller_actor(), fixture_order_event_ptr(&revision_submit.signed_event_id), fixture_order_event_ptr(&revision_submit.signed_event_id), revision_proposal_payload.clone(), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ), &OrderFixtureSigner::new(SELLER_SECRET_KEY_HEX), ) @@ -517,12 +526,14 @@ async fn order_configured_local_signer_enqueues_lifecycle_wrappers_without_expli ); let revision_decision = buyer_sdk .trades() - .enqueue_revision_decision(OrderRevisionDecisionEnqueueRequest::new( + .enqueue_revision_decision(TradeRevisionDecisionEnqueueRequest::new( fixture_buyer_actor(), fixture_order_event_ptr(&revision_submit.signed_event_id), fixture_order_event_ptr(&revision_proposal.signed_event_id), revision_decision_payload, fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, )) .await .expect("configured revision decision"); @@ -539,12 +550,14 @@ async fn order_configured_local_signer_enqueues_lifecycle_wrappers_without_expli let cancel_submit = enqueue_fixture_submit(&buyer_sdk, "order-configured-cancel").await; let cancellation = buyer_sdk .trades() - .enqueue_cancellation(OrderCancellationEnqueueRequest::new( + .enqueue_cancellation(TradeCancellationEnqueueRequest::new( fixture_buyer_actor(), fixture_order_event_ptr(&cancel_submit.signed_event_id), fixture_order_event_ptr(&cancel_submit.signed_event_id), fixture_cancellation("order-configured-cancel"), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, )) .await .expect("configured cancellation"); @@ -556,11 +569,11 @@ async fn order_configured_local_signer_enqueues_lifecycle_wrappers_without_expli assert_eq!(cancellation.state, SdkMutationState::StoredAndQueued); } -async fn enqueue_fixture_submit(sdk: &RadrootsClient, raw_order_id: &str) -> OrderSubmitReceipt { +async fn enqueue_fixture_submit(sdk: &RadrootsClient, raw_order_id: &str) -> TradeSubmitReceipt { let buyer = fixture_buyer_actor(); let plan = sdk .trades() - .prepare_submit(OrderSubmitPrepareRequest::new( + .prepare_submit(TradeSubmitPrepareRequest::new( buyer.clone(), fixture_event_ptr('a'), fixture_order_request(raw_order_id), @@ -571,6 +584,8 @@ async fn enqueue_fixture_submit(sdk: &RadrootsClient, raw_order_id: &str) -> Ord &buyer, plan, fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(BUYER_SECRET_KEY_HEX), ) @@ -603,13 +618,13 @@ fn request_event() -> RadrootsNostrEvent { order::build_order_request_draft(&listing_event, &request) .expect("request draft") .into_wire_parts(), - ORDER_REQUEST_CONTRACT_ID, + TRADE_SUBMIT_CONTRACT_ID, &request.buyer_pubkey, ) } fn order_request_evidence_error( - result: Result<OrderRequestEvidence, RadrootsSdkError>, + result: Result<TradeRequestEvidence, RadrootsSdkError>, ) -> RadrootsSdkError { result.err().expect("expected order request evidence error") } @@ -662,7 +677,7 @@ fn workflow_plan_builders_cover_success_and_actor_mismatch_paths() { created_at, ) .expect("submit plan"); - assert_eq!(submit_plan.workflow.kind, OrderWorkflowKind::Submit); + assert_eq!(submit_plan.workflow.kind, TradeWorkflowKind::Submit); assert_eq!(submit_plan.listing_event_id, listing_event_id); let request_event = ptr(submit_plan.expected_event_id.as_str().to_owned()); @@ -673,7 +688,7 @@ fn workflow_plan_builders_cover_success_and_actor_mismatch_paths() { created_at, ) .expect("decision plan"); - assert_eq!(decision_plan.workflow.kind, OrderWorkflowKind::Decision); + assert_eq!(decision_plan.workflow.kind, TradeWorkflowKind::Decision); let proposal = revision_proposal_payload( &submit_plan.expected_event_id, @@ -689,7 +704,7 @@ fn workflow_plan_builders_cover_success_and_actor_mismatch_paths() { .expect("revision proposal plan"); assert_eq!( proposal_plan.workflow.kind, - OrderWorkflowKind::RevisionProposal + TradeWorkflowKind::RevisionProposal ); let revision_decision = revision_decision_payload( @@ -707,7 +722,7 @@ fn workflow_plan_builders_cover_success_and_actor_mismatch_paths() { .expect("revision decision plan"); assert_eq!( revision_decision_plan.workflow.kind, - OrderWorkflowKind::RevisionDecision + TradeWorkflowKind::RevisionDecision ); let cancellation_plan = order_cancellation_plan( @@ -720,7 +735,7 @@ fn workflow_plan_builders_cover_success_and_actor_mismatch_paths() { .expect("cancellation plan"); assert_eq!( cancellation_plan.workflow.kind, - OrderWorkflowKind::Cancellation + TradeWorkflowKind::Cancellation ); let mut wrong_submit = order_request_payload(); @@ -986,7 +1001,7 @@ fn workflow_plan_builders_cover_success_and_actor_mismatch_paths() { content: "{}".to_owned(), tags: vec![vec!["p".to_owned(), pubkey('c').as_str().to_owned()]], }, - ORDER_REQUEST_CONTRACT_ID, + TRADE_SUBMIT_CONTRACT_ID, pubkey('c').as_str(), 1_700_000_000, "order test", @@ -1133,7 +1148,7 @@ fn order_evidence_parses_all_lifecycle_event_kinds() { order::build_order_decision_draft(&root_event_id, &root_event_id, &decision) .expect("decision draft") .into_wire_parts(), - ORDER_DECISION_CONTRACT_ID, + TRADE_DECISION_CONTRACT_ID, &decision.seller_pubkey, ); assert_eq!( @@ -1150,7 +1165,7 @@ fn order_evidence_parses_all_lifecycle_event_kinds() { order::build_order_revision_proposal_draft(&root_event_id, &decision_event_id, &proposal) .expect("proposal draft") .into_wire_parts(), - ORDER_REVISION_PROPOSAL_CONTRACT_ID, + TRADE_REVISION_PROPOSAL_CONTRACT_ID, &proposal.seller_pubkey, ); assert_eq!( @@ -1175,7 +1190,7 @@ fn order_evidence_parses_all_lifecycle_event_kinds() { ) .expect("revision decision draft") .into_wire_parts(), - ORDER_REVISION_DECISION_CONTRACT_ID, + TRADE_REVISION_DECISION_CONTRACT_ID, &revision_decision.buyer_pubkey, ); assert_eq!( @@ -1190,7 +1205,7 @@ fn order_evidence_parses_all_lifecycle_event_kinds() { order::build_order_cancellation_draft(&root_event_id, &decision_event_id, &cancellation) .expect("cancellation draft") .into_wire_parts(), - ORDER_CANCELLATION_CONTRACT_ID, + TRADE_CANCELLATION_CONTRACT_ID, &cancellation.buyer_pubkey, ); assert_eq!( @@ -1232,7 +1247,7 @@ fn order_request_evidence_parses_and_rejects_malformed_envelopes() { order::build_order_request_draft(&ptr(event_id('a').as_str().to_owned()), &request) .expect("request draft") .into_wire_parts(), - ORDER_REQUEST_CONTRACT_ID, + TRADE_SUBMIT_CONTRACT_ID, &pubkey('d'), ); assert!( @@ -1365,53 +1380,57 @@ fn order_status_next_action_covers_revision_and_fallback_terminal_paths() { projection.status = RadrootsTradeWorkflowState::RevisionProposed; projection.pending_revision_event_id = Some(previous_event_id.clone()); - let eligibility = OrderStatusEligibility::from_projection(&projection); + let eligibility = TradeStatusEligibility::from_projection(&projection); assert!(eligibility.can_decide_revision); assert_eq!( - OrderStatusNextActionKind::from_projection(&projection, &eligibility), - OrderStatusNextActionKind::DecideRevision + TradeStatusNextActionKind::from_projection(&projection, &eligibility), + TradeStatusNextActionKind::DecideRevision ); - let receipt = OrderStatusReceipt::from_query_result(RadrootsOrderProjectionQueryResult { - projection: projection.clone(), - event_count: 2, - limit_applied: 10, - event_ids: vec![root_event_id.clone(), previous_event_id.clone()], - }); + let receipt = TradeStatusReceipt::from_projection( + RadrootsTradeLocator::from_order_id(order_id.clone()) + .with_root_event_id(root_event_id.clone()), + Some(root_event_id.clone()), + Vec::new(), + projection.clone(), + 2, + 10, + vec![root_event_id.clone(), previous_event_id.clone()], + ); assert_eq!( receipt.next_action, - OrderStatusNextActionKind::DecideRevision + TradeStatusNextActionKind::DecideRevision ); assert!(receipt.evidence.has_pending_revision); projection.pending_revision_event_id = None; projection.status = RadrootsTradeWorkflowState::Requested; - let fallback = OrderStatusNextActionKind::from_projection( + let fallback = TradeStatusNextActionKind::from_projection( &projection, - &OrderStatusEligibility { + &TradeStatusEligibility { can_decide: false, can_propose_revision: true, can_decide_revision: false, can_cancel: true, }, ); - assert_eq!(fallback, OrderStatusNextActionKind::Terminal); + assert_eq!(fallback, TradeStatusNextActionKind::Terminal); projection.lifecycle_terminal = true; - let terminal_eligibility = OrderStatusEligibility::from_projection(&projection); + let terminal_eligibility = TradeStatusEligibility::from_projection(&projection); assert_eq!( - OrderStatusNextActionKind::from_projection(&projection, &terminal_eligibility), - OrderStatusNextActionKind::Terminal + TradeStatusNextActionKind::from_projection(&projection, &terminal_eligibility), + TradeStatusNextActionKind::Terminal ); projection.lifecycle_terminal = false; projection.issues = vec![RadrootsOrderIssue::ForkedLifecycle { event_ids: vec![event_id('e')], }]; - let issue_eligibility = OrderStatusEligibility::from_projection(&projection); + let issue_eligibility = TradeStatusEligibility::from_projection(&projection); assert_eq!( - OrderStatusNextActionKind::from_projection(&projection, &issue_eligibility), - OrderStatusNextActionKind::InspectEvidenceIssues + TradeStatusNextActionKind::from_projection(&projection, &issue_eligibility), + TradeStatusNextActionKind::InspectEvidenceIssues ); } @@ -1427,7 +1446,7 @@ fn order_issue_mapping_covers_every_trade_issue_variant() { RadrootsOrderIssue::$issue { event_id: one.clone(), }, - SdkOrderStatusIssueKind::$kind, + SdkTradeStatusIssueKind::$kind, 1, ) }; @@ -1436,14 +1455,14 @@ fn order_issue_mapping_covers_every_trade_issue_variant() { let cases = vec![ ( RadrootsOrderIssue::MissingRequest, - SdkOrderStatusIssueKind::MissingRequest, + SdkTradeStatusIssueKind::MissingRequest, 0, ), ( RadrootsOrderIssue::MultipleRequests { event_ids: many.clone(), }, - SdkOrderStatusIssueKind::MultipleRequests, + SdkTradeStatusIssueKind::MultipleRequests, 2, ), single!(RequestPayloadInvalid, RequestPayloadInvalid), @@ -1474,7 +1493,7 @@ fn order_issue_mapping_covers_every_trade_issue_variant() { RadrootsOrderIssue::ConflictingDecisions { event_ids: many.clone(), }, - SdkOrderStatusIssueKind::ConflictingDecisions, + SdkTradeStatusIssueKind::ConflictingDecisions, 2, ), single!( @@ -1577,7 +1596,7 @@ fn order_issue_mapping_covers_every_trade_issue_variant() { RadrootsOrderIssue::ForkedLifecycle { event_ids: many.clone(), }, - SdkOrderStatusIssueKind::ForkedLifecycle, + SdkTradeStatusIssueKind::ForkedLifecycle, 2, ), single!( @@ -1602,7 +1621,7 @@ fn order_issue_mapping_covers_every_trade_issue_variant() { RadrootsOrderIssue::ConflictingValidationReceipts { event_ids: many.clone(), }, - SdkOrderStatusIssueKind::ConflictingValidationReceipts, + SdkTradeStatusIssueKind::ConflictingValidationReceipts, 2, ), ( @@ -1610,7 +1629,7 @@ fn order_issue_mapping_covers_every_trade_issue_variant() { event_id: one.clone(), reason: "fixture validation failed".to_owned(), }, - SdkOrderStatusIssueKind::DeterministicValidationFailure, + SdkTradeStatusIssueKind::DeterministicValidationFailure, 1, ), ( @@ -1618,13 +1637,13 @@ fn order_issue_mapping_covers_every_trade_issue_variant() { expected_event_id: one, current_event_id: two, }, - SdkOrderStatusIssueKind::StaleListingEvent, + SdkTradeStatusIssueKind::StaleListingEvent, 2, ), ]; for (issue, expected_kind, expected_event_count) in cases { - let sdk_issue = SdkOrderStatusIssue::from(issue); + let sdk_issue = SdkTradeStatusIssue::from(issue); assert_eq!(sdk_issue.kind, expected_kind); assert_eq!(sdk_issue.event_ids.len(), expected_event_count); assert_eq!(sdk_issue.code(), expected_kind.code()); @@ -2054,120 +2073,160 @@ fn order_enqueue_request_mutators_reject_invalid_relays_and_idempotency_keys() { RadrootsOrderRevisionOutcome::Accepted, ); let cancellation_payload = cancellation_payload(); - let policy = SdkRelayTargetPolicy::UseConfiguredRelays; + let policy = RelayResolutionPolicy::ConfiguredRelays; assert_error_display( - OrderSubmitEnqueueRequest::new( + TradeSubmitEnqueueRequest::new( buyer.clone(), listing_event.clone(), submit_payload.clone(), policy.clone(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays(Vec::<String>::new(), SdkRelayUrlPolicy::Public), "target relays", ); assert_error_display( - OrderSubmitEnqueueRequest::new( + TradeSubmitEnqueueRequest::new( buyer.clone(), listing_event, submit_payload, policy.clone(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_idempotency_key(""), "idempotency key", ); assert_error_display( - OrderDecisionEnqueueRequest::new( + TradeDecisionEnqueueRequest::new( seller.clone(), request_event.clone(), decision_payload.clone(), policy.clone(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays(Vec::<String>::new(), SdkRelayUrlPolicy::Public), "target relays", ); assert_error_display( - OrderDecisionEnqueueRequest::new( + TradeDecisionEnqueueRequest::new( seller.clone(), request_event.clone(), decision_payload, policy.clone(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_idempotency_key(" leading"), "idempotency key", ); assert_error_display( - OrderRevisionProposalEnqueueRequest::new( + TradeRevisionProposalEnqueueRequest::new( seller.clone(), request_event.clone(), previous_event.clone(), proposal_payload.clone(), policy.clone(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays(Vec::<String>::new(), SdkRelayUrlPolicy::Public), "target relays", ); assert_error_display( - OrderRevisionProposalEnqueueRequest::new( + TradeRevisionProposalEnqueueRequest::new( seller.clone(), request_event.clone(), previous_event.clone(), proposal_payload.clone(), policy.clone(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_idempotency_key("trailing "), "idempotency key", ); assert_error_display( - OrderRevisionDecisionEnqueueRequest::new( + TradeRevisionDecisionEnqueueRequest::new( buyer.clone(), request_event.clone(), previous_event.clone(), revision_decision_payload.clone(), policy.clone(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays(Vec::<String>::new(), SdkRelayUrlPolicy::Public), "target relays", ); assert_error_display( - OrderRevisionDecisionEnqueueRequest::new( + TradeRevisionDecisionEnqueueRequest::new( buyer.clone(), request_event.clone(), previous_event.clone(), revision_decision_payload, policy.clone(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_idempotency_key("invalid\nkey"), "idempotency key", ); assert_error_display( - OrderCancellationEnqueueRequest::new( + TradeCancellationEnqueueRequest::new( buyer.clone(), request_event.clone(), previous_event.clone(), cancellation_payload.clone(), policy.clone(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays(Vec::<String>::new(), SdkRelayUrlPolicy::Public), "target relays", ); assert_error_display( - OrderCancellationEnqueueRequest::new( + TradeCancellationEnqueueRequest::new( buyer, request_event, previous_event, cancellation_payload, policy, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_idempotency_key(""), "idempotency key", ); } +#[test] +fn trade_enqueue_policy_rejects_publish_modes_without_matching_side_effects() { + assert!(matches!( + validate_trade_enqueue_policy(PublishMode::DryRun, AckPolicy::NoWait), + Err(RadrootsSdkError::InvalidRequest { ref message }) + if message == "trade dry-run publish mode must use a prepare request" + )); + assert!(matches!( + validate_trade_enqueue_policy(PublishMode::EnqueueOnly, AckPolicy::AtLeastOneRelay), + Err(RadrootsSdkError::InvalidRequest { ref message }) + if message == "trade enqueue-only publish mode only supports no-wait acknowledgement" + )); + assert!(matches!( + validate_trade_enqueue_policy(PublishMode::EnqueueAndPublish, AckPolicy::AtLeastOneRelay), + Err(RadrootsSdkError::InvalidRequest { ref message }) + if message == "trade enqueue-and-publish mode requires publish receipt orchestration" + )); + assert!(validate_trade_enqueue_policy(PublishMode::EnqueueOnly, AckPolicy::NoWait).is_ok()); +} + #[tokio::test] async fn orders_client_prepare_methods_resolve_request_created_at() { let sdk = crate::RadrootsClient::builder() @@ -2182,7 +2241,7 @@ async fn orders_client_prepare_methods_resolve_request_created_at() { assert_eq!( sdk.trades() - .prepare_submit(OrderSubmitPrepareRequest::new( + .prepare_submit(TradeSubmitPrepareRequest::new( buyer_actor(), root_event.clone(), order_request_payload(), @@ -2194,7 +2253,7 @@ async fn orders_client_prepare_methods_resolve_request_created_at() { assert_eq!( sdk.trades() .prepare_decision( - OrderDecisionPrepareRequest::new( + TradeDecisionPrepareRequest::new( seller_actor(), root_event.clone(), order_decision_payload(), @@ -2210,7 +2269,7 @@ async fn orders_client_prepare_methods_resolve_request_created_at() { assert_eq!( sdk.trades() .prepare_revision_proposal( - OrderRevisionProposalPrepareRequest::new( + TradeRevisionProposalPrepareRequest::new( seller_actor(), root_event.clone(), previous_event.clone(), @@ -2225,7 +2284,7 @@ async fn orders_client_prepare_methods_resolve_request_created_at() { assert_eq!( sdk.trades() .prepare_revision_decision( - OrderRevisionDecisionPrepareRequest::new( + TradeRevisionDecisionPrepareRequest::new( buyer_actor(), root_event.clone(), ptr(event_id('c').as_str().to_owned()), @@ -2244,7 +2303,7 @@ async fn orders_client_prepare_methods_resolve_request_created_at() { assert_eq!( sdk.trades() .prepare_cancellation( - OrderCancellationPrepareRequest::new( + TradeCancellationPrepareRequest::new( buyer_actor(), root_event, previous_event, @@ -2264,12 +2323,12 @@ async fn prepared_submit_and_decision_enqueue_cover_source_attached_success_path let submit = enqueue_fixture_submit(&sdk, "order-prepared-decision"); let submit = submit.await; assert_eq!(submit.signed_event_id, submit.expected_event_id); - assert_eq!(submit.workflow.kind, OrderWorkflowKind::Submit); + assert_eq!(submit.workflow.kind, TradeWorkflowKind::Submit); let seller = fixture_seller_actor(); let decision_plan = sdk .trades() - .prepare_decision(OrderDecisionPrepareRequest::new( + .prepare_decision(TradeDecisionPrepareRequest::new( seller.clone(), fixture_order_event_ptr(&submit.signed_event_id), fixture_order_decision("order-prepared-decision"), @@ -2281,6 +2340,8 @@ async fn prepared_submit_and_decision_enqueue_cover_source_attached_success_path &seller, decision_plan.clone(), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, Some(SdkIdempotencyKey::new("prepared-decision").expect("idempotency")), &OrderFixtureSigner::new(SELLER_SECRET_KEY_HEX), ) @@ -2288,7 +2349,7 @@ async fn prepared_submit_and_decision_enqueue_cover_source_attached_success_path .expect("enqueue decision"); assert_eq!(decision.signed_event_id, decision.expected_event_id); - assert_eq!(decision.workflow.kind, OrderWorkflowKind::Decision); + assert_eq!(decision.workflow.kind, TradeWorkflowKind::Decision); assert_eq!(decision.request_event_id, submit.signed_event_id); } @@ -2304,7 +2365,7 @@ async fn prepared_revision_lifecycle_enqueue_cover_source_attached_success_paths ); let proposal_plan = sdk .trades() - .prepare_revision_proposal(OrderRevisionProposalPrepareRequest::new( + .prepare_revision_proposal(TradeRevisionProposalPrepareRequest::new( seller.clone(), fixture_order_event_ptr(&submit.signed_event_id), fixture_order_event_ptr(&submit.signed_event_id), @@ -2317,6 +2378,8 @@ async fn prepared_revision_lifecycle_enqueue_cover_source_attached_success_paths &seller, proposal_plan.clone(), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, Some(SdkIdempotencyKey::new("prepared-proposal").expect("idempotency")), &OrderFixtureSigner::new(SELLER_SECRET_KEY_HEX), ) @@ -2324,7 +2387,7 @@ async fn prepared_revision_lifecycle_enqueue_cover_source_attached_success_paths .expect("enqueue proposal"); assert_eq!(proposal.signed_event_id, proposal.expected_event_id); - assert_eq!(proposal.workflow.kind, OrderWorkflowKind::RevisionProposal); + assert_eq!(proposal.workflow.kind, TradeWorkflowKind::RevisionProposal); assert_eq!(proposal.root_event_id, submit.signed_event_id); assert_eq!(proposal.previous_event_id, submit.signed_event_id); @@ -2332,7 +2395,7 @@ async fn prepared_revision_lifecycle_enqueue_cover_source_attached_success_paths let revision_decision = fixture_revision_decision(&proposal_payload, &proposal.signed_event_id); let revision_decision_plan = sdk .trades() - .prepare_revision_decision(OrderRevisionDecisionPrepareRequest::new( + .prepare_revision_decision(TradeRevisionDecisionPrepareRequest::new( buyer.clone(), fixture_order_event_ptr(&submit.signed_event_id), fixture_order_event_ptr(&proposal.signed_event_id), @@ -2345,6 +2408,8 @@ async fn prepared_revision_lifecycle_enqueue_cover_source_attached_success_paths &buyer, revision_decision_plan, fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(BUYER_SECRET_KEY_HEX), ) @@ -2352,7 +2417,7 @@ async fn prepared_revision_lifecycle_enqueue_cover_source_attached_success_paths .expect("enqueue revision decision"); assert_eq!(revision.signed_event_id, revision.expected_event_id); - assert_eq!(revision.workflow.kind, OrderWorkflowKind::RevisionDecision); + assert_eq!(revision.workflow.kind, TradeWorkflowKind::RevisionDecision); assert_eq!(revision.root_event_id, submit.signed_event_id); assert_eq!(revision.previous_event_id, proposal.signed_event_id); } @@ -2364,7 +2429,7 @@ async fn prepared_cancellation_enqueue_covers_source_attached_success_path() { let buyer = fixture_buyer_actor(); let cancellation_plan = sdk .trades() - .prepare_cancellation(OrderCancellationPrepareRequest::new( + .prepare_cancellation(TradeCancellationPrepareRequest::new( buyer.clone(), fixture_order_event_ptr(&submit.signed_event_id), fixture_order_event_ptr(&submit.signed_event_id), @@ -2377,6 +2442,8 @@ async fn prepared_cancellation_enqueue_covers_source_attached_success_path() { &buyer, cancellation_plan, fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, Some(SdkIdempotencyKey::new("prepared-cancellation").expect("idempotency")), &OrderFixtureSigner::new(BUYER_SECRET_KEY_HEX), ) @@ -2384,7 +2451,7 @@ async fn prepared_cancellation_enqueue_covers_source_attached_success_path() { .expect("enqueue cancellation"); assert_eq!(cancellation.signed_event_id, cancellation.expected_event_id); - assert_eq!(cancellation.workflow.kind, OrderWorkflowKind::Cancellation); + assert_eq!(cancellation.workflow.kind, TradeWorkflowKind::Cancellation); assert_eq!(cancellation.root_event_id, submit.signed_event_id); assert_eq!(cancellation.previous_event_id, submit.signed_event_id); } @@ -2395,11 +2462,13 @@ async fn convenience_order_enqueue_methods_cover_source_attached_wrappers() { let decision_submit = sdk .trades() .enqueue_submit_with_explicit_signer( - OrderSubmitEnqueueRequest::new( + TradeSubmitEnqueueRequest::new( fixture_buyer_actor(), fixture_event_ptr('b'), fixture_order_request("order-wrapper-decision"), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ), &OrderFixtureSigner::new(BUYER_SECRET_KEY_HEX), ) @@ -2408,11 +2477,13 @@ async fn convenience_order_enqueue_methods_cover_source_attached_wrappers() { let decision = sdk .trades() .enqueue_decision_with_explicit_signer( - OrderDecisionEnqueueRequest::new( + TradeDecisionEnqueueRequest::new( fixture_seller_actor(), fixture_order_event_ptr(&decision_submit.signed_event_id), fixture_order_decision("order-wrapper-decision"), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ), &OrderFixtureSigner::new(SELLER_SECRET_KEY_HEX), ) @@ -2423,11 +2494,13 @@ async fn convenience_order_enqueue_methods_cover_source_attached_wrappers() { let revision_submit = sdk .trades() .enqueue_submit_with_explicit_signer( - OrderSubmitEnqueueRequest::new( + TradeSubmitEnqueueRequest::new( fixture_buyer_actor(), fixture_event_ptr('c'), fixture_order_request("order-wrapper-revision"), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ), &OrderFixtureSigner::new(BUYER_SECRET_KEY_HEX), ) @@ -2441,12 +2514,14 @@ async fn convenience_order_enqueue_methods_cover_source_attached_wrappers() { let proposal = sdk .trades() .enqueue_revision_proposal_with_explicit_signer( - OrderRevisionProposalEnqueueRequest::new( + TradeRevisionProposalEnqueueRequest::new( fixture_seller_actor(), fixture_order_event_ptr(&revision_submit.signed_event_id), fixture_order_event_ptr(&revision_submit.signed_event_id), proposal_payload.clone(), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ), &OrderFixtureSigner::new(SELLER_SECRET_KEY_HEX), ) @@ -2455,12 +2530,14 @@ async fn convenience_order_enqueue_methods_cover_source_attached_wrappers() { let revision = sdk .trades() .enqueue_revision_decision_with_explicit_signer( - OrderRevisionDecisionEnqueueRequest::new( + TradeRevisionDecisionEnqueueRequest::new( fixture_buyer_actor(), fixture_order_event_ptr(&revision_submit.signed_event_id), fixture_order_event_ptr(&proposal.signed_event_id), fixture_revision_decision(&proposal_payload, &proposal.signed_event_id), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ), &OrderFixtureSigner::new(BUYER_SECRET_KEY_HEX), ) @@ -2471,11 +2548,13 @@ async fn convenience_order_enqueue_methods_cover_source_attached_wrappers() { let cancellation_submit = sdk .trades() .enqueue_submit_with_explicit_signer( - OrderSubmitEnqueueRequest::new( + TradeSubmitEnqueueRequest::new( fixture_buyer_actor(), fixture_event_ptr('d'), fixture_order_request("order-wrapper-cancellation"), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ), &OrderFixtureSigner::new(BUYER_SECRET_KEY_HEX), ) @@ -2484,12 +2563,14 @@ async fn convenience_order_enqueue_methods_cover_source_attached_wrappers() { let cancellation = sdk .trades() .enqueue_cancellation_with_explicit_signer( - OrderCancellationEnqueueRequest::new( + TradeCancellationEnqueueRequest::new( fixture_buyer_actor(), fixture_order_event_ptr(&cancellation_submit.signed_event_id), fixture_order_event_ptr(&cancellation_submit.signed_event_id), fixture_cancellation("order-wrapper-cancellation"), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ), &OrderFixtureSigner::new(BUYER_SECRET_KEY_HEX), ) @@ -2515,7 +2596,7 @@ async fn prepared_lifecycle_enqueues_report_missing_and_closed_preflight_errors( let decision_plan = sdk .trades() - .prepare_decision(OrderDecisionPrepareRequest::new( + .prepare_decision(TradeDecisionPrepareRequest::new( seller.clone(), root.clone(), fixture_order_decision("order-preflight-errors"), @@ -2527,6 +2608,8 @@ async fn prepared_lifecycle_enqueues_report_missing_and_closed_preflight_errors( &seller, decision_plan.clone(), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(SELLER_SECRET_KEY_HEX), ) @@ -2539,7 +2622,7 @@ async fn prepared_lifecycle_enqueues_report_missing_and_closed_preflight_errors( let proposal_plan = sdk .trades() - .prepare_revision_proposal(OrderRevisionProposalPrepareRequest::new( + .prepare_revision_proposal(TradeRevisionProposalPrepareRequest::new( seller.clone(), root.clone(), previous.clone(), @@ -2552,6 +2635,8 @@ async fn prepared_lifecycle_enqueues_report_missing_and_closed_preflight_errors( &seller, proposal_plan.clone(), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(SELLER_SECRET_KEY_HEX), ) @@ -2564,7 +2649,7 @@ async fn prepared_lifecycle_enqueues_report_missing_and_closed_preflight_errors( let revision_decision_plan = sdk .trades() - .prepare_revision_decision(OrderRevisionDecisionPrepareRequest::new( + .prepare_revision_decision(TradeRevisionDecisionPrepareRequest::new( buyer.clone(), root.clone(), previous.clone(), @@ -2577,6 +2662,8 @@ async fn prepared_lifecycle_enqueues_report_missing_and_closed_preflight_errors( &buyer, revision_decision_plan, fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(BUYER_SECRET_KEY_HEX), ) @@ -2589,7 +2676,7 @@ async fn prepared_lifecycle_enqueues_report_missing_and_closed_preflight_errors( let cancellation_plan = sdk .trades() - .prepare_cancellation(OrderCancellationPrepareRequest::new( + .prepare_cancellation(TradeCancellationPrepareRequest::new( buyer.clone(), root, previous, @@ -2602,6 +2689,8 @@ async fn prepared_lifecycle_enqueues_report_missing_and_closed_preflight_errors( &buyer, cancellation_plan, fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(BUYER_SECRET_KEY_HEX), ) @@ -2624,7 +2713,7 @@ async fn prepared_lifecycle_enqueues_report_missing_and_closed_preflight_errors( ); let closed_plan = closed_sdk .trades() - .prepare_decision(OrderDecisionPrepareRequest::new( + .prepare_decision(TradeDecisionPrepareRequest::new( seller.clone(), closed_root.clone(), fixture_order_decision("order-closed-preflight"), @@ -2632,7 +2721,7 @@ async fn prepared_lifecycle_enqueues_report_missing_and_closed_preflight_errors( .expect("closed decision plan"); let closed_proposal_plan = closed_sdk .trades() - .prepare_revision_proposal(OrderRevisionProposalPrepareRequest::new( + .prepare_revision_proposal(TradeRevisionProposalPrepareRequest::new( seller.clone(), closed_root.clone(), closed_previous.clone(), @@ -2641,7 +2730,7 @@ async fn prepared_lifecycle_enqueues_report_missing_and_closed_preflight_errors( .expect("closed proposal plan"); let closed_revision_plan = closed_sdk .trades() - .prepare_revision_decision(OrderRevisionDecisionPrepareRequest::new( + .prepare_revision_decision(TradeRevisionDecisionPrepareRequest::new( buyer.clone(), closed_root.clone(), closed_previous.clone(), @@ -2650,7 +2739,7 @@ async fn prepared_lifecycle_enqueues_report_missing_and_closed_preflight_errors( .expect("closed revision decision plan"); let closed_cancellation_plan = closed_sdk .trades() - .prepare_cancellation(OrderCancellationPrepareRequest::new( + .prepare_cancellation(TradeCancellationPrepareRequest::new( buyer.clone(), closed_root, closed_previous, @@ -2664,6 +2753,8 @@ async fn prepared_lifecycle_enqueues_report_missing_and_closed_preflight_errors( &seller, closed_plan, fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(SELLER_SECRET_KEY_HEX), ) @@ -2676,6 +2767,8 @@ async fn prepared_lifecycle_enqueues_report_missing_and_closed_preflight_errors( &seller, closed_proposal_plan, fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(SELLER_SECRET_KEY_HEX), ) @@ -2691,6 +2784,8 @@ async fn prepared_lifecycle_enqueues_report_missing_and_closed_preflight_errors( &buyer, closed_revision_plan, fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(BUYER_SECRET_KEY_HEX), ) @@ -2706,6 +2801,8 @@ async fn prepared_lifecycle_enqueues_report_missing_and_closed_preflight_errors( &buyer, closed_cancellation_plan, fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(BUYER_SECRET_KEY_HEX), ) @@ -2734,7 +2831,7 @@ async fn configured_prepared_lifecycle_enqueues_run_preflight_guards() { let seller_sdk = configured_order_sdk(SELLER_SECRET_KEY_HEX).await; let decision_plan = seller_sdk .trades() - .prepare_decision(OrderDecisionPrepareRequest::new( + .prepare_decision(TradeDecisionPrepareRequest::new( seller.clone(), root.clone(), fixture_order_decision("order-configured-preflight"), @@ -2742,7 +2839,14 @@ async fn configured_prepared_lifecycle_enqueues_run_preflight_guards() { .expect("decision plan"); let decision_missing = seller_sdk .trades() - .enqueue_prepared_decision(&seller, decision_plan, fixture_target_relays(), None) + .enqueue_prepared_decision( + &seller, + decision_plan, + fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, + None, + ) .await .expect_err("configured missing decision evidence"); assert!(matches!( @@ -2752,7 +2856,7 @@ async fn configured_prepared_lifecycle_enqueues_run_preflight_guards() { let proposal_plan = seller_sdk .trades() - .prepare_revision_proposal(OrderRevisionProposalPrepareRequest::new( + .prepare_revision_proposal(TradeRevisionProposalPrepareRequest::new( seller.clone(), root.clone(), previous.clone(), @@ -2761,7 +2865,14 @@ async fn configured_prepared_lifecycle_enqueues_run_preflight_guards() { .expect("proposal plan"); let proposal_missing = seller_sdk .trades() - .enqueue_prepared_revision_proposal(&seller, proposal_plan, fixture_target_relays(), None) + .enqueue_prepared_revision_proposal( + &seller, + proposal_plan, + fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, + None, + ) .await .expect_err("configured missing proposal evidence"); assert!(matches!( @@ -2772,7 +2883,7 @@ async fn configured_prepared_lifecycle_enqueues_run_preflight_guards() { let buyer_sdk = configured_order_sdk(BUYER_SECRET_KEY_HEX).await; let revision_plan = buyer_sdk .trades() - .prepare_revision_decision(OrderRevisionDecisionPrepareRequest::new( + .prepare_revision_decision(TradeRevisionDecisionPrepareRequest::new( buyer.clone(), root.clone(), previous.clone(), @@ -2781,7 +2892,14 @@ async fn configured_prepared_lifecycle_enqueues_run_preflight_guards() { .expect("revision decision plan"); let revision_missing = buyer_sdk .trades() - .enqueue_prepared_revision_decision(&buyer, revision_plan, fixture_target_relays(), None) + .enqueue_prepared_revision_decision( + &buyer, + revision_plan, + fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, + None, + ) .await .expect_err("configured missing revision decision evidence"); assert!(matches!( @@ -2791,7 +2909,7 @@ async fn configured_prepared_lifecycle_enqueues_run_preflight_guards() { let cancellation_plan = buyer_sdk .trades() - .prepare_cancellation(OrderCancellationPrepareRequest::new( + .prepare_cancellation(TradeCancellationPrepareRequest::new( buyer.clone(), root, previous, @@ -2800,7 +2918,14 @@ async fn configured_prepared_lifecycle_enqueues_run_preflight_guards() { .expect("cancellation plan"); let cancellation_missing = buyer_sdk .trades() - .enqueue_prepared_cancellation(&buyer, cancellation_plan, fixture_target_relays(), None) + .enqueue_prepared_cancellation( + &buyer, + cancellation_plan, + fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, + None, + ) .await .expect_err("configured missing cancellation evidence"); assert!(matches!( @@ -2826,7 +2951,7 @@ async fn configured_prepared_lifecycle_enqueues_report_existing_event_lookup_err let seller_sdk = configured_order_sdk(SELLER_SECRET_KEY_HEX).await; let decision_plan = seller_sdk .trades() - .prepare_decision(OrderDecisionPrepareRequest::new( + .prepare_decision(TradeDecisionPrepareRequest::new( seller.clone(), root.clone(), fixture_order_decision("order-configured-existing-lookup"), @@ -2834,7 +2959,7 @@ async fn configured_prepared_lifecycle_enqueues_report_existing_event_lookup_err .expect("decision plan"); let proposal_plan = seller_sdk .trades() - .prepare_revision_proposal(OrderRevisionProposalPrepareRequest::new( + .prepare_revision_proposal(TradeRevisionProposalPrepareRequest::new( seller.clone(), root.clone(), previous.clone(), @@ -2845,7 +2970,14 @@ async fn configured_prepared_lifecycle_enqueues_report_existing_event_lookup_err assert!(matches!( seller_sdk .trades() - .enqueue_prepared_decision(&seller, decision_plan, fixture_target_relays(), None) + .enqueue_prepared_decision( + &seller, + decision_plan, + fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, + None + ) .await, Err(RadrootsSdkError::EventStore { .. }) )); @@ -2856,6 +2988,8 @@ async fn configured_prepared_lifecycle_enqueues_report_existing_event_lookup_err &seller, proposal_plan, fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, ) .await, @@ -2865,7 +2999,7 @@ async fn configured_prepared_lifecycle_enqueues_report_existing_event_lookup_err let buyer_sdk = configured_order_sdk(BUYER_SECRET_KEY_HEX).await; let revision_plan = buyer_sdk .trades() - .prepare_revision_decision(OrderRevisionDecisionPrepareRequest::new( + .prepare_revision_decision(TradeRevisionDecisionPrepareRequest::new( buyer.clone(), root.clone(), previous.clone(), @@ -2874,7 +3008,7 @@ async fn configured_prepared_lifecycle_enqueues_report_existing_event_lookup_err .expect("revision decision plan"); let cancellation_plan = buyer_sdk .trades() - .prepare_cancellation(OrderCancellationPrepareRequest::new( + .prepare_cancellation(TradeCancellationPrepareRequest::new( buyer.clone(), root, previous, @@ -2889,6 +3023,8 @@ async fn configured_prepared_lifecycle_enqueues_report_existing_event_lookup_err &buyer, revision_plan, fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None ) .await, @@ -2897,7 +3033,14 @@ async fn configured_prepared_lifecycle_enqueues_report_existing_event_lookup_err assert!(matches!( buyer_sdk .trades() - .enqueue_prepared_cancellation(&buyer, cancellation_plan, fixture_target_relays(), None) + .enqueue_prepared_cancellation( + &buyer, + cancellation_plan, + fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, + None + ) .await, Err(RadrootsSdkError::EventStore { .. }) )); @@ -2918,11 +3061,13 @@ async fn configured_enqueue_wrappers_report_prepare_errors_before_signing() { assert!(matches!( buyer_sdk .trades() - .enqueue_submit(OrderSubmitEnqueueRequest::new( + .enqueue_submit(TradeSubmitEnqueueRequest::new( fixture_seller_actor(), fixture_event_ptr('a'), fixture_order_request("order-configured-prepare-submit"), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, )) .await, Err(RadrootsSdkError::UnauthorizedActor { .. }) @@ -2930,11 +3075,13 @@ async fn configured_enqueue_wrappers_report_prepare_errors_before_signing() { assert!(matches!( seller_sdk .trades() - .enqueue_decision(OrderDecisionEnqueueRequest::new( + .enqueue_decision(TradeDecisionEnqueueRequest::new( fixture_buyer_actor(), fixture_event_ptr('a'), fixture_order_decision("order-configured-prepare-decision"), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, )) .await, Err(RadrootsSdkError::UnauthorizedActor { .. }) @@ -2942,12 +3089,14 @@ async fn configured_enqueue_wrappers_report_prepare_errors_before_signing() { assert!(matches!( seller_sdk .trades() - .enqueue_revision_proposal(OrderRevisionProposalEnqueueRequest::new( + .enqueue_revision_proposal(TradeRevisionProposalEnqueueRequest::new( fixture_buyer_actor(), fixture_order_event_ptr(&root_event_id), fixture_order_event_ptr(&previous_event_id), proposal.clone(), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, )) .await, Err(RadrootsSdkError::UnauthorizedActor { .. }) @@ -2955,12 +3104,14 @@ async fn configured_enqueue_wrappers_report_prepare_errors_before_signing() { assert!(matches!( buyer_sdk .trades() - .enqueue_revision_decision(OrderRevisionDecisionEnqueueRequest::new( + .enqueue_revision_decision(TradeRevisionDecisionEnqueueRequest::new( fixture_seller_actor(), fixture_order_event_ptr(&root_event_id), fixture_order_event_ptr(&previous_event_id), fixture_revision_decision(&proposal, &previous_event_id), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, )) .await, Err(RadrootsSdkError::UnauthorizedActor { .. }) @@ -2968,12 +3119,14 @@ async fn configured_enqueue_wrappers_report_prepare_errors_before_signing() { assert!(matches!( buyer_sdk .trades() - .enqueue_cancellation(OrderCancellationEnqueueRequest::new( + .enqueue_cancellation(TradeCancellationEnqueueRequest::new( fixture_seller_actor(), fixture_order_event_ptr(&root_event_id), fixture_order_event_ptr(&previous_event_id), fixture_cancellation("order-configured-prepare-cancel"), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, )) .await, Err(RadrootsSdkError::UnauthorizedActor { .. }) @@ -2988,7 +3141,7 @@ async fn configured_prepared_methods_report_missing_configured_signer_after_pref let submit_plan = sdk .trades() - .prepare_submit(OrderSubmitPrepareRequest::new( + .prepare_submit(TradeSubmitPrepareRequest::new( buyer.clone(), fixture_event_ptr('a'), fixture_order_request("order-missing-configured-submit"), @@ -2996,7 +3149,14 @@ async fn configured_prepared_methods_report_missing_configured_signer_after_pref .expect("submit plan"); assert!(matches!( sdk.trades() - .enqueue_prepared_submit(&buyer, submit_plan, fixture_target_relays(), None) + .enqueue_prepared_submit( + &buyer, + submit_plan, + fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, + None + ) .await, Err(RadrootsSdkError::SignerUnavailable { .. }) )); @@ -3004,7 +3164,7 @@ async fn configured_prepared_methods_report_missing_configured_signer_after_pref let decision_submit = enqueue_fixture_submit(&sdk, "order-missing-configured-decision").await; let decision_plan = sdk .trades() - .prepare_decision(OrderDecisionPrepareRequest::new( + .prepare_decision(TradeDecisionPrepareRequest::new( seller.clone(), fixture_order_event_ptr(&decision_submit.signed_event_id), fixture_order_decision("order-missing-configured-decision"), @@ -3012,7 +3172,14 @@ async fn configured_prepared_methods_report_missing_configured_signer_after_pref .expect("decision plan"); assert!(matches!( sdk.trades() - .enqueue_prepared_decision(&seller, decision_plan, fixture_target_relays(), None) + .enqueue_prepared_decision( + &seller, + decision_plan, + fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, + None + ) .await, Err(RadrootsSdkError::SignerUnavailable { .. }) )); @@ -3025,7 +3192,7 @@ async fn configured_prepared_methods_report_missing_configured_signer_after_pref ); let proposal_plan = sdk .trades() - .prepare_revision_proposal(OrderRevisionProposalPrepareRequest::new( + .prepare_revision_proposal(TradeRevisionProposalPrepareRequest::new( seller.clone(), fixture_order_event_ptr(&proposal_submit.signed_event_id), fixture_order_event_ptr(&proposal_submit.signed_event_id), @@ -3038,6 +3205,8 @@ async fn configured_prepared_methods_report_missing_configured_signer_after_pref &seller, proposal_plan, fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None ) .await, @@ -3052,7 +3221,7 @@ async fn configured_prepared_methods_report_missing_configured_signer_after_pref ); let revision_proposal_plan = sdk .trades() - .prepare_revision_proposal(OrderRevisionProposalPrepareRequest::new( + .prepare_revision_proposal(TradeRevisionProposalPrepareRequest::new( seller.clone(), fixture_order_event_ptr(&revision_submit.signed_event_id), fixture_order_event_ptr(&revision_submit.signed_event_id), @@ -3065,6 +3234,8 @@ async fn configured_prepared_methods_report_missing_configured_signer_after_pref &seller, revision_proposal_plan, fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(SELLER_SECRET_KEY_HEX), ) @@ -3072,7 +3243,7 @@ async fn configured_prepared_methods_report_missing_configured_signer_after_pref .expect("revision proposal evidence"); let revision_plan = sdk .trades() - .prepare_revision_decision(OrderRevisionDecisionPrepareRequest::new( + .prepare_revision_decision(TradeRevisionDecisionPrepareRequest::new( buyer.clone(), fixture_order_event_ptr(&revision_submit.signed_event_id), fixture_order_event_ptr(&revision_proposal.signed_event_id), @@ -3088,6 +3259,8 @@ async fn configured_prepared_methods_report_missing_configured_signer_after_pref &buyer, revision_plan, fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None ) .await, @@ -3097,7 +3270,7 @@ async fn configured_prepared_methods_report_missing_configured_signer_after_pref let cancellation_submit = enqueue_fixture_submit(&sdk, "order-missing-configured-cancel").await; let cancellation_plan = sdk .trades() - .prepare_cancellation(OrderCancellationPrepareRequest::new( + .prepare_cancellation(TradeCancellationPrepareRequest::new( buyer.clone(), fixture_order_event_ptr(&cancellation_submit.signed_event_id), fixture_order_event_ptr(&cancellation_submit.signed_event_id), @@ -3106,7 +3279,14 @@ async fn configured_prepared_methods_report_missing_configured_signer_after_pref .expect("cancellation plan"); assert!(matches!( sdk.trades() - .enqueue_prepared_cancellation(&buyer, cancellation_plan, fixture_target_relays(), None) + .enqueue_prepared_cancellation( + &buyer, + cancellation_plan, + fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, + None + ) .await, Err(RadrootsSdkError::SignerUnavailable { .. }) )); @@ -3129,14 +3309,14 @@ async fn lifecycle_preflight_helpers_map_projection_query_failures() { ); let decision_plan = client - .prepare_decision(OrderDecisionPrepareRequest::new( + .prepare_decision(TradeDecisionPrepareRequest::new( seller.clone(), root.clone(), fixture_order_decision("order-preflight-query-failure"), )) .expect("decision plan"); let proposal_plan = client - .prepare_revision_proposal(OrderRevisionProposalPrepareRequest::new( + .prepare_revision_proposal(TradeRevisionProposalPrepareRequest::new( seller, root.clone(), previous.clone(), @@ -3144,7 +3324,7 @@ async fn lifecycle_preflight_helpers_map_projection_query_failures() { )) .expect("proposal plan"); let revision_plan = client - .prepare_revision_decision(OrderRevisionDecisionPrepareRequest::new( + .prepare_revision_decision(TradeRevisionDecisionPrepareRequest::new( buyer.clone(), root.clone(), previous.clone(), @@ -3152,7 +3332,7 @@ async fn lifecycle_preflight_helpers_map_projection_query_failures() { )) .expect("revision plan"); let cancellation_plan = client - .prepare_cancellation(OrderCancellationPrepareRequest::new( + .prepare_cancellation(TradeCancellationPrepareRequest::new( buyer, root, previous, @@ -3198,7 +3378,7 @@ async fn prepared_lifecycle_enqueues_report_closed_outbox_after_preflight() { ); let proposal_plan = proposal_sdk .trades() - .prepare_revision_proposal(OrderRevisionProposalPrepareRequest::new( + .prepare_revision_proposal(TradeRevisionProposalPrepareRequest::new( seller.clone(), fixture_order_event_ptr(&proposal_submit.signed_event_id), fixture_order_event_ptr(&proposal_submit.signed_event_id), @@ -3212,12 +3392,14 @@ async fn prepared_lifecycle_enqueues_report_closed_outbox_after_preflight() { &seller, proposal_plan, fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(SELLER_SECRET_KEY_HEX), ) .await .expect_err("closed outbox proposal"); - assert_partial_outbox_enqueue(proposal_error, ORDER_REVISION_PROPOSAL_OPERATION_KIND); + assert_partial_outbox_enqueue(proposal_error, TRADE_REVISION_PROPOSAL_OPERATION_KIND); let revision_sdk = prepared_order_sdk().await; let revision_submit = @@ -3229,7 +3411,7 @@ async fn prepared_lifecycle_enqueues_report_closed_outbox_after_preflight() { ); let proposal_plan = revision_sdk .trades() - .prepare_revision_proposal(OrderRevisionProposalPrepareRequest::new( + .prepare_revision_proposal(TradeRevisionProposalPrepareRequest::new( seller.clone(), fixture_order_event_ptr(&revision_submit.signed_event_id), fixture_order_event_ptr(&revision_submit.signed_event_id), @@ -3242,6 +3424,8 @@ async fn prepared_lifecycle_enqueues_report_closed_outbox_after_preflight() { &seller, proposal_plan, fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(SELLER_SECRET_KEY_HEX), ) @@ -3250,7 +3434,7 @@ async fn prepared_lifecycle_enqueues_report_closed_outbox_after_preflight() { let buyer = fixture_buyer_actor(); let revision_plan = revision_sdk .trades() - .prepare_revision_decision(OrderRevisionDecisionPrepareRequest::new( + .prepare_revision_decision(TradeRevisionDecisionPrepareRequest::new( buyer.clone(), fixture_order_event_ptr(&revision_submit.signed_event_id), fixture_order_event_ptr(&proposal.signed_event_id), @@ -3264,19 +3448,21 @@ async fn prepared_lifecycle_enqueues_report_closed_outbox_after_preflight() { &buyer, revision_plan.clone(), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(BUYER_SECRET_KEY_HEX), ) .await .expect_err("closed outbox revision"); - assert_partial_outbox_enqueue(revision_error, ORDER_REVISION_DECISION_OPERATION_KIND); + assert_partial_outbox_enqueue(revision_error, TRADE_REVISION_DECISION_OPERATION_KIND); let cancellation_sdk = prepared_order_sdk().await; let cancellation_submit = enqueue_fixture_submit(&cancellation_sdk, "order-closed-outbox-cancellation").await; let cancellation_plan = cancellation_sdk .trades() - .prepare_cancellation(OrderCancellationPrepareRequest::new( + .prepare_cancellation(TradeCancellationPrepareRequest::new( buyer.clone(), fixture_order_event_ptr(&cancellation_submit.signed_event_id), fixture_order_event_ptr(&cancellation_submit.signed_event_id), @@ -3290,12 +3476,14 @@ async fn prepared_lifecycle_enqueues_report_closed_outbox_after_preflight() { &buyer, cancellation_plan, fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(BUYER_SECRET_KEY_HEX), ) .await .expect_err("closed outbox cancellation"); - assert_partial_outbox_enqueue(cancellation_error, ORDER_CANCELLATION_OPERATION_KIND); + assert_partial_outbox_enqueue(cancellation_error, TRADE_CANCELLATION_OPERATION_KIND); } #[tokio::test] @@ -3305,7 +3493,7 @@ async fn prepared_lifecycle_enqueues_skip_preflight_for_existing_events() { let seller = fixture_seller_actor(); let decision_plan = decision_sdk .trades() - .prepare_decision(OrderDecisionPrepareRequest::new( + .prepare_decision(TradeDecisionPrepareRequest::new( seller.clone(), fixture_order_event_ptr(&decision_submit.signed_event_id), fixture_order_decision("order-existing-decision"), @@ -3317,6 +3505,8 @@ async fn prepared_lifecycle_enqueues_skip_preflight_for_existing_events() { &seller, decision_plan.clone(), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(SELLER_SECRET_KEY_HEX), ) @@ -3328,6 +3518,8 @@ async fn prepared_lifecycle_enqueues_skip_preflight_for_existing_events() { &seller, decision_plan.clone(), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(SELLER_SECRET_KEY_HEX), ) @@ -3349,6 +3541,8 @@ async fn prepared_lifecycle_enqueues_skip_preflight_for_existing_events() { &seller, decision_plan.clone(), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, ) .await @@ -3373,7 +3567,7 @@ async fn prepared_lifecycle_enqueues_skip_preflight_for_existing_events() { ); let proposal_plan = proposal_sdk .trades() - .prepare_revision_proposal(OrderRevisionProposalPrepareRequest::new( + .prepare_revision_proposal(TradeRevisionProposalPrepareRequest::new( seller.clone(), fixture_order_event_ptr(&proposal_submit.signed_event_id), fixture_order_event_ptr(&proposal_submit.signed_event_id), @@ -3386,6 +3580,8 @@ async fn prepared_lifecycle_enqueues_skip_preflight_for_existing_events() { &seller, proposal_plan.clone(), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(SELLER_SECRET_KEY_HEX), ) @@ -3397,6 +3593,8 @@ async fn prepared_lifecycle_enqueues_skip_preflight_for_existing_events() { &seller, proposal_plan.clone(), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(SELLER_SECRET_KEY_HEX), ) @@ -3418,6 +3616,8 @@ async fn prepared_lifecycle_enqueues_skip_preflight_for_existing_events() { &seller, proposal_plan.clone(), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, ) .await @@ -3442,7 +3642,7 @@ async fn prepared_lifecycle_enqueues_skip_preflight_for_existing_events() { ); let proposal_plan = revision_sdk .trades() - .prepare_revision_proposal(OrderRevisionProposalPrepareRequest::new( + .prepare_revision_proposal(TradeRevisionProposalPrepareRequest::new( seller.clone(), fixture_order_event_ptr(&revision_submit.signed_event_id), fixture_order_event_ptr(&revision_submit.signed_event_id), @@ -3455,6 +3655,8 @@ async fn prepared_lifecycle_enqueues_skip_preflight_for_existing_events() { &seller, proposal_plan, fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(SELLER_SECRET_KEY_HEX), ) @@ -3463,7 +3665,7 @@ async fn prepared_lifecycle_enqueues_skip_preflight_for_existing_events() { let buyer = fixture_buyer_actor(); let revision_plan = revision_sdk .trades() - .prepare_revision_decision(OrderRevisionDecisionPrepareRequest::new( + .prepare_revision_decision(TradeRevisionDecisionPrepareRequest::new( buyer.clone(), fixture_order_event_ptr(&revision_submit.signed_event_id), fixture_order_event_ptr(&proposal.signed_event_id), @@ -3476,6 +3678,8 @@ async fn prepared_lifecycle_enqueues_skip_preflight_for_existing_events() { &buyer, revision_plan.clone(), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(BUYER_SECRET_KEY_HEX), ) @@ -3487,6 +3691,8 @@ async fn prepared_lifecycle_enqueues_skip_preflight_for_existing_events() { &buyer, revision_plan.clone(), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(BUYER_SECRET_KEY_HEX), ) @@ -3508,6 +3714,8 @@ async fn prepared_lifecycle_enqueues_skip_preflight_for_existing_events() { &buyer, revision_plan.clone(), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, ) .await @@ -3528,7 +3736,7 @@ async fn prepared_lifecycle_enqueues_skip_preflight_for_existing_events() { enqueue_fixture_submit(&cancellation_sdk, "order-existing-cancellation").await; let cancellation_plan = cancellation_sdk .trades() - .prepare_cancellation(OrderCancellationPrepareRequest::new( + .prepare_cancellation(TradeCancellationPrepareRequest::new( buyer.clone(), fixture_order_event_ptr(&cancellation_submit.signed_event_id), fixture_order_event_ptr(&cancellation_submit.signed_event_id), @@ -3541,6 +3749,8 @@ async fn prepared_lifecycle_enqueues_skip_preflight_for_existing_events() { &buyer, cancellation_plan.clone(), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, None, &OrderFixtureSigner::new(BUYER_SECRET_KEY_HEX), ) @@ -3548,7 +3758,14 @@ async fn prepared_lifecycle_enqueues_skip_preflight_for_existing_events() { .expect("enqueue cancellation"); let cancellation_repeat = cancellation_sdk .trades() - .enqueue_prepared_cancellation(&buyer, cancellation_plan, fixture_target_relays(), None) + .enqueue_prepared_cancellation( + &buyer, + cancellation_plan, + fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, + None, + ) .await .expect("configured cancellation replay"); assert_eq!( @@ -3573,7 +3790,7 @@ async fn order_ingest_and_enqueue_wrappers_report_prepare_timestamp_errors() { assert!(matches!( sdk.trades() .ingest_evidence( - OrderEvidenceIngestRequest::new(request_event()).with_observed_at(out_of_range,) + TradeEvidenceIngestRequest::new(request_event()).with_observed_at(out_of_range,) ) .await, Err(RadrootsSdkError::TimestampOutOfRange { .. }) @@ -3581,7 +3798,7 @@ async fn order_ingest_and_enqueue_wrappers_report_prepare_timestamp_errors() { assert!(matches!( sdk.trades() .ingest_request_evidence( - OrderRequestEvidenceIngestRequest::new(request_event()) + TradeRequestEvidenceIngestRequest::new(request_event()) .with_observed_at(out_of_range,), ) .await, @@ -3590,11 +3807,13 @@ async fn order_ingest_and_enqueue_wrappers_report_prepare_timestamp_errors() { assert!(matches!( sdk.trades() .enqueue_submit_with_explicit_signer( - OrderSubmitEnqueueRequest::new( + TradeSubmitEnqueueRequest::new( buyer.clone(), fixture_event_ptr('a'), fixture_order_request("order-wrapper-submit-error"), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .with_created_at(out_of_range), &OrderFixtureSigner::new(BUYER_SECRET_KEY_HEX), @@ -3605,11 +3824,13 @@ async fn order_ingest_and_enqueue_wrappers_report_prepare_timestamp_errors() { assert!(matches!( sdk.trades() .enqueue_decision_with_explicit_signer( - OrderDecisionEnqueueRequest::new( + TradeDecisionEnqueueRequest::new( seller.clone(), fixture_event_ptr('b'), fixture_order_decision("order-wrapper-decision-error"), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .with_created_at(out_of_range), &OrderFixtureSigner::new(SELLER_SECRET_KEY_HEX), @@ -3628,12 +3849,14 @@ async fn order_ingest_and_enqueue_wrappers_report_prepare_timestamp_errors() { assert!(matches!( sdk.trades() .enqueue_revision_proposal_with_explicit_signer( - OrderRevisionProposalEnqueueRequest::new( + TradeRevisionProposalEnqueueRequest::new( seller.clone(), ptr(root_event_id.as_str().to_owned()), ptr(previous_event_id.as_str().to_owned()), proposal_payload.clone(), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .with_created_at(out_of_range), &OrderFixtureSigner::new(SELLER_SECRET_KEY_HEX), @@ -3644,12 +3867,14 @@ async fn order_ingest_and_enqueue_wrappers_report_prepare_timestamp_errors() { assert!(matches!( sdk.trades() .enqueue_revision_decision_with_explicit_signer( - OrderRevisionDecisionEnqueueRequest::new( + TradeRevisionDecisionEnqueueRequest::new( buyer.clone(), ptr(root_event_id.as_str().to_owned()), ptr(previous_event_id.as_str().to_owned()), fixture_revision_decision(&proposal_payload, &previous_event_id), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .with_created_at(out_of_range), &OrderFixtureSigner::new(BUYER_SECRET_KEY_HEX), @@ -3660,12 +3885,14 @@ async fn order_ingest_and_enqueue_wrappers_report_prepare_timestamp_errors() { assert!(matches!( sdk.trades() .enqueue_cancellation_with_explicit_signer( - OrderCancellationEnqueueRequest::new( + TradeCancellationEnqueueRequest::new( buyer, ptr(root_event_id.as_str().to_owned()), ptr(previous_event_id.as_str().to_owned()), fixture_cancellation("order-wrapper-cancellation-error"), fixture_target_relays(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .with_created_at(out_of_range), &OrderFixtureSigner::new(BUYER_SECRET_KEY_HEX), @@ -3691,21 +3918,21 @@ async fn order_default_timestamp_paths_report_clock_errors() { assert!(matches!( clock_error_sdk .trades() - .ingest_evidence(OrderEvidenceIngestRequest::new(request_event())) + .ingest_evidence(TradeEvidenceIngestRequest::new(request_event())) .await, Err(RadrootsSdkError::ClockBeforeUnixEpoch) )); assert!(matches!( clock_error_sdk .trades() - .ingest_request_evidence(OrderRequestEvidenceIngestRequest::new(request_event())) + .ingest_request_evidence(TradeRequestEvidenceIngestRequest::new(request_event())) .await, Err(RadrootsSdkError::ClockBeforeUnixEpoch) )); assert!(matches!( clock_error_sdk .trades() - .prepare_submit(OrderSubmitPrepareRequest::new( + .prepare_submit(TradeSubmitPrepareRequest::new( buyer.clone(), ptr(root_event_id.as_str().to_owned()), order_request_payload(), @@ -3715,7 +3942,7 @@ async fn order_default_timestamp_paths_report_clock_errors() { assert!(matches!( clock_error_sdk .trades() - .prepare_decision(OrderDecisionPrepareRequest::new( + .prepare_decision(TradeDecisionPrepareRequest::new( seller.clone(), ptr(root_event_id.as_str().to_owned()), order_decision_payload(), @@ -3724,7 +3951,7 @@ async fn order_default_timestamp_paths_report_clock_errors() { )); assert!(matches!( clock_error_sdk.trades().prepare_revision_proposal( - OrderRevisionProposalPrepareRequest::new( + TradeRevisionProposalPrepareRequest::new( seller, ptr(root_event_id.as_str().to_owned()), ptr(previous_event_id.as_str().to_owned()), @@ -3735,7 +3962,7 @@ async fn order_default_timestamp_paths_report_clock_errors() { )); assert!(matches!( clock_error_sdk.trades().prepare_revision_decision( - OrderRevisionDecisionPrepareRequest::new( + TradeRevisionDecisionPrepareRequest::new( buyer.clone(), ptr(root_event_id.as_str().to_owned()), ptr(previous_event_id.as_str().to_owned()), @@ -3751,7 +3978,7 @@ async fn order_default_timestamp_paths_report_clock_errors() { assert!(matches!( clock_error_sdk .trades() - .prepare_cancellation(OrderCancellationPrepareRequest::new( + .prepare_cancellation(TradeCancellationPrepareRequest::new( buyer, ptr(root_event_id.as_str().to_owned()), ptr(previous_event_id.as_str().to_owned()), @@ -3776,10 +4003,10 @@ fn order_runtime_request_builders_and_serializers_cover_source_attached_paths() reason: "not workable".to_owned(), }, ); - let policy = SdkRelayTargetPolicy::UseConfiguredRelays; + let policy = RelayResolutionPolicy::ConfiguredRelays; let submit_prepare = - OrderSubmitPrepareRequest::new(buyer_actor(), root_event.clone(), order_request_payload()) + TradeSubmitPrepareRequest::new(buyer_actor(), root_event.clone(), order_request_payload()) .with_created_at(created_at); assert_struct_serialize_error_paths(&submit_prepare, 4); assert_eq!( @@ -3787,11 +4014,13 @@ fn order_runtime_request_builders_and_serializers_cover_source_attached_paths() 1_700_000_321 ); - let submit_enqueue = OrderSubmitEnqueueRequest::new( + let submit_enqueue = TradeSubmitEnqueueRequest::new( buyer_actor(), root_event.clone(), order_request_payload(), policy.clone(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays(["wss://relay-a.radroots.test"], SdkRelayUrlPolicy::Public) .expect("submit relays") @@ -3800,13 +4029,13 @@ fn order_runtime_request_builders_and_serializers_cover_source_attached_paths() assert_struct_serialize_error_paths(&submit_enqueue, 6); let request_ingest = - OrderRequestEvidenceIngestRequest::new(request_event()).with_observed_at(created_at); + TradeRequestEvidenceIngestRequest::new(request_event()).with_observed_at(created_at); assert_struct_serialize_error_paths(&request_ingest, 2); let evidence_ingest = - OrderEvidenceIngestRequest::new(request_event()).with_observed_at(created_at); + TradeEvidenceIngestRequest::new(request_event()).with_observed_at(created_at); assert_struct_serialize_error_paths(&evidence_ingest, 2); - let decision_prepare = OrderDecisionPrepareRequest::new( + let decision_prepare = TradeDecisionPrepareRequest::new( seller_actor(), root_event.clone(), order_decision_payload(), @@ -3814,11 +4043,13 @@ fn order_runtime_request_builders_and_serializers_cover_source_attached_paths() .with_created_at(created_at); assert_struct_serialize_error_paths(&decision_prepare, 4); - let decision_enqueue = OrderDecisionEnqueueRequest::new( + let decision_enqueue = TradeDecisionEnqueueRequest::new( seller_actor(), root_event.clone(), order_decision_payload(), policy.clone(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays(["wss://relay-b.radroots.test"], SdkRelayUrlPolicy::Public) .expect("decision relays") @@ -3826,7 +4057,7 @@ fn order_runtime_request_builders_and_serializers_cover_source_attached_paths() .with_created_at(created_at); assert_struct_serialize_error_paths(&decision_enqueue, 6); - let proposal_prepare = OrderRevisionProposalPrepareRequest::new( + let proposal_prepare = TradeRevisionProposalPrepareRequest::new( seller_actor(), root_event.clone(), previous_event.clone(), @@ -3835,12 +4066,14 @@ fn order_runtime_request_builders_and_serializers_cover_source_attached_paths() .with_created_at(created_at); assert_struct_serialize_error_paths(&proposal_prepare, 5); - let proposal_enqueue = OrderRevisionProposalEnqueueRequest::new( + let proposal_enqueue = TradeRevisionProposalEnqueueRequest::new( seller_actor(), root_event.clone(), previous_event.clone(), proposal.clone(), policy.clone(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays(["wss://relay-c.radroots.test"], SdkRelayUrlPolicy::Public) .expect("proposal relays") @@ -3848,7 +4081,7 @@ fn order_runtime_request_builders_and_serializers_cover_source_attached_paths() .with_created_at(created_at); assert_struct_serialize_error_paths(&proposal_enqueue, 7); - let revision_decision_prepare = OrderRevisionDecisionPrepareRequest::new( + let revision_decision_prepare = TradeRevisionDecisionPrepareRequest::new( buyer_actor(), root_event.clone(), previous_event.clone(), @@ -3857,12 +4090,14 @@ fn order_runtime_request_builders_and_serializers_cover_source_attached_paths() .with_created_at(created_at); assert_struct_serialize_error_paths(&revision_decision_prepare, 5); - let revision_decision_enqueue = OrderRevisionDecisionEnqueueRequest::new( + let revision_decision_enqueue = TradeRevisionDecisionEnqueueRequest::new( buyer_actor(), root_event.clone(), previous_event.clone(), revision_decision, policy.clone(), + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays(["wss://relay-d.radroots.test"], SdkRelayUrlPolicy::Public) .expect("revision decision relays") @@ -3870,7 +4105,7 @@ fn order_runtime_request_builders_and_serializers_cover_source_attached_paths() .with_created_at(created_at); assert_struct_serialize_error_paths(&revision_decision_enqueue, 7); - let cancellation_prepare = OrderCancellationPrepareRequest::new( + let cancellation_prepare = TradeCancellationPrepareRequest::new( buyer_actor(), root_event.clone(), previous_event.clone(), @@ -3879,12 +4114,14 @@ fn order_runtime_request_builders_and_serializers_cover_source_attached_paths() .with_created_at(created_at); assert_struct_serialize_error_paths(&cancellation_prepare, 5); - let cancellation_enqueue = OrderCancellationEnqueueRequest::new( + let cancellation_enqueue = TradeCancellationEnqueueRequest::new( buyer_actor(), root_event, previous_event, cancellation_payload(), policy, + PublishMode::EnqueueOnly, + AckPolicy::NoWait, ) .try_with_target_relays(["wss://relay-e.radroots.test"], SdkRelayUrlPolicy::Public) .expect("cancellation relays") @@ -3892,17 +4129,17 @@ fn order_runtime_request_builders_and_serializers_cover_source_attached_paths() .with_created_at(created_at); assert_struct_serialize_error_paths(&cancellation_enqueue, 7); - let parsed_status = OrderStatusRequest::parse(order_id().as_str()) + let parsed_status = TradeStatusRequest::parse(order_id().as_str()) .expect("status request") - .with_limit(ORDER_STATUS_DEFAULT_LIMIT); + .with_limit(TRADE_STATUS_DEFAULT_LIMIT); parsed_status.validate().expect("status validates"); assert_eq!( serde_json::to_value(&parsed_status).expect("status json")["limit"], - ORDER_STATUS_DEFAULT_LIMIT + TRADE_STATUS_DEFAULT_LIMIT ); let issue = - SdkOrderStatusIssue::single(SdkOrderStatusIssueKind::ForkedLifecycle, event_id('f')); + SdkTradeStatusIssue::single(SdkTradeStatusIssueKind::ForkedLifecycle, event_id('f')); assert_eq!(issue.code(), "forked_lifecycle"); assert_struct_serialize_error_paths(&issue, 3); } @@ -3913,7 +4150,7 @@ async fn closed_event_store_errors_are_mapped_for_ingest_and_prepared_lookup() { sdk._event_store.pool().close().await; let ingest_error = sdk .trades() - .ingest_evidence(OrderEvidenceIngestRequest::new(request_event())) + .ingest_evidence(TradeEvidenceIngestRequest::new(request_event())) .await .expect_err("closed ingest evidence"); assert!(matches!(ingest_error, RadrootsSdkError::EventStore { .. })); @@ -3922,7 +4159,7 @@ async fn closed_event_store_errors_are_mapped_for_ingest_and_prepared_lookup() { sdk._event_store.pool().close().await; let request_ingest_error = sdk .trades() - .ingest_request_evidence(OrderRequestEvidenceIngestRequest::new(request_event())) + .ingest_request_evidence(TradeRequestEvidenceIngestRequest::new(request_event())) .await .expect_err("closed request evidence ingest"); assert!(matches!( @@ -3946,7 +4183,7 @@ async fn order_status_and_evidence_ingest_cover_source_attached_success_paths() let request_event = request_event(); let request_receipt = sdk .trades() - .ingest_request_evidence(OrderRequestEvidenceIngestRequest::new( + .ingest_request_evidence(TradeRequestEvidenceIngestRequest::new( request_event.clone(), )) .await @@ -3957,15 +4194,15 @@ async fn order_status_and_evidence_ingest_cover_source_attached_success_paths() let submit = enqueue_fixture_submit(&sdk, "order-status-source-attached").await; let status = sdk .trades() - .status(OrderStatusRequest::parse(submit.order_id.as_str()).expect("status request")) + .status(TradeStatusRequest::parse(submit.order_id.as_str()).expect("status request")) .await .expect("order status"); - assert_eq!(status.status, OrderStatusKind::Requested); + assert_eq!(status.status, TradeStatusKind::Requested); assert!(status.evidence.has_request); let duplicate_receipt = sdk .trades() - .ingest_evidence(OrderEvidenceIngestRequest::new(request_event)) + .ingest_evidence(TradeEvidenceIngestRequest::new(request_event)) .await .expect("order evidence ingest"); assert!(!duplicate_receipt.inserted); diff --git a/crates/sdk/tests/unit/relay_targets_tests.rs b/crates/sdk/tests/unit/relay_targets_tests.rs @@ -1,4 +1,7 @@ -use super::{SdkRelayTargetPolicy, SdkRelayTargetSet, SdkRelayUrlPolicy}; +use super::{ + AckPolicy, PublishMode, RelayResolutionPolicy, SdkRelayTargetPolicy, SdkRelayTargetSet, + SdkRelayUrlPolicy, +}; use crate::{RadrootsSdkError, SDK_RELAY_TARGET_MAX_COUNT}; #[path = "../support/serializer_failure.rs"] @@ -6,6 +9,43 @@ mod serializer_failure; use serializer_failure::assert_struct_serialize_error_paths; +#[test] +fn publish_mode_and_ack_policy_serialize_explicit_product_contracts() { + assert_eq!( + serde_json::to_value(PublishMode::DryRun).expect("json"), + serde_json::json!("dry_run") + ); + assert_eq!( + serde_json::to_value(PublishMode::EnqueueOnly).expect("json"), + serde_json::json!("enqueue_only") + ); + assert_eq!( + serde_json::to_value(PublishMode::EnqueueAndPublish).expect("json"), + serde_json::json!("enqueue_and_publish") + ); + assert_eq!( + serde_json::to_value(AckPolicy::NoWait).expect("json"), + serde_json::json!("no_wait") + ); + assert_eq!( + serde_json::to_value(AckPolicy::AtLeastOneRelay).expect("json"), + serde_json::json!("at_least_one_relay") + ); + assert_eq!( + serde_json::to_value(AckPolicy::AllRelays).expect("json"), + serde_json::json!("all_relays") + ); + assert_eq!( + serde_json::to_value(AckPolicy::quorum(2).expect("quorum")).expect("json"), + serde_json::json!({ "quorum": { "required": 2 } }) + ); + assert!(matches!( + AckPolicy::quorum(0), + Err(RadrootsSdkError::InvalidRequest { ref message }) + if message == "ack policy quorum must require at least one relay" + )); +} + fn is_local_ws_relay(value: &str) -> bool { let Some(rest) = value.strip_prefix("ws://") else { return false; @@ -34,6 +74,13 @@ fn relay_authority_host(authority: &str) -> Option<String> { #[test] fn use_configured_policy_serializes_as_kind_only() { + let trade_policy = RelayResolutionPolicy::ConfiguredRelays; + assert_eq!( + serde_json::to_value(&trade_policy).expect("json"), + serde_json::json!({ "kind": "configured_relays" }) + ); + assert_struct_serialize_error_paths(&trade_policy, 1); + let policy = SdkRelayTargetPolicy::UseConfiguredRelays; assert_eq!( serde_json::to_value(&policy).expect("json"), @@ -94,6 +141,21 @@ fn target_set_accessors_and_configured_relays_cover_empty_and_dedupe_paths() { .expect("target set"), ) ); + assert_eq!( + serde_json::to_value( + RelayResolutionPolicy::try_explicit( + vec!["wss://relay-c.example.com".to_owned()], + SdkRelayUrlPolicy::Public, + ) + .expect("trade explicit policy") + ) + .expect("trade policy json"), + serde_json::json!({ + "kind": "explicit", + "relays": ["wss://relay-c.example.com"], + "canonical_relays": ["wss://relay-c.example.com"] + }) + ); } #[test]