lib

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

commit f2197ab754d4cd226d5b7e46216e59ac7b888dff
parent 9b40d20d7193adb5a435aab16ea9a3ec81fd869c
Author: triesap <tyson@radroots.org>
Date:   Fri,  8 May 2026 17:11:50 +0000

trade: remove legacy order request model

- remove the legacy `RadrootsTradeOrder` publish payload from events
- route order-request encoding and projection through active order-request events
- require radrootsd SDK order-request calls to include the listing event pointer
- validate events, codec, trade, SDK tests, formatting, scans, and sdk validator

Diffstat:
Mcrates/events/src/trade.rs | 55++++++++++++-------------------------------------------
Mcrates/events_codec/src/trade/decode.rs | 79++++++++++++++++++++++++++++++++++++-------------------------------------------
Mcrates/events_codec/src/trade/encode.rs | 3+++
Mcrates/sdk/src/adapters/radrootsd.rs | 8+++++---
Mcrates/sdk/src/client.rs | 8++++++--
Mcrates/sdk/tests/radrootsd.rs | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mcrates/trade/src/listing/contract.rs | 14+++++++++++---
Mcrates/trade/src/listing/dvm.rs | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mcrates/trade/src/listing/overlay.rs | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mcrates/trade/src/listing/projection.rs | 358++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mcrates/trade/src/order.rs | 99+++++++++----------------------------------------------------------------------
11 files changed, 542 insertions(+), 334 deletions(-)

diff --git a/crates/events/src/trade.rs b/crates/events/src/trade.rs @@ -401,23 +401,6 @@ pub enum RadrootsTradeOrderStatus { #[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug, PartialEq, Eq)] -pub struct RadrootsTradeOrder { - pub order_id: String, - pub listing_addr: String, - pub buyer_pubkey: String, - pub seller_pubkey: String, - pub items: Vec<RadrootsTradeOrderItem>, - #[cfg_attr( - feature = "ts-rs", - ts(optional, type = "RadrootsCoreDiscountValue[] | null") - )] - pub discounts: Option<Vec<RadrootsCoreDiscountValue>>, -} - -#[cfg_attr(feature = "ts-rs", derive(TS))] -#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsTradeOrderRequested { pub order_id: String, pub listing_addr: String, @@ -1087,7 +1070,7 @@ impl RadrootsTradeMessageType { match kind { KIND_TRADE_LISTING_VALIDATE_REQ => Some(Self::ListingValidateRequest), KIND_TRADE_LISTING_VALIDATE_RES => Some(Self::ListingValidateResult), - KIND_TRADE_ORDER_REQUEST => Some(Self::OrderRequest), + KIND_TRADE_ORDER_REQUEST => None, KIND_TRADE_ORDER_RESPONSE => Some(Self::OrderResponse), KIND_TRADE_ORDER_REVISION => Some(Self::OrderRevision), KIND_TRADE_ORDER_REVISION_RESPONSE => None, @@ -1745,7 +1728,7 @@ impl std::error::Error for RadrootsTradeEnvelopeError {} pub enum RadrootsTradeMessagePayload { ListingValidateRequest(RadrootsTradeListingValidateRequest), ListingValidateResult(RadrootsTradeListingValidateResult), - OrderRequest(RadrootsTradeOrder), + TradeOrderRequested(RadrootsTradeOrderRequested), OrderResponse(RadrootsTradeOrderResponse), OrderRevision(RadrootsTradeOrderRevision), OrderRevisionAccept(RadrootsTradeOrderRevisionResponse), @@ -1767,7 +1750,7 @@ impl RadrootsTradeMessagePayload { match self { Self::ListingValidateRequest(_) => RadrootsTradeMessageType::ListingValidateRequest, Self::ListingValidateResult(_) => RadrootsTradeMessageType::ListingValidateResult, - Self::OrderRequest(_) => RadrootsTradeMessageType::OrderRequest, + Self::TradeOrderRequested(_) => RadrootsTradeMessageType::OrderRequest, Self::OrderResponse(_) => RadrootsTradeMessageType::OrderResponse, Self::OrderRevision(_) => RadrootsTradeMessageType::OrderRevision, Self::OrderRevisionAccept(_) => RadrootsTradeMessageType::OrderRevisionAccept, @@ -1810,20 +1793,6 @@ mod tests { ))) } - fn sample_order() -> RadrootsTradeOrder { - RadrootsTradeOrder { - order_id: "order-1".into(), - listing_addr: sample_listing_addr(), - buyer_pubkey: "buyer".into(), - seller_pubkey: "seller".into(), - items: vec![RadrootsTradeOrderItem { - bin_id: "bin-1".into(), - bin_count: 2, - }], - discounts: Some(vec![sample_discount_value()]), - } - } - fn sample_active_order_request() -> RadrootsTradeOrderRequested { RadrootsTradeOrderRequested { order_id: "order-1".into(), @@ -2033,7 +2002,7 @@ mod tests { fn message_type_classifies_request_and_result_kinds() { assert_eq!( RadrootsTradeMessageType::from_kind(KIND_TRADE_LISTING_ORDER_REQ), - Some(RadrootsTradeMessageType::OrderRequest) + None ); assert_eq!( RadrootsTradeMessageType::from_kind(KIND_TRADE_LISTING_ORDER_RES), @@ -2882,7 +2851,7 @@ mod tests { ); assert_eq!( RadrootsTradeMessageType::from_kind(KIND_TRADE_ORDER_REQUEST), - Some(RadrootsTradeMessageType::OrderRequest) + None ); assert_eq!( RadrootsTradeMessageType::from_kind(KIND_TRADE_ORDER_RESPONSE), @@ -2938,10 +2907,10 @@ mod tests { #[test] fn envelope_requires_order_id_for_order_scoped_messages() { let envelope = RadrootsTradeEnvelope::new( - RadrootsTradeMessageType::OrderRequest, + RadrootsTradeMessageType::OrderResponse, sample_listing_addr(), None, - RadrootsTradeMessagePayload::OrderRequest(sample_order()), + RadrootsTradeMessagePayload::OrderResponse(sample_order_response(true)), ); assert_eq!( envelope.validate().unwrap_err(), @@ -2960,20 +2929,20 @@ mod tests { assert_eq!(service_envelope.validate(), Ok(())); let public_envelope = RadrootsTradeEnvelope::new( - RadrootsTradeMessageType::OrderRequest, + RadrootsTradeMessageType::OrderResponse, sample_listing_addr(), Some("order-1".into()), - RadrootsTradeMessagePayload::OrderRequest(sample_order()), + RadrootsTradeMessagePayload::OrderResponse(sample_order_response(true)), ); assert_eq!(public_envelope.validate(), Ok(())); let invalid_version = RadrootsTradeEnvelope { version: RADROOTS_TRADE_ENVELOPE_VERSION + 1, domain: RadrootsTradeDomain::TradeListing, - message_type: RadrootsTradeMessageType::OrderRequest, + message_type: RadrootsTradeMessageType::OrderResponse, order_id: Some("order-1".into()), listing_addr: sample_listing_addr(), - payload: RadrootsTradeMessagePayload::OrderRequest(sample_order()), + payload: RadrootsTradeMessagePayload::OrderResponse(sample_order_response(true)), }; assert_eq!( invalid_version.validate().unwrap_err(), @@ -3038,7 +3007,7 @@ mod tests { RadrootsTradeMessageType::ListingValidateResult, ), ( - RadrootsTradeMessagePayload::OrderRequest(sample_order()), + RadrootsTradeMessagePayload::TradeOrderRequested(sample_active_order_request()), RadrootsTradeMessageType::OrderRequest, ), ( diff --git a/crates/events_codec/src/trade/decode.rs b/crates/events_codec/src/trade/decode.rs @@ -833,31 +833,16 @@ mod tests { RadrootsActiveTradeMessageType, RadrootsActiveTradePayloadError, RadrootsTradeBuyerReceipt, RadrootsTradeEnvelope, RadrootsTradeFulfillmentUpdated, RadrootsTradeInventoryCommitment, RadrootsTradeMessagePayload, - RadrootsTradeMessageType, RadrootsTradeOrder, RadrootsTradeOrderCancelled, - RadrootsTradeOrderDecision, RadrootsTradeOrderDecisionEvent, - RadrootsTradeOrderEconomicItem, RadrootsTradeOrderEconomicLine, - RadrootsTradeOrderEconomics, RadrootsTradeOrderItem, RadrootsTradeOrderRequested, - RadrootsTradeOrderRevisionDecision, RadrootsTradeOrderRevisionDecisionEvent, - RadrootsTradeOrderRevisionProposed, RadrootsTradePaymentMethod, - RadrootsTradePaymentRecorded, RadrootsTradePricingBasis, + RadrootsTradeMessageType, RadrootsTradeOrderCancelled, RadrootsTradeOrderDecision, + RadrootsTradeOrderDecisionEvent, RadrootsTradeOrderEconomicItem, + RadrootsTradeOrderEconomicLine, RadrootsTradeOrderEconomics, RadrootsTradeOrderItem, + RadrootsTradeOrderRequested, RadrootsTradeOrderRevisionDecision, + RadrootsTradeOrderRevisionDecisionEvent, RadrootsTradeOrderRevisionProposed, + RadrootsTradePaymentMethod, RadrootsTradePaymentRecorded, RadrootsTradePricingBasis, RadrootsTradeSettlementDecision, RadrootsTradeSettlementDecisionEvent, }, }; - fn base_order() -> RadrootsTradeOrder { - RadrootsTradeOrder { - order_id: "order-1".into(), - listing_addr: "30402:seller:AAAAAAAAAAAAAAAAAAAAAg".into(), - buyer_pubkey: "buyer".into(), - seller_pubkey: "seller".into(), - items: vec![RadrootsTradeOrderItem { - bin_id: "lb".into(), - bin_count: 3, - }], - discounts: None, - } - } - fn active_order_request() -> RadrootsTradeOrderRequested { RadrootsTradeOrderRequested { order_id: "order-1".into(), @@ -872,6 +857,15 @@ mod tests { } } + fn generic_order_response() -> RadrootsTradeMessagePayload { + RadrootsTradeMessagePayload::OrderResponse( + radroots_events::trade::RadrootsTradeOrderResponse { + accepted: true, + reason: None, + }, + ) + } + fn decimal(raw: &str) -> RadrootsCoreDecimal { raw.parse().unwrap() } @@ -1050,19 +1044,16 @@ mod tests { } #[test] - fn parse_order_request_roundtrip() { - let payload = RadrootsTradeMessagePayload::OrderRequest(base_order()); + fn parse_generic_order_response_roundtrip() { + let payload = generic_order_response(); let built = trade_envelope_event_build( - "seller", - RadrootsTradeMessageType::OrderRequest, + "buyer", + RadrootsTradeMessageType::OrderResponse, "30402:seller:AAAAAAAAAAAAAAAAAAAAAg", Some("order-1".into()), - Some(&RadrootsNostrEventPtr { - id: "listing-snapshot".into(), - relays: None, - }), - None, None, + Some("root"), + Some("prev"), &payload, ) .expect("build trade envelope"); @@ -1079,7 +1070,7 @@ mod tests { trade_envelope_from_event(&event).expect("parse trade envelope"); assert_eq!( envelope.message_type, - RadrootsTradeMessageType::OrderRequest + RadrootsTradeMessageType::OrderResponse ); assert_eq!(envelope.order_id.as_deref(), Some("order-1")); } @@ -1816,18 +1807,15 @@ mod tests { #[test] fn parse_rejects_listing_addr_mismatch() { - let payload = RadrootsTradeMessagePayload::OrderRequest(base_order()); + let payload = generic_order_response(); let built = trade_envelope_event_build( - "seller", - RadrootsTradeMessageType::OrderRequest, + "buyer", + RadrootsTradeMessageType::OrderResponse, "30402:seller:AAAAAAAAAAAAAAAAAAAAAg", Some("order-1".into()), - Some(&RadrootsNostrEventPtr { - id: "listing-snapshot".into(), - relays: None, - }), - None, None, + Some("root"), + Some("prev"), &payload, ) .expect("build trade envelope"); @@ -1849,18 +1837,23 @@ mod tests { #[test] fn parse_rejects_missing_public_snapshot_tag() { - let payload = RadrootsTradeMessagePayload::OrderRequest(base_order()); + let payload = RadrootsTradeMessagePayload::OrderRevision( + radroots_events::trade::RadrootsTradeOrderRevision { + revision_id: "rev-1".into(), + changes: Vec::new(), + }, + ); let built = trade_envelope_event_build( "seller", - RadrootsTradeMessageType::OrderRequest, + RadrootsTradeMessageType::OrderRevision, "30402:seller:AAAAAAAAAAAAAAAAAAAAAg", Some("order-1".into()), Some(&RadrootsNostrEventPtr { id: "listing-snapshot".into(), relays: None, }), - None, - None, + Some("root"), + Some("prev"), &payload, ) .expect("build trade envelope"); diff --git a/crates/events_codec/src/trade/encode.rs b/crates/events_codec/src/trade/encode.rs @@ -125,6 +125,9 @@ pub fn trade_envelope_event_build( prev_event_id: Option<&str>, payload: &RadrootsTradeMessagePayload, ) -> Result<WireEventParts, EventEncodeError> { + if message_type == RadrootsTradeMessageType::OrderRequest { + return Err(EventEncodeError::InvalidField("message_type")); + } if payload.message_type() != message_type { return Err(EventEncodeError::InvalidField("payload")); } diff --git a/crates/sdk/src/adapters/radrootsd.rs b/crates/sdk/src/adapters/radrootsd.rs @@ -187,7 +187,8 @@ impl fmt::Debug for SdkRadrootsdListingPublishRequest { #[derive(Clone, PartialEq, Eq, Serialize)] pub(crate) struct SdkRadrootsdOrderRequestPublishRequest { - pub order: trade::RadrootsTradeOrder, + pub order: trade::RadrootsTradeOrderRequested, + pub listing_event: RadrootsNostrEventPtr, pub signer_session_id: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub signer_authority: Option<SdkRadrootsdSignerAuthority>, @@ -199,6 +200,7 @@ impl fmt::Debug for SdkRadrootsdOrderRequestPublishRequest { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut debug = f.debug_struct("SdkRadrootsdOrderRequestPublishRequest"); debug.field("order", &self.order); + debug.field("listing_event", &self.listing_event); debug.field("signer_session_id", &"<redacted>"); debug.field("signer_authority", &self.signer_authority); debug.field("idempotency_key", &self.idempotency_key); @@ -375,7 +377,7 @@ impl SdkRadrootsdPublicTradePublishRequest { match &self.payload { trade::RadrootsTradeMessagePayload::ListingValidateRequest(_) => None, trade::RadrootsTradeMessagePayload::ListingValidateResult(_) => None, - trade::RadrootsTradeMessagePayload::OrderRequest(_) => None, + trade::RadrootsTradeMessagePayload::TradeOrderRequested(_) => None, trade::RadrootsTradeMessagePayload::OrderResponse(_) => { Some(trade::RadrootsTradeMessageType::OrderResponse) } @@ -1263,7 +1265,7 @@ pub(crate) async fn publish_public_trade( } trade::RadrootsTradeMessagePayload::ListingValidateRequest(_) | trade::RadrootsTradeMessagePayload::ListingValidateResult(_) - | trade::RadrootsTradeMessagePayload::OrderRequest(_) + | trade::RadrootsTradeMessagePayload::TradeOrderRequested(_) | trade::RadrootsTradeMessagePayload::DiscountDecline(_) => { unreachable!("unsupported trade payload should be rejected by the curated client") } diff --git a/crates/sdk/src/client.rs b/crates/sdk/src/client.rs @@ -2363,11 +2363,13 @@ impl<'a> TradeClient<'a> { #[cfg(feature = "radrootsd-client")] pub async fn publish_order_request_via_radrootsd( &self, - order: &trade::RadrootsTradeOrder, + order: &trade::RadrootsTradeOrderRequested, + listing_event: &RadrootsNostrEventPtr, session: &SdkRadrootsdSignerSessionHandle, ) -> Result<SdkPublishReceipt, SdkPublishError> { self.publish_order_request_via_radrootsd_with_options( order, + listing_event, &SdkRadrootsdOrderRequestPublishOptions::from_signer_session(session), ) .await @@ -2376,11 +2378,13 @@ impl<'a> TradeClient<'a> { #[cfg(feature = "radrootsd-client")] pub async fn publish_order_request_via_radrootsd_with_options( &self, - order: &trade::RadrootsTradeOrder, + order: &trade::RadrootsTradeOrderRequested, + listing_event: &RadrootsNostrEventPtr, options: &SdkRadrootsdOrderRequestPublishOptions, ) -> Result<SdkPublishReceipt, SdkPublishError> { let request = radrootsd::SdkRadrootsdOrderRequestPublishRequest { order: order.clone(), + listing_event: listing_event.clone(), signer_session_id: options.session().session_id().to_owned(), signer_authority: options.signer_authority().cloned(), idempotency_key: options.idempotency_key().map(str::to_owned), diff --git a/crates/sdk/tests/radrootsd.rs b/crates/sdk/tests/radrootsd.rs @@ -18,8 +18,9 @@ use radroots_sdk::listing::{ }; use radroots_sdk::trade::{ RadrootsTradeDiscountDecision, RadrootsTradeMessagePayload, RadrootsTradeMessageType, - RadrootsTradeOrder, RadrootsTradeOrderItem, RadrootsTradeOrderResponse, - RadrootsTradeOrderRevision, RadrootsTradeOrderRevisionResponse, + RadrootsTradeOrderEconomicItem, RadrootsTradeOrderEconomicLine, RadrootsTradeOrderEconomics, + RadrootsTradeOrderItem, RadrootsTradeOrderRequested, RadrootsTradeOrderResponse, + RadrootsTradeOrderRevision, RadrootsTradeOrderRevisionResponse, RadrootsTradePricingBasis, }; use radroots_sdk::{ RadrootsNostrEvent, RadrootsNostrEventPtr, RadrootsProfile, RadrootsProfileType, @@ -414,8 +415,44 @@ fn sample_farm() -> RadrootsFarm { } } -fn sample_trade_order() -> RadrootsTradeOrder { - RadrootsTradeOrder { +fn sample_trade_order_economics() -> RadrootsTradeOrderEconomics { + RadrootsTradeOrderEconomics { + quote_id: "quote-1".to_owned(), + quote_version: 1, + pricing_basis: RadrootsTradePricingBasis::ListingEvent, + currency: RadrootsCoreCurrency::USD, + items: vec![RadrootsTradeOrderEconomicItem { + bin_id: "bin-1".to_owned(), + bin_count: 2, + quantity_amount: RadrootsCoreDecimal::from(1u32), + quantity_unit: RadrootsCoreUnit::MassG, + unit_price_amount: RadrootsCoreDecimal::from(20u32), + unit_price_currency: RadrootsCoreCurrency::USD, + line_subtotal: RadrootsCoreMoney::new( + RadrootsCoreDecimal::from(40u32), + RadrootsCoreCurrency::USD, + ), + }], + discounts: Vec::<RadrootsTradeOrderEconomicLine>::new(), + adjustments: Vec::<RadrootsTradeOrderEconomicLine>::new(), + subtotal: RadrootsCoreMoney::new( + RadrootsCoreDecimal::from(40u32), + RadrootsCoreCurrency::USD, + ), + discount_total: RadrootsCoreMoney::new( + RadrootsCoreDecimal::from(0u32), + RadrootsCoreCurrency::USD, + ), + adjustment_total: RadrootsCoreMoney::new( + RadrootsCoreDecimal::from(0u32), + RadrootsCoreCurrency::USD, + ), + total: RadrootsCoreMoney::new(RadrootsCoreDecimal::from(40u32), RadrootsCoreCurrency::USD), + } +} + +fn sample_trade_order() -> RadrootsTradeOrderRequested { + RadrootsTradeOrderRequested { order_id: "order-1".to_owned(), listing_addr: format!("{KIND_LISTING}:seller:AAAAAAAAAAAAAAAAAAAAAg"), buyer_pubkey: "buyer".to_owned(), @@ -424,7 +461,7 @@ fn sample_trade_order() -> RadrootsTradeOrder { bin_id: "bin-1".to_owned(), bin_count: 2, }], - discounts: Some(Vec::new()), + economics: sample_trade_order_economics(), } } @@ -1597,7 +1634,11 @@ async fn radrootsd_trade_order_request_publish_accepts_session_handle() -> TestR let receipt = client .trade() - .publish_order_request_via_radrootsd_with_options(&sample_trade_order(), &options) + .publish_order_request_via_radrootsd_with_options( + &sample_trade_order(), + &listing_event_ptr_with_relays(Some("wss://radroots.org")), + &options, + ) .await?; let request_json = request_rx.await?; @@ -1609,6 +1650,14 @@ async fn radrootsd_trade_order_request_publish_accepts_session_handle() -> TestR assert_eq!(request_json["params"]["idempotency_key"], "idem-order-1"); assert_eq!(request_json["params"]["order"]["order_id"], "order-1"); assert_eq!( + request_json["params"]["listing_event"]["id"], + "listing-event-1" + ); + assert_eq!( + request_json["params"]["listing_event"]["relays"], + "wss://radroots.org" + ); + assert_eq!( request_json["params"]["signer_authority"]["provider_runtime_id"], "runtime-1" ); @@ -1695,7 +1744,7 @@ fn public_trade_request_validation_rejects_order_request_payload() { sample_trade_order().listing_addr.clone(), "order-1", "buyer", - RadrootsTradeMessagePayload::OrderRequest(sample_trade_order()), + RadrootsTradeMessagePayload::TradeOrderRequested(sample_trade_order()), ); let error = request @@ -1905,7 +1954,11 @@ async fn radrootsd_sdk_workflow_chains_session_listing_trade_and_bridge_job() -> let trade_receipt = client .trade() - .publish_order_request_via_radrootsd(&sample_trade_order(), &handle) + .publish_order_request_via_radrootsd( + &sample_trade_order(), + &listing_event_ptr_with_relays(Some("wss://radroots.org")), + &handle, + ) .await?; let trade_request = request_rx.recv().await.expect("trade publish request"); assert_eq!(trade_request["method"], "bridge.order.request"); @@ -1914,6 +1967,10 @@ async fn radrootsd_sdk_workflow_chains_session_listing_trade_and_bridge_job() -> "session-workflow-1" ); assert_eq!(trade_request["params"]["order"]["order_id"], "order-1"); + assert_eq!( + trade_request["params"]["listing_event"]["id"], + "listing-event-1" + ); let trade_job = match &trade_receipt.transport_receipt { SdkTransportReceipt::Radrootsd(receipt) => receipt.job(), diff --git a/crates/trade/src/listing/contract.rs b/crates/trade/src/listing/contract.rs @@ -21,6 +21,9 @@ pub(crate) use radroots_events::{ RadrootsTradeDiscountDecision as TradeDiscountDecision, RadrootsTradeDiscountOffer as TradeDiscountOffer, RadrootsTradeDiscountRequest as TradeDiscountRequest, + RadrootsTradeEconomicActor as TradeEconomicActor, + RadrootsTradeEconomicEffect as TradeEconomicEffect, + RadrootsTradeEconomicLineKind as TradeEconomicLineKind, RadrootsTradeEnvelope as TradeListingEnvelope, RadrootsTradeEnvelopeError as TradeListingEnvelopeError, RadrootsTradeFulfillmentStatus as TradeFulfillmentStatus, @@ -31,12 +34,17 @@ pub(crate) use radroots_events::{ RadrootsTradeListingValidateResult as TradeListingValidateResult, RadrootsTradeListingValidationError as TradeListingValidationError, RadrootsTradeMessagePayload as TradeListingMessagePayload, - RadrootsTradeMessageType as TradeListingMessageType, RadrootsTradeOrder as TradeOrder, - RadrootsTradeOrderChange as TradeOrderChange, RadrootsTradeOrderItem as TradeOrderItem, + RadrootsTradeMessageType as TradeListingMessageType, + RadrootsTradeOrderChange as TradeOrderChange, + RadrootsTradeOrderEconomicItem as TradeOrderEconomicItem, + RadrootsTradeOrderEconomicLine as TradeOrderEconomicLine, + RadrootsTradeOrderEconomics as TradeOrderEconomics, + RadrootsTradeOrderItem as TradeOrderItem, RadrootsTradeOrderRequested as TradeOrder, RadrootsTradeOrderResponse as TradeOrderResponse, RadrootsTradeOrderRevision as TradeOrderRevision, RadrootsTradeOrderRevisionResponse as TradeOrderRevisionResponse, - RadrootsTradeOrderStatus as TradeOrderStatus, RadrootsTradeQuestion as TradeQuestion, + RadrootsTradeOrderStatus as TradeOrderStatus, + RadrootsTradePricingBasis as TradePricingBasis, RadrootsTradeQuestion as TradeQuestion, RadrootsTradeReceipt as TradeReceipt, }, }; diff --git a/crates/trade/src/listing/dvm.rs b/crates/trade/src/listing/dvm.rs @@ -636,7 +636,7 @@ pub struct TradeListingCancel { pub enum TradeListingMessagePayload { ListingValidateRequest(TradeListingValidateRequest), ListingValidateResult(TradeListingValidateResult), - OrderRequest(TradeOrder), + TradeOrderRequested(TradeOrder), OrderResponse(TradeOrderResponse), OrderRevision(TradeOrderRevision), OrderRevisionAccept(TradeOrderRevisionResponse), @@ -661,7 +661,9 @@ impl TradeListingMessagePayload { TradeListingMessagePayload::ListingValidateResult(_) => { TradeListingMessageType::ListingValidateResult } - TradeListingMessagePayload::OrderRequest(_) => TradeListingMessageType::OrderRequest, + TradeListingMessagePayload::TradeOrderRequested(_) => { + TradeListingMessageType::OrderRequest + } TradeListingMessagePayload::OrderResponse(_) => TradeListingMessageType::OrderResponse, TradeListingMessagePayload::OrderRevision(_) => TradeListingMessageType::OrderRevision, TradeListingMessagePayload::OrderRevisionAccept(_) => { @@ -706,7 +708,12 @@ mod tests { use radroots_events_codec::error::EventEncodeError; #[cfg(feature = "serde_json")] - use crate::listing::order::{TradeOrder, TradeOrderItem}; + use crate::listing::order::{ + TradeOrder, TradeOrderEconomicItem, TradeOrderEconomicLine, TradeOrderEconomics, + TradeOrderItem, TradePricingBasis, + }; + #[cfg(feature = "serde_json")] + use radroots_core::{RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreUnit}; #[test] fn envelope_requires_listing_addr() { @@ -935,17 +942,65 @@ mod tests { } #[cfg(feature = "serde_json")] + fn order_economics(items: &[TradeOrderItem]) -> TradeOrderEconomics { + let economic_items = items + .iter() + .map(|item| { + let line_subtotal = + RadrootsCoreDecimal::from(item.bin_count) * RadrootsCoreDecimal::from(5u32); + TradeOrderEconomicItem { + bin_id: item.bin_id.clone(), + bin_count: item.bin_count, + quantity_amount: RadrootsCoreDecimal::from(1u32), + quantity_unit: RadrootsCoreUnit::Each, + unit_price_amount: RadrootsCoreDecimal::from(5u32), + unit_price_currency: RadrootsCoreCurrency::USD, + line_subtotal: RadrootsCoreMoney::new(line_subtotal, RadrootsCoreCurrency::USD), + } + }) + .collect::<Vec<_>>(); + let subtotal = items + .iter() + .fold(RadrootsCoreDecimal::from(0u32), |total, item| { + total + + (RadrootsCoreDecimal::from(item.bin_count) + * RadrootsCoreDecimal::from(5u32)) + }); + + TradeOrderEconomics { + quote_id: "quote-1".into(), + quote_version: 1, + pricing_basis: TradePricingBasis::ListingEvent, + currency: RadrootsCoreCurrency::USD, + items: economic_items, + discounts: Vec::<TradeOrderEconomicLine>::new(), + adjustments: Vec::<TradeOrderEconomicLine>::new(), + subtotal: RadrootsCoreMoney::new(subtotal, RadrootsCoreCurrency::USD), + discount_total: RadrootsCoreMoney::new( + RadrootsCoreDecimal::from(0u32), + RadrootsCoreCurrency::USD, + ), + adjustment_total: RadrootsCoreMoney::new( + RadrootsCoreDecimal::from(0u32), + RadrootsCoreCurrency::USD, + ), + total: RadrootsCoreMoney::new(subtotal, RadrootsCoreCurrency::USD), + } + } + + #[cfg(feature = "serde_json")] fn base_order() -> TradeOrder { + let items = vec![TradeOrderItem { + bin_id: "bin-1".into(), + bin_count: 2, + }]; TradeOrder { order_id: "order-1".into(), listing_addr: format!("{KIND_LISTING}:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg"), buyer_pubkey: "buyer-pubkey".into(), seller_pubkey: "seller-pubkey".into(), - items: vec![TradeOrderItem { - bin_id: "bin-1".into(), - bin_count: 2, - }], - discounts: None, + economics: order_economics(&items), + items, } } @@ -996,7 +1051,7 @@ mod tests { #[test] fn envelope_event_build_includes_order_and_snapshot_tags() { let listing_addr = format!("{KIND_LISTING}:pubkey:AAAAAAAAAAAAAAAAAAAAAg"); - let payload = TradeListingMessagePayload::OrderRequest(base_order()); + let payload = TradeListingMessagePayload::TradeOrderRequested(base_order()); let built = super::trade_listing_envelope_event_build( "pubkey", TradeListingMessageType::OrderRequest, @@ -1054,7 +1109,7 @@ mod tests { #[test] fn envelope_event_build_requires_snapshot_for_order_request() { let listing_addr = format!("{KIND_LISTING}:pubkey:AAAAAAAAAAAAAAAAAAAAAg"); - let payload = TradeListingMessagePayload::OrderRequest(base_order()); + let payload = TradeListingMessagePayload::TradeOrderRequested(base_order()); let err = super::trade_listing_envelope_event_build( "pubkey", TradeListingMessageType::OrderRequest, @@ -1097,7 +1152,7 @@ mod tests { #[cfg(feature = "serde_json")] #[test] fn envelope_from_event_parses_canonical_order_request() { - let payload = TradeListingMessagePayload::OrderRequest(base_order()); + let payload = TradeListingMessagePayload::TradeOrderRequested(base_order()); let event = base_event( "buyer-pubkey", "seller-pubkey", @@ -1117,7 +1172,7 @@ mod tests { #[cfg(feature = "serde_json")] #[test] fn envelope_from_event_rejects_kind_mismatch() { - let payload = TradeListingMessagePayload::OrderRequest(base_order()); + let payload = TradeListingMessagePayload::TradeOrderRequested(base_order()); let mut event = base_event( "buyer-pubkey", "seller-pubkey", @@ -1142,7 +1197,7 @@ mod tests { #[cfg(feature = "serde_json")] #[test] fn envelope_from_event_rejects_listing_addr_tag_mismatch() { - let payload = TradeListingMessagePayload::OrderRequest(base_order()); + let payload = TradeListingMessagePayload::TradeOrderRequested(base_order()); let mut event = base_event( "buyer-pubkey", "seller-pubkey", @@ -1161,7 +1216,7 @@ mod tests { #[cfg(feature = "serde_json")] #[test] fn envelope_from_event_rejects_order_id_tag_mismatch() { - let payload = TradeListingMessagePayload::OrderRequest(base_order()); + let payload = TradeListingMessagePayload::TradeOrderRequested(base_order()); let mut event = base_event( "buyer-pubkey", "seller-pubkey", diff --git a/crates/trade/src/listing/overlay.rs b/crates/trade/src/listing/overlay.rs @@ -513,8 +513,9 @@ mod tests { }; use crate::listing::{ order::{ - TradeFulfillmentStatus, TradeFulfillmentUpdate, TradeOrder, TradeOrderItem, - TradeOrderStatus, TradeReceipt, + TradeEconomicActor, TradeEconomicEffect, TradeEconomicLineKind, TradeFulfillmentStatus, + TradeFulfillmentUpdate, TradeOrder, TradeOrderEconomicItem, TradeOrderEconomicLine, + TradeOrderEconomics, TradeOrderItem, TradeOrderStatus, TradePricingBasis, TradeReceipt, }, projection::{ RadrootsTradeListingSort, RadrootsTradeListingSortField, RadrootsTradeOrderQuery, @@ -523,8 +524,8 @@ mod tests { }, }; use radroots_core::{ - RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCorePercent, - RadrootsCoreQuantity, RadrootsCoreQuantityPrice, RadrootsCoreUnit, + RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreQuantity, + RadrootsCoreQuantityPrice, RadrootsCoreUnit, }; use radroots_events::RadrootsNostrEventPtr; use radroots_events::farm::RadrootsFarmRef; @@ -589,7 +590,7 @@ mod tests { None, None, ), - (TradeListingMessagePayload::OrderRequest(order), Some(order_id)) => { + (TradeListingMessagePayload::TradeOrderRequested(order), Some(order_id)) => { let event_id = format!("{order_id}:request"); TEST_WORKFLOW_CHAINS.with(|chains| { chains.borrow_mut().insert( @@ -775,33 +776,90 @@ mod tests { } } + fn order_economics(items: &[TradeOrderItem], include_discount: bool) -> TradeOrderEconomics { + let mut subtotal = RadrootsCoreDecimal::from(0u32); + let economic_items = items + .iter() + .map(|item| { + let line_subtotal = + RadrootsCoreDecimal::from(item.bin_count) * RadrootsCoreDecimal::from(5u32); + subtotal = subtotal + line_subtotal; + TradeOrderEconomicItem { + bin_id: item.bin_id.clone(), + bin_count: item.bin_count, + quantity_amount: RadrootsCoreDecimal::from(1u32), + quantity_unit: RadrootsCoreUnit::Each, + unit_price_amount: RadrootsCoreDecimal::from(5u32), + unit_price_currency: RadrootsCoreCurrency::USD, + line_subtotal: RadrootsCoreMoney::new(line_subtotal, RadrootsCoreCurrency::USD), + } + }) + .collect::<Vec<_>>(); + let discounts = include_discount + .then(|| { + vec![TradeOrderEconomicLine { + id: "discount-1".into(), + kind: TradeEconomicLineKind::ListingDiscount, + actor: TradeEconomicActor::Seller, + effect: TradeEconomicEffect::Decrease, + amount: RadrootsCoreMoney::new( + RadrootsCoreDecimal::from(1u32), + RadrootsCoreCurrency::USD, + ), + reason: "listing discount".into(), + }] + }) + .unwrap_or_default(); + let discount_total = if include_discount { + RadrootsCoreDecimal::from(1u32) + } else { + RadrootsCoreDecimal::from(0u32) + }; + TradeOrderEconomics { + quote_id: "quote-1".into(), + quote_version: 1, + pricing_basis: TradePricingBasis::ListingEvent, + currency: RadrootsCoreCurrency::USD, + items: economic_items, + discounts, + adjustments: Vec::new(), + subtotal: RadrootsCoreMoney::new(subtotal, RadrootsCoreCurrency::USD), + discount_total: RadrootsCoreMoney::new(discount_total, RadrootsCoreCurrency::USD), + adjustment_total: RadrootsCoreMoney::new( + RadrootsCoreDecimal::from(0u32), + RadrootsCoreCurrency::USD, + ), + total: RadrootsCoreMoney::new(subtotal - discount_total, RadrootsCoreCurrency::USD), + } + } + fn base_order() -> TradeOrder { + let items = vec![TradeOrderItem { + bin_id: "bin-1".into(), + bin_count: 2, + }]; TradeOrder { order_id: "order-1".into(), listing_addr: "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg".into(), buyer_pubkey: "buyer-pubkey".into(), seller_pubkey: "seller-pubkey".into(), - items: vec![TradeOrderItem { - bin_id: "bin-1".into(), - bin_count: 2, - }], - discounts: Some(vec![radroots_core::RadrootsCoreDiscountValue::Percent( - RadrootsCorePercent::new(RadrootsCoreDecimal::from(10u32)), - )]), + economics: order_economics(&items, true), + items, } } fn alternate_order() -> TradeOrder { + let items = vec![TradeOrderItem { + bin_id: "bin-1".into(), + bin_count: 3, + }]; TradeOrder { order_id: "order-2".into(), listing_addr: "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAw".into(), buyer_pubkey: "buyer-pubkey-2".into(), seller_pubkey: "seller-pubkey".into(), - items: vec![TradeOrderItem { - bin_id: "bin-1".into(), - bin_count: 3, - }], - discounts: None, + economics: order_economics(&items, false), + items, } } @@ -1043,7 +1101,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-1"), - TradeListingMessagePayload::OrderRequest(base_order()), + TradeListingMessagePayload::TradeOrderRequested(base_order()), )) .expect("first order"); index @@ -1051,7 +1109,7 @@ mod tests { "buyer-pubkey-2", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAw", Some("order-2"), - TradeListingMessagePayload::OrderRequest(alternate_order()), + TradeListingMessagePayload::TradeOrderRequested(alternate_order()), )) .expect("second order"); index diff --git a/crates/trade/src/listing/projection.rs b/crates/trade/src/listing/projection.rs @@ -11,7 +11,7 @@ use radroots_core::{RadrootsCoreDecimal, RadrootsCoreDiscount, RadrootsCoreDisco use radroots_events::{ RadrootsNostrEvent, RadrootsNostrEventPtr, farm::RadrootsFarmRef, - kinds::{KIND_LISTING, is_listing_kind}, + kinds::{KIND_LISTING, KIND_TRADE_ORDER_REQUEST, is_listing_kind}, listing::{ RadrootsListing, RadrootsListingAvailability, RadrootsListingBin, RadrootsListingDeliveryMethod, RadrootsListingImage, RadrootsListingLocation, @@ -31,12 +31,16 @@ use crate::listing::{ }, model::RadrootsTradeListingTotal, order::{ - TradeFulfillmentStatus, TradeOrder, TradeOrderChange, TradeOrderItem, TradeOrderStatus, + TradeFulfillmentStatus, TradeOrder, TradeOrderChange, TradeOrderEconomicLine, + TradeOrderItem, TradeOrderStatus, }, price_ext::BinPricingExt, }; #[cfg(feature = "serde_json")] -use radroots_events_codec::trade::trade_event_context_from_tags; +use radroots_events_codec::trade::{ + RadrootsActiveTradeEnvelopeParseError, active_trade_order_request_from_event, + trade_event_context_from_tags, +}; #[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] @@ -116,9 +120,9 @@ pub struct RadrootsTradeOrderWorkflowProjection { pub items: Vec<TradeOrderItem>, #[cfg_attr( feature = "ts-rs", - ts(optional, type = "RadrootsCoreDiscountValue[] | null") + ts(optional, type = "RadrootsTradeOrderEconomicLine[] | null") )] - pub requested_discounts: Option<Vec<RadrootsCoreDiscountValue>>, + pub requested_discounts: Option<Vec<TradeOrderEconomicLine>>, pub status: TradeOrderStatus, #[cfg_attr(feature = "ts-rs", ts(optional, type = "RadrootsNostrEventPtr | null"))] pub listing_snapshot: Option<RadrootsNostrEventPtr>, @@ -678,7 +682,8 @@ impl RadrootsTradeOrderWorkflowProjection { buyer_pubkey: order.buyer_pubkey.clone(), seller_pubkey: order.seller_pubkey.clone(), items: order.items.clone(), - requested_discounts: order.discounts.clone(), + requested_discounts: (!order.economics.discounts.is_empty()) + .then(|| order.economics.discounts.clone()), status: TradeOrderStatus::Requested, listing_snapshot: Some(listing_snapshot), root_event_id: message.event_id.clone(), @@ -710,6 +715,24 @@ impl RadrootsTradeOrderWorkflowProjection { impl RadrootsTradeOrderWorkflowMessage { #[cfg(feature = "serde_json")] pub fn from_event(event: &RadrootsNostrEvent) -> Result<Self, TradeListingEnvelopeParseError> { + if event.kind == KIND_TRADE_ORDER_REQUEST { + let envelope = active_trade_order_request_from_event(event) + .map_err(map_active_order_request_parse_error)?; + let context = + trade_event_context_from_tags(TradeListingMessageType::OrderRequest, &event.tags)?; + return Ok(Self { + event_id: event.id.clone(), + actor_pubkey: event.author.clone(), + counterparty_pubkey: context.counterparty_pubkey, + listing_addr: envelope.listing_addr, + order_id: Some(envelope.order_id), + listing_event: context.listing_event, + root_event_id: context.root_event_id, + prev_event_id: context.prev_event_id, + payload: TradeListingMessagePayload::TradeOrderRequested(envelope.payload), + }); + } + let envelope = trade_listing_envelope_from_event::<TradeListingMessagePayload>(event)?; trade_event_context_from_tags(envelope.message_type, &event.tags).map(|context| Self { event_id: event.id.clone(), @@ -732,7 +755,9 @@ impl RadrootsTradeOrderWorkflowMessage { TradeListingMessagePayload::ListingValidateResult(_) => { TradeListingMessageType::ListingValidateResult } - TradeListingMessagePayload::OrderRequest(_) => TradeListingMessageType::OrderRequest, + TradeListingMessagePayload::TradeOrderRequested(_) => { + TradeListingMessageType::OrderRequest + } TradeListingMessagePayload::OrderResponse(_) => TradeListingMessageType::OrderResponse, TradeListingMessagePayload::OrderRevision(_) => TradeListingMessageType::OrderRevision, TradeListingMessagePayload::OrderRevisionAccept(_) => { @@ -762,6 +787,41 @@ impl RadrootsTradeOrderWorkflowMessage { } } +#[cfg(feature = "serde_json")] +fn map_active_order_request_parse_error( + error: RadrootsActiveTradeEnvelopeParseError, +) -> TradeListingEnvelopeParseError { + match error { + RadrootsActiveTradeEnvelopeParseError::InvalidKind(kind) => { + TradeListingEnvelopeParseError::InvalidKind(kind) + } + RadrootsActiveTradeEnvelopeParseError::MissingTag(tag) => { + TradeListingEnvelopeParseError::MissingTag(tag) + } + RadrootsActiveTradeEnvelopeParseError::InvalidTag(tag) => { + TradeListingEnvelopeParseError::InvalidTag(tag) + } + RadrootsActiveTradeEnvelopeParseError::ListingAddrTagMismatch => { + TradeListingEnvelopeParseError::ListingAddrTagMismatch + } + RadrootsActiveTradeEnvelopeParseError::OrderIdTagMismatch => { + TradeListingEnvelopeParseError::OrderIdTagMismatch + } + RadrootsActiveTradeEnvelopeParseError::InvalidListingAddr(error) => { + TradeListingEnvelopeParseError::InvalidListingAddr(error) + } + RadrootsActiveTradeEnvelopeParseError::InvalidJson + | RadrootsActiveTradeEnvelopeParseError::InvalidEnvelope(_) + | RadrootsActiveTradeEnvelopeParseError::InvalidPayload(_) + | RadrootsActiveTradeEnvelopeParseError::MessageTypeKindMismatch { .. } + | RadrootsActiveTradeEnvelopeParseError::PayloadBindingMismatch(_) + | RadrootsActiveTradeEnvelopeParseError::AuthorMismatch + | RadrootsActiveTradeEnvelopeParseError::CounterpartyTagMismatch => { + TradeListingEnvelopeParseError::InvalidJson + } + } +} + impl RadrootsTradeReadIndex { pub fn new() -> Self { Self::default() @@ -956,7 +1016,7 @@ impl RadrootsTradeReadIndex { | TradeListingMessagePayload::ListingValidateResult(_) => Err( RadrootsTradeProjectionError::NonOrderWorkflowMessage(message.message_type()), ), - TradeListingMessagePayload::OrderRequest(order) => { + TradeListingMessagePayload::TradeOrderRequested(order) => { self.apply_order_request(message, order) } TradeListingMessagePayload::OrderResponse(response) => { @@ -1762,8 +1822,10 @@ mod tests { }, order::{ TradeAnswer, TradeDiscountDecision, TradeDiscountOffer, TradeDiscountRequest, - TradeFulfillmentStatus, TradeFulfillmentUpdate, TradeOrder, TradeOrderChange, - TradeOrderItem, TradeOrderRevision, TradeOrderStatus, TradeQuestion, TradeReceipt, + TradeEconomicActor, TradeEconomicEffect, TradeEconomicLineKind, TradeFulfillmentStatus, + TradeFulfillmentUpdate, TradeOrder, TradeOrderChange, TradeOrderEconomicItem, + TradeOrderEconomicLine, TradeOrderEconomics, TradeOrderItem, TradeOrderRevision, + TradeOrderStatus, TradePricingBasis, TradeQuestion, TradeReceipt, }, }; use radroots_core::{ @@ -1777,6 +1839,7 @@ mod tests { RadrootsListingStatus, }; use radroots_events::{RadrootsNostrEvent, RadrootsNostrEventPtr, kinds::KIND_LISTING}; + use radroots_events_codec::trade::active_trade_order_request_event_build; #[derive(Clone, Debug)] struct TestWorkflowChain { @@ -1833,7 +1896,7 @@ mod tests { None, None, ), - (TradeListingMessagePayload::OrderRequest(order), Some(order_id)) => { + (TradeListingMessagePayload::TradeOrderRequested(order), Some(order_id)) => { let event_id = format!("{order_id}:request"); TEST_WORKFLOW_CHAINS.with(|chains| { chains.borrow_mut().insert( @@ -1978,19 +2041,75 @@ mod tests { } } + fn order_economics(items: &[TradeOrderItem], include_discount: bool) -> TradeOrderEconomics { + let mut subtotal = RadrootsCoreDecimal::from(0u32); + let economic_items = items + .iter() + .map(|item| { + let line_subtotal = + RadrootsCoreDecimal::from(item.bin_count) * RadrootsCoreDecimal::from(5u32); + subtotal = subtotal + line_subtotal; + TradeOrderEconomicItem { + bin_id: item.bin_id.clone(), + bin_count: item.bin_count, + quantity_amount: RadrootsCoreDecimal::from(1u32), + quantity_unit: RadrootsCoreUnit::Each, + unit_price_amount: RadrootsCoreDecimal::from(5u32), + unit_price_currency: RadrootsCoreCurrency::USD, + line_subtotal: RadrootsCoreMoney::new(line_subtotal, RadrootsCoreCurrency::USD), + } + }) + .collect::<Vec<_>>(); + let discounts = include_discount + .then(|| { + vec![TradeOrderEconomicLine { + id: "discount-1".into(), + kind: TradeEconomicLineKind::ListingDiscount, + actor: TradeEconomicActor::Seller, + effect: TradeEconomicEffect::Decrease, + amount: RadrootsCoreMoney::new( + RadrootsCoreDecimal::from(1u32), + RadrootsCoreCurrency::USD, + ), + reason: "listing discount".into(), + }] + }) + .unwrap_or_default(); + let discount_total = if include_discount { + RadrootsCoreDecimal::from(1u32) + } else { + RadrootsCoreDecimal::from(0u32) + }; + TradeOrderEconomics { + quote_id: "quote-1".into(), + quote_version: 1, + pricing_basis: TradePricingBasis::ListingEvent, + currency: RadrootsCoreCurrency::USD, + items: economic_items, + discounts, + adjustments: Vec::new(), + subtotal: RadrootsCoreMoney::new(subtotal, RadrootsCoreCurrency::USD), + discount_total: RadrootsCoreMoney::new(discount_total, RadrootsCoreCurrency::USD), + adjustment_total: RadrootsCoreMoney::new( + RadrootsCoreDecimal::from(0u32), + RadrootsCoreCurrency::USD, + ), + total: RadrootsCoreMoney::new(subtotal - discount_total, RadrootsCoreCurrency::USD), + } + } + fn base_order() -> TradeOrder { + let items = vec![TradeOrderItem { + bin_id: "bin-1".into(), + bin_count: 2, + }]; TradeOrder { order_id: "order-1".into(), listing_addr: "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg".into(), buyer_pubkey: "buyer-pubkey".into(), seller_pubkey: "seller-pubkey".into(), - items: vec![TradeOrderItem { - bin_id: "bin-1".into(), - bin_count: 2, - }], - discounts: Some(vec![radroots_core::RadrootsCoreDiscountValue::Percent( - RadrootsCorePercent::new(RadrootsCoreDecimal::from(10u32)), - )]), + economics: order_economics(&items, true), + items, } } @@ -2061,22 +2180,23 @@ mod tests { } fn alternate_order() -> TradeOrder { + let items = vec![ + TradeOrderItem { + bin_id: "bin-1".into(), + bin_count: 3, + }, + TradeOrderItem { + bin_id: "bin-2".into(), + bin_count: 1, + }, + ]; TradeOrder { order_id: "order-2".into(), listing_addr: "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAw".into(), buyer_pubkey: "buyer-pubkey-2".into(), seller_pubkey: "seller-pubkey".into(), - items: vec![ - TradeOrderItem { - bin_id: "bin-1".into(), - bin_count: 3, - }, - TradeOrderItem { - bin_id: "bin-2".into(), - bin_count: 1, - }, - ], - discounts: None, + economics: order_economics(&items, false), + items, } } @@ -2123,17 +2243,30 @@ mod tests { ) -> RadrootsNostrEvent { let (_, _, listing_event, root_event_id, prev_event_id) = workflow_refs(actor_pubkey, listing_addr, order_id, payload); - let built = trade_listing_envelope_event_build( - recipient_pubkey, - message_type, - listing_addr.to_string(), - order_id.map(str::to_string), - listing_event.as_ref(), - root_event_id.as_deref(), - prev_event_id.as_deref(), - payload, - ) - .expect("trade workflow event"); + let built = if message_type == crate::listing::dvm::TradeListingMessageType::OrderRequest { + let TradeListingMessagePayload::TradeOrderRequested(order) = payload else { + panic!("order-request workflow event requires active order payload") + }; + active_trade_order_request_event_build( + listing_event + .as_ref() + .expect("order-request workflow event requires listing snapshot"), + order, + ) + .expect("trade workflow event") + } else { + trade_listing_envelope_event_build( + recipient_pubkey, + message_type, + listing_addr.to_string(), + order_id.map(str::to_string), + listing_event.as_ref(), + root_event_id.as_deref(), + prev_event_id.as_deref(), + payload, + ) + .expect("trade workflow event") + }; RadrootsNostrEvent { id: "workflow-event-id".into(), author: actor_pubkey.into(), @@ -2200,7 +2333,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-1"), - TradeListingMessagePayload::OrderRequest(base_order()), + TradeListingMessagePayload::TradeOrderRequested(base_order()), )) .expect("order request"); assert_eq!(index.listings().len(), 1); @@ -2481,7 +2614,7 @@ mod tests { crate::listing::dvm::TradeListingMessageType::OrderRequest, "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-1"), - &TradeListingMessagePayload::OrderRequest(base_order()), + &TradeListingMessagePayload::TradeOrderRequested(base_order()), ); let order = index @@ -2506,7 +2639,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-1"), - TradeListingMessagePayload::OrderRequest(base_order()), + TradeListingMessagePayload::TradeOrderRequested(base_order()), )) .expect("order request"); let listing_after_request = index @@ -2576,7 +2709,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-1"), - TradeListingMessagePayload::OrderRequest(base_order()), + TradeListingMessagePayload::TradeOrderRequested(base_order()), )) .expect("order request"); index @@ -2625,7 +2758,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-1"), - TradeListingMessagePayload::OrderRequest(base_order()), + TradeListingMessagePayload::TradeOrderRequested(base_order()), )) .expect("order request"); index @@ -2679,7 +2812,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-1"), - TradeListingMessagePayload::OrderRequest(base_order()), + TradeListingMessagePayload::TradeOrderRequested(base_order()), )) .expect("order request"); @@ -2745,7 +2878,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-1"), - TradeListingMessagePayload::OrderRequest(base_order()), + TradeListingMessagePayload::TradeOrderRequested(base_order()), )) .expect("order request"); @@ -2815,7 +2948,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-1"), - TradeListingMessagePayload::OrderRequest(base_order()), + TradeListingMessagePayload::TradeOrderRequested(base_order()), )) .expect("order request"); @@ -2880,7 +3013,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-1"), - TradeListingMessagePayload::OrderRequest(base_order()), + TradeListingMessagePayload::TradeOrderRequested(base_order()), )) .expect("order request"); assert_eq!( @@ -2889,6 +3022,10 @@ mod tests { "canonical helper should still create a requested order" ); + let missing_snapshot_items = vec![TradeOrderItem { + bin_id: "bin-1".into(), + bin_count: 1, + }]; let err = index .apply_workflow_message(&RadrootsTradeOrderWorkflowMessage { event_id: "missing-snapshot".into(), @@ -2899,16 +3036,13 @@ mod tests { listing_event: None, root_event_id: None, prev_event_id: None, - payload: TradeListingMessagePayload::OrderRequest(TradeOrder { + payload: TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-2".into(), listing_addr: "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg".into(), buyer_pubkey: "buyer-pubkey".into(), seller_pubkey: "seller-pubkey".into(), - items: vec![TradeOrderItem { - bin_id: "bin-1".into(), - bin_count: 1, - }], - discounts: None, + economics: order_economics(&missing_snapshot_items, false), + items: missing_snapshot_items, }), }) .expect_err("order request without snapshot should fail"); @@ -2924,7 +3058,7 @@ mod tests { crate::listing::dvm::TradeListingMessageType::OrderRequest, "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-1"), - &TradeListingMessagePayload::OrderRequest(base_order()), + &TradeListingMessagePayload::TradeOrderRequested(base_order()), ); event.tags[1][1] = "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAw".into(); @@ -2954,7 +3088,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-1"), - TradeListingMessagePayload::OrderRequest(base_order()), + TradeListingMessagePayload::TradeOrderRequested(base_order()), )) .expect("open order"); index @@ -2962,7 +3096,7 @@ mod tests { "buyer-pubkey-2", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAw", Some("order-2"), - TradeListingMessagePayload::OrderRequest(alternate_order()), + TradeListingMessagePayload::TradeOrderRequested(alternate_order()), )) .expect("second order request"); index @@ -3149,7 +3283,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-1"), - TradeListingMessagePayload::OrderRequest(base_order()), + TradeListingMessagePayload::TradeOrderRequested(base_order()), ); let order_a = RadrootsTradeOrderWorkflowProjection::from_order_request( &request_message, @@ -3250,7 +3384,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("message-type-order-request"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "message-type-order-request".into(), ..base_order() }), @@ -3470,7 +3604,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-1"), - TradeListingMessagePayload::OrderRequest(base_order()), + TradeListingMessagePayload::TradeOrderRequested(base_order()), )) .expect("first order"); index @@ -3478,7 +3612,7 @@ mod tests { "buyer-pubkey-2", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAw", Some("order-2"), - TradeListingMessagePayload::OrderRequest(alternate_order()), + TradeListingMessagePayload::TradeOrderRequested(alternate_order()), )) .expect("second order"); index @@ -3587,7 +3721,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-1"), - TradeListingMessagePayload::OrderRequest(base_order()), + TradeListingMessagePayload::TradeOrderRequested(base_order()), )) .expect("order request"); @@ -3668,7 +3802,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-1"), - TradeListingMessagePayload::OrderRequest(base_order()), + TradeListingMessagePayload::TradeOrderRequested(base_order()), )) .expect("decline order request"); let declined = decline_index @@ -3697,7 +3831,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-2"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-2".into(), ..base_order() }), @@ -3724,7 +3858,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-2"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-2".into(), ..base_order() }), @@ -3751,7 +3885,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-2"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-2".into(), ..base_order() }), @@ -3777,7 +3911,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-2"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-2".into(), ..base_order() }), @@ -3807,7 +3941,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("wrong-order-id"), - TradeListingMessagePayload::OrderRequest(mismatched_order.clone()), + TradeListingMessagePayload::TradeOrderRequested(mismatched_order.clone()), )) .expect_err("order id mismatch"), RadrootsTradeProjectionError::OrderIdMismatch @@ -3819,7 +3953,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-3"), - TradeListingMessagePayload::OrderRequest(mismatched_order), + TradeListingMessagePayload::TradeOrderRequested(mismatched_order), )) .expect_err("listing addr mismatch"), RadrootsTradeProjectionError::ListingAddrMismatch @@ -3836,7 +3970,7 @@ mod tests { "buyer-pubkey-2", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-1"), - TradeListingMessagePayload::OrderRequest(duplicate_order), + TradeListingMessagePayload::TradeOrderRequested(duplicate_order), )) .expect_err("duplicate order identity mismatch"), RadrootsTradeProjectionError::ListingAddrMismatch @@ -3858,7 +3992,9 @@ mod tests { )), root_event_id: None, prev_event_id: None, - payload: TradeListingMessagePayload::OrderRequest(duplicate_listing_mismatch_order), + payload: TradeListingMessagePayload::TradeOrderRequested( + duplicate_listing_mismatch_order, + ), }; assert_eq!( index @@ -3872,7 +4008,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-1"), - TradeListingMessagePayload::OrderRequest(base_order()), + TradeListingMessagePayload::TradeOrderRequested(base_order()), )) .expect("duplicate same order"); assert_eq!(duplicate_same.order_id, "order-1"); @@ -3888,7 +4024,7 @@ mod tests { )), root_event_id: None, prev_event_id: None, - payload: TradeListingMessagePayload::OrderRequest(TradeOrder { + payload: TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-1".into(), seller_pubkey: "other-seller".into(), ..base_order() @@ -3907,7 +4043,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-4"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-4".into(), ..base_order() }), @@ -3933,7 +4069,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-4"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-4".into(), ..base_order() }), @@ -3968,7 +4104,7 @@ mod tests { "intruder", listing_addr, Some("order-request-actor"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-request-actor".into(), ..base_order() }), @@ -3981,7 +4117,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-request-counterparty"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-request-counterparty".into(), ..base_order() }), @@ -4005,7 +4141,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-helper"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-helper".into(), ..base_order() }), @@ -4563,7 +4699,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-response-actor"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-response-actor".into(), ..base_order() }), @@ -4590,7 +4726,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-revision-actor"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-revision-actor".into(), ..base_order() }), @@ -4655,7 +4791,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-revision-accept-actor"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-revision-accept-actor".into(), ..base_order() }), @@ -4682,7 +4818,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-revision-decline-actor"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-revision-decline-actor".into(), ..base_order() }), @@ -4709,7 +4845,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-answer-actor"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-answer-actor".into(), ..base_order() }), @@ -4745,7 +4881,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-discount-request-actor"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-discount-request-actor".into(), ..base_order() }), @@ -4800,7 +4936,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-discount-offer-actor"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-discount-offer-actor".into(), ..base_order() }), @@ -4855,7 +4991,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-discount-accept-actor"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-discount-accept-actor".into(), ..base_order() }), @@ -4883,7 +5019,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-discount-decline-actor"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-discount-decline-actor".into(), ..base_order() }), @@ -4909,7 +5045,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-fulfillment-actor"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-fulfillment-actor".into(), ..base_order() }), @@ -4935,7 +5071,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-receipt-actor"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-receipt-actor".into(), ..base_order() }), @@ -4967,7 +5103,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-response-transition"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-response-transition".into(), ..base_order() }), @@ -5002,7 +5138,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-revision-transition"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-revision-transition".into(), ..base_order() }), @@ -5040,7 +5176,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-revision-accept-transition"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-revision-accept-transition".into(), ..base_order() }), @@ -5075,7 +5211,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-revision-decline-transition"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-revision-decline-transition".into(), ..base_order() }), @@ -5110,7 +5246,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-question-transition"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-question-transition".into(), ..base_order() }), @@ -5144,7 +5280,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-answer-transition"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-answer-transition".into(), ..base_order() }), @@ -5178,7 +5314,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-discount-offer-transition"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-discount-offer-transition".into(), ..base_order() }), @@ -5215,7 +5351,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-discount-accept-transition"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-discount-accept-transition".into(), ..base_order() }), @@ -5251,7 +5387,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-discount-decline-transition"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-discount-decline-transition".into(), ..base_order() }), @@ -5285,7 +5421,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-cancel-transition"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-cancel-transition".into(), ..base_order() }), @@ -5319,7 +5455,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-fulfillment-transition"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-fulfillment-transition".into(), ..base_order() }), @@ -5348,7 +5484,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-receipt-transition"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-receipt-transition".into(), ..base_order() }), @@ -5387,7 +5523,7 @@ mod tests { "buyer-pubkey", listing_addr, Some("order-revision-invalid-change"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-revision-invalid-change".into(), ..base_order() }), @@ -5424,7 +5560,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-question"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-question".into(), ..base_order() }), @@ -5462,7 +5598,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-revision-accept"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-revision-accept".into(), ..base_order() }), @@ -5510,7 +5646,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-revision-decline"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-revision-decline".into(), ..base_order() }), @@ -5553,7 +5689,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-discount"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-discount".into(), ..base_order() }), @@ -5609,7 +5745,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-discount-decline"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-discount-decline".into(), ..base_order() }), @@ -5650,7 +5786,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-seller-cancel"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-seller-cancel".into(), ..base_order() }), @@ -5677,7 +5813,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-preparing"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-preparing".into(), ..base_order() }), @@ -5715,7 +5851,7 @@ mod tests { "buyer-pubkey", "30402:seller-pubkey:AAAAAAAAAAAAAAAAAAAAAg", Some("order-receipt"), - TradeListingMessagePayload::OrderRequest(TradeOrder { + TradeListingMessagePayload::TradeOrderRequested(TradeOrder { order_id: "order-receipt".into(), ..base_order() }), diff --git a/crates/trade/src/order.rs b/crates/trade/src/order.rs @@ -10,10 +10,9 @@ use radroots_core::{RadrootsCoreCurrency, RadrootsCoreDecimal}; use radroots_events::kinds::KIND_LISTING; use radroots_events::trade::{ RadrootsActiveTradeFulfillmentState, RadrootsTradeBuyerReceipt, - RadrootsTradeFulfillmentUpdated, RadrootsTradeInventoryCommitment, - RadrootsTradeOrder as TradeOrder, RadrootsTradeOrderCancelled, RadrootsTradeOrderDecision, - RadrootsTradeOrderDecisionEvent, RadrootsTradeOrderEconomics, RadrootsTradeOrderItem, - RadrootsTradeOrderRequested, RadrootsTradeOrderRevisionDecision, + RadrootsTradeFulfillmentUpdated, RadrootsTradeInventoryCommitment, RadrootsTradeOrderCancelled, + RadrootsTradeOrderDecision, RadrootsTradeOrderDecisionEvent, RadrootsTradeOrderEconomics, + RadrootsTradeOrderItem, RadrootsTradeOrderRequested, RadrootsTradeOrderRevisionDecision, RadrootsTradeOrderRevisionDecisionEvent, RadrootsTradeOrderRevisionProposed, RadrootsTradePaymentMethod, RadrootsTradePaymentRecorded, RadrootsTradeSettlementDecision, RadrootsTradeSettlementDecisionEvent, @@ -844,58 +843,6 @@ where } } -pub fn canonicalize_order_request_for_signer( - mut order: TradeOrder, - signer_pubkey: &str, -) -> Result<TradeOrder, RadrootsTradeOrderCanonicalizationError> { - let order_id = normalized_required_string(core::mem::take(&mut order.order_id), "order_id")?; - let listing_addr_raw = - normalized_required_string(core::mem::take(&mut order.listing_addr), "listing_addr")?; - let listing_addr = TradeListingAddress::parse(&listing_addr_raw).map_err(|error| { - RadrootsTradeOrderCanonicalizationError::InvalidListingAddress(error.to_string()) - })?; - if u32::from(listing_addr.kind) != KIND_LISTING { - return Err(RadrootsTradeOrderCanonicalizationError::InvalidListingKind); - } - - let buyer_pubkey = if order.buyer_pubkey.trim().is_empty() { - signer_pubkey.to_string() - } else { - normalized_required_string(core::mem::take(&mut order.buyer_pubkey), "buyer_pubkey")? - }; - if buyer_pubkey != signer_pubkey { - return Err(RadrootsTradeOrderCanonicalizationError::InvalidBuyerSigner); - } - - let seller_pubkey = if order.seller_pubkey.trim().is_empty() { - listing_addr.seller_pubkey.clone() - } else { - normalized_required_string(core::mem::take(&mut order.seller_pubkey), "seller_pubkey")? - }; - if seller_pubkey != listing_addr.seller_pubkey { - return Err(RadrootsTradeOrderCanonicalizationError::InvalidSellerListing); - } - - if order.items.is_empty() { - return Err(RadrootsTradeOrderCanonicalizationError::MissingItems); - } - for (index, item) in order.items.iter_mut().enumerate() { - item.bin_id = normalized_required_string(item.bin_id.clone(), "bin_id")?; - if item.bin_count == 0 { - return Err(RadrootsTradeOrderCanonicalizationError::InvalidBinCount { index }); - } - } - - order.order_id = order_id; - order.listing_addr = listing_addr.as_str(); - order.buyer_pubkey = buyer_pubkey; - order.seller_pubkey = seller_pubkey; - if order.discounts.as_ref().is_some_and(Vec::is_empty) { - order.discounts = None; - } - Ok(order) -} - pub fn canonicalize_active_order_request_for_signer( mut request: RadrootsTradeOrderRequested, signer_pubkey: &str, @@ -3683,13 +3630,13 @@ mod tests { use radroots_events::trade::{ RadrootsActiveTradeFulfillmentState, RadrootsTradeBuyerReceipt, RadrootsTradeFulfillmentUpdated, RadrootsTradeInventoryCommitment, - RadrootsTradeOrder as TradeOrder, RadrootsTradeOrderCancelled, RadrootsTradeOrderDecision, - RadrootsTradeOrderDecisionEvent, RadrootsTradeOrderEconomicItem, - RadrootsTradeOrderEconomicLine, RadrootsTradeOrderEconomics, RadrootsTradeOrderItem, - RadrootsTradeOrderRequested, RadrootsTradeOrderRevisionDecision, - RadrootsTradeOrderRevisionDecisionEvent, RadrootsTradeOrderRevisionProposed, - RadrootsTradePaymentMethod, RadrootsTradePaymentRecorded, RadrootsTradePricingBasis, - RadrootsTradeSettlementDecision, RadrootsTradeSettlementDecisionEvent, + RadrootsTradeOrderCancelled, RadrootsTradeOrderDecision, RadrootsTradeOrderDecisionEvent, + RadrootsTradeOrderEconomicItem, RadrootsTradeOrderEconomicLine, + RadrootsTradeOrderEconomics, RadrootsTradeOrderItem, RadrootsTradeOrderRequested, + RadrootsTradeOrderRevisionDecision, RadrootsTradeOrderRevisionDecisionEvent, + RadrootsTradeOrderRevisionProposed, RadrootsTradePaymentMethod, + RadrootsTradePaymentRecorded, RadrootsTradePricingBasis, RadrootsTradeSettlementDecision, + RadrootsTradeSettlementDecisionEvent, }; use super::{ @@ -3705,8 +3652,7 @@ mod tests { RadrootsListingInventoryBinAvailability, RadrootsListingInventoryOrderReservation, RadrootsTradeOrderCanonicalizationError, add_inventory_reservation, canonicalize_active_order_decision_for_signer, - canonicalize_active_order_request_for_signer, canonicalize_order_request_for_signer, - radroots_trade_order_economics_digest, + canonicalize_active_order_request_for_signer, radroots_trade_order_economics_digest, reduce_active_order_events as reduce_active_order_events_with_revisions, reduce_listing_inventory_accounting as reduce_listing_inventory_accounting_with_revisions, }; @@ -3714,20 +3660,6 @@ mod tests { const SELLER: &str = "1111111111111111111111111111111111111111111111111111111111111111"; const BUYER: &str = "2222222222222222222222222222222222222222222222222222222222222222"; - fn base_order(buyer_pubkey: &str, seller_pubkey: &str) -> TradeOrder { - TradeOrder { - order_id: "order-1".to_string(), - listing_addr: format!("{KIND_LISTING}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg"), - buyer_pubkey: buyer_pubkey.to_string(), - seller_pubkey: seller_pubkey.to_string(), - items: vec![RadrootsTradeOrderItem { - bin_id: "bin-1".to_string(), - bin_count: 1, - }], - discounts: None, - } - } - fn active_request(buyer_pubkey: &str, seller_pubkey: &str) -> RadrootsTradeOrderRequested { RadrootsTradeOrderRequested { order_id: " order-1 ".to_string(), @@ -4147,15 +4079,6 @@ mod tests { } #[test] - fn canonicalize_order_request_sets_missing_pubkeys() { - let order = canonicalize_order_request_for_signer(base_order("", ""), SELLER) - .expect("canonical order"); - - assert_eq!(order.buyer_pubkey, SELLER); - assert_eq!(order.seller_pubkey, SELLER); - } - - #[test] fn canonicalize_active_order_request_sets_authority_and_trims_items() { let request = canonicalize_active_order_request_for_signer(active_request("", ""), BUYER).unwrap();