cli

Command-line interface for Radroots
git clone https://radroots.dev/git/cli.git
Log | Files | Refs | README | LICENSE

commit e24f234fe34026b157d0ba9d24d03febea7110f8
parent 44ab15546bca8034df7f70408d0b977479dbb203
Author: triesap <tyson@radroots.org>
Date:   Fri, 26 Jun 2026 13:15:35 +0000

Refactor CLI to trade product surface

Replace the active order command namespace with trade, route workflow status through SDK evidence ingestion, add private farm location set/get/clear handling with shared GeoNames cache wiring, and update CLI unit/target coverage for the new surface and removed command behavior.

Diffstat:
MCargo.lock | 2++
Msrc/cli/farm.rs | 20++++++++++++++++----
Msrc/cli/global.rs | 41+++++++++++++++++++++++++++--------------
Msrc/cli/input.rs | 85++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Msrc/cli/mod.rs | 150++++++++++++++++++++++++++++++++++++++++---------------------------------------
Dsrc/cli/order.rs | 146-------------------------------------------------------------------------------
Asrc/cli/trade.rs | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/cli/validation.rs | 4++--
Msrc/main.rs | 70++++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/ops/exec/basket.rs | 34++++++++++++++++++----------------
Msrc/ops/exec/core.rs | 4++++
Msrc/ops/exec/farm.rs | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/ops/exec/listing.rs | 2++
Msrc/ops/exec/market.rs | 14++++++++------
Msrc/ops/exec/mod.rs | 4++--
Dsrc/ops/exec/order.rs | 1890-------------------------------------------------------------------------------
Msrc/ops/exec/runtime.rs | 2++
Asrc/ops/exec/trade.rs | 1890+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/ops/exec/validation.rs | 2+-
Msrc/ops/mod.rs | 42+++++++++++++++++++++---------------------
Msrc/ops/target.rs | 34++++++++++++++++++----------------
Msrc/out/envelope.rs | 19++++++++++---------
Msrc/registry/farm.rs | 46+++++++++++++++++++++++++++++++++++++++-------
Msrc/registry/mod.rs | 204+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Dsrc/registry/order.rs | 241-------------------------------------------------------------------------------
Asrc/registry/trade.rs | 241+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/runtime/config.rs | 1+
Msrc/runtime/farm.rs | 208+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/runtime/farm_config.rs | 1+
Msrc/runtime/local_events.rs | 1+
Msrc/runtime/order.rs | 1856++++++++++++++++++-------------------------------------------------------------
Msrc/runtime/order/sdk_status.rs | 6+++---
Msrc/runtime/paths.rs | 2++
Msrc/runtime/provider.rs | 1+
Msrc/runtime/sdk.rs | 22++++++++++++++--------
Msrc/runtime/signer.rs | 40++++++++++++++++++++--------------------
Msrc/runtime/sync.rs | 2++
Msrc/view/runtime.rs | 115++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mtests/signer_runtime_modes.rs | 54+++++++++++++++++++++++++++---------------------------
Mtests/support/mod.rs | 10+++++-----
Mtests/target_cli.rs | 601+++++++++++++++++++++++++++++++++++++++----------------------------------------
41 files changed, 3892 insertions(+), 4454 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -3944,6 +3944,7 @@ dependencies = [ "radroots_publish_proxy_protocol", "radroots_relay_transport", "radroots_runtime_paths", + "radroots_sp1_guest_trade", "radroots_trade", "reqwest", "serde", @@ -4012,6 +4013,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "sqlx", "thiserror 1.0.69", ] diff --git a/src/cli/farm.rs b/src/cli/farm.rs @@ -80,15 +80,27 @@ pub struct FarmLocationArgs { #[derive(Debug, Clone, Subcommand)] pub enum FarmLocationCommand { - Update(FarmLocationUpdateArgs), + Set(FarmLocationSetArgs), + Get(FarmLocationKeyArgs), + Clear(FarmLocationKeyArgs), } #[derive(Debug, Clone, Args)] -pub struct FarmLocationUpdateArgs { +pub struct FarmLocationSetArgs { #[arg(long)] - pub field: Option<String>, + pub lat: Option<f64>, #[arg(long)] - pub value: Option<String>, + pub lng: Option<f64>, + #[arg(long = "farm-d-tag")] + pub farm_d_tag: Option<String>, + #[arg(long = "lookup", default_value = "geonames")] + pub lookup: String, +} + +#[derive(Debug, Clone, Args)] +pub struct FarmLocationKeyArgs { + #[arg(long = "farm-d-tag")] + pub farm_d_tag: Option<String>, } #[derive(Debug, Clone, Args)] diff --git a/src/cli/global.rs b/src/cli/global.rs @@ -132,6 +132,19 @@ pub struct FarmUpdateArgs { pub value: Vec<String>, } +#[derive(Debug, Clone)] +pub struct FarmPrivateLocationSetArgs { + pub farm_d_tag: Option<String>, + pub latitude: f64, + pub longitude: f64, + pub lookup: String, +} + +#[derive(Debug, Clone, Default)] +pub struct FarmPrivateLocationKeyArgs { + pub farm_d_tag: Option<String>, +} + #[derive(Debug, Clone, Default)] pub struct FarmPublishArgs { pub scope: Option<FarmScopeArg>, @@ -208,30 +221,30 @@ pub struct OrderDraftAdjustmentArgs { } #[derive(Debug, Clone)] -pub struct OrderSubmitArgs { +pub struct TradeSubmitArgs { pub key: String, pub idempotency_key: Option<String>, } #[derive(Debug, Clone)] -pub struct OrderAppRecordExportArgs { +pub struct TradeAppRecordExportArgs { pub record_id: String, pub output: Option<PathBuf>, } #[derive(Debug, Clone)] -pub struct OrderRebindArgs { +pub struct TradeRebindArgs { pub key: String, pub selector: String, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum OrderDecisionArg { +pub enum TradeDecisionArg { Accept, Decline, } -impl OrderDecisionArg { +impl TradeDecisionArg { pub fn as_str(self) -> &'static str { match self { Self::Accept => "accepted", @@ -248,22 +261,22 @@ impl OrderDecisionArg { } #[derive(Debug, Clone)] -pub struct OrderDecisionArgs { +pub struct TradeDecisionArgs { pub key: String, - pub decision: OrderDecisionArg, + pub decision: TradeDecisionArg, pub reason: Option<String>, pub idempotency_key: Option<String>, } #[derive(Debug, Clone)] -pub struct OrderCancelArgs { +pub struct TradeCancelArgs { pub key: String, pub reason: String, pub idempotency_key: Option<String>, } #[derive(Debug, Clone)] -pub struct OrderRevisionProposeArgs { +pub struct TradeRevisionProposeArgs { pub key: String, pub reason: String, pub bin_id: Option<String>, @@ -277,12 +290,12 @@ pub struct OrderRevisionProposeArgs { } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum OrderRevisionDecisionArg { +pub enum TradeRevisionDecisionArg { Accept, Decline, } -impl OrderRevisionDecisionArg { +impl TradeRevisionDecisionArg { pub fn as_str(self) -> &'static str { match self { Self::Accept => "accepted", @@ -299,16 +312,16 @@ impl OrderRevisionDecisionArg { } #[derive(Debug, Clone)] -pub struct OrderRevisionDecisionArgs { +pub struct TradeRevisionDecisionArgs { pub key: String, pub revision_id: String, - pub decision: OrderRevisionDecisionArg, + pub decision: TradeRevisionDecisionArg, pub reason: Option<String>, pub idempotency_key: Option<String>, } #[derive(Debug, Clone)] -pub struct OrderStatusArgs { +pub struct TradeStatusArgs { pub key: String, } diff --git a/src/cli/input.rs b/src/cli/input.rs @@ -46,9 +46,9 @@ pub fn target_operation_input(command: &TargetCommand) -> OperationData { AccountCommand, AccountSelectionCommand, BasketAdjustmentCommand, BasketCommand, BasketItemCommand, BasketQuoteCommand, FarmCommand, FarmFulfillmentCommand, FarmLocationCommand, FarmProfileCommand, ListingAppCommand, ListingCommand, MarketCommand, - MarketListingCommand, MarketProductCommand, OrderAppCommand, OrderCommand, - OrderEventCommand, OrderRevisionCommand, OrderStatusCommand, StoreBackupCommand, - StoreCommand, ValidationCommand, ValidationReceiptCommand, + MarketListingCommand, MarketProductCommand, StoreBackupCommand, StoreCommand, + TradeAppCommand, TradeCommand, TradeEventCommand, TradeRevisionCommand, TradeStatusCommand, + ValidationCommand, ValidationReceiptCommand, }; let mut input = OperationData::new(); @@ -103,9 +103,18 @@ pub fn target_operation_input(command: &TargetCommand) -> OperationData { } }, FarmCommand::Location(args) => match &args.command { - FarmLocationCommand::Update(args) => { - insert_string(&mut input, "field", &args.field); - insert_string(&mut input, "value", &args.value); + FarmLocationCommand::Set(args) => { + if let Some(latitude) = args.lat { + insert_number(&mut input, "latitude", latitude); + } + if let Some(longitude) = args.lng { + insert_number(&mut input, "longitude", longitude); + } + insert_string(&mut input, "farm_d_tag", &args.farm_d_tag); + input.insert("lookup".to_owned(), Value::String(args.lookup.clone())); + } + FarmLocationCommand::Get(args) | FarmLocationCommand::Clear(args) => { + insert_string(&mut input, "farm_d_tag", &args.farm_d_tag); } }, FarmCommand::Fulfillment(args) => match &args.command { @@ -227,34 +236,34 @@ pub fn target_operation_input(command: &TargetCommand) -> OperationData { }, BasketCommand::List => {} }, - TargetCommand::Order(args) => match &args.command { - OrderCommand::Submit(args) => { - insert_string(&mut input, "order_id", &args.order_id); + TargetCommand::Trade(args) => match &args.command { + TradeCommand::Submit(args) => { + insert_string(&mut input, "trade_id", &args.trade_id); } - OrderCommand::Get(args) => insert_string(&mut input, "order_id", &args.order_id), - OrderCommand::App(args) => match &args.command { - OrderAppCommand::Export(args) => { + TradeCommand::Get(args) => insert_string(&mut input, "trade_id", &args.trade_id), + TradeCommand::App(args) => match &args.command { + TradeAppCommand::Export(args) => { insert_string(&mut input, "record_id", &args.record_id); insert_path(&mut input, "output", &args.output); } - OrderAppCommand::List => {} + TradeAppCommand::List => {} }, - OrderCommand::Rebind(args) => { - insert_string(&mut input, "order_id", &args.order_id); + TradeCommand::Rebind(args) => { + insert_string(&mut input, "trade_id", &args.trade_id); insert_string(&mut input, "selector", &args.selector); } - OrderCommand::Accept(args) => insert_string(&mut input, "order_id", &args.order_id), - OrderCommand::Decline(args) => { - insert_string(&mut input, "order_id", &args.order_id); + TradeCommand::Accept(args) => insert_string(&mut input, "trade_id", &args.trade_id), + TradeCommand::Decline(args) => { + insert_string(&mut input, "trade_id", &args.trade_id); insert_string(&mut input, "reason", &args.reason); } - OrderCommand::Cancel(args) => { - insert_string(&mut input, "order_id", &args.order_id); + TradeCommand::Cancel(args) => { + insert_string(&mut input, "trade_id", &args.trade_id); insert_string(&mut input, "reason", &args.reason); } - OrderCommand::Revision(revision) => match &revision.command { - OrderRevisionCommand::Propose(args) => { - insert_string(&mut input, "order_id", &args.order_id); + TradeCommand::Revision(revision) => match &revision.command { + TradeRevisionCommand::Propose(args) => { + insert_string(&mut input, "trade_id", &args.trade_id); insert_string(&mut input, "reason", &args.reason); insert_string(&mut input, "bin_id", &args.bin_id); if let Some(bin_count) = args.bin_count { @@ -269,27 +278,27 @@ pub fn target_operation_input(command: &TargetCommand) -> OperationData { insert_string(&mut input, "adjustment_currency", &args.adjustment_currency); insert_string(&mut input, "adjustment_reason", &args.adjustment_reason); } - OrderRevisionCommand::Accept(args) => { - insert_string(&mut input, "order_id", &args.order_id); + TradeRevisionCommand::Accept(args) => { + insert_string(&mut input, "trade_id", &args.trade_id); insert_string(&mut input, "revision_id", &args.revision_id); } - OrderRevisionCommand::Decline(args) => { - insert_string(&mut input, "order_id", &args.order_id); + TradeRevisionCommand::Decline(args) => { + insert_string(&mut input, "trade_id", &args.trade_id); insert_string(&mut input, "revision_id", &args.revision_id); insert_string(&mut input, "reason", &args.reason); } }, - OrderCommand::Status(status) => match &status.command { - OrderStatusCommand::Get(args) => { - insert_string(&mut input, "order_id", &args.order_id) + TradeCommand::Status(status) => match &status.command { + TradeStatusCommand::Get(args) => { + insert_string(&mut input, "trade_id", &args.trade_id) } }, - OrderCommand::Event(event) => match &event.command { - OrderEventCommand::List(args) | OrderEventCommand::Watch(args) => { - insert_string(&mut input, "order_id", &args.order_id) + TradeCommand::Event(event) => match &event.command { + TradeEventCommand::List(args) | TradeEventCommand::Watch(args) => { + insert_string(&mut input, "trade_id", &args.trade_id) } }, - OrderCommand::List => {} + TradeCommand::List => {} }, TargetCommand::Validation(args) => match &args.command { ValidationCommand::Receipt(receipt) => match &receipt.command { @@ -297,7 +306,7 @@ pub fn target_operation_input(command: &TargetCommand) -> OperationData { insert_string(&mut input, "receipt_event_id", &args.receipt_event_id); } ValidationReceiptCommand::List(args) => { - insert_string(&mut input, "order_id", &args.order_id); + insert_string(&mut input, "trade_id", &args.trade_id); } }, }, @@ -329,6 +338,12 @@ fn insert_string_array(input: &mut OperationData, key: &str, values: &[String]) } } +fn insert_number(input: &mut OperationData, key: &str, value: f64) { + if let Some(number) = serde_json::Number::from_f64(value) { + input.insert(key.to_owned(), Value::Number(number)); + } +} + fn insert_path(input: &mut OperationData, key: &str, value: &Option<std::path::PathBuf>) { if let Some(value) = value { input.insert( diff --git a/src/cli/mod.rs b/src/cli/mod.rs @@ -10,11 +10,11 @@ pub mod health; pub mod input; pub mod listing; pub mod market; -pub mod order; pub mod relay; pub mod signer; pub mod store; pub mod sync; +pub mod trade; pub mod validation; pub mod workspace; @@ -25,11 +25,11 @@ pub use farm::*; pub use health::*; pub use listing::*; pub use market::*; -pub use order::*; pub use relay::*; pub use signer::*; pub use store::*; pub use sync::*; +pub use trade::*; pub use validation::*; pub use workspace::*; @@ -130,10 +130,10 @@ pub enum TargetCommand { Listing(ListingArgs), #[command(about = "Refresh and query market data from the local replica.")] Market(MarketArgs), - #[command(about = "Prepare baskets and quotes before order coordination.")] + #[command(about = "Prepare baskets and quotes before trade coordination.")] Basket(BasketArgs), - #[command(about = "Coordinate buyer and farmer order agreement events.")] - Order(OrderArgs), + #[command(about = "Coordinate buyer and farmer trade agreement events.")] + Trade(TradeArgs), #[command(about = "Inspect validation receipts and proof state.")] Validation(ValidationArgs), } @@ -204,7 +204,9 @@ impl TargetCommand { FarmProfileCommand::Update(_) => "farm.profile.update", }, FarmCommand::Location(location) => match location.command { - FarmLocationCommand::Update(_) => "farm.location.update", + FarmLocationCommand::Set(_) => "farm.location.set", + FarmLocationCommand::Get(_) => "farm.location.get", + FarmLocationCommand::Clear(_) => "farm.location.clear", }, FarmCommand::Fulfillment(fulfillment) => match fulfillment.command { FarmFulfillmentCommand::Update(_) => "farm.fulfillment.update", @@ -255,29 +257,29 @@ impl TargetCommand { BasketQuoteCommand::Create(_) => "basket.quote.create", }, }, - Self::Order(args) => match &args.command { - OrderCommand::Submit(_) => "order.submit", - OrderCommand::Get(_) => "order.get", - OrderCommand::List => "order.list", - OrderCommand::App(app) => match &app.command { - OrderAppCommand::List => "order.app.list", - OrderAppCommand::Export(_) => "order.app.export", + Self::Trade(args) => match &args.command { + TradeCommand::Submit(_) => "trade.submit", + TradeCommand::Get(_) => "trade.get", + TradeCommand::List => "trade.list", + TradeCommand::App(app) => match &app.command { + TradeAppCommand::List => "trade.app.list", + TradeAppCommand::Export(_) => "trade.app.export", }, - OrderCommand::Rebind(_) => "order.rebind", - OrderCommand::Accept(_) => "order.accept", - OrderCommand::Decline(_) => "order.decline", - OrderCommand::Cancel(_) => "order.cancel", - OrderCommand::Revision(revision) => match &revision.command { - OrderRevisionCommand::Propose(_) => "order.revision.propose", - OrderRevisionCommand::Accept(_) => "order.revision.accept", - OrderRevisionCommand::Decline(_) => "order.revision.decline", + TradeCommand::Rebind(_) => "trade.rebind", + TradeCommand::Accept(_) => "trade.accept", + TradeCommand::Decline(_) => "trade.decline", + TradeCommand::Cancel(_) => "trade.cancel", + TradeCommand::Revision(revision) => match &revision.command { + TradeRevisionCommand::Propose(_) => "trade.revision.propose", + TradeRevisionCommand::Accept(_) => "trade.revision.accept", + TradeRevisionCommand::Decline(_) => "trade.revision.decline", }, - OrderCommand::Status(status) => match &status.command { - OrderStatusCommand::Get(_) => "order.status.get", + TradeCommand::Status(status) => match &status.command { + TradeStatusCommand::Get(_) => "trade.status.get", }, - OrderCommand::Event(event) => match &event.command { - OrderEventCommand::List(_) => "order.event.list", - OrderEventCommand::Watch(_) => "order.event.watch", + TradeCommand::Event(event) => match &event.command { + TradeEventCommand::List(_) => "trade.event.list", + TradeEventCommand::Watch(_) => "trade.event.watch", }, }, Self::Validation(args) => match &args.command { @@ -297,8 +299,8 @@ mod tests { use clap::{CommandFactory, Parser}; use super::{ - AccountCommand, FarmCommand, ListingCommand, OrderCommand, OrderRevisionCommand, - TargetCliArgs, TargetOutputFormat, ValidationCommand, ValidationReceiptCommand, + AccountCommand, FarmCommand, ListingCommand, TargetCliArgs, TargetOutputFormat, + TradeCommand, TradeRevisionCommand, ValidationCommand, ValidationReceiptCommand, }; use crate::registry::OPERATION_REGISTRY; @@ -332,7 +334,7 @@ mod tests { "listing", "market", "basket", - "order", + "trade", "validation", ] .into_iter() @@ -460,17 +462,17 @@ mod tests { #[test] fn target_parser_accepts_order_rebind_inputs() { let parsed = - TargetCliArgs::try_parse_from(["radroots", "order", "rebind", "ord_test", "acct_test"]) + TargetCliArgs::try_parse_from(["radroots", "trade", "rebind", "ord_test", "acct_test"]) .expect("target args parse"); - assert_eq!(parsed.command.operation_id(), "order.rebind"); - let crate::cli::TargetCommand::Order(order) = parsed.command else { - panic!("expected order command") + assert_eq!(parsed.command.operation_id(), "trade.rebind"); + let crate::cli::TargetCommand::Trade(trade) = parsed.command else { + panic!("expected trade command") }; - let OrderCommand::Rebind(args) = order.command else { - panic!("expected order rebind command") + let TradeCommand::Rebind(args) = trade.command else { + panic!("expected trade rebind command") }; - assert_eq!(args.order_id.as_deref(), Some("ord_test")); + assert_eq!(args.trade_id.as_deref(), Some("ord_test")); assert_eq!(args.selector.as_deref(), Some("acct_test")); } @@ -478,7 +480,7 @@ mod tests { fn target_parser_accepts_order_cancel_reason() { let parsed = TargetCliArgs::try_parse_from([ "radroots", - "order", + "trade", "cancel", "ord_test", "--reason", @@ -486,14 +488,14 @@ mod tests { ]) .expect("target args parse"); - assert_eq!(parsed.command.operation_id(), "order.cancel"); - let crate::cli::TargetCommand::Order(order) = parsed.command else { - panic!("expected order command") + assert_eq!(parsed.command.operation_id(), "trade.cancel"); + let crate::cli::TargetCommand::Trade(trade) = parsed.command else { + panic!("expected trade command") }; - let OrderCommand::Cancel(args) = order.command else { - panic!("expected order cancel command") + let TradeCommand::Cancel(args) = trade.command else { + panic!("expected trade cancel command") }; - assert_eq!(args.order_id.as_deref(), Some("ord_test")); + assert_eq!(args.trade_id.as_deref(), Some("ord_test")); assert_eq!(args.reason.as_deref(), Some("changed plans")); } @@ -501,7 +503,7 @@ mod tests { fn target_parser_accepts_order_revision_propose_inputs() { let parsed = TargetCliArgs::try_parse_from([ "radroots", - "order", + "trade", "revision", "propose", "ord_test", @@ -524,17 +526,17 @@ mod tests { ]) .expect("target args parse"); - assert_eq!(parsed.command.operation_id(), "order.revision.propose"); - let crate::cli::TargetCommand::Order(order) = parsed.command else { - panic!("expected order command") + assert_eq!(parsed.command.operation_id(), "trade.revision.propose"); + let crate::cli::TargetCommand::Trade(trade) = parsed.command else { + panic!("expected trade command") }; - let OrderCommand::Revision(revision) = order.command else { - panic!("expected order revision command") + let TradeCommand::Revision(revision) = trade.command else { + panic!("expected trade revision command") }; - let OrderRevisionCommand::Propose(args) = revision.command else { - panic!("expected order revision propose command") + let TradeRevisionCommand::Propose(args) = revision.command else { + panic!("expected trade revision propose command") }; - assert_eq!(args.order_id.as_deref(), Some("ord_test")); + assert_eq!(args.trade_id.as_deref(), Some("ord_test")); assert_eq!(args.reason.as_deref(), Some("update count")); assert_eq!(args.bin_id.as_deref(), Some("bin-1")); assert_eq!(args.bin_count, Some(3)); @@ -546,7 +548,7 @@ mod tests { fn target_parser_accepts_order_revision_decision_inputs() { let accepted = TargetCliArgs::try_parse_from([ "radroots", - "order", + "trade", "revision", "accept", "ord_test", @@ -555,45 +557,45 @@ mod tests { ]) .expect("target args parse"); - assert_eq!(accepted.command.operation_id(), "order.revision.accept"); - let crate::cli::TargetCommand::Order(order) = accepted.command else { - panic!("expected order command") + assert_eq!(accepted.command.operation_id(), "trade.revision.accept"); + let crate::cli::TargetCommand::Trade(trade) = accepted.command else { + panic!("expected trade command") }; - let OrderCommand::Revision(revision) = order.command else { - panic!("expected order revision command") + let TradeCommand::Revision(revision) = trade.command else { + panic!("expected trade revision command") }; - let OrderRevisionCommand::Accept(args) = revision.command else { - panic!("expected order revision accept command") + let TradeRevisionCommand::Accept(args) = revision.command else { + panic!("expected trade revision accept command") }; - assert_eq!(args.order_id.as_deref(), Some("ord_test")); + assert_eq!(args.trade_id.as_deref(), Some("ord_test")); assert_eq!(args.revision_id.as_deref(), Some("rev_test")); let declined = TargetCliArgs::try_parse_from([ "radroots", - "order", + "trade", "revision", "decline", "ord_test", "--revision-id", "rev_test", "--reason", - "keep original order", + "keep original trade", ]) .expect("target args parse"); - assert_eq!(declined.command.operation_id(), "order.revision.decline"); - let crate::cli::TargetCommand::Order(order) = declined.command else { - panic!("expected order command") + assert_eq!(declined.command.operation_id(), "trade.revision.decline"); + let crate::cli::TargetCommand::Trade(trade) = declined.command else { + panic!("expected trade command") }; - let OrderCommand::Revision(revision) = order.command else { - panic!("expected order revision command") + let TradeCommand::Revision(revision) = trade.command else { + panic!("expected trade revision command") }; - let OrderRevisionCommand::Decline(args) = revision.command else { - panic!("expected order revision decline command") + let TradeRevisionCommand::Decline(args) = revision.command else { + panic!("expected trade revision decline command") }; - assert_eq!(args.order_id.as_deref(), Some("ord_test")); + assert_eq!(args.trade_id.as_deref(), Some("ord_test")); assert_eq!(args.revision_id.as_deref(), Some("rev_test")); - assert_eq!(args.reason.as_deref(), Some("keep original order")); + assert_eq!(args.reason.as_deref(), Some("keep original trade")); } #[test] @@ -624,7 +626,7 @@ mod tests { "validation", "receipt", "list", - "--order-id", + "--trade-id", "ord_1", ]) .expect("target args parse"); @@ -636,7 +638,7 @@ mod tests { let ValidationReceiptCommand::List(args) = receipt.command else { panic!("expected validation receipt list command") }; - assert_eq!(args.order_id.as_deref(), Some("ord_1")); + assert_eq!(args.trade_id.as_deref(), Some("ord_1")); let verify = TargetCliArgs::try_parse_from([ "radroots", diff --git a/src/cli/order.rs b/src/cli/order.rs @@ -1,146 +0,0 @@ -use std::path::PathBuf; - -use clap::{Args, Subcommand}; - -#[derive(Debug, Clone, Args)] -pub struct OrderArgs { - #[command(subcommand)] - pub command: OrderCommand, -} - -#[derive(Debug, Clone, Subcommand)] -pub enum OrderCommand { - Submit(OrderSubmitArgs), - Get(OrderKeyArgs), - List, - App(OrderAppArgs), - Rebind(OrderRebindArgs), - Accept(OrderKeyArgs), - Decline(OrderDeclineArgs), - Cancel(OrderCancelArgs), - Revision(OrderRevisionArgs), - Status(OrderStatusArgs), - Event(OrderEventArgs), -} - -#[derive(Debug, Clone, Args)] -pub struct OrderSubmitArgs { - pub order_id: Option<String>, -} - -#[derive(Debug, Clone, Args)] -pub struct OrderKeyArgs { - pub order_id: Option<String>, -} - -#[derive(Debug, Clone, Args)] -pub struct OrderAppArgs { - #[command(subcommand)] - pub command: OrderAppCommand, -} - -#[derive(Debug, Clone, Subcommand)] -pub enum OrderAppCommand { - List, - Export(OrderAppExportArgs), -} - -#[derive(Debug, Clone, Args)] -pub struct OrderAppExportArgs { - pub record_id: Option<String>, - #[arg(long)] - pub output: Option<PathBuf>, -} - -#[derive(Debug, Clone, Args)] -pub struct OrderRebindArgs { - pub order_id: Option<String>, - pub selector: Option<String>, -} - -#[derive(Debug, Clone, Args)] -pub struct OrderDeclineArgs { - pub order_id: Option<String>, - #[arg(long)] - pub reason: Option<String>, -} - -#[derive(Debug, Clone, Args)] -pub struct OrderCancelArgs { - pub order_id: Option<String>, - #[arg(long)] - pub reason: Option<String>, -} - -#[derive(Debug, Clone, Args)] -pub struct OrderRevisionArgs { - #[command(subcommand)] - pub command: OrderRevisionCommand, -} - -#[derive(Debug, Clone, Subcommand)] -pub enum OrderRevisionCommand { - Propose(OrderRevisionProposeArgs), - Accept(OrderRevisionDecisionArgs), - Decline(OrderRevisionDeclineArgs), -} - -#[derive(Debug, Clone, Args)] -pub struct OrderRevisionProposeArgs { - pub order_id: Option<String>, - #[arg(long)] - pub reason: Option<String>, - #[arg(long)] - pub bin_id: Option<String>, - #[arg(long)] - pub bin_count: Option<u32>, - #[arg(long)] - pub adjustment_id: Option<String>, - #[arg(long)] - pub adjustment_effect: Option<String>, - #[arg(long)] - pub adjustment_amount: Option<String>, - #[arg(long)] - pub adjustment_currency: Option<String>, - #[arg(long)] - pub adjustment_reason: Option<String>, -} - -#[derive(Debug, Clone, Args)] -pub struct OrderRevisionDecisionArgs { - pub order_id: Option<String>, - #[arg(long)] - pub revision_id: Option<String>, -} - -#[derive(Debug, Clone, Args)] -pub struct OrderRevisionDeclineArgs { - pub order_id: Option<String>, - #[arg(long)] - pub revision_id: Option<String>, - #[arg(long)] - pub reason: Option<String>, -} - -#[derive(Debug, Clone, Args)] -pub struct OrderStatusArgs { - #[command(subcommand)] - pub command: OrderStatusCommand, -} - -#[derive(Debug, Clone, Subcommand)] -pub enum OrderStatusCommand { - Get(OrderKeyArgs), -} - -#[derive(Debug, Clone, Args)] -pub struct OrderEventArgs { - #[command(subcommand)] - pub command: OrderEventCommand, -} - -#[derive(Debug, Clone, Subcommand)] -pub enum OrderEventCommand { - List(OrderKeyArgs), - Watch(OrderKeyArgs), -} diff --git a/src/cli/trade.rs b/src/cli/trade.rs @@ -0,0 +1,146 @@ +use std::path::PathBuf; + +use clap::{Args, Subcommand}; + +#[derive(Debug, Clone, Args)] +pub struct TradeArgs { + #[command(subcommand)] + pub command: TradeCommand, +} + +#[derive(Debug, Clone, Subcommand)] +pub enum TradeCommand { + Submit(TradeSubmitArgs), + Get(TradeKeyArgs), + List, + App(TradeAppArgs), + Rebind(TradeRebindArgs), + Accept(TradeKeyArgs), + Decline(TradeDeclineArgs), + Cancel(TradeCancelArgs), + Revision(TradeRevisionArgs), + Status(TradeStatusArgs), + Event(TradeEventArgs), +} + +#[derive(Debug, Clone, Args)] +pub struct TradeSubmitArgs { + pub trade_id: Option<String>, +} + +#[derive(Debug, Clone, Args)] +pub struct TradeKeyArgs { + pub trade_id: Option<String>, +} + +#[derive(Debug, Clone, Args)] +pub struct TradeAppArgs { + #[command(subcommand)] + pub command: TradeAppCommand, +} + +#[derive(Debug, Clone, Subcommand)] +pub enum TradeAppCommand { + List, + Export(TradeAppExportArgs), +} + +#[derive(Debug, Clone, Args)] +pub struct TradeAppExportArgs { + pub record_id: Option<String>, + #[arg(long)] + pub output: Option<PathBuf>, +} + +#[derive(Debug, Clone, Args)] +pub struct TradeRebindArgs { + pub trade_id: Option<String>, + pub selector: Option<String>, +} + +#[derive(Debug, Clone, Args)] +pub struct TradeDeclineArgs { + pub trade_id: Option<String>, + #[arg(long)] + pub reason: Option<String>, +} + +#[derive(Debug, Clone, Args)] +pub struct TradeCancelArgs { + pub trade_id: Option<String>, + #[arg(long)] + pub reason: Option<String>, +} + +#[derive(Debug, Clone, Args)] +pub struct TradeRevisionArgs { + #[command(subcommand)] + pub command: TradeRevisionCommand, +} + +#[derive(Debug, Clone, Subcommand)] +pub enum TradeRevisionCommand { + Propose(TradeRevisionProposeArgs), + Accept(TradeRevisionDecisionArgs), + Decline(TradeRevisionDeclineArgs), +} + +#[derive(Debug, Clone, Args)] +pub struct TradeRevisionProposeArgs { + pub trade_id: Option<String>, + #[arg(long)] + pub reason: Option<String>, + #[arg(long)] + pub bin_id: Option<String>, + #[arg(long)] + pub bin_count: Option<u32>, + #[arg(long)] + pub adjustment_id: Option<String>, + #[arg(long)] + pub adjustment_effect: Option<String>, + #[arg(long)] + pub adjustment_amount: Option<String>, + #[arg(long)] + pub adjustment_currency: Option<String>, + #[arg(long)] + pub adjustment_reason: Option<String>, +} + +#[derive(Debug, Clone, Args)] +pub struct TradeRevisionDecisionArgs { + pub trade_id: Option<String>, + #[arg(long)] + pub revision_id: Option<String>, +} + +#[derive(Debug, Clone, Args)] +pub struct TradeRevisionDeclineArgs { + pub trade_id: Option<String>, + #[arg(long)] + pub revision_id: Option<String>, + #[arg(long)] + pub reason: Option<String>, +} + +#[derive(Debug, Clone, Args)] +pub struct TradeStatusArgs { + #[command(subcommand)] + pub command: TradeStatusCommand, +} + +#[derive(Debug, Clone, Subcommand)] +pub enum TradeStatusCommand { + Get(TradeKeyArgs), +} + +#[derive(Debug, Clone, Args)] +pub struct TradeEventArgs { + #[command(subcommand)] + pub command: TradeEventCommand, +} + +#[derive(Debug, Clone, Subcommand)] +pub enum TradeEventCommand { + List(TradeKeyArgs), + Watch(TradeKeyArgs), +} diff --git a/src/cli/validation.rs b/src/cli/validation.rs @@ -33,8 +33,8 @@ pub struct ValidationReceiptEventArgs { #[derive(Debug, Clone, Args)] pub struct ValidationReceiptListArgs { - #[arg(long)] - pub order_id: Option<String>, + #[arg(long = "trade-id")] + pub trade_id: Option<String>, } #[derive(Debug, Clone, Args)] diff --git a/src/main.rs b/src/main.rs @@ -19,7 +19,7 @@ use crate::cli::input::runtime_invocation_args_from_target; use crate::cli::{TargetCliArgs, TargetOutputFormat}; use crate::ops::exec::{ BasketOperationService, CoreOperationService, FarmOperationService, ListingOperationService, - MarketOperationService, OrderOperationService, RuntimeOperationService, + MarketOperationService, RuntimeOperationService, TradeOperationService, ValidationOperationService, }; use crate::ops::{ @@ -158,7 +158,13 @@ fn execute_request( TargetOperationRequest::FarmProfileUpdate(request) => { execute_with(FarmOperationService::new(config), request) } - TargetOperationRequest::FarmLocationUpdate(request) => { + TargetOperationRequest::FarmLocationSet(request) => { + execute_with(FarmOperationService::new(config), request) + } + TargetOperationRequest::FarmLocationGet(request) => { + execute_with(FarmOperationService::new(config), request) + } + TargetOperationRequest::FarmLocationClear(request) => { execute_with(FarmOperationService::new(config), request) } TargetOperationRequest::FarmFulfillmentUpdate(request) => { @@ -239,50 +245,50 @@ fn execute_request( TargetOperationRequest::BasketQuoteCreate(request) => { execute_with(BasketOperationService::new(config), request) } - TargetOperationRequest::OrderSubmit(request) => { - execute_with(OrderOperationService::new(config), request) + TargetOperationRequest::TradeSubmit(request) => { + execute_with(TradeOperationService::new(config), request) } - TargetOperationRequest::OrderGet(request) => { - execute_with(OrderOperationService::new(config), request) + TargetOperationRequest::TradeGet(request) => { + execute_with(TradeOperationService::new(config), request) } - TargetOperationRequest::OrderList(request) => { - execute_with(OrderOperationService::new(config), request) + TargetOperationRequest::TradeList(request) => { + execute_with(TradeOperationService::new(config), request) } - TargetOperationRequest::OrderAppList(request) => { - execute_with(OrderOperationService::new(config), request) + TargetOperationRequest::TradeAppList(request) => { + execute_with(TradeOperationService::new(config), request) } - TargetOperationRequest::OrderAppExport(request) => { - execute_with(OrderOperationService::new(config), request) + TargetOperationRequest::TradeAppExport(request) => { + execute_with(TradeOperationService::new(config), request) } - TargetOperationRequest::OrderRebind(request) => { - execute_with(OrderOperationService::new(config), request) + TargetOperationRequest::TradeRebind(request) => { + execute_with(TradeOperationService::new(config), request) } - TargetOperationRequest::OrderAccept(request) => { - execute_with(OrderOperationService::new(config), request) + TargetOperationRequest::TradeAccept(request) => { + execute_with(TradeOperationService::new(config), request) } - TargetOperationRequest::OrderDecline(request) => { - execute_with(OrderOperationService::new(config), request) + TargetOperationRequest::TradeDecline(request) => { + execute_with(TradeOperationService::new(config), request) } - TargetOperationRequest::OrderCancel(request) => { - execute_with(OrderOperationService::new(config), request) + TargetOperationRequest::TradeCancel(request) => { + execute_with(TradeOperationService::new(config), request) } - TargetOperationRequest::OrderRevisionPropose(request) => { - execute_with(OrderOperationService::new(config), request) + TargetOperationRequest::TradeRevisionPropose(request) => { + execute_with(TradeOperationService::new(config), request) } - TargetOperationRequest::OrderRevisionAccept(request) => { - execute_with(OrderOperationService::new(config), request) + TargetOperationRequest::TradeRevisionAccept(request) => { + execute_with(TradeOperationService::new(config), request) } - TargetOperationRequest::OrderRevisionDecline(request) => { - execute_with(OrderOperationService::new(config), request) + TargetOperationRequest::TradeRevisionDecline(request) => { + execute_with(TradeOperationService::new(config), request) } - TargetOperationRequest::OrderStatusGet(request) => { - execute_with(OrderOperationService::new(config), request) + TargetOperationRequest::TradeStatusGet(request) => { + execute_with(TradeOperationService::new(config), request) } - TargetOperationRequest::OrderEventList(request) => { - execute_with(OrderOperationService::new(config), request) + TargetOperationRequest::TradeEventList(request) => { + execute_with(TradeOperationService::new(config), request) } - TargetOperationRequest::OrderEventWatch(request) => { - execute_with(OrderOperationService::new(config), request) + TargetOperationRequest::TradeEventWatch(request) => { + execute_with(TradeOperationService::new(config), request) } TargetOperationRequest::ValidationReceiptGet(request) => { execute_with(ValidationOperationService::new(config), request) diff --git a/src/ops/exec/basket.rs b/src/ops/exec/basket.rs @@ -79,8 +79,8 @@ struct BasketAdjustment { struct BasketQuote { quote_id: String, quote_version: u32, - order_id: String, - order_file: String, + trade_id: String, + trade_file: String, #[serde(default, skip_serializing_if = "Option::is_none")] economics: Option<RadrootsOrderEconomics>, ready_for_submit: bool, @@ -516,7 +516,7 @@ impl OperationService<BasketQuoteCreateRequest> for BasketOperationService<'_> { "basket_id": basket_id, "file": loaded.file.display().to_string(), "item": item, - "order": order, + "trade": order, "actions": ["radroots basket quote create"], })); } @@ -542,8 +542,8 @@ impl OperationService<BasketQuoteCreateRequest> for BasketOperationService<'_> { .as_ref() .map(|economics| economics.quote_version) .unwrap_or(1), - order_id: order.order_id.clone(), - order_file: order.file.clone(), + trade_id: order.order_id.clone(), + trade_file: order.file.clone(), economics: quote_economics, ready_for_submit: order.ready_for_submit, created_at_unix: now_unix(), @@ -559,7 +559,7 @@ impl OperationService<BasketQuoteCreateRequest> for BasketOperationService<'_> { "basket_id": loaded.document.basket.basket_id, "file": loaded.file.display().to_string(), "quote": quote, - "order": order, + "trade": order, "actions": quote_actions(&order), })) } @@ -1083,9 +1083,9 @@ fn basket_actions(document: &BasketDocument, issues: &[BasketIssue]) -> Vec<Stri fn quote_actions(order: &OrderNewView) -> Vec<String> { if order.ready_for_submit { - vec![format!("radroots order submit {}", order.order_id)] + vec![format!("radroots trade submit {}", order.order_id)] } else { - let mut actions = vec![format!("radroots order get {}", order.order_id)]; + let mut actions = vec![format!("radroots trade get {}", order.order_id)]; actions.extend(order.actions.iter().cloned()); actions } @@ -1528,30 +1528,30 @@ mod tests { assert_eq!(envelope.operation_id, "basket.quote.create"); assert_eq!(envelope.result["state"], "quoted"); assert!( - envelope.result["quote"]["order_id"] + envelope.result["quote"]["trade_id"] .as_str() .unwrap() .starts_with("ord_") ); assert!( - envelope.result["order"]["buyer_account_id"] + envelope.result["trade"]["buyer_account_id"] .as_str() .expect("buyer account id") .len() > 8 ); assert!( - envelope.result["order"]["buyer_pubkey"] + envelope.result["trade"]["buyer_pubkey"] .as_str() .expect("buyer pubkey") .len() == 64 ); assert_eq!( - envelope.result["order"]["buyer_actor_source"], + envelope.result["trade"]["buyer_actor_source"], "resolved_account" ); - let order_file = PathBuf::from(envelope.result["quote"]["order_file"].as_str().unwrap()); + let order_file = PathBuf::from(envelope.result["quote"]["trade_file"].as_str().unwrap()); assert!(order_file.exists()); let draft = std::fs::read_to_string(order_file).expect("read order draft"); assert!(draft.contains("[buyer_actor]")); @@ -1584,12 +1584,12 @@ mod tests { assert_eq!(envelope.operation_id, "basket.quote.create"); assert_eq!(envelope.dry_run, true); assert_eq!(envelope.result["state"], "dry_run"); - assert_eq!(envelope.result["order"]["state"], "dry_run"); + assert_eq!(envelope.result["trade"]["state"], "dry_run"); assert_eq!( - envelope.result["order"]["buyer_actor_source"], + envelope.result["trade"]["buyer_actor_source"], "resolved_account" ); - assert!(!PathBuf::from(envelope.result["order"]["file"].as_str().unwrap()).exists()); + assert!(!PathBuf::from(envelope.result["trade"]["file"].as_str().unwrap()).exists()); } #[test] @@ -1848,6 +1848,7 @@ mod tests { fn sample_config(root: &Path) -> RuntimeConfig { let data = root.join("data"); + let cache = root.join("cache"); let logs = root.join("logs"); let secrets = root.join("secrets"); RuntimeConfig { @@ -1879,6 +1880,7 @@ mod tests { app_config_path: root.join("config/apps/cli/config.toml"), workspace_config_path: None, app_data_root: data.join("apps/cli"), + shared_cache_root: cache.clone(), app_logs_root: logs.join("apps/cli"), shared_accounts_data_root: data.join("shared/accounts"), shared_accounts_secrets_root: secrets.join("shared/accounts"), diff --git a/src/ops/exec/core.rs b/src/ops/exec/core.rs @@ -87,6 +87,7 @@ impl OperationService<WorkspaceGetRequest> for CoreOperationService<'_> { "workspace_config_path": self.config.paths.workspace_config_path.as_ref().map(|path| path.display().to_string()), "app_config_path": self.config.paths.app_config_path.display().to_string(), "app_data_root": self.config.paths.app_data_root.display().to_string(), + "shared_cache_root": self.config.paths.shared_cache_root.display().to_string(), "app_logs_root": self.config.paths.app_logs_root.display().to_string(), "local_root": self.config.local.root.display().to_string(), "replica_db_path": self.config.local.replica_db_path.display().to_string(), @@ -213,6 +214,7 @@ impl OperationService<ConfigGetRequest> for CoreOperationService<'_> { "app_config_path": self.config.paths.app_config_path.display().to_string(), "workspace_config_path": self.config.paths.workspace_config_path.as_ref().map(|path| path.display().to_string()), "app_data_root": self.config.paths.app_data_root.display().to_string(), + "shared_cache_root": self.config.paths.shared_cache_root.display().to_string(), "app_logs_root": self.config.paths.app_logs_root.display().to_string(), }, "account": { @@ -1311,6 +1313,7 @@ mod tests { fn sample_config(root: &Path) -> RuntimeConfig { let data = root.join("data"); + let cache = root.join("cache"); let logs = root.join("logs"); let secrets = root.join("secrets"); RuntimeConfig { @@ -1342,6 +1345,7 @@ mod tests { app_config_path: root.join("config/apps/cli/config.toml"), workspace_config_path: None, app_data_root: data.join("apps/cli"), + shared_cache_root: cache.clone(), app_logs_root: logs.join("apps/cli"), shared_accounts_data_root: data.join("shared/accounts"), shared_accounts_secrets_root: secrets.join("shared/accounts"), diff --git a/src/ops/exec/farm.rs b/src/ops/exec/farm.rs @@ -2,12 +2,13 @@ use serde::Serialize; use serde_json::Value; use crate::cli::global::{ - FarmCreateArgs, FarmFieldArg, FarmPublishArgs, FarmRebindArgs, FarmScopeArg, FarmScopedArgs, - FarmUpdateArgs, + FarmCreateArgs, FarmFieldArg, FarmPrivateLocationKeyArgs, FarmPrivateLocationSetArgs, + FarmPublishArgs, FarmRebindArgs, FarmScopeArg, FarmScopedArgs, FarmUpdateArgs, }; use crate::ops::{ FarmCreateRequest, FarmCreateResult, FarmFulfillmentUpdateRequest, FarmFulfillmentUpdateResult, - FarmGetRequest, FarmGetResult, FarmLocationUpdateRequest, FarmLocationUpdateResult, + FarmGetRequest, FarmGetResult, FarmLocationClearRequest, FarmLocationClearResult, + FarmLocationGetRequest, FarmLocationGetResult, FarmLocationSetRequest, FarmLocationSetResult, FarmProfileUpdateRequest, FarmProfileUpdateResult, FarmPublishRequest, FarmPublishResult, FarmReadinessCheckRequest, FarmReadinessCheckResult, FarmRebindRequest, FarmRebindResult, OperationAdapterError, OperationRequest, OperationRequestData, OperationRequestPayload, @@ -122,14 +123,72 @@ impl OperationService<FarmProfileUpdateRequest> for FarmOperationService<'_> { } } -impl OperationService<FarmLocationUpdateRequest> for FarmOperationService<'_> { - type Result = FarmLocationUpdateResult; +impl OperationService<FarmLocationSetRequest> for FarmOperationService<'_> { + type Result = FarmLocationSetResult; fn execute( &self, - request: OperationRequest<FarmLocationUpdateRequest>, + request: OperationRequest<FarmLocationSetRequest>, ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { - farm_set::<FarmLocationUpdateResult>(&request, self.config, location_field(&request)?) + let lookup = string_input(&request, "lookup").unwrap_or_else(|| "geonames".to_owned()); + if lookup != "geonames" { + return Err(invalid_input( + request.operation_id(), + format!("farm location lookup `{lookup}` is not supported"), + )); + } + let args = FarmPrivateLocationSetArgs { + farm_d_tag: string_input(&request, "farm_d_tag"), + latitude: required_f64(&request, "latitude")?, + longitude: required_f64(&request, "longitude")?, + lookup, + }; + let view = + crate::runtime::farm::private_location_set(self.config, &args).map_err(|error| { + OperationAdapterError::sdk_adapter_failure(request.operation_id(), error) + })?; + serialized_operation_result::<FarmLocationSetResult, _>(&view) + } +} + +impl OperationService<FarmLocationGetRequest> for FarmOperationService<'_> { + type Result = FarmLocationGetResult; + + fn execute( + &self, + request: OperationRequest<FarmLocationGetRequest>, + ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { + let args = FarmPrivateLocationKeyArgs { + farm_d_tag: string_input(&request, "farm_d_tag"), + }; + let view = + crate::runtime::farm::private_location_get(self.config, &args).map_err(|error| { + OperationAdapterError::sdk_adapter_failure(request.operation_id(), error) + })?; + serialized_operation_result::<FarmLocationGetResult, _>(&view) + } +} + +impl OperationService<FarmLocationClearRequest> for FarmOperationService<'_> { + type Result = FarmLocationClearResult; + + fn execute( + &self, + request: OperationRequest<FarmLocationClearRequest>, + ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { + if request.context.requires_approval_token() { + return Err(OperationAdapterError::approval_required( + request.operation_id(), + )); + } + let args = FarmPrivateLocationKeyArgs { + farm_d_tag: string_input(&request, "farm_d_tag"), + }; + let view = + crate::runtime::farm::private_location_clear(self.config, &args).map_err(|error| { + OperationAdapterError::sdk_adapter_failure(request.operation_id(), error) + })?; + serialized_operation_result::<FarmLocationClearResult, _>(&view) } } @@ -363,6 +422,24 @@ where .map(str::to_owned) } +fn required_f64<P>(request: &OperationRequest<P>, key: &str) -> Result<f64, OperationAdapterError> +where + P: OperationRequestPayload + OperationRequestData, +{ + request + .payload + .input() + .get(key) + .and_then(Value::as_f64) + .filter(|value| value.is_finite()) + .ok_or_else(|| { + invalid_input( + request.operation_id(), + format!("missing required finite `{key}` input"), + ) + }) +} + fn bool_input<P>(request: &OperationRequest<P>, key: &str) -> Option<bool> where P: OperationRequestPayload + OperationRequestData, @@ -485,6 +562,7 @@ mod tests { fn sample_config(root: &Path) -> RuntimeConfig { let data = root.join("data"); + let cache = root.join("cache"); let logs = root.join("logs"); let secrets = root.join("secrets"); RuntimeConfig { @@ -516,6 +594,7 @@ mod tests { app_config_path: root.join("config/apps/cli/config.toml"), workspace_config_path: None, app_data_root: data.join("apps/cli"), + shared_cache_root: cache.clone(), app_logs_root: logs.join("apps/cli"), shared_accounts_data_root: data.join("shared/accounts"), shared_accounts_secrets_root: secrets.join("shared/accounts"), diff --git a/src/ops/exec/listing.rs b/src/ops/exec/listing.rs @@ -549,6 +549,7 @@ mod tests { fn sample_config(root: &Path) -> RuntimeConfig { let data = root.join("data"); + let cache = root.join("cache"); let logs = root.join("logs"); let secrets = root.join("secrets"); RuntimeConfig { @@ -580,6 +581,7 @@ mod tests { app_config_path: root.join("config/apps/cli/config.toml"), workspace_config_path: None, app_data_root: data.join("apps/cli"), + shared_cache_root: cache.clone(), app_logs_root: logs.join("apps/cli"), shared_accounts_data_root: data.join("shared/accounts"), shared_accounts_secrets_root: secrets.join("shared/accounts"), diff --git a/src/ops/exec/market.rs b/src/ops/exec/market.rs @@ -104,7 +104,7 @@ fn market_product_search_view(mut view: FindView) -> FindView { "radroots market listing get {}", result.product_key )]; - if result.readiness.order_request_enabled { + if result.readiness.checkout_enabled { actions.push("radroots basket create".to_owned()); } actions @@ -126,7 +126,7 @@ fn market_product_search_view(mut view: FindView) -> FindView { fn market_listing_get_view(mut view: ListingGetView) -> ListingGetView { view.actions = match view.state.as_str() { "ready" => { - if view.readiness.order_request_enabled { + if view.readiness.checkout_enabled { vec!["radroots basket create".to_owned()] } else { Vec::new() @@ -495,7 +495,7 @@ mod tests { } #[test] - fn market_ready_actions_require_order_request_enabled() { + fn market_ready_actions_require_checkout_enabled() { let disabled_search = market_product_search_view(FindView { state: "ready".to_owned(), source: "test".to_owned(), @@ -592,7 +592,7 @@ mod tests { MarketReadinessView { protocol_valid: true, marketplace_eligible: true, - order_request_enabled: true, + checkout_enabled: true, primary_bin_verified: true, reason_codes: Vec::new(), } @@ -602,10 +602,10 @@ mod tests { MarketReadinessView { protocol_valid: true, marketplace_eligible: true, - order_request_enabled: false, + checkout_enabled: false, primary_bin_verified: true, reason_codes: vec![ - "listing_order_request_disabled".to_owned(), + "listing_checkout_disabled".to_owned(), "listing_inventory_unavailable".to_owned(), ], } @@ -613,6 +613,7 @@ mod tests { fn sample_config(root: &Path) -> RuntimeConfig { let data = root.join("data"); + let cache = root.join("cache"); let logs = root.join("logs"); let secrets = root.join("secrets"); RuntimeConfig { @@ -644,6 +645,7 @@ mod tests { app_config_path: root.join("config/apps/cli/config.toml"), workspace_config_path: None, app_data_root: data.join("apps/cli"), + shared_cache_root: cache.clone(), app_logs_root: logs.join("apps/cli"), shared_accounts_data_root: data.join("shared/accounts"), shared_accounts_secrets_root: secrets.join("shared/accounts"), diff --git a/src/ops/exec/mod.rs b/src/ops/exec/mod.rs @@ -3,8 +3,8 @@ pub mod core; pub mod farm; pub mod listing; pub mod market; -pub mod order; pub mod runtime; +pub mod trade; pub mod validation; pub use basket::BasketOperationService; @@ -12,6 +12,6 @@ pub use core::CoreOperationService; pub use farm::FarmOperationService; pub use listing::ListingOperationService; pub use market::MarketOperationService; -pub use order::OrderOperationService; pub use runtime::RuntimeOperationService; +pub use trade::TradeOperationService; pub use validation::ValidationOperationService; diff --git a/src/ops/exec/order.rs b/src/ops/exec/order.rs @@ -1,1890 +0,0 @@ -use std::path::PathBuf; - -use serde::Serialize; -use serde_json::{Value, json}; - -use crate::cli::global::{ - OrderAppRecordExportArgs, OrderCancelArgs, OrderDecisionArg, OrderDecisionArgs, - OrderRebindArgs, OrderRevisionDecisionArg, OrderRevisionDecisionArgs, OrderRevisionProposeArgs, - OrderStatusArgs, OrderSubmitArgs, RecordLookupArgs, -}; -use crate::ops::{ - OperationAdapterError, OperationRequest, OperationRequestData, OperationRequestPayload, - OperationResult, OperationResultData, OperationService, OrderAcceptRequest, OrderAcceptResult, - OrderAppExportRequest, OrderAppExportResult, OrderAppListRequest, OrderAppListResult, - OrderCancelRequest, OrderCancelResult, OrderDeclineRequest, OrderDeclineResult, - OrderEventListRequest, OrderEventListResult, OrderEventWatchRequest, OrderEventWatchResult, - OrderGetRequest, OrderGetResult, OrderListRequest, OrderListResult, OrderRebindRequest, - OrderRebindResult, OrderRevisionAcceptRequest, OrderRevisionAcceptResult, - OrderRevisionDeclineRequest, OrderRevisionDeclineResult, OrderRevisionProposeRequest, - OrderRevisionProposeResult, OrderStatusGetRequest, OrderStatusGetResult, OrderSubmitRequest, - OrderSubmitResult, -}; -use crate::runtime::RuntimeError; -use crate::runtime::config::RuntimeConfig; -use crate::view::runtime::{ - CommandDisposition, OrderAppRecordExportView, OrderCancellationView, OrderDecisionView, - OrderRebindView, OrderRevisionDecisionView, OrderRevisionProposalView, OrderStatusView, - OrderSubmitView, -}; - -const ORDER_EVENT_WATCH_DEFERRED_REASON: &str = "relay-backed order event watch is not implemented"; - -pub struct OrderOperationService<'a> { - config: &'a RuntimeConfig, -} - -impl<'a> OrderOperationService<'a> { - pub fn new(config: &'a RuntimeConfig) -> Self { - Self { config } - } -} - -impl OperationService<OrderSubmitRequest> for OrderOperationService<'_> { - type Result = OrderSubmitResult; - - fn execute( - &self, - request: OperationRequest<OrderSubmitRequest>, - ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { - if request.context.requires_approval_token() { - return Err(OperationAdapterError::approval_required( - request.operation_id(), - )); - } - - let key = required_order_key(&request)?; - let args = OrderSubmitArgs { - key, - idempotency_key: request - .context - .idempotency_key - .clone() - .or_else(|| string_input(&request, "idempotency_key")), - }; - let mut config = self.config.clone(); - if request.context.dry_run { - config.output.dry_run = true; - } - let view = crate::runtime::order::submit(&config, &args).map_err(|error| { - OperationAdapterError::sdk_adapter_failure(request.operation_id(), error) - })?; - submit_result::<OrderSubmitResult>(request.operation_id(), &view) - } -} - -impl OperationService<OrderGetRequest> for OrderOperationService<'_> { - type Result = OrderGetResult; - - fn execute( - &self, - request: OperationRequest<OrderGetRequest>, - ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { - let args = RecordLookupArgs { - key: required_order_key(&request)?, - }; - let view = map_runtime(crate::runtime::order::get(self.config, &args))?; - serialized_target_result::<OrderGetResult, _>(&view) - } -} - -impl OperationService<OrderListRequest> for OrderOperationService<'_> { - type Result = OrderListResult; - - fn execute( - &self, - _request: OperationRequest<OrderListRequest>, - ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { - let view = map_runtime(crate::runtime::order::list(self.config))?; - serialized_target_result::<OrderListResult, _>(&view) - } -} - -impl OperationService<OrderAppListRequest> for OrderOperationService<'_> { - type Result = OrderAppListResult; - - fn execute( - &self, - _request: OperationRequest<OrderAppListRequest>, - ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { - let view = map_runtime(crate::runtime::order::app_record_list(self.config))?; - serialized_target_result::<OrderAppListResult, _>(&view) - } -} - -impl OperationService<OrderAppExportRequest> for OrderOperationService<'_> { - type Result = OrderAppExportResult; - - fn execute( - &self, - request: OperationRequest<OrderAppExportRequest>, - ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { - let args = OrderAppRecordExportArgs { - record_id: required_string_input(&request, "record_id")?, - output: optional_path_input(&request, "output"), - }; - let mut config = self.config.clone(); - if request.context.dry_run { - config.output.dry_run = true; - } - let view = crate::runtime::order::app_record_export(&config, &args).map_err(|error| { - OperationAdapterError::runtime_failure(request.operation_id(), error) - })?; - order_app_record_export_result::<OrderAppExportResult>(request.operation_id(), &view) - } -} - -impl OperationService<OrderRebindRequest> for OrderOperationService<'_> { - type Result = OrderRebindResult; - - fn execute( - &self, - request: OperationRequest<OrderRebindRequest>, - ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { - let args = OrderRebindArgs { - key: required_order_key(&request)?, - selector: required_string_input(&request, "selector")?, - }; - if request.context.dry_run { - let view = - crate::runtime::order::rebind_preflight(self.config, &args).map_err(|error| { - OperationAdapterError::runtime_failure(request.operation_id(), error) - })?; - return order_rebind_result::<OrderRebindResult>(request.operation_id(), &view); - } - if request.context.requires_approval_token() { - return Err(OperationAdapterError::approval_required( - request.operation_id(), - )); - } - - let view = crate::runtime::order::rebind(self.config, &args).map_err(|error| { - OperationAdapterError::runtime_failure(request.operation_id(), error) - })?; - order_rebind_result::<OrderRebindResult>(request.operation_id(), &view) - } -} - -impl OperationService<OrderAcceptRequest> for OrderOperationService<'_> { - type Result = OrderAcceptResult; - - fn execute( - &self, - request: OperationRequest<OrderAcceptRequest>, - ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { - if request.context.requires_approval_token() { - return Err(OperationAdapterError::approval_required( - request.operation_id(), - )); - } - - let args = OrderDecisionArgs { - key: required_order_key(&request)?, - decision: OrderDecisionArg::Accept, - reason: None, - idempotency_key: request - .context - .idempotency_key - .clone() - .or_else(|| string_input(&request, "idempotency_key")), - }; - let mut config = self.config.clone(); - if request.context.dry_run { - config.output.dry_run = true; - } - let view = crate::runtime::order::decide(&config, &args).map_err(|error| { - OperationAdapterError::runtime_failure(request.operation_id(), error) - })?; - decision_result::<OrderAcceptResult>(request.operation_id(), &view) - } -} - -impl OperationService<OrderDeclineRequest> for OrderOperationService<'_> { - type Result = OrderDeclineResult; - - fn execute( - &self, - request: OperationRequest<OrderDeclineRequest>, - ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { - let reason = string_input(&request, "reason") - .map(|reason| reason.trim().to_owned()) - .filter(|reason| !reason.is_empty()) - .ok_or_else(|| { - invalid_input( - request.operation_id(), - "missing required `reason` input".to_owned(), - ) - })?; - if request.context.requires_approval_token() { - return Err(OperationAdapterError::approval_required( - request.operation_id(), - )); - } - - let args = OrderDecisionArgs { - key: required_order_key(&request)?, - decision: OrderDecisionArg::Decline, - reason: Some(reason), - idempotency_key: request - .context - .idempotency_key - .clone() - .or_else(|| string_input(&request, "idempotency_key")), - }; - let mut config = self.config.clone(); - if request.context.dry_run { - config.output.dry_run = true; - } - let view = crate::runtime::order::decide(&config, &args).map_err(|error| { - OperationAdapterError::runtime_failure(request.operation_id(), error) - })?; - decision_result::<OrderDeclineResult>(request.operation_id(), &view) - } -} - -impl OperationService<OrderCancelRequest> for OrderOperationService<'_> { - type Result = OrderCancelResult; - - fn execute( - &self, - request: OperationRequest<OrderCancelRequest>, - ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { - let reason = string_input(&request, "reason") - .map(|reason| reason.trim().to_owned()) - .filter(|reason| !reason.is_empty()) - .ok_or_else(|| { - invalid_input( - request.operation_id(), - "missing required `reason` input".to_owned(), - ) - })?; - if request.context.requires_approval_token() { - return Err(OperationAdapterError::approval_required( - request.operation_id(), - )); - } - - let args = OrderCancelArgs { - key: required_order_key(&request)?, - reason, - idempotency_key: request - .context - .idempotency_key - .clone() - .or_else(|| string_input(&request, "idempotency_key")), - }; - let mut config = self.config.clone(); - if request.context.dry_run { - config.output.dry_run = true; - } - let view = crate::runtime::order::cancel(&config, &args).map_err(|error| { - OperationAdapterError::runtime_failure(request.operation_id(), error) - })?; - cancellation_result::<OrderCancelResult>(request.operation_id(), &view) - } -} - -impl OperationService<OrderRevisionProposeRequest> for OrderOperationService<'_> { - type Result = OrderRevisionProposeResult; - - fn execute( - &self, - request: OperationRequest<OrderRevisionProposeRequest>, - ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { - let reason = string_input(&request, "reason") - .map(|reason| reason.trim().to_owned()) - .filter(|reason| !reason.is_empty()) - .ok_or_else(|| { - invalid_input( - request.operation_id(), - "missing required `reason` input".to_owned(), - ) - })?; - if request.context.requires_approval_token() { - return Err(OperationAdapterError::approval_required( - request.operation_id(), - )); - } - - let args = OrderRevisionProposeArgs { - key: required_order_key(&request)?, - reason, - bin_id: string_input(&request, "bin_id"), - bin_count: u32_input(&request, "bin_count"), - adjustment_id: string_input(&request, "adjustment_id"), - adjustment_effect: string_input(&request, "adjustment_effect"), - adjustment_amount: string_input(&request, "adjustment_amount"), - adjustment_currency: string_input(&request, "adjustment_currency"), - adjustment_reason: string_input(&request, "adjustment_reason"), - idempotency_key: request - .context - .idempotency_key - .clone() - .or_else(|| string_input(&request, "idempotency_key")), - }; - let mut config = self.config.clone(); - if request.context.dry_run { - config.output.dry_run = true; - } - let view = crate::runtime::order::revision_propose(&config, &args).map_err(|error| { - OperationAdapterError::runtime_failure(request.operation_id(), error) - })?; - revision_proposal_result::<OrderRevisionProposeResult>(request.operation_id(), &view) - } -} - -impl OperationService<OrderRevisionAcceptRequest> for OrderOperationService<'_> { - type Result = OrderRevisionAcceptResult; - - fn execute( - &self, - request: OperationRequest<OrderRevisionAcceptRequest>, - ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { - let args = OrderRevisionDecisionArgs { - key: required_order_key(&request)?, - revision_id: required_string_input(&request, "revision_id")?, - decision: OrderRevisionDecisionArg::Accept, - reason: None, - idempotency_key: request - .context - .idempotency_key - .clone() - .or_else(|| string_input(&request, "idempotency_key")), - }; - if request.context.requires_approval_token() { - return Err(OperationAdapterError::approval_required( - request.operation_id(), - )); - } - - let mut config = self.config.clone(); - if request.context.dry_run { - config.output.dry_run = true; - } - let view = crate::runtime::order::revision_decide(&config, &args).map_err(|error| { - OperationAdapterError::runtime_failure(request.operation_id(), error) - })?; - revision_decision_result::<OrderRevisionAcceptResult>(request.operation_id(), &view) - } -} - -impl OperationService<OrderRevisionDeclineRequest> for OrderOperationService<'_> { - type Result = OrderRevisionDeclineResult; - - fn execute( - &self, - request: OperationRequest<OrderRevisionDeclineRequest>, - ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { - let reason = string_input(&request, "reason") - .map(|reason| reason.trim().to_owned()) - .filter(|reason| !reason.is_empty()) - .ok_or_else(|| { - invalid_input( - request.operation_id(), - "missing required `reason` input".to_owned(), - ) - })?; - let args = OrderRevisionDecisionArgs { - key: required_order_key(&request)?, - revision_id: required_string_input(&request, "revision_id")?, - decision: OrderRevisionDecisionArg::Decline, - reason: Some(reason), - idempotency_key: request - .context - .idempotency_key - .clone() - .or_else(|| string_input(&request, "idempotency_key")), - }; - if request.context.requires_approval_token() { - return Err(OperationAdapterError::approval_required( - request.operation_id(), - )); - } - - let mut config = self.config.clone(); - if request.context.dry_run { - config.output.dry_run = true; - } - let view = crate::runtime::order::revision_decide(&config, &args).map_err(|error| { - OperationAdapterError::runtime_failure(request.operation_id(), error) - })?; - revision_decision_result::<OrderRevisionDeclineResult>(request.operation_id(), &view) - } -} - -impl OperationService<OrderStatusGetRequest> for OrderOperationService<'_> { - type Result = OrderStatusGetResult; - - fn execute( - &self, - request: OperationRequest<OrderStatusGetRequest>, - ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { - let args = OrderStatusArgs { - key: required_order_key(&request)?, - }; - let view = crate::runtime::order::status(self.config, &args).map_err(|error| { - OperationAdapterError::sdk_adapter_failure(request.operation_id(), error) - })?; - status_result::<OrderStatusGetResult>(request.operation_id(), &view) - } -} - -impl OperationService<OrderEventListRequest> for OrderOperationService<'_> { - type Result = OrderEventListResult; - - fn execute( - &self, - request: OperationRequest<OrderEventListRequest>, - ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { - let order_id = string_input(&request, "order_id"); - let view = crate::runtime::order::event_list(self.config, order_id.as_deref()).map_err( - |error| OperationAdapterError::runtime_failure(request.operation_id(), error), - )?; - event_list_result::<OrderEventListResult>(request.operation_id(), &view) - } -} - -impl OperationService<OrderEventWatchRequest> for OrderOperationService<'_> { - type Result = OrderEventWatchResult; - - fn execute( - &self, - request: OperationRequest<OrderEventWatchRequest>, - ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { - let order_id = required_order_key(&request)?; - let action = format!("radroots order status get {order_id}"); - Err(OperationAdapterError::not_implemented_with_detail( - request.operation_id(), - ORDER_EVENT_WATCH_DEFERRED_REASON.to_owned(), - json!({ - "state": "not_implemented", - "order_id": order_id, - "reason": ORDER_EVENT_WATCH_DEFERRED_REASON, - "actions": [action], - }), - )) - } -} - -fn decision_result<R>( - operation_id: &str, - view: &OrderDecisionView, -) -> Result<OperationResult<R>, OperationAdapterError> -where - R: OperationResultData, -{ - match view.disposition() { - CommandDisposition::Success => serialized_target_result::<R, _>(view), - CommandDisposition::ValidationFailed => { - let message = view.reason.clone().unwrap_or_else(|| { - format!( - "order decision failed validation with state `{}`", - view.state - ) - }); - Err(OperationAdapterError::validation_failed_with_detail( - operation_id, - message, - order_decision_error_detail(view), - )) - } - disposition => { - let message = view - .reason - .clone() - .unwrap_or_else(|| format!("order decision finished with state `{}`", view.state)); - if disposition == CommandDisposition::ExternalUnavailable { - let detail = order_decision_error_detail(view); - if !view.failed_relays.is_empty() && view.connected_relays.is_empty() { - Err(OperationAdapterError::network_unavailable_with_detail( - operation_id, - message, - detail, - )) - } else { - Err(OperationAdapterError::operation_unavailable_with_detail( - operation_id, - message, - detail, - )) - } - } else if disposition == CommandDisposition::Unconfigured { - Err(OperationAdapterError::operation_unavailable_with_detail( - operation_id, - message, - order_decision_error_detail(view), - )) - } else if disposition == CommandDisposition::NotFound { - Err(OperationAdapterError::not_found_with_detail( - operation_id, - message, - order_decision_error_detail(view), - )) - } else { - Err(OperationAdapterError::from_command_disposition( - operation_id, - disposition, - message, - )) - } - } - } -} - -fn order_decision_error_detail(view: &OrderDecisionView) -> Value { - json!({ - "state": &view.state, - "order_id": &view.order_id, - "listing_addr": &view.listing_addr, - "listing_event_id": &view.listing_event_id, - "request_event_id": &view.request_event_id, - "root_event_id": &view.root_event_id, - "prev_event_id": &view.prev_event_id, - "event_id": &view.event_id, - "event_kind": view.event_kind, - "inventory": &view.inventory, - "buyer_pubkey": &view.buyer_pubkey, - "seller_pubkey": &view.seller_pubkey, - "decision": &view.decision, - "dry_run": view.dry_run, - "target_relays": &view.target_relays, - "connected_relays": &view.connected_relays, - "acknowledged_relays": &view.acknowledged_relays, - "failed_relays": &view.failed_relays, - "fetched_count": view.fetched_count, - "decoded_count": view.decoded_count, - "skipped_count": view.skipped_count, - "idempotency_key": &view.idempotency_key, - "signer_mode": &view.signer_mode, - "issues": &view.issues, - "actions": &view.actions, - }) -} - -fn cancellation_result<R>( - operation_id: &str, - view: &OrderCancellationView, -) -> Result<OperationResult<R>, OperationAdapterError> -where - R: OperationResultData, -{ - match view.disposition() { - CommandDisposition::Success => serialized_target_result::<R, _>(view), - CommandDisposition::ValidationFailed => { - let message = view.reason.clone().unwrap_or_else(|| { - format!("order cancel failed validation with state `{}`", view.state) - }); - Err(OperationAdapterError::validation_failed_with_detail( - operation_id, - message, - order_cancellation_error_detail(view), - )) - } - disposition => { - let message = view - .reason - .clone() - .unwrap_or_else(|| format!("order cancel finished with state `{}`", view.state)); - if disposition == CommandDisposition::ExternalUnavailable { - let detail = order_cancellation_error_detail(view); - if !view.failed_relays.is_empty() && view.connected_relays.is_empty() { - Err(OperationAdapterError::network_unavailable_with_detail( - operation_id, - message, - detail, - )) - } else { - Err(OperationAdapterError::operation_unavailable_with_detail( - operation_id, - message, - detail, - )) - } - } else if disposition == CommandDisposition::Unconfigured { - Err(OperationAdapterError::operation_unavailable_with_detail( - operation_id, - message, - order_cancellation_error_detail(view), - )) - } else { - Err(OperationAdapterError::from_command_disposition( - operation_id, - disposition, - message, - )) - } - } - } -} - -fn order_cancellation_error_detail(view: &OrderCancellationView) -> Value { - json!({ - "state": &view.state, - "order_id": &view.order_id, - "listing_addr": &view.listing_addr, - "request_event_id": &view.request_event_id, - "decision_event_id": &view.decision_event_id, - "root_event_id": &view.root_event_id, - "prev_event_id": &view.prev_event_id, - "event_id": &view.event_id, - "event_kind": view.event_kind, - "buyer_pubkey": &view.buyer_pubkey, - "seller_pubkey": &view.seller_pubkey, - "cancellation_reason": &view.cancellation_reason, - "dry_run": view.dry_run, - "target_relays": &view.target_relays, - "connected_relays": &view.connected_relays, - "acknowledged_relays": &view.acknowledged_relays, - "failed_relays": &view.failed_relays, - "fetched_count": view.fetched_count, - "decoded_count": view.decoded_count, - "skipped_count": view.skipped_count, - "idempotency_key": &view.idempotency_key, - "signer_mode": &view.signer_mode, - "issues": &view.issues, - "actions": &view.actions, - }) -} - -fn revision_proposal_result<R>( - operation_id: &str, - view: &OrderRevisionProposalView, -) -> Result<OperationResult<R>, OperationAdapterError> -where - R: OperationResultData, -{ - match view.disposition() { - CommandDisposition::Success => serialized_target_result::<R, _>(view), - CommandDisposition::ValidationFailed => { - let message = view.reason.clone().unwrap_or_else(|| { - format!( - "order revision propose failed validation with state `{}`", - view.state - ) - }); - Err(OperationAdapterError::validation_failed_with_detail( - operation_id, - message, - order_revision_proposal_error_detail(view), - )) - } - disposition => { - let message = view.reason.clone().unwrap_or_else(|| { - format!( - "order revision propose finished with state `{}`", - view.state - ) - }); - if disposition == CommandDisposition::ExternalUnavailable { - let detail = order_revision_proposal_error_detail(view); - if !view.failed_relays.is_empty() && view.connected_relays.is_empty() { - Err(OperationAdapterError::network_unavailable_with_detail( - operation_id, - message, - detail, - )) - } else { - Err(OperationAdapterError::operation_unavailable_with_detail( - operation_id, - message, - detail, - )) - } - } else if disposition == CommandDisposition::Unconfigured { - Err(OperationAdapterError::operation_unavailable_with_detail( - operation_id, - message, - order_revision_proposal_error_detail(view), - )) - } else { - Err(OperationAdapterError::from_command_disposition( - operation_id, - disposition, - message, - )) - } - } - } -} - -fn order_revision_proposal_error_detail(view: &OrderRevisionProposalView) -> Value { - json!({ - "state": &view.state, - "order_id": &view.order_id, - "revision_id": &view.revision_id, - "listing_addr": &view.listing_addr, - "request_event_id": &view.request_event_id, - "decision_event_id": &view.decision_event_id, - "root_event_id": &view.root_event_id, - "prev_event_id": &view.prev_event_id, - "event_id": &view.event_id, - "event_kind": view.event_kind, - "items": &view.items, - "economics": &view.economics, - "inventory": &view.inventory, - "buyer_pubkey": &view.buyer_pubkey, - "seller_pubkey": &view.seller_pubkey, - "dry_run": view.dry_run, - "target_relays": &view.target_relays, - "connected_relays": &view.connected_relays, - "acknowledged_relays": &view.acknowledged_relays, - "failed_relays": &view.failed_relays, - "fetched_count": view.fetched_count, - "decoded_count": view.decoded_count, - "skipped_count": view.skipped_count, - "idempotency_key": &view.idempotency_key, - "signer_mode": &view.signer_mode, - "issues": &view.issues, - "actions": &view.actions, - }) -} - -fn revision_decision_result<R>( - operation_id: &str, - view: &OrderRevisionDecisionView, -) -> Result<OperationResult<R>, OperationAdapterError> -where - R: OperationResultData, -{ - match view.disposition() { - CommandDisposition::Success => serialized_target_result::<R, _>(view), - CommandDisposition::ValidationFailed => { - let message = view.reason.clone().unwrap_or_else(|| { - format!( - "order revision {} failed validation with state `{}`", - view.decision.as_deref().unwrap_or("decision"), - view.state - ) - }); - Err(OperationAdapterError::validation_failed_with_detail( - operation_id, - message, - order_revision_decision_error_detail(view), - )) - } - disposition => { - let message = view.reason.clone().unwrap_or_else(|| { - format!( - "order revision {} finished with state `{}`", - view.decision.as_deref().unwrap_or("decision"), - view.state - ) - }); - if disposition == CommandDisposition::ExternalUnavailable { - let detail = order_revision_decision_error_detail(view); - if !view.failed_relays.is_empty() && view.connected_relays.is_empty() { - Err(OperationAdapterError::network_unavailable_with_detail( - operation_id, - message, - detail, - )) - } else { - Err(OperationAdapterError::operation_unavailable_with_detail( - operation_id, - message, - detail, - )) - } - } else if disposition == CommandDisposition::Unconfigured { - Err(OperationAdapterError::operation_unavailable_with_detail( - operation_id, - message, - order_revision_decision_error_detail(view), - )) - } else { - Err(OperationAdapterError::from_command_disposition( - operation_id, - disposition, - message, - )) - } - } - } -} - -fn order_revision_decision_error_detail(view: &OrderRevisionDecisionView) -> Value { - json!({ - "state": &view.state, - "order_id": &view.order_id, - "revision_id": &view.revision_id, - "decision": &view.decision, - "listing_addr": &view.listing_addr, - "request_event_id": &view.request_event_id, - "decision_event_id": &view.decision_event_id, - "agreement_event_id": &view.agreement_event_id, - "root_event_id": &view.root_event_id, - "prev_event_id": &view.prev_event_id, - "event_id": &view.event_id, - "event_kind": view.event_kind, - "economics": &view.economics, - "inventory": &view.inventory, - "buyer_pubkey": &view.buyer_pubkey, - "seller_pubkey": &view.seller_pubkey, - "dry_run": view.dry_run, - "target_relays": &view.target_relays, - "connected_relays": &view.connected_relays, - "acknowledged_relays": &view.acknowledged_relays, - "failed_relays": &view.failed_relays, - "fetched_count": view.fetched_count, - "decoded_count": view.decoded_count, - "skipped_count": view.skipped_count, - "idempotency_key": &view.idempotency_key, - "signer_mode": &view.signer_mode, - "issues": &view.issues, - "actions": &view.actions, - }) -} - -fn status_result<R>( - operation_id: &str, - view: &OrderStatusView, -) -> Result<OperationResult<R>, OperationAdapterError> -where - R: OperationResultData, -{ - match view.disposition() { - CommandDisposition::Success => serialized_target_result::<R, _>(view), - disposition => { - let message = view - .reason - .clone() - .unwrap_or_else(|| format!("order status finished with state `{}`", view.state)); - if disposition == CommandDisposition::ExternalUnavailable { - let detail = order_status_error_detail(view); - if !view.failed_relays.is_empty() && view.connected_relays.is_empty() { - Err(OperationAdapterError::network_unavailable_with_detail( - operation_id, - message, - detail, - )) - } else { - Err(OperationAdapterError::operation_unavailable_with_detail( - operation_id, - message, - detail, - )) - } - } else if disposition == CommandDisposition::Unconfigured { - Err(OperationAdapterError::operation_unavailable_with_detail( - operation_id, - message, - order_status_error_detail(view), - )) - } else { - Err(OperationAdapterError::from_command_disposition( - operation_id, - disposition, - message, - )) - } - } - } -} - -fn order_status_error_detail(view: &OrderStatusView) -> Value { - json!({ - "state": &view.state, - "order_id": &view.order_id, - "request_event_id": &view.request_event_id, - "decision_event_id": &view.decision_event_id, - "agreement_event_id": &view.agreement_event_id, - "listing_event_id": &view.listing_event_id, - "listing_addr": &view.listing_addr, - "buyer_pubkey": &view.buyer_pubkey, - "seller_pubkey": &view.seller_pubkey, - "last_event_id": &view.last_event_id, - "revision": &view.revision, - "inventory": &view.inventory, - "lifecycle": &view.lifecycle, - "reducer_issues": &view.reducer_issues, - "target_relays": &view.target_relays, - "connected_relays": &view.connected_relays, - "failed_relays": &view.failed_relays, - "fetched_count": view.fetched_count, - "decoded_count": view.decoded_count, - "skipped_count": view.skipped_count, - "actions": &view.actions, - }) -} - -fn serialized_target_result<R, T>(value: &T) -> Result<OperationResult<R>, OperationAdapterError> -where - R: OperationResultData, - T: Serialize, -{ - OperationResult::new(R::from_serializable(value)?) -} - -fn submit_result<R>( - operation_id: &str, - view: &OrderSubmitView, -) -> Result<OperationResult<R>, OperationAdapterError> -where - R: OperationResultData, -{ - match view.disposition() { - CommandDisposition::Success => serialized_target_result::<R, _>(view), - disposition => { - let message = view - .reason - .clone() - .unwrap_or_else(|| format!("order submit finished with state `{}`", view.state)); - let detail = order_submit_error_detail(view); - match disposition { - CommandDisposition::NotFound => Err(OperationAdapterError::not_found_with_detail( - operation_id, - message, - detail, - )), - CommandDisposition::ValidationFailed => { - Err(OperationAdapterError::validation_failed_with_detail( - operation_id, - message, - detail, - )) - } - CommandDisposition::Unconfigured => { - Err(OperationAdapterError::operation_unavailable_with_detail( - operation_id, - message, - detail, - )) - } - CommandDisposition::ExternalUnavailable => { - if !view.failed_relays.is_empty() && view.connected_relays.is_empty() { - Err(OperationAdapterError::network_unavailable_with_detail( - operation_id, - message, - detail, - )) - } else { - Err(OperationAdapterError::operation_unavailable_with_detail( - operation_id, - message, - detail, - )) - } - } - _ => Err(OperationAdapterError::from_command_disposition( - operation_id, - disposition, - message, - )), - } - } - } -} - -fn order_app_record_export_result<R>( - operation_id: &str, - view: &OrderAppRecordExportView, -) -> Result<OperationResult<R>, OperationAdapterError> -where - R: OperationResultData, -{ - match view.disposition() { - CommandDisposition::Success => serialized_target_result::<R, _>(view), - CommandDisposition::NotFound => Err(OperationAdapterError::not_found_with_detail( - operation_id, - view.reason.clone().unwrap_or_else(|| { - format!( - "app-authored local order record `{}` was not found", - view.record_id - ) - }), - serde_json::to_value(view).unwrap_or(Value::Null), - )), - CommandDisposition::ValidationFailed => { - Err(OperationAdapterError::validation_failed_with_detail( - operation_id, - view.reason.clone().unwrap_or_else(|| { - format!( - "app-authored local order record `{}` cannot be exported", - view.record_id - ) - }), - serde_json::to_value(view).unwrap_or(Value::Null), - )) - } - disposition => Err(OperationAdapterError::from_command_disposition( - operation_id, - disposition, - view.reason.clone().unwrap_or_else(|| { - format!( - "app-authored local order record export finished with state `{}`", - view.state - ) - }), - )), - } -} - -fn order_rebind_result<R>( - operation_id: &str, - view: &OrderRebindView, -) -> Result<OperationResult<R>, OperationAdapterError> -where - R: OperationResultData, -{ - match view.disposition() { - CommandDisposition::Success => serialized_target_result::<R, _>(view), - CommandDisposition::NotFound => Err(OperationAdapterError::not_found_with_detail( - operation_id, - view.reason - .clone() - .unwrap_or_else(|| format!("order draft `{}` was not found", view.lookup)), - order_rebind_error_detail(view), - )), - CommandDisposition::ValidationFailed => { - Err(OperationAdapterError::validation_failed_with_detail( - operation_id, - view.reason.clone().unwrap_or_else(|| { - format!("order rebind finished with state `{}`", view.state) - }), - order_rebind_error_detail(view), - )) - } - disposition => Err(OperationAdapterError::from_command_disposition( - operation_id, - disposition, - view.reason - .clone() - .unwrap_or_else(|| format!("order rebind finished with state `{}`", view.state)), - )), - } -} - -fn order_rebind_error_detail(view: &OrderRebindView) -> Value { - json!({ - "state": &view.state, - "source": &view.source, - "lookup": &view.lookup, - "file": &view.file, - "dry_run": view.dry_run, - "from_order_id": &view.from_order_id, - "to_order_id": &view.to_order_id, - "order_id_changed": view.order_id_changed, - "from_buyer_account_id": &view.from_buyer_account_id, - "from_buyer_pubkey": &view.from_buyer_pubkey, - "from_buyer_actor_source": &view.from_buyer_actor_source, - "to_buyer_account_id": &view.to_buyer_account_id, - "to_buyer_pubkey": &view.to_buyer_pubkey, - "to_buyer_actor_source": &view.to_buyer_actor_source, - "buyer_pubkey_changed": view.buyer_pubkey_changed, - "existing_request_check": &view.existing_request_check, - "existing_request_event_ids": &view.existing_request_event_ids, - "actions": &view.actions, - }) -} - -fn order_submit_error_detail(view: &OrderSubmitView) -> Value { - json!({ - "state": &view.state, - "source": &view.source, - "order_id": &view.order_id, - "file": &view.file, - "listing_lookup": &view.listing_lookup, - "listing_addr": &view.listing_addr, - "listing_event_id": &view.listing_event_id, - "listing_relays": &view.listing_relays, - "buyer_account_id": &view.buyer_account_id, - "buyer_pubkey": &view.buyer_pubkey, - "seller_pubkey": &view.seller_pubkey, - "event_id": &view.event_id, - "event_kind": view.event_kind, - "dry_run": view.dry_run, - "deduplicated": view.deduplicated, - "target_relays": &view.target_relays, - "connected_relays": &view.connected_relays, - "acknowledged_relays": &view.acknowledged_relays, - "failed_relays": &view.failed_relays, - "idempotency_key": &view.idempotency_key, - "signer_mode": &view.signer_mode, - "issues": &view.issues, - "actions": &view.actions, - }) -} - -fn event_list_result<R>( - operation_id: &str, - view: &crate::view::runtime::OrderEventListView, -) -> Result<OperationResult<R>, OperationAdapterError> -where - R: OperationResultData, -{ - match view.disposition() { - CommandDisposition::Success => serialized_target_result::<R, _>(view), - disposition => { - let message = view.reason.clone().unwrap_or_else(|| { - format!("order event list finished with state `{}`", view.state) - }); - if disposition == CommandDisposition::ExternalUnavailable { - let detail = order_event_list_error_detail(view); - Err(OperationAdapterError::network_unavailable_with_detail( - operation_id, - message, - detail, - )) - } else if disposition == CommandDisposition::Unconfigured { - Err(OperationAdapterError::operation_unavailable_with_detail( - operation_id, - message, - order_event_list_error_detail(view), - )) - } else { - Err(OperationAdapterError::from_command_disposition( - operation_id, - disposition, - message, - )) - } - } - } -} - -fn order_event_list_error_detail(view: &crate::view::runtime::OrderEventListView) -> Value { - json!({ - "state": &view.state, - "seller_pubkey": &view.seller_pubkey, - "target_relays": &view.target_relays, - "connected_relays": &view.connected_relays, - "failed_relays": &view.failed_relays, - "fetched_count": view.fetched_count, - "decoded_count": view.decoded_count, - "skipped_count": view.skipped_count, - "count": view.count, - "actions": &view.actions, - }) -} - -fn required_order_key<P>(request: &OperationRequest<P>) -> Result<String, OperationAdapterError> -where - P: OperationRequestPayload + OperationRequestData, -{ - string_input(request, "order_id") - .or_else(|| string_input(request, "key")) - .ok_or_else(|| { - invalid_input( - request.operation_id(), - "missing required `order_id` input".to_owned(), - ) - }) -} - -fn required_string_input<P>( - request: &OperationRequest<P>, - key: &str, -) -> Result<String, OperationAdapterError> -where - P: OperationRequestPayload + OperationRequestData, -{ - string_input(request, key) - .map(|value| value.trim().to_owned()) - .filter(|value| !value.is_empty()) - .ok_or_else(|| { - invalid_input( - request.operation_id(), - format!("missing required `{key}` input"), - ) - }) -} - -fn string_input<P>(request: &OperationRequest<P>, key: &str) -> Option<String> -where - P: OperationRequestPayload + OperationRequestData, -{ - request - .payload - .input() - .get(key) - .and_then(Value::as_str) - .map(str::to_owned) -} - -fn optional_path_input<P>(request: &OperationRequest<P>, key: &str) -> Option<PathBuf> -where - P: OperationRequestPayload + OperationRequestData, -{ - string_input(request, key).map(PathBuf::from) -} - -fn bool_input<P>(request: &OperationRequest<P>, key: &str) -> Option<bool> -where - P: OperationRequestPayload + OperationRequestData, -{ - request.payload.input().get(key).and_then(Value::as_bool) -} - -fn u32_input<P>(request: &OperationRequest<P>, key: &str) -> Option<u32> -where - P: OperationRequestPayload + OperationRequestData, -{ - request - .payload - .input() - .get(key) - .and_then(Value::as_u64) - .and_then(|value| u32::try_from(value).ok()) -} - -fn map_runtime<T>(result: Result<T, RuntimeError>) -> Result<T, OperationAdapterError> { - result.map_err(|error| OperationAdapterError::Runtime(error.to_string())) -} - -fn invalid_input(operation_id: &str, message: String) -> OperationAdapterError { - OperationAdapterError::InvalidInput { - operation_id: operation_id.to_owned(), - message, - } -} - -#[cfg(test)] -mod tests { - use std::path::{Path, PathBuf}; - - use radroots_runtime_paths::RadrootsMigrationReport; - use radroots_secret_vault::RadrootsSecretBackend; - use serde_json::{Map, Value}; - use tempfile::tempdir; - - use super::{OrderOperationService, decision_result}; - use crate::ops::{ - OperationAdapter, OperationContext, OperationData, OperationRequest, OrderAcceptRequest, - OrderAcceptResult, OrderCancelRequest, OrderDeclineRequest, OrderDeclineResult, - OrderEventListRequest, OrderEventWatchRequest, OrderGetRequest, OrderListRequest, - OrderRevisionAcceptRequest, OrderRevisionDeclineRequest, OrderRevisionProposeRequest, - OrderStatusGetRequest, OrderSubmitRequest, - }; - use crate::runtime::config::{ - AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig, - LocalConfig, LoggingConfig, MigrationConfig, MycConfig, OutputConfig, OutputFormat, - PathsConfig, PublishConfig, PublishTransport, PublishTransportSource, RelayConfig, - RelayConfigSource, RelayPublishPolicy, RpcConfig, RuntimeConfig, SignerBackend, - SignerConfig, Verbosity, - }; - use crate::view::runtime::OrderDecisionView; - - #[test] - fn order_service_get_and_list_preserve_order_truth() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(OrderOperationService::new(&config)); - let get = OperationRequest::new( - OperationContext::default(), - OrderGetRequest::from_data(data(&[("order_id", "ord_missing")])), - ) - .expect("order get request"); - let get_envelope = service - .execute(get) - .expect("order get result") - .to_envelope(OperationContext::default().envelope_context("req_order_get")) - .expect("order get envelope"); - - assert_eq!(get_envelope.operation_id, "order.get"); - assert_eq!(get_envelope.result["state"], "missing"); - assert_eq!(get_envelope.result["actions"][0], "radroots order list"); - assert_eq!(get_envelope.result["actions"][1], "radroots basket create"); - - let list = OperationRequest::new(OperationContext::default(), OrderListRequest::default()) - .expect("order list request"); - let list_envelope = service - .execute(list) - .expect("order list result") - .to_envelope(OperationContext::default().envelope_context("req_order_list")) - .expect("order list envelope"); - assert_eq!(list_envelope.operation_id, "order.list"); - assert_eq!(list_envelope.result["state"], "empty"); - assert_eq!(list_envelope.result["actions"][0], "radroots basket create"); - } - - #[test] - fn order_submit_requires_approval_token() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(OrderOperationService::new(&config)); - let submit = OperationRequest::new( - OperationContext::default(), - OrderSubmitRequest::from_data(data(&[("order_id", "ord_missing")])), - ) - .expect("order submit request"); - let error = service.execute(submit).expect_err("approval required"); - - assert!(format!("{error}").contains("approval_token")); - assert_eq!(error.to_output_error().code, "approval_required"); - assert_eq!(error.to_output_error().exit_code, 6); - } - - #[test] - fn order_submit_with_approval_returns_not_found_for_missing_order() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(OrderOperationService::new(&config)); - let mut context = OperationContext::default(); - context.approval_token = Some("approve_test".to_owned()); - let submit = OperationRequest::new( - context.clone(), - OrderSubmitRequest::from_data(data(&[("order_id", "ord_missing")])), - ) - .expect("order submit request"); - let error = service.execute(submit).expect_err("missing order error"); - let output_error = error.to_output_error(); - - assert_eq!(output_error.code, "not_found"); - assert_eq!(output_error.exit_code, 4); - assert!(output_error.message.contains("ord_missing")); - let envelope = crate::out::envelope::OutputEnvelope::failure( - "order.submit", - output_error, - context.envelope_context("req_order_submit"), - ); - let detail = envelope.errors[0].detail.as_ref().expect("submit detail"); - assert_eq!(detail["state"], "missing"); - assert_eq!(detail["order_id"], "ord_missing"); - assert_eq!(detail["actions"][0], "radroots order list"); - assert_eq!(detail["actions"][1], "radroots basket create"); - assert_eq!( - envelope.next_actions[0].command.as_deref(), - Some("radroots order list") - ); - assert_eq!( - envelope.next_actions[1].command.as_deref(), - Some("radroots basket create") - ); - } - - #[test] - fn order_accept_requires_approval_token() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(OrderOperationService::new(&config)); - let accept = OperationRequest::new( - OperationContext::default(), - OrderAcceptRequest::from_data(data(&[("order_id", "ord_pending")])), - ) - .expect("order accept request"); - let error = service.execute(accept).expect_err("approval required"); - - assert_eq!(error.to_output_error().code, "approval_required"); - } - - #[test] - fn order_accept_unconfigured_preserves_decision_detail() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(OrderOperationService::new(&config)); - let mut context = OperationContext::default(); - context.dry_run = true; - let accept = OperationRequest::new( - context, - OrderAcceptRequest::from_data(data(&[("order_id", "ord_pending")])), - ) - .expect("order accept request"); - let error = service - .execute(accept) - .expect_err("order accept unconfigured"); - let output_error = error.to_output_error(); - let detail = output_error.detail.as_ref().expect("decision detail"); - - assert_eq!(output_error.code, "operation_unavailable"); - assert_eq!(detail["state"], "unconfigured"); - assert_eq!(detail["order_id"], "ord_pending"); - assert_eq!(detail["decision"], "accepted"); - assert!(detail["target_relays"].as_array().unwrap().is_empty()); - } - - #[test] - fn order_decision_already_decided_maps_to_validation_failure() { - let view = already_decided_view(); - let error = match decision_result::<OrderAcceptResult>("order.accept", &view) { - Ok(_) => panic!("already decided view should fail validation"), - Err(error) => error, - }; - let output_error = error.to_output_error(); - - assert_eq!(output_error.code, "validation_failed"); - assert_eq!(output_error.exit_code, 10); - let detail = output_error.detail.expect("validation detail"); - assert_eq!(detail["state"], "already_decided"); - assert_eq!(detail["operation_id"], "order.accept"); - assert_eq!(detail["listing_event_id"], "l".repeat(64)); - assert_eq!(detail["event_id"], "d".repeat(64)); - assert_eq!(detail["event_kind"], 3423); - assert_eq!(detail["idempotency_key"], "idem_test"); - assert_eq!(detail["signer_mode"], "local"); - assert_eq!(detail["actions"][0], "radroots order status get ord_test"); - } - - #[test] - fn order_decision_invalid_maps_to_validation_failure() { - let view = invalid_decision_view(); - let error = match decision_result::<OrderDeclineResult>("order.decline", &view) { - Ok(_) => panic!("invalid view should fail validation"), - Err(error) => error, - }; - let output_error = error.to_output_error(); - - assert_eq!(output_error.code, "validation_failed"); - assert_eq!(output_error.exit_code, 10); - assert_eq!( - output_error.message, - "active order events for `ord_test` failed reducer validation" - ); - let detail = output_error.detail.expect("validation detail"); - assert_eq!(detail["state"], "invalid"); - assert_eq!(detail["operation_id"], "order.decline"); - assert_eq!(detail["listing_event_id"], "l".repeat(64)); - assert_eq!(detail["event_id"], Value::Null); - assert_eq!(detail["event_kind"], Value::Null); - assert_eq!(detail["idempotency_key"], "idem_test"); - assert_eq!(detail["signer_mode"], "local"); - } - - #[test] - fn order_decline_requires_reason_before_approval() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(OrderOperationService::new(&config)); - let decline = OperationRequest::new( - OperationContext::default(), - OrderDeclineRequest::from_data(data(&[("order_id", "ord_pending")])), - ) - .expect("order decline request"); - let error = service.execute(decline).expect_err("reason required"); - let output_error = error.to_output_error(); - - assert_eq!(output_error.code, "invalid_input"); - assert!(output_error.message.contains("reason")); - } - - #[test] - fn order_decline_rejects_blank_reason_before_approval() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(OrderOperationService::new(&config)); - let decline = OperationRequest::new( - OperationContext::default(), - OrderDeclineRequest::from_data(data(&[("order_id", "ord_pending"), ("reason", " ")])), - ) - .expect("order decline request"); - let error = service.execute(decline).expect_err("reason required"); - let output_error = error.to_output_error(); - - assert_eq!(output_error.code, "invalid_input"); - assert!(output_error.message.contains("reason")); - } - - #[test] - fn order_cancel_requires_reason_before_approval() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(OrderOperationService::new(&config)); - let cancel = OperationRequest::new( - OperationContext::default(), - OrderCancelRequest::from_data(data(&[("order_id", "ord_pending")])), - ) - .expect("order cancel request"); - let error = service.execute(cancel).expect_err("reason required"); - let output_error = error.to_output_error(); - - assert_eq!(output_error.code, "invalid_input"); - assert!(output_error.message.contains("reason")); - } - - #[test] - fn order_cancel_requires_approval_token() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(OrderOperationService::new(&config)); - let cancel = OperationRequest::new( - OperationContext::default(), - OrderCancelRequest::from_data(data(&[ - ("order_id", "ord_pending"), - ("reason", "changed plans"), - ])), - ) - .expect("order cancel request"); - let error = service.execute(cancel).expect_err("approval required"); - - assert_eq!(error.to_output_error().code, "approval_required"); - } - - #[test] - fn order_revision_propose_requires_reason_before_approval() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(OrderOperationService::new(&config)); - let revision = OperationRequest::new( - OperationContext::default(), - OrderRevisionProposeRequest::from_data(data(&[("order_id", "ord_pending")])), - ) - .expect("order revision request"); - let error = service.execute(revision).expect_err("reason required"); - let output_error = error.to_output_error(); - - assert_eq!(output_error.code, "invalid_input"); - assert!(output_error.message.contains("reason")); - } - - #[test] - fn order_revision_propose_requires_approval_token() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(OrderOperationService::new(&config)); - let mut input = data(&[ - ("order_id", "ord_pending"), - ("reason", "update count"), - ("bin_id", "bin-1"), - ]); - input.insert("bin_count".to_owned(), Value::from(3)); - let revision = OperationRequest::new( - OperationContext::default(), - OrderRevisionProposeRequest::from_data(input), - ) - .expect("order revision request"); - let error = service.execute(revision).expect_err("approval required"); - - assert_eq!(error.to_output_error().code, "approval_required"); - } - - #[test] - fn order_revision_accept_requires_approval_token() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(OrderOperationService::new(&config)); - let revision = OperationRequest::new( - OperationContext::default(), - OrderRevisionAcceptRequest::from_data(data(&[ - ("order_id", "ord_pending"), - ("revision_id", "rev_pending"), - ])), - ) - .expect("order revision accept request"); - let error = service.execute(revision).expect_err("approval required"); - - assert_eq!(error.to_output_error().code, "approval_required"); - } - - #[test] - fn order_revision_decline_requires_reason_before_approval() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(OrderOperationService::new(&config)); - let revision = OperationRequest::new( - OperationContext::default(), - OrderRevisionDeclineRequest::from_data(data(&[ - ("order_id", "ord_pending"), - ("revision_id", "rev_pending"), - ])), - ) - .expect("order revision decline request"); - let error = service.execute(revision).expect_err("reason required"); - let output_error = error.to_output_error(); - - assert_eq!(output_error.code, "invalid_input"); - assert!(output_error.message.contains("reason")); - } - - #[test] - fn order_revision_decline_requires_approval_token() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(OrderOperationService::new(&config)); - let revision = OperationRequest::new( - OperationContext::default(), - OrderRevisionDeclineRequest::from_data(data(&[ - ("order_id", "ord_pending"), - ("revision_id", "rev_pending"), - ("reason", "keep original order"), - ])), - ) - .expect("order revision decline request"); - let error = service.execute(revision).expect_err("approval required"); - - assert_eq!(error.to_output_error().code, "approval_required"); - } - - #[test] - fn order_status_get_uses_local_sdk_projection_without_relay() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(OrderOperationService::new(&config)); - let status = OperationRequest::new( - OperationContext::default(), - OrderStatusGetRequest::from_data(data(&[("order_id", "ord_pending")])), - ) - .expect("order status request"); - let envelope = service - .execute(status) - .expect("status result") - .to_envelope(OperationContext::default().envelope_context("req_order_status")) - .expect("status envelope"); - - assert_eq!(envelope.operation_id, "order.status.get"); - assert_eq!(envelope.result["state"], "missing"); - assert_eq!(envelope.result["source"], "SDK local order projection"); - assert_eq!( - envelope.result["actor_context_source"], - "sdk_local_projection" - ); - assert_eq!(envelope.result["order_id"], "ord_pending"); - assert_eq!(envelope.result["fetched_count"], 0); - assert_eq!(envelope.result["decoded_count"], 0); - assert_eq!(envelope.result["skipped_count"], 0); - assert!(envelope.next_actions.is_empty()); - } - - #[test] - fn order_event_list_requires_relay_configuration() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(OrderOperationService::new(&config)); - let context = OperationContext::default(); - let request = OperationRequest::new(context.clone(), OrderEventListRequest::default()) - .expect("order event list request"); - let output_error = service - .execute(request) - .expect_err("order event list unconfigured") - .to_output_error(); - let envelope = crate::out::envelope::OutputEnvelope::failure( - "order.event.list", - output_error, - context.envelope_context("req_order_event_list"), - ); - - assert_eq!(envelope.errors[0].code, "operation_unavailable"); - assert_eq!(envelope.errors[0].exit_code, 3); - assert!(envelope.errors[0].message.contains("configured relay")); - assert_eq!( - envelope.errors[0].detail.as_ref().unwrap()["actions"][0], - "radroots --relay wss://relay.example.com order event list" - ); - assert_eq!( - envelope.next_actions[0].command.as_deref(), - Some("radroots --relay wss://relay.example.com order event list") - ); - } - - #[test] - fn order_event_list_requires_seller_account_with_account_action() { - let dir = tempdir().expect("tempdir"); - let mut config = sample_config(dir.path()); - config.relay.urls = vec!["ws://127.0.0.1:9".to_owned()]; - let service = OperationAdapter::new(OrderOperationService::new(&config)); - let context = OperationContext::default(); - let request = OperationRequest::new(context.clone(), OrderEventListRequest::default()) - .expect("order event list request"); - let output_error = service - .execute(request) - .expect_err("order event list missing account") - .to_output_error(); - let envelope = crate::out::envelope::OutputEnvelope::failure( - "order.event.list", - output_error, - context.envelope_context("req_order_event_list"), - ); - - assert_eq!(envelope.errors[0].code, "operation_unavailable"); - assert!( - envelope.errors[0] - .message - .contains("selected seller account") - ); - assert_eq!( - envelope.errors[0].detail.as_ref().unwrap()["actions"][0], - "radroots account create" - ); - assert_eq!( - envelope.next_actions[0].command.as_deref(), - Some("radroots account create") - ); - } - - #[test] - fn order_event_watch_returns_deferred_error_with_target_action() { - let dir = tempdir().expect("tempdir"); - let config = sample_config(dir.path()); - let service = OperationAdapter::new(OrderOperationService::new(&config)); - let request = OperationRequest::new( - OperationContext::default(), - OrderEventWatchRequest::from_data(data(&[("order_id", "ord_missing")])), - ) - .expect("order event watch request"); - let error = service - .execute(request) - .expect_err("order event watch deferred"); - let envelope = crate::out::envelope::OutputEnvelope::failure( - "order.event.watch", - error.to_output_error(), - OperationContext::default().envelope_context("req_order_watch"), - ); - - assert_eq!(envelope.operation_id, "order.event.watch"); - assert!(envelope.result.is_null()); - assert_eq!(envelope.errors[0].code, "not_implemented"); - assert_eq!( - envelope.errors[0].detail.as_ref().unwrap()["state"], - "not_implemented" - ); - assert_eq!( - envelope.errors[0].detail.as_ref().unwrap()["order_id"], - "ord_missing" - ); - assert_eq!( - envelope.next_actions[0].command.as_deref(), - Some("radroots order status get ord_missing") - ); - } - - fn sample_config(root: &Path) -> RuntimeConfig { - let data = root.join("data"); - let logs = root.join("logs"); - let secrets = root.join("secrets"); - RuntimeConfig { - output: OutputConfig { - format: OutputFormat::Human, - verbosity: Verbosity::Normal, - color: true, - dry_run: false, - }, - interaction: InteractionConfig { - input_enabled: true, - assume_yes: false, - stdin_tty: false, - stdout_tty: false, - prompts_allowed: false, - confirmations_allowed: false, - }, - paths: PathsConfig { - profile: "interactive_user".into(), - profile_source: "test".into(), - allowed_profiles: vec!["interactive_user".into(), "repo_local".into()], - root_source: "test".into(), - repo_local_root: None, - repo_local_root_source: None, - subordinate_path_override_source: "runtime_config".into(), - app_namespace: "apps/cli".into(), - shared_accounts_namespace: "shared/accounts".into(), - shared_identities_namespace: "shared/identities".into(), - app_config_path: root.join("config/apps/cli/config.toml"), - workspace_config_path: None, - app_data_root: data.join("apps/cli"), - app_logs_root: logs.join("apps/cli"), - shared_accounts_data_root: data.join("shared/accounts"), - shared_accounts_secrets_root: secrets.join("shared/accounts"), - default_identity_path: secrets.join("shared/identities/default.json"), - }, - migration: MigrationConfig { - report: RadrootsMigrationReport::empty(), - }, - logging: LoggingConfig { - filter: "info".into(), - directory: None, - stdout: false, - }, - account: AccountConfig { - selector: None, - store_path: data.join("shared/accounts/store.json"), - secrets_dir: secrets.join("shared/accounts"), - secret_backend: RadrootsSecretBackend::EncryptedFile, - secret_fallback: None, - }, - account_secret_contract: AccountSecretContractConfig { - default_backend: "host_vault".into(), - default_fallback: Some("encrypted_file".into()), - allowed_backends: vec!["host_vault".into(), "encrypted_file".into()], - host_vault_policy: Some("desktop".into()), - uses_protected_store: true, - }, - identity: IdentityConfig { - path: secrets.join("shared/identities/default.json"), - }, - signer: SignerConfig { - backend: SignerBackend::Local, - }, - publish: PublishConfig { - transport: PublishTransport::DirectNostrRelay, - source: PublishTransportSource::Defaults, - radrootsd_proxy: crate::runtime::config::RadrootsdProxyConfig::default(), - }, - relay: RelayConfig { - urls: Vec::new(), - publish_policy: RelayPublishPolicy::Any, - source: RelayConfigSource::Defaults, - }, - local: LocalConfig { - root: data.join("apps/cli/replica"), - replica_db_path: data.join("apps/cli/replica/replica.sqlite"), - backups_dir: data.join("apps/cli/replica/backups"), - exports_dir: data.join("apps/cli/replica/exports"), - }, - myc: MycConfig { - executable: PathBuf::from("myc"), - status_timeout_ms: 2_000, - }, - hyf: HyfConfig { - enabled: false, - executable: PathBuf::from("hyfd"), - }, - rpc: RpcConfig { - url: "http://127.0.0.1:7070".into(), - }, - rhi: crate::runtime::config::RhiConfig { - trusted_worker_pubkeys: Vec::new(), - }, - capability_bindings: Vec::new(), - } - } - - fn data(entries: &[(&str, &str)]) -> OperationData { - entries - .iter() - .map(|(key, value)| ((*key).to_owned(), Value::String((*value).to_owned()))) - .collect::<Map<String, Value>>() - } - - fn already_decided_view() -> OrderDecisionView { - OrderDecisionView { - state: "already_decided".to_owned(), - source: "test".to_owned(), - order_id: "ord_test".to_owned(), - listing_addr: Some("30402:seller:listing".to_owned()), - buyer_pubkey: Some("b".repeat(64)), - seller_pubkey: Some("s".repeat(64)), - decision: "accepted".to_owned(), - request_event_id: Some("r".repeat(64)), - listing_event_id: Some("l".repeat(64)), - root_event_id: Some("r".repeat(64)), - prev_event_id: Some("r".repeat(64)), - event_id: Some("d".repeat(64)), - event_kind: Some(3423), - inventory: None, - dry_run: false, - target_relays: vec!["ws://relay.test".to_owned()], - connected_relays: vec!["ws://relay.test".to_owned()], - acknowledged_relays: Vec::new(), - failed_relays: Vec::new(), - fetched_count: 2, - decoded_count: 2, - skipped_count: 0, - idempotency_key: Some("idem_test".to_owned()), - signer_mode: Some("local".to_owned()), - reason: Some( - "order accept refused because order `ord_test` already has a visible `accepted` seller decision" - .to_owned(), - ), - issues: Vec::new(), - actions: vec!["radroots order status get ord_test".to_owned()], - } - } - - fn invalid_decision_view() -> OrderDecisionView { - let mut view = already_decided_view(); - view.state = "invalid".to_owned(); - view.decision = "declined".to_owned(); - view.event_id = None; - view.event_kind = None; - view.reason = - Some("active order events for `ord_test` failed reducer validation".to_owned()); - view - } -} diff --git a/src/ops/exec/runtime.rs b/src/ops/exec/runtime.rs @@ -293,6 +293,7 @@ mod tests { fn sample_config(root: &Path, relays: Vec<String>) -> RuntimeConfig { let data = root.join("data"); + let cache = root.join("cache"); let logs = root.join("logs"); let secrets = root.join("secrets"); RuntimeConfig { @@ -324,6 +325,7 @@ mod tests { app_config_path: root.join("config/apps/cli/config.toml"), workspace_config_path: None, app_data_root: data.join("apps/cli"), + shared_cache_root: cache.clone(), app_logs_root: logs.join("apps/cli"), shared_accounts_data_root: data.join("shared/accounts"), shared_accounts_secrets_root: secrets.join("shared/accounts"), diff --git a/src/ops/exec/trade.rs b/src/ops/exec/trade.rs @@ -0,0 +1,1890 @@ +use std::path::PathBuf; + +use serde::Serialize; +use serde_json::{Value, json}; + +use crate::cli::global::{ + RecordLookupArgs, TradeAppRecordExportArgs, TradeCancelArgs, TradeDecisionArg, + TradeDecisionArgs, TradeRebindArgs, TradeRevisionDecisionArg, TradeRevisionDecisionArgs, + TradeRevisionProposeArgs, TradeStatusArgs, TradeSubmitArgs, +}; +use crate::ops::{ + OperationAdapterError, OperationRequest, OperationRequestData, OperationRequestPayload, + OperationResult, OperationResultData, OperationService, TradeAcceptRequest, TradeAcceptResult, + TradeAppExportRequest, TradeAppExportResult, TradeAppListRequest, TradeAppListResult, + TradeCancelRequest, TradeCancelResult, TradeDeclineRequest, TradeDeclineResult, + TradeEventListRequest, TradeEventListResult, TradeEventWatchRequest, TradeEventWatchResult, + TradeGetRequest, TradeGetResult, TradeListRequest, TradeListResult, TradeRebindRequest, + TradeRebindResult, TradeRevisionAcceptRequest, TradeRevisionAcceptResult, + TradeRevisionDeclineRequest, TradeRevisionDeclineResult, TradeRevisionProposeRequest, + TradeRevisionProposeResult, TradeStatusGetRequest, TradeStatusGetResult, TradeSubmitRequest, + TradeSubmitResult, +}; +use crate::runtime::RuntimeError; +use crate::runtime::config::RuntimeConfig; +use crate::view::runtime::{ + CommandDisposition, OrderAppRecordExportView, OrderCancellationView, OrderDecisionView, + OrderRebindView, OrderRevisionDecisionView, OrderRevisionProposalView, OrderStatusView, + OrderSubmitView, +}; + +const TRADE_EVENT_WATCH_DEFERRED_REASON: &str = "relay-backed trade event watch is not implemented"; + +pub struct TradeOperationService<'a> { + config: &'a RuntimeConfig, +} + +impl<'a> TradeOperationService<'a> { + pub fn new(config: &'a RuntimeConfig) -> Self { + Self { config } + } +} + +impl OperationService<TradeSubmitRequest> for TradeOperationService<'_> { + type Result = TradeSubmitResult; + + fn execute( + &self, + request: OperationRequest<TradeSubmitRequest>, + ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { + if request.context.requires_approval_token() { + return Err(OperationAdapterError::approval_required( + request.operation_id(), + )); + } + + let key = required_trade_key(&request)?; + let args = TradeSubmitArgs { + key, + idempotency_key: request + .context + .idempotency_key + .clone() + .or_else(|| string_input(&request, "idempotency_key")), + }; + let mut config = self.config.clone(); + if request.context.dry_run { + config.output.dry_run = true; + } + let view = crate::runtime::order::submit(&config, &args).map_err(|error| { + OperationAdapterError::sdk_adapter_failure(request.operation_id(), error) + })?; + submit_result::<TradeSubmitResult>(request.operation_id(), &view) + } +} + +impl OperationService<TradeGetRequest> for TradeOperationService<'_> { + type Result = TradeGetResult; + + fn execute( + &self, + request: OperationRequest<TradeGetRequest>, + ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { + let args = RecordLookupArgs { + key: required_trade_key(&request)?, + }; + let view = map_runtime(crate::runtime::order::get(self.config, &args))?; + serialized_target_result::<TradeGetResult, _>(&view) + } +} + +impl OperationService<TradeListRequest> for TradeOperationService<'_> { + type Result = TradeListResult; + + fn execute( + &self, + _request: OperationRequest<TradeListRequest>, + ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { + let view = map_runtime(crate::runtime::order::list(self.config))?; + serialized_target_result::<TradeListResult, _>(&view) + } +} + +impl OperationService<TradeAppListRequest> for TradeOperationService<'_> { + type Result = TradeAppListResult; + + fn execute( + &self, + _request: OperationRequest<TradeAppListRequest>, + ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { + let view = map_runtime(crate::runtime::order::app_record_list(self.config))?; + serialized_target_result::<TradeAppListResult, _>(&view) + } +} + +impl OperationService<TradeAppExportRequest> for TradeOperationService<'_> { + type Result = TradeAppExportResult; + + fn execute( + &self, + request: OperationRequest<TradeAppExportRequest>, + ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { + let args = TradeAppRecordExportArgs { + record_id: required_string_input(&request, "record_id")?, + output: optional_path_input(&request, "output"), + }; + let mut config = self.config.clone(); + if request.context.dry_run { + config.output.dry_run = true; + } + let view = crate::runtime::order::app_record_export(&config, &args).map_err(|error| { + OperationAdapterError::runtime_failure(request.operation_id(), error) + })?; + order_app_record_export_result::<TradeAppExportResult>(request.operation_id(), &view) + } +} + +impl OperationService<TradeRebindRequest> for TradeOperationService<'_> { + type Result = TradeRebindResult; + + fn execute( + &self, + request: OperationRequest<TradeRebindRequest>, + ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { + let args = TradeRebindArgs { + key: required_trade_key(&request)?, + selector: required_string_input(&request, "selector")?, + }; + if request.context.dry_run { + let view = + crate::runtime::order::rebind_preflight(self.config, &args).map_err(|error| { + OperationAdapterError::runtime_failure(request.operation_id(), error) + })?; + return order_rebind_result::<TradeRebindResult>(request.operation_id(), &view); + } + if request.context.requires_approval_token() { + return Err(OperationAdapterError::approval_required( + request.operation_id(), + )); + } + + let view = crate::runtime::order::rebind(self.config, &args).map_err(|error| { + OperationAdapterError::runtime_failure(request.operation_id(), error) + })?; + order_rebind_result::<TradeRebindResult>(request.operation_id(), &view) + } +} + +impl OperationService<TradeAcceptRequest> for TradeOperationService<'_> { + type Result = TradeAcceptResult; + + fn execute( + &self, + request: OperationRequest<TradeAcceptRequest>, + ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { + if request.context.requires_approval_token() { + return Err(OperationAdapterError::approval_required( + request.operation_id(), + )); + } + + let args = TradeDecisionArgs { + key: required_trade_key(&request)?, + decision: TradeDecisionArg::Accept, + reason: None, + idempotency_key: request + .context + .idempotency_key + .clone() + .or_else(|| string_input(&request, "idempotency_key")), + }; + let mut config = self.config.clone(); + if request.context.dry_run { + config.output.dry_run = true; + } + let view = crate::runtime::order::decide(&config, &args).map_err(|error| { + OperationAdapterError::runtime_failure(request.operation_id(), error) + })?; + decision_result::<TradeAcceptResult>(request.operation_id(), &view) + } +} + +impl OperationService<TradeDeclineRequest> for TradeOperationService<'_> { + type Result = TradeDeclineResult; + + fn execute( + &self, + request: OperationRequest<TradeDeclineRequest>, + ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { + let reason = string_input(&request, "reason") + .map(|reason| reason.trim().to_owned()) + .filter(|reason| !reason.is_empty()) + .ok_or_else(|| { + invalid_input( + request.operation_id(), + "missing required `reason` input".to_owned(), + ) + })?; + if request.context.requires_approval_token() { + return Err(OperationAdapterError::approval_required( + request.operation_id(), + )); + } + + let args = TradeDecisionArgs { + key: required_trade_key(&request)?, + decision: TradeDecisionArg::Decline, + reason: Some(reason), + idempotency_key: request + .context + .idempotency_key + .clone() + .or_else(|| string_input(&request, "idempotency_key")), + }; + let mut config = self.config.clone(); + if request.context.dry_run { + config.output.dry_run = true; + } + let view = crate::runtime::order::decide(&config, &args).map_err(|error| { + OperationAdapterError::runtime_failure(request.operation_id(), error) + })?; + decision_result::<TradeDeclineResult>(request.operation_id(), &view) + } +} + +impl OperationService<TradeCancelRequest> for TradeOperationService<'_> { + type Result = TradeCancelResult; + + fn execute( + &self, + request: OperationRequest<TradeCancelRequest>, + ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { + let reason = string_input(&request, "reason") + .map(|reason| reason.trim().to_owned()) + .filter(|reason| !reason.is_empty()) + .ok_or_else(|| { + invalid_input( + request.operation_id(), + "missing required `reason` input".to_owned(), + ) + })?; + if request.context.requires_approval_token() { + return Err(OperationAdapterError::approval_required( + request.operation_id(), + )); + } + + let args = TradeCancelArgs { + key: required_trade_key(&request)?, + reason, + idempotency_key: request + .context + .idempotency_key + .clone() + .or_else(|| string_input(&request, "idempotency_key")), + }; + let mut config = self.config.clone(); + if request.context.dry_run { + config.output.dry_run = true; + } + let view = crate::runtime::order::cancel(&config, &args).map_err(|error| { + OperationAdapterError::runtime_failure(request.operation_id(), error) + })?; + cancellation_result::<TradeCancelResult>(request.operation_id(), &view) + } +} + +impl OperationService<TradeRevisionProposeRequest> for TradeOperationService<'_> { + type Result = TradeRevisionProposeResult; + + fn execute( + &self, + request: OperationRequest<TradeRevisionProposeRequest>, + ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { + let reason = string_input(&request, "reason") + .map(|reason| reason.trim().to_owned()) + .filter(|reason| !reason.is_empty()) + .ok_or_else(|| { + invalid_input( + request.operation_id(), + "missing required `reason` input".to_owned(), + ) + })?; + if request.context.requires_approval_token() { + return Err(OperationAdapterError::approval_required( + request.operation_id(), + )); + } + + let args = TradeRevisionProposeArgs { + key: required_trade_key(&request)?, + reason, + bin_id: string_input(&request, "bin_id"), + bin_count: u32_input(&request, "bin_count"), + adjustment_id: string_input(&request, "adjustment_id"), + adjustment_effect: string_input(&request, "adjustment_effect"), + adjustment_amount: string_input(&request, "adjustment_amount"), + adjustment_currency: string_input(&request, "adjustment_currency"), + adjustment_reason: string_input(&request, "adjustment_reason"), + idempotency_key: request + .context + .idempotency_key + .clone() + .or_else(|| string_input(&request, "idempotency_key")), + }; + let mut config = self.config.clone(); + if request.context.dry_run { + config.output.dry_run = true; + } + let view = crate::runtime::order::revision_propose(&config, &args).map_err(|error| { + OperationAdapterError::runtime_failure(request.operation_id(), error) + })?; + revision_proposal_result::<TradeRevisionProposeResult>(request.operation_id(), &view) + } +} + +impl OperationService<TradeRevisionAcceptRequest> for TradeOperationService<'_> { + type Result = TradeRevisionAcceptResult; + + fn execute( + &self, + request: OperationRequest<TradeRevisionAcceptRequest>, + ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { + let args = TradeRevisionDecisionArgs { + key: required_trade_key(&request)?, + revision_id: required_string_input(&request, "revision_id")?, + decision: TradeRevisionDecisionArg::Accept, + reason: None, + idempotency_key: request + .context + .idempotency_key + .clone() + .or_else(|| string_input(&request, "idempotency_key")), + }; + if request.context.requires_approval_token() { + return Err(OperationAdapterError::approval_required( + request.operation_id(), + )); + } + + let mut config = self.config.clone(); + if request.context.dry_run { + config.output.dry_run = true; + } + let view = crate::runtime::order::revision_decide(&config, &args).map_err(|error| { + OperationAdapterError::runtime_failure(request.operation_id(), error) + })?; + revision_decision_result::<TradeRevisionAcceptResult>(request.operation_id(), &view) + } +} + +impl OperationService<TradeRevisionDeclineRequest> for TradeOperationService<'_> { + type Result = TradeRevisionDeclineResult; + + fn execute( + &self, + request: OperationRequest<TradeRevisionDeclineRequest>, + ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { + let reason = string_input(&request, "reason") + .map(|reason| reason.trim().to_owned()) + .filter(|reason| !reason.is_empty()) + .ok_or_else(|| { + invalid_input( + request.operation_id(), + "missing required `reason` input".to_owned(), + ) + })?; + let args = TradeRevisionDecisionArgs { + key: required_trade_key(&request)?, + revision_id: required_string_input(&request, "revision_id")?, + decision: TradeRevisionDecisionArg::Decline, + reason: Some(reason), + idempotency_key: request + .context + .idempotency_key + .clone() + .or_else(|| string_input(&request, "idempotency_key")), + }; + if request.context.requires_approval_token() { + return Err(OperationAdapterError::approval_required( + request.operation_id(), + )); + } + + let mut config = self.config.clone(); + if request.context.dry_run { + config.output.dry_run = true; + } + let view = crate::runtime::order::revision_decide(&config, &args).map_err(|error| { + OperationAdapterError::runtime_failure(request.operation_id(), error) + })?; + revision_decision_result::<TradeRevisionDeclineResult>(request.operation_id(), &view) + } +} + +impl OperationService<TradeStatusGetRequest> for TradeOperationService<'_> { + type Result = TradeStatusGetResult; + + fn execute( + &self, + request: OperationRequest<TradeStatusGetRequest>, + ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { + let args = TradeStatusArgs { + key: required_trade_key(&request)?, + }; + let view = crate::runtime::order::status(self.config, &args).map_err(|error| { + OperationAdapterError::sdk_adapter_failure(request.operation_id(), error) + })?; + status_result::<TradeStatusGetResult>(request.operation_id(), &view) + } +} + +impl OperationService<TradeEventListRequest> for TradeOperationService<'_> { + type Result = TradeEventListResult; + + fn execute( + &self, + request: OperationRequest<TradeEventListRequest>, + ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { + let trade_id = string_input(&request, "trade_id"); + let view = crate::runtime::order::event_list(self.config, trade_id.as_deref()).map_err( + |error| OperationAdapterError::runtime_failure(request.operation_id(), error), + )?; + event_list_result::<TradeEventListResult>(request.operation_id(), &view) + } +} + +impl OperationService<TradeEventWatchRequest> for TradeOperationService<'_> { + type Result = TradeEventWatchResult; + + fn execute( + &self, + request: OperationRequest<TradeEventWatchRequest>, + ) -> Result<OperationResult<Self::Result>, OperationAdapterError> { + let trade_id = required_trade_key(&request)?; + let action = format!("radroots trade status get {trade_id}"); + Err(OperationAdapterError::not_implemented_with_detail( + request.operation_id(), + TRADE_EVENT_WATCH_DEFERRED_REASON.to_owned(), + json!({ + "state": "not_implemented", + "trade_id": trade_id, + "reason": TRADE_EVENT_WATCH_DEFERRED_REASON, + "actions": [action], + }), + )) + } +} + +fn decision_result<R>( + operation_id: &str, + view: &OrderDecisionView, +) -> Result<OperationResult<R>, OperationAdapterError> +where + R: OperationResultData, +{ + match view.disposition() { + CommandDisposition::Success => serialized_target_result::<R, _>(view), + CommandDisposition::ValidationFailed => { + let message = view.reason.clone().unwrap_or_else(|| { + format!( + "order decision failed validation with state `{}`", + view.state + ) + }); + Err(OperationAdapterError::validation_failed_with_detail( + operation_id, + message, + order_decision_error_detail(view), + )) + } + disposition => { + let message = view + .reason + .clone() + .unwrap_or_else(|| format!("order decision finished with state `{}`", view.state)); + if disposition == CommandDisposition::ExternalUnavailable { + let detail = order_decision_error_detail(view); + if !view.failed_relays.is_empty() && view.connected_relays.is_empty() { + Err(OperationAdapterError::network_unavailable_with_detail( + operation_id, + message, + detail, + )) + } else { + Err(OperationAdapterError::operation_unavailable_with_detail( + operation_id, + message, + detail, + )) + } + } else if disposition == CommandDisposition::Unconfigured { + Err(OperationAdapterError::operation_unavailable_with_detail( + operation_id, + message, + order_decision_error_detail(view), + )) + } else if disposition == CommandDisposition::NotFound { + Err(OperationAdapterError::not_found_with_detail( + operation_id, + message, + order_decision_error_detail(view), + )) + } else { + Err(OperationAdapterError::from_command_disposition( + operation_id, + disposition, + message, + )) + } + } + } +} + +fn order_decision_error_detail(view: &OrderDecisionView) -> Value { + json!({ + "state": &view.state, + "trade_id": &view.order_id, + "listing_addr": &view.listing_addr, + "listing_event_id": &view.listing_event_id, + "request_event_id": &view.request_event_id, + "root_event_id": &view.root_event_id, + "prev_event_id": &view.prev_event_id, + "event_id": &view.event_id, + "event_kind": view.event_kind, + "inventory": &view.inventory, + "buyer_pubkey": &view.buyer_pubkey, + "seller_pubkey": &view.seller_pubkey, + "decision": &view.decision, + "dry_run": view.dry_run, + "target_relays": &view.target_relays, + "connected_relays": &view.connected_relays, + "acknowledged_relays": &view.acknowledged_relays, + "failed_relays": &view.failed_relays, + "fetched_count": view.fetched_count, + "decoded_count": view.decoded_count, + "skipped_count": view.skipped_count, + "idempotency_key": &view.idempotency_key, + "signer_mode": &view.signer_mode, + "issues": &view.issues, + "actions": &view.actions, + }) +} + +fn cancellation_result<R>( + operation_id: &str, + view: &OrderCancellationView, +) -> Result<OperationResult<R>, OperationAdapterError> +where + R: OperationResultData, +{ + match view.disposition() { + CommandDisposition::Success => serialized_target_result::<R, _>(view), + CommandDisposition::ValidationFailed => { + let message = view.reason.clone().unwrap_or_else(|| { + format!("order cancel failed validation with state `{}`", view.state) + }); + Err(OperationAdapterError::validation_failed_with_detail( + operation_id, + message, + order_cancellation_error_detail(view), + )) + } + disposition => { + let message = view + .reason + .clone() + .unwrap_or_else(|| format!("order cancel finished with state `{}`", view.state)); + if disposition == CommandDisposition::ExternalUnavailable { + let detail = order_cancellation_error_detail(view); + if !view.failed_relays.is_empty() && view.connected_relays.is_empty() { + Err(OperationAdapterError::network_unavailable_with_detail( + operation_id, + message, + detail, + )) + } else { + Err(OperationAdapterError::operation_unavailable_with_detail( + operation_id, + message, + detail, + )) + } + } else if disposition == CommandDisposition::Unconfigured { + Err(OperationAdapterError::operation_unavailable_with_detail( + operation_id, + message, + order_cancellation_error_detail(view), + )) + } else { + Err(OperationAdapterError::from_command_disposition( + operation_id, + disposition, + message, + )) + } + } + } +} + +fn order_cancellation_error_detail(view: &OrderCancellationView) -> Value { + json!({ + "state": &view.state, + "trade_id": &view.order_id, + "listing_addr": &view.listing_addr, + "request_event_id": &view.request_event_id, + "decision_event_id": &view.decision_event_id, + "root_event_id": &view.root_event_id, + "prev_event_id": &view.prev_event_id, + "event_id": &view.event_id, + "event_kind": view.event_kind, + "buyer_pubkey": &view.buyer_pubkey, + "seller_pubkey": &view.seller_pubkey, + "cancellation_reason": &view.cancellation_reason, + "dry_run": view.dry_run, + "target_relays": &view.target_relays, + "connected_relays": &view.connected_relays, + "acknowledged_relays": &view.acknowledged_relays, + "failed_relays": &view.failed_relays, + "fetched_count": view.fetched_count, + "decoded_count": view.decoded_count, + "skipped_count": view.skipped_count, + "idempotency_key": &view.idempotency_key, + "signer_mode": &view.signer_mode, + "issues": &view.issues, + "actions": &view.actions, + }) +} + +fn revision_proposal_result<R>( + operation_id: &str, + view: &OrderRevisionProposalView, +) -> Result<OperationResult<R>, OperationAdapterError> +where + R: OperationResultData, +{ + match view.disposition() { + CommandDisposition::Success => serialized_target_result::<R, _>(view), + CommandDisposition::ValidationFailed => { + let message = view.reason.clone().unwrap_or_else(|| { + format!( + "order revision propose failed validation with state `{}`", + view.state + ) + }); + Err(OperationAdapterError::validation_failed_with_detail( + operation_id, + message, + order_revision_proposal_error_detail(view), + )) + } + disposition => { + let message = view.reason.clone().unwrap_or_else(|| { + format!( + "order revision propose finished with state `{}`", + view.state + ) + }); + if disposition == CommandDisposition::ExternalUnavailable { + let detail = order_revision_proposal_error_detail(view); + if !view.failed_relays.is_empty() && view.connected_relays.is_empty() { + Err(OperationAdapterError::network_unavailable_with_detail( + operation_id, + message, + detail, + )) + } else { + Err(OperationAdapterError::operation_unavailable_with_detail( + operation_id, + message, + detail, + )) + } + } else if disposition == CommandDisposition::Unconfigured { + Err(OperationAdapterError::operation_unavailable_with_detail( + operation_id, + message, + order_revision_proposal_error_detail(view), + )) + } else { + Err(OperationAdapterError::from_command_disposition( + operation_id, + disposition, + message, + )) + } + } + } +} + +fn order_revision_proposal_error_detail(view: &OrderRevisionProposalView) -> Value { + json!({ + "state": &view.state, + "trade_id": &view.order_id, + "revision_id": &view.revision_id, + "listing_addr": &view.listing_addr, + "request_event_id": &view.request_event_id, + "decision_event_id": &view.decision_event_id, + "root_event_id": &view.root_event_id, + "prev_event_id": &view.prev_event_id, + "event_id": &view.event_id, + "event_kind": view.event_kind, + "items": &view.items, + "economics": &view.economics, + "inventory": &view.inventory, + "buyer_pubkey": &view.buyer_pubkey, + "seller_pubkey": &view.seller_pubkey, + "dry_run": view.dry_run, + "target_relays": &view.target_relays, + "connected_relays": &view.connected_relays, + "acknowledged_relays": &view.acknowledged_relays, + "failed_relays": &view.failed_relays, + "fetched_count": view.fetched_count, + "decoded_count": view.decoded_count, + "skipped_count": view.skipped_count, + "idempotency_key": &view.idempotency_key, + "signer_mode": &view.signer_mode, + "issues": &view.issues, + "actions": &view.actions, + }) +} + +fn revision_decision_result<R>( + operation_id: &str, + view: &OrderRevisionDecisionView, +) -> Result<OperationResult<R>, OperationAdapterError> +where + R: OperationResultData, +{ + match view.disposition() { + CommandDisposition::Success => serialized_target_result::<R, _>(view), + CommandDisposition::ValidationFailed => { + let message = view.reason.clone().unwrap_or_else(|| { + format!( + "order revision {} failed validation with state `{}`", + view.decision.as_deref().unwrap_or("decision"), + view.state + ) + }); + Err(OperationAdapterError::validation_failed_with_detail( + operation_id, + message, + order_revision_decision_error_detail(view), + )) + } + disposition => { + let message = view.reason.clone().unwrap_or_else(|| { + format!( + "order revision {} finished with state `{}`", + view.decision.as_deref().unwrap_or("decision"), + view.state + ) + }); + if disposition == CommandDisposition::ExternalUnavailable { + let detail = order_revision_decision_error_detail(view); + if !view.failed_relays.is_empty() && view.connected_relays.is_empty() { + Err(OperationAdapterError::network_unavailable_with_detail( + operation_id, + message, + detail, + )) + } else { + Err(OperationAdapterError::operation_unavailable_with_detail( + operation_id, + message, + detail, + )) + } + } else if disposition == CommandDisposition::Unconfigured { + Err(OperationAdapterError::operation_unavailable_with_detail( + operation_id, + message, + order_revision_decision_error_detail(view), + )) + } else { + Err(OperationAdapterError::from_command_disposition( + operation_id, + disposition, + message, + )) + } + } + } +} + +fn order_revision_decision_error_detail(view: &OrderRevisionDecisionView) -> Value { + json!({ + "state": &view.state, + "trade_id": &view.order_id, + "revision_id": &view.revision_id, + "decision": &view.decision, + "listing_addr": &view.listing_addr, + "request_event_id": &view.request_event_id, + "decision_event_id": &view.decision_event_id, + "agreement_event_id": &view.agreement_event_id, + "root_event_id": &view.root_event_id, + "prev_event_id": &view.prev_event_id, + "event_id": &view.event_id, + "event_kind": view.event_kind, + "economics": &view.economics, + "inventory": &view.inventory, + "buyer_pubkey": &view.buyer_pubkey, + "seller_pubkey": &view.seller_pubkey, + "dry_run": view.dry_run, + "target_relays": &view.target_relays, + "connected_relays": &view.connected_relays, + "acknowledged_relays": &view.acknowledged_relays, + "failed_relays": &view.failed_relays, + "fetched_count": view.fetched_count, + "decoded_count": view.decoded_count, + "skipped_count": view.skipped_count, + "idempotency_key": &view.idempotency_key, + "signer_mode": &view.signer_mode, + "issues": &view.issues, + "actions": &view.actions, + }) +} + +fn status_result<R>( + operation_id: &str, + view: &OrderStatusView, +) -> Result<OperationResult<R>, OperationAdapterError> +where + R: OperationResultData, +{ + match view.disposition() { + CommandDisposition::Success => serialized_target_result::<R, _>(view), + disposition => { + let message = view + .reason + .clone() + .unwrap_or_else(|| format!("order status finished with state `{}`", view.state)); + if disposition == CommandDisposition::ExternalUnavailable { + let detail = order_status_error_detail(view); + if !view.failed_relays.is_empty() && view.connected_relays.is_empty() { + Err(OperationAdapterError::network_unavailable_with_detail( + operation_id, + message, + detail, + )) + } else { + Err(OperationAdapterError::operation_unavailable_with_detail( + operation_id, + message, + detail, + )) + } + } else if disposition == CommandDisposition::Unconfigured { + Err(OperationAdapterError::operation_unavailable_with_detail( + operation_id, + message, + order_status_error_detail(view), + )) + } else { + Err(OperationAdapterError::from_command_disposition( + operation_id, + disposition, + message, + )) + } + } + } +} + +fn order_status_error_detail(view: &OrderStatusView) -> Value { + json!({ + "state": &view.state, + "trade_id": &view.order_id, + "request_event_id": &view.request_event_id, + "decision_event_id": &view.decision_event_id, + "agreement_event_id": &view.agreement_event_id, + "listing_event_id": &view.listing_event_id, + "listing_addr": &view.listing_addr, + "buyer_pubkey": &view.buyer_pubkey, + "seller_pubkey": &view.seller_pubkey, + "last_event_id": &view.last_event_id, + "revision": &view.revision, + "inventory": &view.inventory, + "lifecycle": &view.lifecycle, + "reducer_issues": &view.reducer_issues, + "target_relays": &view.target_relays, + "connected_relays": &view.connected_relays, + "failed_relays": &view.failed_relays, + "fetched_count": view.fetched_count, + "decoded_count": view.decoded_count, + "skipped_count": view.skipped_count, + "actions": &view.actions, + }) +} + +fn serialized_target_result<R, T>(value: &T) -> Result<OperationResult<R>, OperationAdapterError> +where + R: OperationResultData, + T: Serialize, +{ + OperationResult::new(R::from_serializable(value)?) +} + +fn submit_result<R>( + operation_id: &str, + view: &OrderSubmitView, +) -> Result<OperationResult<R>, OperationAdapterError> +where + R: OperationResultData, +{ + match view.disposition() { + CommandDisposition::Success => serialized_target_result::<R, _>(view), + disposition => { + let message = view + .reason + .clone() + .unwrap_or_else(|| format!("order submit finished with state `{}`", view.state)); + let detail = order_submit_error_detail(view); + match disposition { + CommandDisposition::NotFound => Err(OperationAdapterError::not_found_with_detail( + operation_id, + message, + detail, + )), + CommandDisposition::ValidationFailed => { + Err(OperationAdapterError::validation_failed_with_detail( + operation_id, + message, + detail, + )) + } + CommandDisposition::Unconfigured => { + Err(OperationAdapterError::operation_unavailable_with_detail( + operation_id, + message, + detail, + )) + } + CommandDisposition::ExternalUnavailable => { + if !view.failed_relays.is_empty() && view.connected_relays.is_empty() { + Err(OperationAdapterError::network_unavailable_with_detail( + operation_id, + message, + detail, + )) + } else { + Err(OperationAdapterError::operation_unavailable_with_detail( + operation_id, + message, + detail, + )) + } + } + _ => Err(OperationAdapterError::from_command_disposition( + operation_id, + disposition, + message, + )), + } + } + } +} + +fn order_app_record_export_result<R>( + operation_id: &str, + view: &OrderAppRecordExportView, +) -> Result<OperationResult<R>, OperationAdapterError> +where + R: OperationResultData, +{ + match view.disposition() { + CommandDisposition::Success => serialized_target_result::<R, _>(view), + CommandDisposition::NotFound => Err(OperationAdapterError::not_found_with_detail( + operation_id, + view.reason.clone().unwrap_or_else(|| { + format!( + "app-authored local order record `{}` was not found", + view.record_id + ) + }), + serde_json::to_value(view).unwrap_or(Value::Null), + )), + CommandDisposition::ValidationFailed => { + Err(OperationAdapterError::validation_failed_with_detail( + operation_id, + view.reason.clone().unwrap_or_else(|| { + format!( + "app-authored local order record `{}` cannot be exported", + view.record_id + ) + }), + serde_json::to_value(view).unwrap_or(Value::Null), + )) + } + disposition => Err(OperationAdapterError::from_command_disposition( + operation_id, + disposition, + view.reason.clone().unwrap_or_else(|| { + format!( + "app-authored local order record export finished with state `{}`", + view.state + ) + }), + )), + } +} + +fn order_rebind_result<R>( + operation_id: &str, + view: &OrderRebindView, +) -> Result<OperationResult<R>, OperationAdapterError> +where + R: OperationResultData, +{ + match view.disposition() { + CommandDisposition::Success => serialized_target_result::<R, _>(view), + CommandDisposition::NotFound => Err(OperationAdapterError::not_found_with_detail( + operation_id, + view.reason + .clone() + .unwrap_or_else(|| format!("order draft `{}` was not found", view.lookup)), + order_rebind_error_detail(view), + )), + CommandDisposition::ValidationFailed => { + Err(OperationAdapterError::validation_failed_with_detail( + operation_id, + view.reason.clone().unwrap_or_else(|| { + format!("order rebind finished with state `{}`", view.state) + }), + order_rebind_error_detail(view), + )) + } + disposition => Err(OperationAdapterError::from_command_disposition( + operation_id, + disposition, + view.reason + .clone() + .unwrap_or_else(|| format!("order rebind finished with state `{}`", view.state)), + )), + } +} + +fn order_rebind_error_detail(view: &OrderRebindView) -> Value { + json!({ + "state": &view.state, + "source": &view.source, + "lookup": &view.lookup, + "file": &view.file, + "dry_run": view.dry_run, + "from_order_id": &view.from_order_id, + "to_order_id": &view.to_order_id, + "order_id_changed": view.order_id_changed, + "from_buyer_account_id": &view.from_buyer_account_id, + "from_buyer_pubkey": &view.from_buyer_pubkey, + "from_buyer_actor_source": &view.from_buyer_actor_source, + "to_buyer_account_id": &view.to_buyer_account_id, + "to_buyer_pubkey": &view.to_buyer_pubkey, + "to_buyer_actor_source": &view.to_buyer_actor_source, + "buyer_pubkey_changed": view.buyer_pubkey_changed, + "existing_request_check": &view.existing_request_check, + "existing_request_event_ids": &view.existing_request_event_ids, + "actions": &view.actions, + }) +} + +fn order_submit_error_detail(view: &OrderSubmitView) -> Value { + json!({ + "state": &view.state, + "source": &view.source, + "trade_id": &view.order_id, + "file": &view.file, + "listing_lookup": &view.listing_lookup, + "listing_addr": &view.listing_addr, + "listing_event_id": &view.listing_event_id, + "listing_relays": &view.listing_relays, + "buyer_account_id": &view.buyer_account_id, + "buyer_pubkey": &view.buyer_pubkey, + "seller_pubkey": &view.seller_pubkey, + "event_id": &view.event_id, + "event_kind": view.event_kind, + "dry_run": view.dry_run, + "deduplicated": view.deduplicated, + "target_relays": &view.target_relays, + "connected_relays": &view.connected_relays, + "acknowledged_relays": &view.acknowledged_relays, + "failed_relays": &view.failed_relays, + "idempotency_key": &view.idempotency_key, + "signer_mode": &view.signer_mode, + "issues": &view.issues, + "actions": &view.actions, + }) +} + +fn event_list_result<R>( + operation_id: &str, + view: &crate::view::runtime::OrderEventListView, +) -> Result<OperationResult<R>, OperationAdapterError> +where + R: OperationResultData, +{ + match view.disposition() { + CommandDisposition::Success => serialized_target_result::<R, _>(view), + disposition => { + let message = view.reason.clone().unwrap_or_else(|| { + format!("order event list finished with state `{}`", view.state) + }); + if disposition == CommandDisposition::ExternalUnavailable { + let detail = order_event_list_error_detail(view); + Err(OperationAdapterError::network_unavailable_with_detail( + operation_id, + message, + detail, + )) + } else if disposition == CommandDisposition::Unconfigured { + Err(OperationAdapterError::operation_unavailable_with_detail( + operation_id, + message, + order_event_list_error_detail(view), + )) + } else { + Err(OperationAdapterError::from_command_disposition( + operation_id, + disposition, + message, + )) + } + } + } +} + +fn order_event_list_error_detail(view: &crate::view::runtime::OrderEventListView) -> Value { + json!({ + "state": &view.state, + "seller_pubkey": &view.seller_pubkey, + "target_relays": &view.target_relays, + "connected_relays": &view.connected_relays, + "failed_relays": &view.failed_relays, + "fetched_count": view.fetched_count, + "decoded_count": view.decoded_count, + "skipped_count": view.skipped_count, + "count": view.count, + "actions": &view.actions, + }) +} + +fn required_trade_key<P>(request: &OperationRequest<P>) -> Result<String, OperationAdapterError> +where + P: OperationRequestPayload + OperationRequestData, +{ + string_input(request, "trade_id").ok_or_else(|| { + invalid_input( + request.operation_id(), + "missing required `trade_id` input".to_owned(), + ) + }) +} + +fn required_string_input<P>( + request: &OperationRequest<P>, + key: &str, +) -> Result<String, OperationAdapterError> +where + P: OperationRequestPayload + OperationRequestData, +{ + string_input(request, key) + .map(|value| value.trim().to_owned()) + .filter(|value| !value.is_empty()) + .ok_or_else(|| { + invalid_input( + request.operation_id(), + format!("missing required `{key}` input"), + ) + }) +} + +fn string_input<P>(request: &OperationRequest<P>, key: &str) -> Option<String> +where + P: OperationRequestPayload + OperationRequestData, +{ + request + .payload + .input() + .get(key) + .and_then(Value::as_str) + .map(str::to_owned) +} + +fn optional_path_input<P>(request: &OperationRequest<P>, key: &str) -> Option<PathBuf> +where + P: OperationRequestPayload + OperationRequestData, +{ + string_input(request, key).map(PathBuf::from) +} + +fn bool_input<P>(request: &OperationRequest<P>, key: &str) -> Option<bool> +where + P: OperationRequestPayload + OperationRequestData, +{ + request.payload.input().get(key).and_then(Value::as_bool) +} + +fn u32_input<P>(request: &OperationRequest<P>, key: &str) -> Option<u32> +where + P: OperationRequestPayload + OperationRequestData, +{ + request + .payload + .input() + .get(key) + .and_then(Value::as_u64) + .and_then(|value| u32::try_from(value).ok()) +} + +fn map_runtime<T>(result: Result<T, RuntimeError>) -> Result<T, OperationAdapterError> { + result.map_err(|error| OperationAdapterError::Runtime(error.to_string())) +} + +fn invalid_input(operation_id: &str, message: String) -> OperationAdapterError { + OperationAdapterError::InvalidInput { + operation_id: operation_id.to_owned(), + message, + } +} + +#[cfg(test)] +mod tests { + use std::path::{Path, PathBuf}; + + use radroots_runtime_paths::RadrootsMigrationReport; + use radroots_secret_vault::RadrootsSecretBackend; + use serde_json::{Map, Value}; + use tempfile::tempdir; + + use super::{TradeOperationService, decision_result}; + use crate::ops::{ + OperationAdapter, OperationContext, OperationData, OperationRequest, TradeAcceptRequest, + TradeAcceptResult, TradeCancelRequest, TradeDeclineRequest, TradeDeclineResult, + TradeEventListRequest, TradeEventWatchRequest, TradeGetRequest, TradeListRequest, + TradeRevisionAcceptRequest, TradeRevisionDeclineRequest, TradeRevisionProposeRequest, + TradeStatusGetRequest, TradeSubmitRequest, + }; + use crate::runtime::config::{ + AccountConfig, AccountSecretContractConfig, HyfConfig, IdentityConfig, InteractionConfig, + LocalConfig, LoggingConfig, MigrationConfig, MycConfig, OutputConfig, OutputFormat, + PathsConfig, PublishConfig, PublishTransport, PublishTransportSource, RelayConfig, + RelayConfigSource, RelayPublishPolicy, RpcConfig, RuntimeConfig, SignerBackend, + SignerConfig, Verbosity, + }; + use crate::view::runtime::OrderDecisionView; + + #[test] + fn order_service_get_and_list_preserve_order_truth() { + let dir = tempdir().expect("tempdir"); + let config = sample_config(dir.path()); + let service = OperationAdapter::new(TradeOperationService::new(&config)); + let get = OperationRequest::new( + OperationContext::default(), + TradeGetRequest::from_data(data(&[("trade_id", "ord_missing")])), + ) + .expect("order get request"); + let get_envelope = service + .execute(get) + .expect("order get result") + .to_envelope(OperationContext::default().envelope_context("req_order_get")) + .expect("order get envelope"); + + assert_eq!(get_envelope.operation_id, "trade.get"); + assert_eq!(get_envelope.result["state"], "missing"); + assert_eq!(get_envelope.result["actions"][0], "radroots trade list"); + assert_eq!(get_envelope.result["actions"][1], "radroots basket create"); + + let list = OperationRequest::new(OperationContext::default(), TradeListRequest::default()) + .expect("order list request"); + let list_envelope = service + .execute(list) + .expect("order list result") + .to_envelope(OperationContext::default().envelope_context("req_order_list")) + .expect("order list envelope"); + assert_eq!(list_envelope.operation_id, "trade.list"); + assert_eq!(list_envelope.result["state"], "empty"); + assert_eq!(list_envelope.result["actions"][0], "radroots basket create"); + } + + #[test] + fn order_submit_requires_approval_token() { + let dir = tempdir().expect("tempdir"); + let config = sample_config(dir.path()); + let service = OperationAdapter::new(TradeOperationService::new(&config)); + let submit = OperationRequest::new( + OperationContext::default(), + TradeSubmitRequest::from_data(data(&[("trade_id", "ord_missing")])), + ) + .expect("order submit request"); + let error = service.execute(submit).expect_err("approval required"); + + assert!(format!("{error}").contains("approval_token")); + assert_eq!(error.to_output_error().code, "approval_required"); + assert_eq!(error.to_output_error().exit_code, 6); + } + + #[test] + fn order_submit_with_approval_returns_not_found_for_missing_order() { + let dir = tempdir().expect("tempdir"); + let config = sample_config(dir.path()); + let service = OperationAdapter::new(TradeOperationService::new(&config)); + let mut context = OperationContext::default(); + context.approval_token = Some("approve_test".to_owned()); + let submit = OperationRequest::new( + context.clone(), + TradeSubmitRequest::from_data(data(&[("trade_id", "ord_missing")])), + ) + .expect("order submit request"); + let error = service.execute(submit).expect_err("missing order error"); + let output_error = error.to_output_error(); + + assert_eq!(output_error.code, "not_found"); + assert_eq!(output_error.exit_code, 4); + assert!(output_error.message.contains("ord_missing")); + let envelope = crate::out::envelope::OutputEnvelope::failure( + "trade.submit", + output_error, + context.envelope_context("req_order_submit"), + ); + let detail = envelope.errors[0].detail.as_ref().expect("submit detail"); + assert_eq!(detail["state"], "missing"); + assert_eq!(detail["trade_id"], "ord_missing"); + assert_eq!(detail["actions"][0], "radroots trade list"); + assert_eq!(detail["actions"][1], "radroots basket create"); + assert_eq!( + envelope.next_actions[0].command.as_deref(), + Some("radroots trade list") + ); + assert_eq!( + envelope.next_actions[1].command.as_deref(), + Some("radroots basket create") + ); + } + + #[test] + fn order_accept_requires_approval_token() { + let dir = tempdir().expect("tempdir"); + let config = sample_config(dir.path()); + let service = OperationAdapter::new(TradeOperationService::new(&config)); + let accept = OperationRequest::new( + OperationContext::default(), + TradeAcceptRequest::from_data(data(&[("trade_id", "ord_pending")])), + ) + .expect("order accept request"); + let error = service.execute(accept).expect_err("approval required"); + + assert_eq!(error.to_output_error().code, "approval_required"); + } + + #[test] + fn order_accept_unconfigured_preserves_decision_detail() { + let dir = tempdir().expect("tempdir"); + let config = sample_config(dir.path()); + let service = OperationAdapter::new(TradeOperationService::new(&config)); + let mut context = OperationContext::default(); + context.dry_run = true; + let accept = OperationRequest::new( + context, + TradeAcceptRequest::from_data(data(&[("trade_id", "ord_pending")])), + ) + .expect("order accept request"); + let error = service + .execute(accept) + .expect_err("order accept unconfigured"); + let output_error = error.to_output_error(); + let detail = output_error.detail.as_ref().expect("decision detail"); + + assert_eq!(output_error.code, "operation_unavailable"); + assert_eq!(detail["state"], "unconfigured"); + assert_eq!(detail["trade_id"], "ord_pending"); + assert_eq!(detail["decision"], "accepted"); + assert!(detail["target_relays"].as_array().unwrap().is_empty()); + } + + #[test] + fn order_decision_already_decided_maps_to_validation_failure() { + let view = already_decided_view(); + let error = match decision_result::<TradeAcceptResult>("trade.accept", &view) { + Ok(_) => panic!("already decided view should fail validation"), + Err(error) => error, + }; + let output_error = error.to_output_error(); + + assert_eq!(output_error.code, "validation_failed"); + assert_eq!(output_error.exit_code, 10); + let detail = output_error.detail.expect("validation detail"); + assert_eq!(detail["state"], "already_decided"); + assert_eq!(detail["operation_id"], "trade.accept"); + assert_eq!(detail["listing_event_id"], "l".repeat(64)); + assert_eq!(detail["event_id"], "d".repeat(64)); + assert_eq!(detail["event_kind"], 3423); + assert_eq!(detail["idempotency_key"], "idem_test"); + assert_eq!(detail["signer_mode"], "local"); + assert_eq!(detail["actions"][0], "radroots trade status get ord_test"); + } + + #[test] + fn order_decision_invalid_maps_to_validation_failure() { + let view = invalid_decision_view(); + let error = match decision_result::<TradeDeclineResult>("trade.decline", &view) { + Ok(_) => panic!("invalid view should fail validation"), + Err(error) => error, + }; + let output_error = error.to_output_error(); + + assert_eq!(output_error.code, "validation_failed"); + assert_eq!(output_error.exit_code, 10); + assert_eq!( + output_error.message, + "active trade events for `ord_test` failed reducer validation" + ); + let detail = output_error.detail.expect("validation detail"); + assert_eq!(detail["state"], "invalid"); + assert_eq!(detail["operation_id"], "trade.decline"); + assert_eq!(detail["listing_event_id"], "l".repeat(64)); + assert_eq!(detail["event_id"], Value::Null); + assert_eq!(detail["event_kind"], Value::Null); + assert_eq!(detail["idempotency_key"], "idem_test"); + assert_eq!(detail["signer_mode"], "local"); + } + + #[test] + fn order_decline_requires_reason_before_approval() { + let dir = tempdir().expect("tempdir"); + let config = sample_config(dir.path()); + let service = OperationAdapter::new(TradeOperationService::new(&config)); + let decline = OperationRequest::new( + OperationContext::default(), + TradeDeclineRequest::from_data(data(&[("trade_id", "ord_pending")])), + ) + .expect("order decline request"); + let error = service.execute(decline).expect_err("reason required"); + let output_error = error.to_output_error(); + + assert_eq!(output_error.code, "invalid_input"); + assert!(output_error.message.contains("reason")); + } + + #[test] + fn order_decline_rejects_blank_reason_before_approval() { + let dir = tempdir().expect("tempdir"); + let config = sample_config(dir.path()); + let service = OperationAdapter::new(TradeOperationService::new(&config)); + let decline = OperationRequest::new( + OperationContext::default(), + TradeDeclineRequest::from_data(data(&[("trade_id", "ord_pending"), ("reason", " ")])), + ) + .expect("order decline request"); + let error = service.execute(decline).expect_err("reason required"); + let output_error = error.to_output_error(); + + assert_eq!(output_error.code, "invalid_input"); + assert!(output_error.message.contains("reason")); + } + + #[test] + fn order_cancel_requires_reason_before_approval() { + let dir = tempdir().expect("tempdir"); + let config = sample_config(dir.path()); + let service = OperationAdapter::new(TradeOperationService::new(&config)); + let cancel = OperationRequest::new( + OperationContext::default(), + TradeCancelRequest::from_data(data(&[("trade_id", "ord_pending")])), + ) + .expect("order cancel request"); + let error = service.execute(cancel).expect_err("reason required"); + let output_error = error.to_output_error(); + + assert_eq!(output_error.code, "invalid_input"); + assert!(output_error.message.contains("reason")); + } + + #[test] + fn order_cancel_requires_approval_token() { + let dir = tempdir().expect("tempdir"); + let config = sample_config(dir.path()); + let service = OperationAdapter::new(TradeOperationService::new(&config)); + let cancel = OperationRequest::new( + OperationContext::default(), + TradeCancelRequest::from_data(data(&[ + ("trade_id", "ord_pending"), + ("reason", "changed plans"), + ])), + ) + .expect("order cancel request"); + let error = service.execute(cancel).expect_err("approval required"); + + assert_eq!(error.to_output_error().code, "approval_required"); + } + + #[test] + fn order_revision_propose_requires_reason_before_approval() { + let dir = tempdir().expect("tempdir"); + let config = sample_config(dir.path()); + let service = OperationAdapter::new(TradeOperationService::new(&config)); + let revision = OperationRequest::new( + OperationContext::default(), + TradeRevisionProposeRequest::from_data(data(&[("trade_id", "ord_pending")])), + ) + .expect("order revision request"); + let error = service.execute(revision).expect_err("reason required"); + let output_error = error.to_output_error(); + + assert_eq!(output_error.code, "invalid_input"); + assert!(output_error.message.contains("reason")); + } + + #[test] + fn order_revision_propose_requires_approval_token() { + let dir = tempdir().expect("tempdir"); + let config = sample_config(dir.path()); + let service = OperationAdapter::new(TradeOperationService::new(&config)); + let mut input = data(&[ + ("trade_id", "ord_pending"), + ("reason", "update count"), + ("bin_id", "bin-1"), + ]); + input.insert("bin_count".to_owned(), Value::from(3)); + let revision = OperationRequest::new( + OperationContext::default(), + TradeRevisionProposeRequest::from_data(input), + ) + .expect("order revision request"); + let error = service.execute(revision).expect_err("approval required"); + + assert_eq!(error.to_output_error().code, "approval_required"); + } + + #[test] + fn order_revision_accept_requires_approval_token() { + let dir = tempdir().expect("tempdir"); + let config = sample_config(dir.path()); + let service = OperationAdapter::new(TradeOperationService::new(&config)); + let revision = OperationRequest::new( + OperationContext::default(), + TradeRevisionAcceptRequest::from_data(data(&[ + ("trade_id", "ord_pending"), + ("revision_id", "rev_pending"), + ])), + ) + .expect("order revision accept request"); + let error = service.execute(revision).expect_err("approval required"); + + assert_eq!(error.to_output_error().code, "approval_required"); + } + + #[test] + fn order_revision_decline_requires_reason_before_approval() { + let dir = tempdir().expect("tempdir"); + let config = sample_config(dir.path()); + let service = OperationAdapter::new(TradeOperationService::new(&config)); + let revision = OperationRequest::new( + OperationContext::default(), + TradeRevisionDeclineRequest::from_data(data(&[ + ("trade_id", "ord_pending"), + ("revision_id", "rev_pending"), + ])), + ) + .expect("order revision decline request"); + let error = service.execute(revision).expect_err("reason required"); + let output_error = error.to_output_error(); + + assert_eq!(output_error.code, "invalid_input"); + assert!(output_error.message.contains("reason")); + } + + #[test] + fn order_revision_decline_requires_approval_token() { + let dir = tempdir().expect("tempdir"); + let config = sample_config(dir.path()); + let service = OperationAdapter::new(TradeOperationService::new(&config)); + let revision = OperationRequest::new( + OperationContext::default(), + TradeRevisionDeclineRequest::from_data(data(&[ + ("trade_id", "ord_pending"), + ("revision_id", "rev_pending"), + ("reason", "keep original order"), + ])), + ) + .expect("order revision decline request"); + let error = service.execute(revision).expect_err("approval required"); + + assert_eq!(error.to_output_error().code, "approval_required"); + } + + #[test] + fn order_status_get_uses_local_sdk_projection_without_relay() { + let dir = tempdir().expect("tempdir"); + let config = sample_config(dir.path()); + let service = OperationAdapter::new(TradeOperationService::new(&config)); + let status = OperationRequest::new( + OperationContext::default(), + TradeStatusGetRequest::from_data(data(&[("trade_id", "ord_pending")])), + ) + .expect("order status request"); + let envelope = service + .execute(status) + .expect("status result") + .to_envelope(OperationContext::default().envelope_context("req_order_status")) + .expect("status envelope"); + + assert_eq!(envelope.operation_id, "trade.status.get"); + assert_eq!(envelope.result["state"], "missing"); + assert_eq!(envelope.result["source"], "SDK local trade projection"); + assert_eq!( + envelope.result["actor_context_source"], + "sdk_local_projection" + ); + assert_eq!(envelope.result["trade_id"], "ord_pending"); + assert_eq!(envelope.result["fetched_count"], 0); + assert_eq!(envelope.result["decoded_count"], 0); + assert_eq!(envelope.result["skipped_count"], 0); + assert!(envelope.next_actions.is_empty()); + } + + #[test] + fn order_event_list_requires_relay_configuration() { + let dir = tempdir().expect("tempdir"); + let config = sample_config(dir.path()); + let service = OperationAdapter::new(TradeOperationService::new(&config)); + let context = OperationContext::default(); + let request = OperationRequest::new(context.clone(), TradeEventListRequest::default()) + .expect("order event list request"); + let output_error = service + .execute(request) + .expect_err("order event list unconfigured") + .to_output_error(); + let envelope = crate::out::envelope::OutputEnvelope::failure( + "trade.event.list", + output_error, + context.envelope_context("req_order_event_list"), + ); + + assert_eq!(envelope.errors[0].code, "operation_unavailable"); + assert_eq!(envelope.errors[0].exit_code, 3); + assert!(envelope.errors[0].message.contains("configured relay")); + assert_eq!( + envelope.errors[0].detail.as_ref().unwrap()["actions"][0], + "radroots --relay wss://relay.example.com trade event list" + ); + assert_eq!( + envelope.next_actions[0].command.as_deref(), + Some("radroots --relay wss://relay.example.com trade event list") + ); + } + + #[test] + fn order_event_list_requires_seller_account_with_account_action() { + let dir = tempdir().expect("tempdir"); + let mut config = sample_config(dir.path()); + config.relay.urls = vec!["ws://127.0.0.1:9".to_owned()]; + let service = OperationAdapter::new(TradeOperationService::new(&config)); + let context = OperationContext::default(); + let request = OperationRequest::new(context.clone(), TradeEventListRequest::default()) + .expect("order event list request"); + let output_error = service + .execute(request) + .expect_err("order event list missing account") + .to_output_error(); + let envelope = crate::out::envelope::OutputEnvelope::failure( + "trade.event.list", + output_error, + context.envelope_context("req_order_event_list"), + ); + + assert_eq!(envelope.errors[0].code, "operation_unavailable"); + assert!( + envelope.errors[0] + .message + .contains("selected seller account") + ); + assert_eq!( + envelope.errors[0].detail.as_ref().unwrap()["actions"][0], + "radroots account create" + ); + assert_eq!( + envelope.next_actions[0].command.as_deref(), + Some("radroots account create") + ); + } + + #[test] + fn order_event_watch_returns_deferred_error_with_target_action() { + let dir = tempdir().expect("tempdir"); + let config = sample_config(dir.path()); + let service = OperationAdapter::new(TradeOperationService::new(&config)); + let request = OperationRequest::new( + OperationContext::default(), + TradeEventWatchRequest::from_data(data(&[("trade_id", "ord_missing")])), + ) + .expect("order event watch request"); + let error = service + .execute(request) + .expect_err("order event watch deferred"); + let envelope = crate::out::envelope::OutputEnvelope::failure( + "trade.event.watch", + error.to_output_error(), + OperationContext::default().envelope_context("req_order_watch"), + ); + + assert_eq!(envelope.operation_id, "trade.event.watch"); + assert!(envelope.result.is_null()); + assert_eq!(envelope.errors[0].code, "not_implemented"); + assert_eq!( + envelope.errors[0].detail.as_ref().unwrap()["state"], + "not_implemented" + ); + assert_eq!( + envelope.errors[0].detail.as_ref().unwrap()["trade_id"], + "ord_missing" + ); + assert_eq!( + envelope.next_actions[0].command.as_deref(), + Some("radroots trade status get ord_missing") + ); + } + + fn sample_config(root: &Path) -> RuntimeConfig { + let data = root.join("data"); + let cache = root.join("cache"); + let logs = root.join("logs"); + let secrets = root.join("secrets"); + RuntimeConfig { + output: OutputConfig { + format: OutputFormat::Human, + verbosity: Verbosity::Normal, + color: true, + dry_run: false, + }, + interaction: InteractionConfig { + input_enabled: true, + assume_yes: false, + stdin_tty: false, + stdout_tty: false, + prompts_allowed: false, + confirmations_allowed: false, + }, + paths: PathsConfig { + profile: "interactive_user".into(), + profile_source: "test".into(), + allowed_profiles: vec!["interactive_user".into(), "repo_local".into()], + root_source: "test".into(), + repo_local_root: None, + repo_local_root_source: None, + subordinate_path_override_source: "runtime_config".into(), + app_namespace: "apps/cli".into(), + shared_accounts_namespace: "shared/accounts".into(), + shared_identities_namespace: "shared/identities".into(), + app_config_path: root.join("config/apps/cli/config.toml"), + workspace_config_path: None, + app_data_root: data.join("apps/cli"), + shared_cache_root: cache.clone(), + app_logs_root: logs.join("apps/cli"), + shared_accounts_data_root: data.join("shared/accounts"), + shared_accounts_secrets_root: secrets.join("shared/accounts"), + default_identity_path: secrets.join("shared/identities/default.json"), + }, + migration: MigrationConfig { + report: RadrootsMigrationReport::empty(), + }, + logging: LoggingConfig { + filter: "info".into(), + directory: None, + stdout: false, + }, + account: AccountConfig { + selector: None, + store_path: data.join("shared/accounts/store.json"), + secrets_dir: secrets.join("shared/accounts"), + secret_backend: RadrootsSecretBackend::EncryptedFile, + secret_fallback: None, + }, + account_secret_contract: AccountSecretContractConfig { + default_backend: "host_vault".into(), + default_fallback: Some("encrypted_file".into()), + allowed_backends: vec!["host_vault".into(), "encrypted_file".into()], + host_vault_policy: Some("desktop".into()), + uses_protected_store: true, + }, + identity: IdentityConfig { + path: secrets.join("shared/identities/default.json"), + }, + signer: SignerConfig { + backend: SignerBackend::Local, + }, + publish: PublishConfig { + transport: PublishTransport::DirectNostrRelay, + source: PublishTransportSource::Defaults, + radrootsd_proxy: crate::runtime::config::RadrootsdProxyConfig::default(), + }, + relay: RelayConfig { + urls: Vec::new(), + publish_policy: RelayPublishPolicy::Any, + source: RelayConfigSource::Defaults, + }, + local: LocalConfig { + root: data.join("apps/cli/replica"), + replica_db_path: data.join("apps/cli/replica/replica.sqlite"), + backups_dir: data.join("apps/cli/replica/backups"), + exports_dir: data.join("apps/cli/replica/exports"), + }, + myc: MycConfig { + executable: PathBuf::from("myc"), + status_timeout_ms: 2_000, + }, + hyf: HyfConfig { + enabled: false, + executable: PathBuf::from("hyfd"), + }, + rpc: RpcConfig { + url: "http://127.0.0.1:7070".into(), + }, + rhi: crate::runtime::config::RhiConfig { + trusted_worker_pubkeys: Vec::new(), + }, + capability_bindings: Vec::new(), + } + } + + fn data(entries: &[(&str, &str)]) -> OperationData { + entries + .iter() + .map(|(key, value)| ((*key).to_owned(), Value::String((*value).to_owned()))) + .collect::<Map<String, Value>>() + } + + fn already_decided_view() -> OrderDecisionView { + OrderDecisionView { + state: "already_decided".to_owned(), + source: "test".to_owned(), + order_id: "ord_test".to_owned(), + listing_addr: Some("30402:seller:listing".to_owned()), + buyer_pubkey: Some("b".repeat(64)), + seller_pubkey: Some("s".repeat(64)), + decision: "accepted".to_owned(), + request_event_id: Some("r".repeat(64)), + listing_event_id: Some("l".repeat(64)), + root_event_id: Some("r".repeat(64)), + prev_event_id: Some("r".repeat(64)), + event_id: Some("d".repeat(64)), + event_kind: Some(3423), + inventory: None, + dry_run: false, + target_relays: vec!["ws://relay.test".to_owned()], + connected_relays: vec!["ws://relay.test".to_owned()], + acknowledged_relays: Vec::new(), + failed_relays: Vec::new(), + fetched_count: 2, + decoded_count: 2, + skipped_count: 0, + idempotency_key: Some("idem_test".to_owned()), + signer_mode: Some("local".to_owned()), + reason: Some( + "order accept refused because order `ord_test` already has a visible `accepted` seller decision" + .to_owned(), + ), + issues: Vec::new(), + actions: vec!["radroots trade status get ord_test".to_owned()], + } + } + + fn invalid_decision_view() -> OrderDecisionView { + let mut view = already_decided_view(); + view.state = "invalid".to_owned(); + view.decision = "declined".to_owned(); + view.event_id = None; + view.event_kind = None; + view.reason = + Some("active trade events for `ord_test` failed reducer validation".to_owned()); + view + } +} diff --git a/src/ops/exec/validation.rs b/src/ops/exec/validation.rs @@ -87,7 +87,7 @@ where P: OperationRequestPayload + OperationRequestData, { Ok(ValidationReceiptListArgs { - order_id: required_string(request, "order_id")?, + order_id: required_string(request, "trade_id")?, }) } diff --git a/src/ops/mod.rs b/src/ops/mod.rs @@ -215,21 +215,21 @@ mod tests { #[test] fn adapter_maps_order_rebind_inputs() { let parsed = - TargetCliArgs::try_parse_from(["radroots", "order", "rebind", "ord_test", "acct_test"]) + TargetCliArgs::try_parse_from(["radroots", "trade", "rebind", "ord_test", "acct_test"]) .expect("target args parse"); let request = TargetOperationRequest::from_target_args(&parsed) .expect("operation request from target args"); - let TargetOperationRequest::OrderRebind(request) = request else { + let TargetOperationRequest::TradeRebind(request) = request else { panic!("expected order rebind request") }; - assert_eq!(request.operation_id(), "order.rebind"); + assert_eq!(request.operation_id(), "trade.rebind"); assert_eq!( request .payload .input - .get("order_id") + .get("trade_id") .and_then(Value::as_str), Some("ord_test") ); @@ -247,7 +247,7 @@ mod tests { fn adapter_maps_order_lifecycle_inputs() { let revision = TargetCliArgs::try_parse_from([ "radroots", - "order", + "trade", "revision", "propose", "ord_test", @@ -271,15 +271,15 @@ mod tests { .expect("target args parse"); let request = TargetOperationRequest::from_target_args(&revision).expect("operation request"); - let TargetOperationRequest::OrderRevisionPropose(request) = request else { + let TargetOperationRequest::TradeRevisionPropose(request) = request else { panic!("expected order revision propose request") }; - assert_eq!(request.operation_id(), "order.revision.propose"); + assert_eq!(request.operation_id(), "trade.revision.propose"); assert_eq!( request .payload .input - .get("order_id") + .get("trade_id") .and_then(Value::as_str), Some("ord_test") ); @@ -342,7 +342,7 @@ mod tests { let revision_accept = TargetCliArgs::try_parse_from([ "radroots", - "order", + "trade", "revision", "accept", "ord_test", @@ -352,15 +352,15 @@ mod tests { .expect("target args parse"); let request = TargetOperationRequest::from_target_args(&revision_accept).expect("operation request"); - let TargetOperationRequest::OrderRevisionAccept(request) = request else { + let TargetOperationRequest::TradeRevisionAccept(request) = request else { panic!("expected order revision accept request") }; - assert_eq!(request.operation_id(), "order.revision.accept"); + assert_eq!(request.operation_id(), "trade.revision.accept"); assert_eq!( request .payload .input - .get("order_id") + .get("trade_id") .and_then(Value::as_str), Some("ord_test") ); @@ -375,7 +375,7 @@ mod tests { let revision_decline = TargetCliArgs::try_parse_from([ "radroots", - "order", + "trade", "revision", "decline", "ord_test", @@ -387,15 +387,15 @@ mod tests { .expect("target args parse"); let request = TargetOperationRequest::from_target_args(&revision_decline).expect("operation request"); - let TargetOperationRequest::OrderRevisionDecline(request) = request else { + let TargetOperationRequest::TradeRevisionDecline(request) = request else { panic!("expected order revision decline request") }; - assert_eq!(request.operation_id(), "order.revision.decline"); + assert_eq!(request.operation_id(), "trade.revision.decline"); assert_eq!( request .payload .input - .get("order_id") + .get("trade_id") .and_then(Value::as_str), Some("ord_test") ); @@ -414,7 +414,7 @@ mod tests { let cancel = TargetCliArgs::try_parse_from([ "radroots", - "order", + "trade", "cancel", "ord_test", "--reason", @@ -422,15 +422,15 @@ mod tests { ]) .expect("target args parse"); let request = TargetOperationRequest::from_target_args(&cancel).expect("operation request"); - let TargetOperationRequest::OrderCancel(request) = request else { + let TargetOperationRequest::TradeCancel(request) = request else { panic!("expected order cancel request") }; - assert_eq!(request.operation_id(), "order.cancel"); + assert_eq!(request.operation_id(), "trade.cancel"); assert_eq!( request .payload .input - .get("order_id") + .get("trade_id") .and_then(Value::as_str), Some("ord_test") ); @@ -473,7 +473,7 @@ mod tests { #[test] fn approval_errors_map_to_structured_exit_code() { - let error = OperationAdapterError::approval_required("order.submit"); + let error = OperationAdapterError::approval_required("trade.submit"); let output_error = error.to_output_error(); assert_eq!(output_error.code, "approval_required"); diff --git a/src/ops/target.rs b/src/ops/target.rs @@ -197,7 +197,9 @@ target_operation_contracts! { FarmGet => (FarmGetRequest, FarmGetResult, "farm.get"), FarmRebind => (FarmRebindRequest, FarmRebindResult, "farm.rebind"), FarmProfileUpdate => (FarmProfileUpdateRequest, FarmProfileUpdateResult, "farm.profile.update"), - FarmLocationUpdate => (FarmLocationUpdateRequest, FarmLocationUpdateResult, "farm.location.update"), + FarmLocationSet => (FarmLocationSetRequest, FarmLocationSetResult, "farm.location.set"), + FarmLocationGet => (FarmLocationGetRequest, FarmLocationGetResult, "farm.location.get"), + FarmLocationClear => (FarmLocationClearRequest, FarmLocationClearResult, "farm.location.clear"), FarmFulfillmentUpdate => (FarmFulfillmentUpdateRequest, FarmFulfillmentUpdateResult, "farm.fulfillment.update"), FarmReadinessCheck => (FarmReadinessCheckRequest, FarmReadinessCheckResult, "farm.readiness.check"), FarmPublish => (FarmPublishRequest, FarmPublishResult, "farm.publish"), @@ -224,21 +226,21 @@ target_operation_contracts! { BasketAdjustmentRemove => (BasketAdjustmentRemoveRequest, BasketAdjustmentRemoveResult, "basket.adjustment.remove"), BasketValidate => (BasketValidateRequest, BasketValidateResult, "basket.validate"), BasketQuoteCreate => (BasketQuoteCreateRequest, BasketQuoteCreateResult, "basket.quote.create"), - OrderSubmit => (OrderSubmitRequest, OrderSubmitResult, "order.submit"), - OrderGet => (OrderGetRequest, OrderGetResult, "order.get"), - OrderList => (OrderListRequest, OrderListResult, "order.list"), - OrderAppList => (OrderAppListRequest, OrderAppListResult, "order.app.list"), - OrderAppExport => (OrderAppExportRequest, OrderAppExportResult, "order.app.export"), - OrderRebind => (OrderRebindRequest, OrderRebindResult, "order.rebind"), - OrderAccept => (OrderAcceptRequest, OrderAcceptResult, "order.accept"), - OrderDecline => (OrderDeclineRequest, OrderDeclineResult, "order.decline"), - OrderCancel => (OrderCancelRequest, OrderCancelResult, "order.cancel"), - OrderRevisionPropose => (OrderRevisionProposeRequest, OrderRevisionProposeResult, "order.revision.propose"), - OrderRevisionAccept => (OrderRevisionAcceptRequest, OrderRevisionAcceptResult, "order.revision.accept"), - OrderRevisionDecline => (OrderRevisionDeclineRequest, OrderRevisionDeclineResult, "order.revision.decline"), - OrderStatusGet => (OrderStatusGetRequest, OrderStatusGetResult, "order.status.get"), - OrderEventList => (OrderEventListRequest, OrderEventListResult, "order.event.list"), - OrderEventWatch => (OrderEventWatchRequest, OrderEventWatchResult, "order.event.watch"), + TradeSubmit => (TradeSubmitRequest, TradeSubmitResult, "trade.submit"), + TradeGet => (TradeGetRequest, TradeGetResult, "trade.get"), + TradeList => (TradeListRequest, TradeListResult, "trade.list"), + TradeAppList => (TradeAppListRequest, TradeAppListResult, "trade.app.list"), + TradeAppExport => (TradeAppExportRequest, TradeAppExportResult, "trade.app.export"), + TradeRebind => (TradeRebindRequest, TradeRebindResult, "trade.rebind"), + TradeAccept => (TradeAcceptRequest, TradeAcceptResult, "trade.accept"), + TradeDecline => (TradeDeclineRequest, TradeDeclineResult, "trade.decline"), + TradeCancel => (TradeCancelRequest, TradeCancelResult, "trade.cancel"), + TradeRevisionPropose => (TradeRevisionProposeRequest, TradeRevisionProposeResult, "trade.revision.propose"), + TradeRevisionAccept => (TradeRevisionAcceptRequest, TradeRevisionAcceptResult, "trade.revision.accept"), + TradeRevisionDecline => (TradeRevisionDeclineRequest, TradeRevisionDeclineResult, "trade.revision.decline"), + TradeStatusGet => (TradeStatusGetRequest, TradeStatusGetResult, "trade.status.get"), + TradeEventList => (TradeEventListRequest, TradeEventListResult, "trade.event.list"), + TradeEventWatch => (TradeEventWatchRequest, TradeEventWatchResult, "trade.event.watch"), ValidationReceiptGet => (ValidationReceiptGetRequest, ValidationReceiptGetResult, "validation.receipt.get"), ValidationReceiptList => (ValidationReceiptListRequest, ValidationReceiptListResult, "validation.receipt.list"), ValidationReceiptVerify => (ValidationReceiptVerifyRequest, ValidationReceiptVerifyResult, "validation.receipt.verify"), diff --git a/src/out/envelope.rs b/src/out/envelope.rs @@ -180,7 +180,7 @@ fn output_resource_from_value(value: &Value) -> Option<OutputResource> { "listing", "basket", "quote", - "order", + "trade", ]; nested_fields .into_iter() @@ -198,6 +198,7 @@ fn nested_output_resource(field: &str, value: &Value) -> Option<OutputResource> if resource.kind == "resource" { resource.kind = match field { "resolved_account" | "default_account" | "bound_account" => "account", + "order" => "trade", other => other, } .to_owned(); @@ -232,7 +233,7 @@ fn output_resource_from_fields(object: &serde_json::Map<String, Value>) -> Optio ("listing_address", "listing"), ("listing_addr", "listing"), ("basket_id", "basket"), - ("order_id", "order"), + ("order_id", "trade"), ] .into_iter() .find_map(|(field, kind)| { @@ -550,14 +551,14 @@ mod tests { CliExitCode::ApprovalRequiredOrDenied, ); let envelope = OutputEnvelope::failure( - "order.submit", + "trade.submit", error, EnvelopeContext::new("req_order", false), ); let value = serde_json::to_value(envelope).expect("serialize envelope"); assert_eq!(value["schema_version"], OUTPUT_SCHEMA_VERSION); - assert_eq!(value["operation_id"], "order.submit"); + assert_eq!(value["operation_id"], "trade.submit"); assert_eq!(value["status"], "error"); assert_eq!(value["reason_code"], "approval_required"); assert_eq!(value["result"], Value::Null); @@ -575,22 +576,22 @@ mod tests { ); error.detail = Some(json!({ "actions": [ - "radroots order list", + "radroots trade list", "run radroots basket create" ] })); let envelope = OutputEnvelope::failure( - "order.submit", + "trade.submit", error, EnvelopeContext::new("req_order", true), ); assert_eq!(envelope.next_actions.len(), 2); assert_eq!(envelope.next_actions[0].kind, NextActionKind::CliCommand); - assert_eq!(envelope.next_actions[0].label, "order list"); + assert_eq!(envelope.next_actions[0].label, "trade list"); assert_eq!( envelope.next_actions[0].command.as_deref(), - Some("radroots order list") + Some("radroots trade list") ); assert_eq!(envelope.next_actions[1].kind, NextActionKind::CliCommand); assert_eq!(envelope.next_actions[1].label, "basket create"); @@ -714,7 +715,7 @@ mod tests { assert_eq!(frames[0].payload["output_format"], "human"); assert_eq!(frames[1].payload["status"], "error"); assert_eq!(frames[1].payload["reason_code"], "not_implemented"); - assert_eq!(frames[1].payload["resource"]["kind"], "order"); + assert_eq!(frames[1].payload["resource"]["kind"], "trade"); assert_eq!(frames[1].payload["resource"]["id"], "ord_test"); assert_eq!(frames[1].errors[0].reason_code, "not_implemented"); } diff --git a/src/registry/farm.rs b/src/registry/farm.rs @@ -64,14 +64,14 @@ pub const FARM_PROFILE_UPDATE: OperationSpec = operation!( true ); -pub const FARM_LOCATION_UPDATE: OperationSpec = operation!( - "farm.location.update", - "radroots farm location update", +pub const FARM_LOCATION_SET: OperationSpec = operation!( + "farm.location.set", + "radroots farm location set", "farm", - "farm_location_update", - "FarmLocationUpdateRequest", - "FarmLocationUpdateResult", - "Update farm location fields.", + "farm_location_set", + "FarmLocationSetRequest", + "FarmLocationSetResult", + "Set private exact farm location and derived public locality.", Seller, true, Conditional, @@ -80,6 +80,38 @@ pub const FARM_LOCATION_UPDATE: OperationSpec = operation!( true ); +pub const FARM_LOCATION_GET: OperationSpec = operation!( + "farm.location.get", + "radroots farm location get", + "farm", + "farm_location_get", + "FarmLocationGetRequest", + "FarmLocationGetResult", + "Get private exact farm location for the configured farm.", + Seller, + false, + None, + Low, + false, + false +); + +pub const FARM_LOCATION_CLEAR: OperationSpec = operation!( + "farm.location.clear", + "radroots farm location clear", + "farm", + "farm_location_clear", + "FarmLocationClearRequest", + "FarmLocationClearResult", + "Clear private exact farm location for the configured farm.", + Seller, + true, + Required, + High, + false, + true +); + pub const FARM_FULFILLMENT_UPDATE: OperationSpec = operation!( "farm.fulfillment.update", "radroots farm fulfillment update", diff --git a/src/registry/mod.rs b/src/registry/mod.rs @@ -88,11 +88,11 @@ mod farm; mod health; mod listing; mod market; -mod order; mod relay; mod signer; mod store; mod sync; +mod trade; mod validation; mod workspace; @@ -126,7 +126,9 @@ pub const OPERATION_REGISTRY: &[OperationSpec] = &[ farm::FARM_GET, farm::FARM_REBIND, farm::FARM_PROFILE_UPDATE, - farm::FARM_LOCATION_UPDATE, + farm::FARM_LOCATION_SET, + farm::FARM_LOCATION_GET, + farm::FARM_LOCATION_CLEAR, farm::FARM_FULFILLMENT_UPDATE, farm::FARM_READINESS_CHECK, farm::FARM_PUBLISH, @@ -153,21 +155,21 @@ pub const OPERATION_REGISTRY: &[OperationSpec] = &[ basket::BASKET_ADJUSTMENT_REMOVE, basket::BASKET_VALIDATE, basket::BASKET_QUOTE_CREATE, - order::ORDER_SUBMIT, - order::ORDER_GET, - order::ORDER_LIST, - order::ORDER_APP_LIST, - order::ORDER_APP_EXPORT, - order::ORDER_REBIND, - order::ORDER_ACCEPT, - order::ORDER_DECLINE, - order::ORDER_CANCEL, - order::ORDER_REVISION_PROPOSE, - order::ORDER_REVISION_ACCEPT, - order::ORDER_REVISION_DECLINE, - order::ORDER_STATUS_GET, - order::ORDER_EVENT_LIST, - order::ORDER_EVENT_WATCH, + trade::TRADE_SUBMIT, + trade::TRADE_GET, + trade::TRADE_LIST, + trade::TRADE_APP_LIST, + trade::TRADE_APP_EXPORT, + trade::TRADE_REBIND, + trade::TRADE_ACCEPT, + trade::TRADE_DECLINE, + trade::TRADE_CANCEL, + trade::TRADE_REVISION_PROPOSE, + trade::TRADE_REVISION_ACCEPT, + trade::TRADE_REVISION_DECLINE, + trade::TRADE_STATUS_GET, + trade::TRADE_EVENT_LIST, + trade::TRADE_EVENT_WATCH, validation::VALIDATION_RECEIPT_GET, validation::VALIDATION_RECEIPT_LIST, validation::VALIDATION_RECEIPT_VERIFY, @@ -189,19 +191,19 @@ pub fn network_requirement(operation_id: &str) -> NetworkRequirement { | "listing.publish" | "listing.update" | "listing.archive" - | "order.submit" - | "order.event.list" + | "trade.submit" + | "trade.event.list" | "validation.receipt.get" | "validation.receipt.list" | "validation.receipt.verify" => NetworkRequirement::External { dry_run_requires_network: false, }, - "order.accept" - | "order.decline" - | "order.cancel" - | "order.revision.propose" - | "order.revision.accept" - | "order.revision.decline" => NetworkRequirement::External { + "trade.accept" + | "trade.decline" + | "trade.cancel" + | "trade.revision.propose" + | "trade.revision.accept" + | "trade.revision.decline" => NetworkRequirement::External { dry_run_requires_network: true, }, _ => NetworkRequirement::Local, @@ -212,13 +214,13 @@ pub fn requires_local_signer_mode(operation_id: &str) -> bool { matches!( operation_id, "sync.push" - | "order.submit" - | "order.accept" - | "order.decline" - | "order.cancel" - | "order.revision.propose" - | "order.revision.accept" - | "order.revision.decline" + | "trade.submit" + | "trade.accept" + | "trade.decline" + | "trade.cancel" + | "trade.revision.propose" + | "trade.revision.accept" + | "trade.revision.decline" ) } @@ -231,13 +233,13 @@ pub fn requires_direct_nostr_relay_publish_transport(operation_id: &str) -> bool | "listing.publish" | "listing.update" | "listing.archive" - | "order.submit" - | "order.accept" - | "order.decline" - | "order.cancel" - | "order.revision.propose" - | "order.revision.accept" - | "order.revision.decline" + | "trade.submit" + | "trade.accept" + | "trade.decline" + | "trade.cancel" + | "trade.revision.propose" + | "trade.revision.accept" + | "trade.revision.decline" ) } @@ -290,7 +292,9 @@ mod tests { "farm.get", "farm.rebind", "farm.profile.update", - "farm.location.update", + "farm.location.set", + "farm.location.get", + "farm.location.clear", "farm.fulfillment.update", "farm.readiness.check", "farm.publish", @@ -317,21 +321,21 @@ mod tests { "basket.adjustment.remove", "basket.validate", "basket.quote.create", - "order.submit", - "order.get", - "order.list", - "order.app.list", - "order.app.export", - "order.rebind", - "order.accept", - "order.decline", - "order.cancel", - "order.revision.propose", - "order.revision.accept", - "order.revision.decline", - "order.status.get", - "order.event.list", - "order.event.watch", + "trade.submit", + "trade.get", + "trade.list", + "trade.app.list", + "trade.app.export", + "trade.rebind", + "trade.accept", + "trade.decline", + "trade.cancel", + "trade.revision.propose", + "trade.revision.accept", + "trade.revision.decline", + "trade.status.get", + "trade.event.list", + "trade.event.watch", "validation.receipt.get", "validation.receipt.list", "validation.receipt.verify", @@ -353,7 +357,8 @@ mod tests { "farm.create", "farm.rebind", "farm.profile.update", - "farm.location.update", + "farm.location.set", + "farm.location.clear", "farm.fulfillment.update", "farm.publish", "listing.create", @@ -370,15 +375,15 @@ mod tests { "basket.adjustment.add", "basket.adjustment.remove", "basket.quote.create", - "order.submit", - "order.app.export", - "order.rebind", - "order.accept", - "order.decline", - "order.cancel", - "order.revision.propose", - "order.revision.accept", - "order.revision.decline", + "trade.submit", + "trade.app.export", + "trade.rebind", + "trade.accept", + "trade.decline", + "trade.cancel", + "trade.revision.propose", + "trade.revision.accept", + "trade.revision.decline", ]; const INTENTIONALLY_UNSUPPORTED_MUTATING_DRY_RUN_OPERATION_IDS: &[&str] = &[]; @@ -391,7 +396,7 @@ mod tests { .copied() .collect::<BTreeSet<_>>(); assert_eq!(actual, expected); - assert_eq!(OPERATION_REGISTRY.len(), 74); + assert_eq!(OPERATION_REGISTRY.len(), 76); } #[test] @@ -437,19 +442,20 @@ mod tests { "account.remove", "sync.push", "farm.rebind", + "farm.location.clear", "farm.publish", "listing.rebind", "listing.publish", "listing.update", "listing.archive", - "order.submit", - "order.rebind", - "order.accept", - "order.decline", - "order.cancel", - "order.revision.propose", - "order.revision.accept", - "order.revision.decline", + "trade.submit", + "trade.rebind", + "trade.accept", + "trade.decline", + "trade.cancel", + "trade.revision.propose", + "trade.revision.accept", + "trade.revision.decline", ] .into_iter() .collect::<BTreeSet<_>>(); @@ -515,8 +521,8 @@ mod tests { "market.refresh", "market.product.search", "basket.list", - "order.list", - "order.event.list", + "trade.list", + "trade.event.list", "validation.receipt.list", ] .into_iter() @@ -546,14 +552,14 @@ mod tests { "listing.publish", "listing.update", "listing.archive", - "order.submit", - "order.accept", - "order.decline", - "order.cancel", - "order.revision.propose", - "order.revision.accept", - "order.revision.decline", - "order.event.list", + "trade.submit", + "trade.accept", + "trade.decline", + "trade.cancel", + "trade.revision.propose", + "trade.revision.accept", + "trade.revision.decline", + "trade.event.list", "validation.receipt.get", "validation.receipt.list", "validation.receipt.verify", @@ -573,13 +579,13 @@ mod tests { .collect::<BTreeSet<_>>(); let expected = [ "sync.push", - "order.submit", - "order.accept", - "order.decline", - "order.cancel", - "order.revision.propose", - "order.revision.accept", - "order.revision.decline", + "trade.submit", + "trade.accept", + "trade.decline", + "trade.cancel", + "trade.revision.propose", + "trade.revision.accept", + "trade.revision.decline", ] .into_iter() .collect::<BTreeSet<_>>(); @@ -602,13 +608,13 @@ mod tests { "listing.publish", "listing.update", "listing.archive", - "order.submit", - "order.accept", - "order.decline", - "order.cancel", - "order.revision.propose", - "order.revision.accept", - "order.revision.decline", + "trade.submit", + "trade.accept", + "trade.decline", + "trade.cancel", + "trade.revision.propose", + "trade.revision.accept", + "trade.revision.decline", ] .into_iter() .collect::<BTreeSet<_>>(); @@ -642,7 +648,7 @@ mod tests { OperationRole::Buyer ); assert_eq!( - get_operation("order.list").unwrap().role, + get_operation("trade.list").unwrap().role, OperationRole::Any ); } diff --git a/src/registry/order.rs b/src/registry/order.rs @@ -1,241 +0,0 @@ -use super::{ApprovalPolicy, OperationRole, OperationSpec, RiskLevel}; - -pub const ORDER_SUBMIT: OperationSpec = operation!( - "order.submit", - "radroots order submit", - "order", - "order_submit", - "OrderSubmitRequest", - "OrderSubmitResult", - "Submit quoted basket as an order.", - Buyer, - true, - Required, - Critical, - false, - true -); - -pub const ORDER_GET: OperationSpec = operation!( - "order.get", - "radroots order get", - "order", - "order_get", - "OrderGetRequest", - "OrderGetResult", - "Get order details.", - Buyer, - false, - None, - Low, - false, - false -); - -pub const ORDER_LIST: OperationSpec = operation!( - "order.list", - "radroots order list", - "order", - "order_list", - "OrderListRequest", - "OrderListResult", - "List orders.", - Any, - false, - None, - Low, - true, - false -); - -pub const ORDER_APP_LIST: OperationSpec = operation!( - "order.app.list", - "radroots order app list", - "order", - "order_app_list", - "OrderAppListRequest", - "OrderAppListResult", - "List app-authored shared local order records.", - Buyer, - false, - None, - Low, - false, - false -); - -pub const ORDER_APP_EXPORT: OperationSpec = operation!( - "order.app.export", - "radroots order app export", - "order", - "order_app_export", - "OrderAppExportRequest", - "OrderAppExportResult", - "Export an app-authored shared order record as a CLI draft.", - Buyer, - true, - None, - Medium, - false, - true -); - -pub const ORDER_REBIND: OperationSpec = operation!( - "order.rebind", - "radroots order rebind", - "order", - "order_rebind", - "OrderRebindRequest", - "OrderRebindResult", - "Rebind a local order draft to an explicit buyer actor.", - Buyer, - true, - Required, - High, - false, - true -); - -pub const ORDER_ACCEPT: OperationSpec = operation!( - "order.accept", - "radroots order accept", - "order", - "order_accept", - "OrderAcceptRequest", - "OrderAcceptResult", - "Accept a buyer order request.", - Seller, - true, - Required, - Critical, - false, - true -); - -pub const ORDER_DECLINE: OperationSpec = operation!( - "order.decline", - "radroots order decline", - "order", - "order_decline", - "OrderDeclineRequest", - "OrderDeclineResult", - "Decline a buyer order request.", - Seller, - true, - Required, - High, - false, - true -); - -pub const ORDER_CANCEL: OperationSpec = operation!( - "order.cancel", - "radroots order cancel", - "order", - "order_cancel", - "OrderCancelRequest", - "OrderCancelResult", - "Withdraw a buyer order before agreement finalization.", - Buyer, - true, - Required, - High, - false, - true -); - -pub const ORDER_REVISION_PROPOSE: OperationSpec = operation!( - "order.revision.propose", - "radroots order revision propose", - "order", - "order_revision_propose", - "OrderRevisionProposeRequest", - "OrderRevisionProposeResult", - "Propose seller-authored order revision.", - Seller, - true, - Required, - High, - false, - true -); - -pub const ORDER_REVISION_ACCEPT: OperationSpec = operation!( - "order.revision.accept", - "radroots order revision accept", - "order", - "order_revision_accept", - "OrderRevisionAcceptRequest", - "OrderRevisionAcceptResult", - "Accept a seller-authored order revision.", - Buyer, - true, - Required, - High, - false, - true -); - -pub const ORDER_REVISION_DECLINE: OperationSpec = operation!( - "order.revision.decline", - "radroots order revision decline", - "order", - "order_revision_decline", - "OrderRevisionDeclineRequest", - "OrderRevisionDeclineResult", - "Decline a seller-authored order revision.", - Buyer, - true, - Required, - High, - false, - true -); - -pub const ORDER_STATUS_GET: OperationSpec = operation!( - "order.status.get", - "radroots order status get", - "order", - "order_status_get", - "OrderStatusGetRequest", - "OrderStatusGetResult", - "Get reducer-derived order status.", - Any, - false, - None, - Low, - false, - false -); - -pub const ORDER_EVENT_LIST: OperationSpec = operation!( - "order.event.list", - "radroots order event list", - "order", - "order_event_list", - "OrderEventListRequest", - "OrderEventListResult", - "List order events.", - Any, - false, - None, - Low, - true, - false -); - -pub const ORDER_EVENT_WATCH: OperationSpec = operation!( - "order.event.watch", - "radroots order event watch", - "order", - "order_event_watch", - "OrderEventWatchRequest", - "OrderEventWatchResult", - "Report deferred order event watch status.", - Any, - false, - None, - Low, - false, - false -); diff --git a/src/registry/trade.rs b/src/registry/trade.rs @@ -0,0 +1,241 @@ +use super::{ApprovalPolicy, OperationRole, OperationSpec, RiskLevel}; + +pub const TRADE_SUBMIT: OperationSpec = operation!( + "trade.submit", + "radroots trade submit", + "trade", + "trade_submit", + "TradeSubmitRequest", + "TradeSubmitResult", + "Submit quoted basket as a trade.", + Buyer, + true, + Required, + Critical, + false, + true +); + +pub const TRADE_GET: OperationSpec = operation!( + "trade.get", + "radroots trade get", + "trade", + "trade_get", + "TradeGetRequest", + "TradeGetResult", + "Get trade details.", + Buyer, + false, + None, + Low, + false, + false +); + +pub const TRADE_LIST: OperationSpec = operation!( + "trade.list", + "radroots trade list", + "trade", + "trade_list", + "TradeListRequest", + "TradeListResult", + "List trades.", + Any, + false, + None, + Low, + true, + false +); + +pub const TRADE_APP_LIST: OperationSpec = operation!( + "trade.app.list", + "radroots trade app list", + "trade", + "trade_app_list", + "TradeAppListRequest", + "TradeAppListResult", + "List app-authored shared local trade records.", + Buyer, + false, + None, + Low, + false, + false +); + +pub const TRADE_APP_EXPORT: OperationSpec = operation!( + "trade.app.export", + "radroots trade app export", + "trade", + "trade_app_export", + "TradeAppExportRequest", + "TradeAppExportResult", + "Export an app-authored shared trade record as a CLI draft.", + Buyer, + true, + None, + Medium, + false, + true +); + +pub const TRADE_REBIND: OperationSpec = operation!( + "trade.rebind", + "radroots trade rebind", + "trade", + "trade_rebind", + "TradeRebindRequest", + "TradeRebindResult", + "Rebind a local trade draft to an explicit buyer actor.", + Buyer, + true, + Required, + High, + false, + true +); + +pub const TRADE_ACCEPT: OperationSpec = operation!( + "trade.accept", + "radroots trade accept", + "trade", + "trade_accept", + "TradeAcceptRequest", + "TradeAcceptResult", + "Accept a buyer trade request.", + Seller, + true, + Required, + Critical, + false, + true +); + +pub const TRADE_DECLINE: OperationSpec = operation!( + "trade.decline", + "radroots trade decline", + "trade", + "trade_decline", + "TradeDeclineRequest", + "TradeDeclineResult", + "Decline a buyer trade request.", + Seller, + true, + Required, + High, + false, + true +); + +pub const TRADE_CANCEL: OperationSpec = operation!( + "trade.cancel", + "radroots trade cancel", + "trade", + "trade_cancel", + "TradeCancelRequest", + "TradeCancelResult", + "Withdraw a buyer trade before agreement finalization.", + Buyer, + true, + Required, + High, + false, + true +); + +pub const TRADE_REVISION_PROPOSE: OperationSpec = operation!( + "trade.revision.propose", + "radroots trade revision propose", + "trade", + "trade_revision_propose", + "TradeRevisionProposeRequest", + "TradeRevisionProposeResult", + "Propose seller-authored trade revision.", + Seller, + true, + Required, + High, + false, + true +); + +pub const TRADE_REVISION_ACCEPT: OperationSpec = operation!( + "trade.revision.accept", + "radroots trade revision accept", + "trade", + "trade_revision_accept", + "TradeRevisionAcceptRequest", + "TradeRevisionAcceptResult", + "Accept a seller-authored trade revision.", + Buyer, + true, + Required, + High, + false, + true +); + +pub const TRADE_REVISION_DECLINE: OperationSpec = operation!( + "trade.revision.decline", + "radroots trade revision decline", + "trade", + "trade_revision_decline", + "TradeRevisionDeclineRequest", + "TradeRevisionDeclineResult", + "Decline a seller-authored trade revision.", + Buyer, + true, + Required, + High, + false, + true +); + +pub const TRADE_STATUS_GET: OperationSpec = operation!( + "trade.status.get", + "radroots trade status get", + "trade", + "trade_status_get", + "TradeStatusGetRequest", + "TradeStatusGetResult", + "Get reducer-derived trade status.", + Any, + false, + None, + Low, + false, + false +); + +pub const TRADE_EVENT_LIST: OperationSpec = operation!( + "trade.event.list", + "radroots trade event list", + "trade", + "trade_event_list", + "TradeEventListRequest", + "TradeEventListResult", + "List trade events.", + Any, + false, + None, + Low, + true, + false +); + +pub const TRADE_EVENT_WATCH: OperationSpec = operation!( + "trade.event.watch", + "radroots trade event watch", + "trade", + "trade_event_watch", + "TradeEventWatchRequest", + "TradeEventWatchResult", + "Report deferred trade event watch status.", + Any, + false, + None, + Low, + false, + false +); diff --git a/src/runtime/config.rs b/src/runtime/config.rs @@ -2103,6 +2103,7 @@ mod tests { ), workspace_config_path: None, app_data_root: PathBuf::from("/home/tester/.radroots/data/apps/cli"), + shared_cache_root: PathBuf::from("/home/tester/.radroots/cache"), app_logs_root: PathBuf::from("/home/tester/.radroots/logs/apps/cli"), shared_accounts_data_root: PathBuf::from( "/home/tester/.radroots/data/shared/accounts" diff --git a/src/runtime/farm.rs b/src/runtime/farm.rs @@ -4,21 +4,23 @@ use std::time::{SystemTime, UNIX_EPOCH}; use radroots_authority::RadrootsActorContext; use radroots_events::contract::RadrootsActorRole; use radroots_events::farm::{RadrootsFarm, RadrootsFarmPublicLocation}; +use radroots_events::ids::RadrootsAddressableCoordinate; use radroots_events::kinds::{KIND_FARM, KIND_PROFILE}; use radroots_events::listing::RadrootsListingPublicLocation; use radroots_events::profile::{RadrootsProfile, RadrootsProfileType}; use radroots_events_codec::d_tag::is_d_tag_base64url; use radroots_events_codec::profile::encode::to_wire_parts_with_profile_type; use radroots_sdk::{ - FarmEnqueuePublishRequest, FarmEnqueueReceipt, FarmPreparePublishRequest, FarmPublishPlan, - PushOutboxEventReceipt, PushOutboxEventState, PushOutboxReceipt, PushOutboxRelayOutcomeKind, - PushOutboxRequest, SdkMutationState, + FarmEnqueuePublishRequest, FarmEnqueueReceipt, FarmPreparePublishRequest, + FarmPrivateLocationClearRequest, FarmPrivateLocationReceipt, FarmPrivateLocationUpsertRequest, + FarmPublishPlan, PushOutboxEventReceipt, PushOutboxEventState, PushOutboxReceipt, + PushOutboxRelayOutcomeKind, PushOutboxRequest, SdkExactLocation, SdkMutationState, }; use serde_json::json; use crate::cli::global::{ - FarmCreateArgs, FarmFieldArg, FarmPublishArgs, FarmRebindArgs, FarmScopeArg, FarmScopedArgs, - FarmUpdateArgs, + FarmCreateArgs, FarmFieldArg, FarmPrivateLocationKeyArgs, FarmPrivateLocationSetArgs, + FarmPublishArgs, FarmRebindArgs, FarmScopeArg, FarmScopedArgs, FarmUpdateArgs, }; use crate::runtime::RuntimeError; use crate::runtime::account::{self, AccountRecordView}; @@ -35,6 +37,7 @@ use crate::runtime::sdk::{ use crate::runtime::signer::ActorWriteBindingError; use crate::view::runtime::{ FarmConfigDocumentView, FarmConfigSummaryView, FarmGetView, FarmListingDefaultsView, + FarmPrivateExactLocationView, FarmPrivateLocationView, FarmPrivatePublicLocalityView, FarmPublicationView, FarmPublishComponentView, FarmPublishEventView, FarmPublishView, FarmRebindView, FarmSelectionView, FarmSetView, FarmSetupView, FarmStatusView, RelayFailureView, @@ -45,6 +48,7 @@ const FARM_SELLER_ACTOR_SOURCE: &str = "farm_config"; const SDK_FARM_WRITE_SOURCE: &str = "SDK farm publish · configured signer"; const SDK_PROFILE_NOT_SUBMITTED_METHOD: &str = "sdk.farm.profile.not_submitted"; const SDK_FARM_PUBLISH_METHOD: &str = "sdk.farm.publish.v1"; +const SDK_FARM_PRIVATE_LOCATION_SOURCE: &str = "SDK private farm location · local private store"; const SDK_PROFILE_NOT_SUBMITTED_REASON: &str = "profile publish is not part of SDK farm.publish.v1; profile draft was not submitted"; @@ -338,6 +342,94 @@ pub fn set_preflight( }) } +pub fn private_location_set( + config: &RuntimeConfig, + args: &FarmPrivateLocationSetArgs, +) -> Result<FarmPrivateLocationView, CliSdkAdapterError> { + let target = match private_location_target(config, args.farm_d_tag.as_deref())? { + Some(target) => target, + None => return Ok(private_location_unconfigured_view()), + }; + let session = CliSdkSession::connect(config)?; + let geonames_status = session.sdk().geonames().ensure()?; + let request = FarmPrivateLocationUpsertRequest::new( + target.actor.clone(), + target.farm_d_tag.clone(), + SdkExactLocation::new(args.latitude, args.longitude), + ); + let receipt = session.block_on(session.sdk().farms().upsert_private_location(request))?; + Ok(private_location_view_from_receipt( + "set", + target, + Some(receipt), + Some(geonames_status.path.display().to_string()), + None, + )) +} + +pub fn private_location_get( + config: &RuntimeConfig, + args: &FarmPrivateLocationKeyArgs, +) -> Result<FarmPrivateLocationView, CliSdkAdapterError> { + let target = match private_location_target(config, args.farm_d_tag.as_deref())? { + Some(target) => target, + None => return Ok(private_location_unconfigured_view()), + }; + let session = CliSdkSession::connect(config)?; + let receipt = session.block_on(session.sdk().farms().private_location(&target.farm_addr))?; + let state = if receipt.is_some() { + "ready" + } else { + "missing" + }; + let reason = receipt + .is_none() + .then(|| "no private exact farm location is stored for this farm".to_owned()); + Ok(private_location_view_from_receipt( + state, target, receipt, None, reason, + )) +} + +pub fn private_location_clear( + config: &RuntimeConfig, + args: &FarmPrivateLocationKeyArgs, +) -> Result<FarmPrivateLocationView, CliSdkAdapterError> { + let target = match private_location_target(config, args.farm_d_tag.as_deref())? { + Some(target) => target, + None => return Ok(private_location_unconfigured_view()), + }; + let session = CliSdkSession::connect(config)?; + let request = + FarmPrivateLocationClearRequest::new(target.actor.clone(), target.farm_d_tag.clone()); + let receipt = session.block_on(session.sdk().farms().clear_private_location(request))?; + Ok(FarmPrivateLocationView { + state: if receipt.cleared { + "cleared" + } else { + "missing" + } + .to_owned(), + source: SDK_FARM_PRIVATE_LOCATION_SOURCE.to_owned(), + farm_addr: Some(receipt.farm_addr.to_string()), + farm_d_tag: Some(target.farm_d_tag), + seller_account_id: Some(target.seller_account_id), + seller_pubkey: Some(target.seller_pubkey), + exact_location: None, + public_locality: None, + geonames_feature_id: None, + geonames_country_id: None, + geonames_database_path: None, + cleared: Some(receipt.cleared), + reason: (!receipt.cleared) + .then(|| "no private exact farm location was stored for this farm".to_owned()), + actions: if receipt.cleared { + Vec::new() + } else { + vec!["radroots farm location set --lat <lat> --lng <lng>".to_owned()] + }, + }) +} + pub fn status( config: &RuntimeConfig, args: &FarmScopedArgs, @@ -741,6 +833,15 @@ struct SdkFarmPublishInput { } #[derive(Debug, Clone)] +struct PrivateFarmLocationTarget { + actor: RadrootsActorContext, + farm_addr: RadrootsAddressableCoordinate, + farm_d_tag: String, + seller_account_id: String, + seller_pubkey: String, +} + +#[derive(Debug, Clone)] struct FarmPublishPreviews { profile: FarmPublishEventDraft, } @@ -1243,6 +1344,42 @@ fn selected_account_for_draft( account::resolve_account(config) } +fn private_location_target( + config: &RuntimeConfig, + farm_d_tag: Option<&str>, +) -> Result<Option<PrivateFarmLocationTarget>, RuntimeError> { + let resolved_scope = farm_config::resolve_scope(&config.paths, None)?; + let Some(resolved) = farm_config::load(config, Some(resolved_scope))? else { + return Ok(None); + }; + let Some(account) = configured_account(config, &resolved.document.selection.account)? else { + return Ok(None); + }; + let seller_pubkey = account.record.public_identity.public_key_hex.clone(); + let seller_account_id = account.record.account_id.to_string(); + let farm_d_tag = farm_d_tag + .map(str::to_owned) + .unwrap_or_else(|| resolved.document.selection.farm_d_tag.clone()); + let actor = RadrootsActorContext::local_account( + seller_pubkey.as_str(), + seller_account_id.clone(), + [RadrootsActorRole::Farmer], + ) + .map_err(|error| { + RuntimeError::Config(format!("invalid farm private location actor: {error}")) + })?; + let farm_addr = + RadrootsAddressableCoordinate::parse(format!("{KIND_FARM}:{seller_pubkey}:{farm_d_tag}")) + .map_err(|error| RuntimeError::Config(format!("invalid farm address: {error}")))?; + Ok(Some(PrivateFarmLocationTarget { + actor, + farm_addr, + farm_d_tag, + seller_account_id, + seller_pubkey, + })) +} + fn missing_selected_account_setup_view() -> FarmSetupView { FarmSetupView { state: "unconfigured".to_owned(), @@ -1253,6 +1390,67 @@ fn missing_selected_account_setup_view() -> FarmSetupView { } } +fn private_location_unconfigured_view() -> FarmPrivateLocationView { + FarmPrivateLocationView { + state: "unconfigured".to_owned(), + source: SDK_FARM_PRIVATE_LOCATION_SOURCE.to_owned(), + farm_addr: None, + farm_d_tag: None, + seller_account_id: None, + seller_pubkey: None, + exact_location: None, + public_locality: None, + geonames_feature_id: None, + geonames_country_id: None, + geonames_database_path: None, + cleared: None, + reason: Some("create and bind a farm before storing private location".to_owned()), + actions: vec![ + "radroots account create".to_owned(), + "radroots farm create".to_owned(), + ], + } +} + +fn private_location_view_from_receipt( + state: &str, + target: PrivateFarmLocationTarget, + receipt: Option<FarmPrivateLocationReceipt>, + geonames_database_path: Option<String>, + reason: Option<String>, +) -> FarmPrivateLocationView { + let receipt_ref = receipt.as_ref(); + FarmPrivateLocationView { + state: state.to_owned(), + source: SDK_FARM_PRIVATE_LOCATION_SOURCE.to_owned(), + farm_addr: Some(target.farm_addr.to_string()), + farm_d_tag: Some(target.farm_d_tag), + seller_account_id: Some(target.seller_account_id), + seller_pubkey: Some(target.seller_pubkey), + exact_location: receipt_ref.map(|receipt| FarmPrivateExactLocationView { + lat: receipt.exact_location.latitude, + lng: receipt.exact_location.longitude, + }), + public_locality: receipt_ref.map(|receipt| FarmPrivatePublicLocalityView { + primary: receipt.public_locality.primary.clone(), + city: receipt.public_locality.city.clone(), + region: receipt.public_locality.region.clone(), + country: receipt.public_locality.country.clone(), + geohash5: receipt.public_locality.geohash5.clone(), + }), + geonames_feature_id: receipt_ref.and_then(|receipt| receipt.geonames_feature_id), + geonames_country_id: receipt_ref.and_then(|receipt| receipt.geonames_country_id.clone()), + geonames_database_path, + cleared: None, + reason, + actions: if receipt.is_some() { + Vec::new() + } else { + vec!["radroots farm location set --lat <lat> --lng <lng>".to_owned()] + }, + } +} + fn init_document( scope: FarmConfigScope, account: &AccountRecordView, diff --git a/src/runtime/farm_config.rs b/src/runtime/farm_config.rs @@ -408,6 +408,7 @@ mod tests { workspace_config_path: (profile == "repo_local") .then(|| repo_local_root.join("config.toml")), app_data_root: root.join("home/.radroots/data/apps/cli"), + shared_cache_root: root.join("home/.radroots/cache"), app_logs_root: root.join("home/.radroots/logs/apps/cli"), shared_accounts_data_root: root.join("home/.radroots/data/shared/accounts"), shared_accounts_secrets_root: root.join("home/.radroots/secrets/shared/accounts"), diff --git a/src/runtime/local_events.rs b/src/runtime/local_events.rs @@ -168,6 +168,7 @@ mod tests { ), workspace_config_path: None, app_data_root: PathBuf::from("/repo/infra/local/runtime/radroots/data/apps/cli"), + shared_cache_root: PathBuf::from("/repo/infra/local/runtime/radroots/cache"), app_logs_root: PathBuf::from("/repo/infra/local/runtime/radroots/logs/apps/cli"), shared_accounts_data_root: PathBuf::from(shared_accounts_data_root), shared_accounts_secrets_root: PathBuf::from( diff --git a/src/runtime/order.rs b/src/runtime/order.rs @@ -77,20 +77,18 @@ use radroots_sql_core::SqliteExecutor; use radroots_trade::order::{ RadrootsListingInventoryAccountingInputs, RadrootsListingInventoryAccountingIssue, RadrootsListingInventoryAccountingProjection, RadrootsListingInventoryBinAvailability, - RadrootsOrderCancellationRecord, RadrootsOrderDecisionRecord, RadrootsOrderIssue, - RadrootsOrderReductionInputs, RadrootsOrderRequestRecord, RadrootsOrderRevisionDecisionRecord, - RadrootsOrderRevisionProposalRecord, canonicalize_order_decision_for_signer, - canonicalize_order_request_for_signer, reduce_listing_inventory_accounting, - reduce_order_events, + RadrootsOrderCancellationRecord, RadrootsOrderDecisionRecord, RadrootsOrderRequestRecord, + RadrootsOrderRevisionDecisionRecord, RadrootsOrderRevisionProposalRecord, + canonicalize_order_decision_for_signer, canonicalize_order_request_for_signer, + reduce_listing_inventory_accounting, }; -use radroots_trade::workflow::RadrootsTradeWorkflowState; use serde::{Deserialize, Serialize}; use serde_json::{Value, json}; use crate::cli::global::{ - OrderAppRecordExportArgs, OrderCancelArgs, OrderDecisionArg, OrderDecisionArgs, - OrderDraftCreateArgs, OrderRebindArgs, OrderRevisionDecisionArg, OrderRevisionDecisionArgs, - OrderRevisionProposeArgs, OrderStatusArgs, OrderSubmitArgs, RecordLookupArgs, + OrderDraftCreateArgs, RecordLookupArgs, TradeAppRecordExportArgs, TradeCancelArgs, + TradeDecisionArg, TradeDecisionArgs, TradeRebindArgs, TradeRevisionDecisionArg, + TradeRevisionDecisionArgs, TradeRevisionProposeArgs, TradeStatusArgs, TradeSubmitArgs, }; use crate::runtime::RuntimeError; use crate::runtime::account; @@ -113,26 +111,24 @@ use crate::view::runtime::{ OrderCancellationView, OrderDecisionView, OrderDraftItemView, OrderEventListEntryView, OrderEventListView, OrderGetView, OrderInventoryBinView, OrderInventoryView, OrderIssueView, OrderListView, OrderNewView, OrderRebindView, OrderRevisionDecisionView, - OrderRevisionProposalView, OrderStatusLifecycleCancellationView, OrderStatusLifecycleView, - OrderStatusRevisionView, OrderStatusView, OrderSubmitView, OrderSummaryView, RelayFailureView, + OrderRevisionProposalView, OrderStatusView, OrderSubmitView, OrderSummaryView, + RelayFailureView, }; use self::sdk_status::sdk_order_status_view; const ORDER_DRAFT_KIND: &str = "order_draft_v1"; -const ORDER_SOURCE: &str = "local order drafts · local first"; -const ORDER_APP_RECORD_SOURCE: &str = "app-authored shared local order records"; -const ORDER_SUBMIT_SOURCE: &str = "SDK order submit · local key"; -const ORDER_DECISION_SOURCE: &str = "SDK order decision · local key"; -const ORDER_REVISION_PROPOSAL_SOURCE: &str = "SDK order revision proposal · local key"; -const ORDER_REVISION_DECISION_SOURCE: &str = "SDK order revision decision · local key"; -const ORDER_CANCELLATION_SOURCE: &str = "SDK order cancellation · local key"; +const ORDER_SOURCE: &str = "local trade drafts · local first"; +const ORDER_APP_RECORD_SOURCE: &str = "app-authored shared local trade records"; +const ORDER_SUBMIT_SOURCE: &str = "SDK trade submit · local key"; +const ORDER_DECISION_SOURCE: &str = "SDK trade decision · local key"; +const ORDER_REVISION_PROPOSAL_SOURCE: &str = "SDK trade revision proposal · local key"; +const ORDER_REVISION_DECISION_SOURCE: &str = "SDK trade revision decision · local key"; +const ORDER_CANCELLATION_SOURCE: &str = "SDK trade cancellation · local key"; const ORDER_EVENT_LIST_SOURCE: &str = "direct Nostr relay fetch · selected seller identity"; -const LEGACY_ORDER_PREFLIGHT_STATUS_SOURCE: &str = - "legacy direct Nostr relay preflight status · active order reducer"; -const ORDER_STATUS_SDK_SOURCE: &str = "SDK local order projection"; +const ORDER_STATUS_SDK_SOURCE: &str = "SDK local trade projection"; const ORDER_EVENT_LIST_RELAY_ACTION: &str = - "radroots --relay wss://relay.example.com order event list"; + "radroots --relay wss://relay.example.com trade event list"; const ORDER_BUYER_ACTOR_SOURCE_RESOLVED_ACCOUNT: &str = "resolved_account"; const ORDER_BUYER_ACTOR_SOURCE_REBIND: &str = "order_rebind"; const ORDER_APP_RECORD_LIST_LIMIT: u32 = 500; @@ -160,7 +156,7 @@ fn protocol_listing_addr(value: &str, field: &str) -> Result<RadrootsListingAddr fn protocol_revision_id(value: &str, field: &str) -> Result<RadrootsOrderRevisionId, RuntimeError> { value.parse().map_err(|error| { - RuntimeError::Config(format!("{field} is not a valid order revision id: {error}")) + RuntimeError::Config(format!("{field} is not a valid trade revision id: {error}")) }) } @@ -200,10 +196,6 @@ fn protocol_pubkey(value: &str, field: &str) -> Result<RadrootsPublicKey, Runtim .map_err(|error| RuntimeError::Config(format!("{field} is not a valid pubkey: {error}"))) } -fn optional_string<T: ToString>(value: Option<T>) -> Option<String> { - value.map(|value| value.to_string()) -} - fn required_order_context_event_id( event_id: Option<RadrootsEventId>, tag: &'static str, @@ -503,7 +495,7 @@ pub fn scaffold( )? .into(); view.actions - .insert(0, format!("radroots order get {}", view.order_id)); + .insert(0, format!("radroots trade get {}", view.order_id)); Ok(view) } @@ -586,7 +578,7 @@ pub fn scaffold_preflight( .into(); view.state = "dry_run".to_owned(); view.actions - .insert(0, format!("radroots order get {}", view.order_id)); + .insert(0, format!("radroots trade get {}", view.order_id)); Ok(view) } @@ -624,10 +616,10 @@ pub fn get(config: &RuntimeConfig, args: &RecordLookupArgs) -> Result<OrderGetVi updated_at_unix: None, job: None, workflow: None, - reason: Some(format!("order draft `{lookup}` was not found")), + reason: Some(format!("trade draft `{lookup}` was not found")), issues: Vec::new(), actions: vec![ - "radroots order list".to_owned(), + "radroots trade list".to_owned(), "radroots basket create".to_owned(), ], }); @@ -768,7 +760,7 @@ pub fn app_record_list(config: &RuntimeConfig) -> Result<OrderAppRecordListView, pub fn app_record_export( config: &RuntimeConfig, - args: &OrderAppRecordExportArgs, + args: &TradeAppRecordExportArgs, ) -> Result<OrderAppRecordExportView, RuntimeError> { let Some(record) = get_shared_record(config, args.record_id.as_str())? else { return Ok(OrderAppRecordExportView { @@ -792,10 +784,10 @@ pub fn app_record_export( seller_pubkey: None, issues: Vec::new(), reason: Some(format!( - "app-authored local order record `{}` was not found", + "app-authored local trade record `{}` was not found", args.record_id )), - actions: vec!["radroots order app list".to_owned()], + actions: vec!["radroots trade app list".to_owned()], }); }; @@ -827,7 +819,7 @@ pub fn app_record_export( seller_pubkey: non_empty_string(app_order.loaded.document.order.seller_pubkey.clone()), issues, reason: Some(format!( - "app-authored local order record `{}` is not ready as a CLI order draft", + "app-authored local trade record `{}` is not ready as a CLI trade draft", args.record_id )), actions, @@ -869,17 +861,17 @@ pub fn app_record_export( seller_pubkey: non_empty_string(app_order.loaded.document.order.seller_pubkey.clone()), issues, reason: Some(if config.output.dry_run { - "dry run requested; order draft was not written".to_owned() + "dry run requested; trade draft was not written".to_owned() } else { - "app-authored local order record exported as a CLI order draft".to_owned() + "app-authored local trade record exported as a CLI trade draft".to_owned() }), actions: vec![ format!( - "radroots order get {}", + "radroots trade get {}", app_order.loaded.document.order.order_id ), format!( - "radroots --relay wss://relay.example.com order submit {}", + "radroots --relay wss://relay.example.com trade submit {}", app_order.loaded.document.order.order_id ), ], @@ -888,7 +880,7 @@ pub fn app_record_export( pub fn submit( config: &RuntimeConfig, - args: &OrderSubmitArgs, + args: &TradeSubmitArgs, ) -> Result<OrderSubmitView, CliSdkAdapterError> { let file = draft_lookup_path(config, args.key.as_str()); let (loaded, source_issues) = if file.exists() { @@ -955,11 +947,11 @@ pub fn submit( failed_relays: Vec::new(), idempotency_key: args.idempotency_key.clone(), signer_mode: None, - reason: Some(format!("order draft `{}` was not found", args.key)), + reason: Some(format!("trade draft `{}` was not found", args.key)), job: None, issues: Vec::new(), actions: vec![ - "radroots order list".to_owned(), + "radroots trade list".to_owned(), "radroots basket create".to_owned(), ], }); @@ -973,7 +965,7 @@ pub fn submit( if !issues.is_empty() { let mut actions = actions_for_document(&loaded.document, loaded.file.as_path(), &issues); actions.push(format!( - "radroots order get {}", + "radroots trade get {}", loaded.document.order.order_id )); return Ok(OrderSubmitView { @@ -1001,7 +993,7 @@ pub fn submit( failed_relays: Vec::new(), idempotency_key: args.idempotency_key.clone(), signer_mode: None, - reason: Some("order draft is not ready for submit".to_owned()), + reason: Some("trade draft is not ready for submit".to_owned()), job: None, issues, actions, @@ -1052,21 +1044,21 @@ pub fn submit( pub fn rebind( config: &RuntimeConfig, - args: &OrderRebindArgs, + args: &TradeRebindArgs, ) -> Result<OrderRebindView, RuntimeError> { rebind_inner(config, args, false) } pub fn rebind_preflight( config: &RuntimeConfig, - args: &OrderRebindArgs, + args: &TradeRebindArgs, ) -> Result<OrderRebindView, RuntimeError> { rebind_inner(config, args, true) } fn rebind_inner( config: &RuntimeConfig, - args: &OrderRebindArgs, + args: &TradeRebindArgs, dry_run: bool, ) -> Result<OrderRebindView, RuntimeError> { let file = draft_lookup_path(config, args.key.as_str()); @@ -1089,9 +1081,9 @@ fn rebind_inner( buyer_pubkey_changed: false, existing_request_check: "not_checked".to_owned(), existing_request_event_ids: Vec::new(), - reason: Some(format!("order draft `{}` was not found", args.key)), + reason: Some(format!("trade draft `{}` was not found", args.key)), actions: vec![ - "radroots order list".to_owned(), + "radroots trade list".to_owned(), "radroots basket create".to_owned(), ], }); @@ -1138,7 +1130,7 @@ fn rebind_inner( .to_owned(), ), actions: vec![ - format!("radroots order status get {from_order_id}"), + format!("radroots trade status get {from_order_id}"), "radroots basket quote create <basket-id>".to_owned(), ], }); @@ -1208,7 +1200,7 @@ fn rebind_inner( args.key, args.selector )] } else { - vec![format!("radroots order get {to_order_id}")] + vec![format!("radroots trade get {to_order_id}")] }, }) } @@ -1221,7 +1213,7 @@ pub fn event_list( return Ok(order_event_list_unconfigured( None, ORDER_ACTOR_CONTEXT_NETWORK_ONLY, - "order event list requires at least one configured relay".to_owned(), + "trade event list requires at least one configured relay".to_owned(), Vec::new(), vec![ORDER_EVENT_LIST_RELAY_ACTION.to_owned()], )); @@ -1233,7 +1225,7 @@ pub fn event_list( return Ok(order_event_list_unconfigured( None, ORDER_ACTOR_CONTEXT_NETWORK_ONLY, - "order event list requires a selected seller account".to_owned(), + "trade event list requires a selected seller account".to_owned(), config.relay.urls.clone(), vec!["radroots account create".to_owned()], )); @@ -1269,7 +1261,7 @@ pub fn event_list( pub fn decide( config: &RuntimeConfig, - args: &OrderDecisionArgs, + args: &TradeDecisionArgs, ) -> Result<OrderDecisionView, RuntimeError> { if config.relay.urls.is_empty() { let mut view = @@ -1329,12 +1321,34 @@ pub fn decide( } if resolution.requests.len() == 1 { let request = resolution.requests[0].clone(); - let status_view = legacy_order_preflight_relay_status( + let filter = order_status_filter(args.key.as_str())?; + let receipt = match fetch_events_from_relays(&config.relay.urls, filter) { + Ok(receipt) => receipt, + Err(DirectRelayFetchError::Connect { + reason, + target_relays, + failed_relays, + }) => { + let mut view = + order_decision_base_view(config, args, "unavailable", config.output.dry_run); + apply_order_decision_resolution(&mut view, &resolution); + apply_order_decision_request(&mut view, &request); + view.seller_pubkey = Some(seller_pubkey); + view.target_relays = target_relays; + view.failed_relays = relay_failures(failed_relays); + view.reason = Some(format!("direct relay connection failed: {reason}")); + return Ok(view); + } + Err(error) => return Err(RuntimeError::Network(error.to_string())), + }; + let mut status_view = sdk_order_status_from_relay_receipt( config, - &OrderStatusArgs { - key: args.key.clone(), - }, - )?; + args.key.as_str(), + ORDER_ACTOR_CONTEXT_RESOLVED_ACCOUNT, + receipt, + ) + .map_err(cli_sdk_error_to_runtime)?; + enrich_order_status_inventory(config, &mut status_view)?; if let Some(view) = order_decision_preflight_view_from_status( config, args, @@ -1405,7 +1419,7 @@ pub fn decide( pub fn revision_propose( config: &RuntimeConfig, - args: &OrderRevisionProposeArgs, + args: &TradeRevisionProposeArgs, ) -> Result<OrderRevisionProposalView, RuntimeError> { if let Some(view) = order_revision_args_preflight_view(config, args) { return Ok(view); @@ -1414,7 +1428,7 @@ pub fn revision_propose( let mut view = order_revision_base_view(config, args, "unconfigured", config.output.dry_run); view.reason = - Some("order revision propose requires at least one configured relay".to_owned()); + Some("trade revision propose requires at least one configured relay".to_owned()); return Ok(view); } @@ -1424,7 +1438,7 @@ pub fn revision_propose( let mut view = order_revision_base_view(config, args, "unconfigured", config.output.dry_run); view.reason = - Some("order revision propose requires a selected seller account".to_owned()); + Some("trade revision propose requires a selected seller account".to_owned()); view.actions = vec!["radroots account create".to_owned()]; return Ok(view); } @@ -1452,17 +1466,13 @@ pub fn revision_propose( let evidence_events = order_evidence_from_relay_events(receipt.events.as_slice()); let revision_candidates = order_revision_proposals_from_events(args.key.as_str(), receipt.events.as_slice()); - let reduction = order_status_reduction_from_receipt_with_context( - OrderStatusContext { - order_id: args.key.as_str(), - buyer_pubkey: None, - seller_pubkey: None, - selected_account_pubkey: Some(selected_pubkey.as_str()), - actor_context_source: ORDER_ACTOR_CONTEXT_RESOLVED_ACCOUNT, - }, + let mut status_view = sdk_order_status_from_relay_receipt( + config, + args.key.as_str(), + ORDER_ACTOR_CONTEXT_RESOLVED_ACCOUNT, receipt, - ); - let mut status_view = reduction.view; + ) + .map_err(cli_sdk_error_to_runtime)?; enrich_order_status_inventory(config, &mut status_view)?; if let Some(view) = order_revision_preflight_view_from_status( config, @@ -1497,7 +1507,7 @@ pub fn revision_propose( args, &status_view, format!( - "order revision propose inputs for `{}` are invalid", + "trade revision propose inputs for `{}` are invalid", args.key ), vec![issue_with_code( @@ -1527,7 +1537,7 @@ pub fn revision_propose( pub fn revision_decide( config: &RuntimeConfig, - args: &OrderRevisionDecisionArgs, + args: &TradeRevisionDecisionArgs, ) -> Result<OrderRevisionDecisionView, RuntimeError> { if let Some(view) = order_revision_decision_args_preflight_view(config, args) { return Ok(view); @@ -1536,7 +1546,7 @@ pub fn revision_decide( let mut view = order_revision_decision_base_view(config, args, "unconfigured", config.output.dry_run); view.reason = - Some("order revision decision requires at least one configured relay".to_owned()); + Some("trade revision decision requires at least one configured relay".to_owned()); return Ok(view); } @@ -1550,7 +1560,7 @@ pub fn revision_decide( config.output.dry_run, ); view.reason = - Some("order revision decision requires a selected buyer account".to_owned()); + Some("trade revision decision requires a selected buyer account".to_owned()); view.actions = vec!["radroots account create".to_owned()]; return Ok(view); } @@ -1582,20 +1592,13 @@ pub fn revision_decide( let evidence_events = order_evidence_from_relay_events(receipt.events.as_slice()); let revision_candidates = order_revision_proposals_from_events(args.key.as_str(), receipt.events.as_slice()); - let reduction = order_status_reduction_from_receipt_with_context( - OrderStatusContext { - order_id: args.key.as_str(), - buyer_pubkey: actor_context.status_buyer_pubkey.as_deref(), - seller_pubkey: actor_context.status_seller_pubkey.as_deref(), - selected_account_pubkey: actor_context - .bound - .is_none() - .then_some(selected_pubkey.as_str()), - actor_context_source: actor_context.status_context_source, - }, + let mut status_view = sdk_order_status_from_relay_receipt( + config, + args.key.as_str(), + actor_context.status_context_source, receipt, - ); - let mut status_view = reduction.view; + ) + .map_err(cli_sdk_error_to_runtime)?; enrich_order_status_inventory(config, &mut status_view)?; if let Some(view) = order_revision_decision_preflight_view_from_status( config, @@ -1617,7 +1620,7 @@ pub fn revision_decide( args, &status_view, format!( - "order revision {} refused because revision `{}` is not the latest pending proposal", + "trade revision {} refused because revision `{}` is not the latest pending proposal", args.decision.command(), args.revision_id.trim() ), @@ -1643,7 +1646,7 @@ pub fn revision_decide( Some(bound) => resolve_local_order_bound_buyer_signing_identity( config, &bound.loaded, - format!("order revision {}", args.decision.command()).as_str(), + format!("trade revision {}", args.decision.command()).as_str(), ), None => resolve_local_order_revision_decision_signing_identity(config, buyer_pubkey, args), }; @@ -1659,14 +1662,14 @@ pub fn revision_decide( )); } }; - if args.decision == OrderRevisionDecisionArg::Accept { + if args.decision == TradeRevisionDecisionArg::Accept { let issues = order_revision_inventory_issues(&status_view, &proposal.payload); if !issues.is_empty() { let mut view = order_revision_decision_invalid_view( config, args, &status_view, - "order revision accept refused because visible inventory is unavailable for the revised items", + "trade revision accept refused because visible inventory is unavailable for the revised items", issues, ); apply_order_revision_decision_proposal(&mut view, proposal); @@ -1697,12 +1700,12 @@ pub fn revision_decide( pub fn cancel( config: &RuntimeConfig, - args: &OrderCancelArgs, + args: &TradeCancelArgs, ) -> Result<OrderCancellationView, RuntimeError> { if config.relay.urls.is_empty() { let mut view = order_cancellation_base_view(config, args, "unconfigured", config.output.dry_run); - view.reason = Some("order cancel requires at least one configured relay".to_owned()); + view.reason = Some("trade cancel requires at least one configured relay".to_owned()); return Ok(view); } @@ -1711,7 +1714,7 @@ pub fn cancel( None => { let mut view = order_cancellation_base_view(config, args, "unconfigured", config.output.dry_run); - view.reason = Some("order cancel requires a selected buyer account".to_owned()); + view.reason = Some("trade cancel requires a selected buyer account".to_owned()); view.actions = vec!["radroots account create".to_owned()]; return Ok(view); } @@ -1737,20 +1740,13 @@ pub fn cancel( }; let evidence_events = order_evidence_from_relay_events(receipt.events.as_slice()); - let reduction = order_status_reduction_from_receipt_with_context( - OrderStatusContext { - order_id: args.key.as_str(), - buyer_pubkey: actor_context.status_buyer_pubkey.as_deref(), - seller_pubkey: actor_context.status_seller_pubkey.as_deref(), - selected_account_pubkey: actor_context - .bound - .is_none() - .then_some(selected_pubkey.as_str()), - actor_context_source: actor_context.status_context_source, - }, + let status_view = sdk_order_status_from_relay_receipt( + config, + args.key.as_str(), + actor_context.status_context_source, receipt, - ); - let status_view = reduction.view; + ) + .map_err(cli_sdk_error_to_runtime)?; if let Some(view) = order_cancellation_preflight_view_from_status( config, args, @@ -1766,7 +1762,7 @@ pub fn cancel( .ok_or_else(|| RuntimeError::Config("order is missing buyer_pubkey".to_owned()))?; let signing = match actor_context.bound.as_ref() { Some(bound) => { - resolve_local_order_bound_buyer_signing_identity(config, &bound.loaded, "order cancel") + resolve_local_order_bound_buyer_signing_identity(config, &bound.loaded, "trade cancel") } None => resolve_local_order_cancellation_signing_identity(config, buyer_pubkey), }; @@ -1792,7 +1788,7 @@ pub fn cancel( pub fn status( config: &RuntimeConfig, - args: &OrderStatusArgs, + args: &TradeStatusArgs, ) -> Result<OrderStatusView, CliSdkAdapterError> { let request = OrderStatusRequest::parse(args.key.as_str())?; let session = CliSdkSession::connect(config)?; @@ -1800,96 +1796,63 @@ pub fn status( Ok(sdk_order_status_view(receipt)) } -fn legacy_order_preflight_relay_status( +fn sdk_order_status_from_relay_receipt( config: &RuntimeConfig, - args: &OrderStatusArgs, -) -> Result<OrderStatusView, RuntimeError> { - if config.relay.urls.is_empty() { - return Ok(OrderStatusView { - state: "unconfigured".to_owned(), - source: LEGACY_ORDER_PREFLIGHT_STATUS_SOURCE.to_owned(), - order_id: args.key.clone(), - actor_context_source: ORDER_ACTOR_CONTEXT_NETWORK_ONLY.to_owned(), - request_event_id: None, - decision_event_id: None, - agreement_event_id: None, - listing_event_id: None, - listing_addr: None, - buyer_pubkey: None, - seller_pubkey: None, - economics: None, - last_event_id: None, - revision: None, - inventory: None, - lifecycle: None, - sdk_receipt: None, - reducer_issues: Vec::new(), - target_relays: Vec::new(), - connected_relays: Vec::new(), - failed_relays: Vec::new(), - fetched_count: 0, - decoded_count: 0, - skipped_count: 0, - reason: Some("order status get requires at least one configured relay".to_owned()), - actions: vec![format!( - "radroots --relay wss://relay.example.com order status get {}", - args.key - )], - }); - } + order_id: &str, + actor_context_source: &'static str, + receipt: DirectRelayFetchReceipt, +) -> Result<OrderStatusView, CliSdkAdapterError> { + let DirectRelayFetchReceipt { + target_relays, + connected_relays, + failed_relays, + events, + } = receipt; + let fetched_count = events.len(); + let mut decoded_count = 0usize; + let mut skipped_count = 0usize; + let mut issues = Vec::new(); + let session = CliSdkSession::connect(config)?; - let filter = order_status_filter(args.key.as_str())?; - let receipt = match fetch_events_from_relays(&config.relay.urls, filter) { - Ok(receipt) => receipt, - Err(DirectRelayFetchError::Connect { - reason, - target_relays, - failed_relays, - }) => { - return Ok(OrderStatusView { - state: "unavailable".to_owned(), - source: LEGACY_ORDER_PREFLIGHT_STATUS_SOURCE.to_owned(), - order_id: args.key.clone(), - actor_context_source: ORDER_ACTOR_CONTEXT_NETWORK_ONLY.to_owned(), - request_event_id: None, - decision_event_id: None, - agreement_event_id: None, - listing_event_id: None, - listing_addr: None, - buyer_pubkey: None, - seller_pubkey: None, - economics: None, - last_event_id: None, - revision: None, - inventory: None, - lifecycle: None, - sdk_receipt: None, - reducer_issues: Vec::new(), - target_relays, - connected_relays: Vec::new(), - failed_relays: relay_failures(failed_relays), - fetched_count: 0, - decoded_count: 0, - skipped_count: 0, - reason: Some(format!("direct relay connection failed: {reason}")), - actions: Vec::new(), - }); + for event in events { + let event_id = event.id.to_string(); + let event = radroots_event_from_nostr(&event); + match session.block_on( + session + .sdk() + .trades() + .ingest_evidence(OrderEvidenceIngestRequest::new(event)), + ) { + Ok(_) => decoded_count += 1, + Err(error) => { + skipped_count += 1; + issues.push(issue_with_events( + "invalid_trade_evidence", + "event_id", + format!("trade evidence event `{event_id}` failed SDK ingest: {error}"), + vec![event_id], + )); + } } - Err(error) => return Err(RuntimeError::Network(error.to_string())), - }; + } - let actor_context = order_status_actor_context(config, args.key.as_str())?; - let mut view = order_status_from_receipt_with_context( - OrderStatusContext { - order_id: args.key.as_str(), - buyer_pubkey: actor_context.buyer_pubkey.as_deref(), - seller_pubkey: actor_context.seller_pubkey.as_deref(), - selected_account_pubkey: actor_context.selected_account_pubkey.as_deref(), - actor_context_source: actor_context.source, - }, - receipt, - ); - enrich_order_status_inventory(config, &mut view)?; + let request = OrderStatusRequest::parse(order_id)?; + let receipt = session.block_on(session.sdk().trades().status(request))?; + let mut view = sdk_order_status_view(receipt); + view.actor_context_source = actor_context_source.to_owned(); + view.target_relays = target_relays; + view.connected_relays = connected_relays; + view.failed_relays = relay_failures(failed_relays); + view.fetched_count = fetched_count; + view.decoded_count = decoded_count; + view.skipped_count = skipped_count; + if !issues.is_empty() { + view.state = "invalid".to_owned(); + view.reason = Some(format!( + "relay trade evidence for `{order_id}` failed SDK ingest" + )); + view.reducer_issues.extend(issues); + } Ok(view) } @@ -1913,318 +1876,12 @@ struct OrderRevisionProposalCandidates { issues: Vec<OrderIssueView>, } -#[derive(Debug, Clone)] -struct OrderStatusReduction { - view: OrderStatusView, -} - #[derive(Debug, Clone, Copy)] struct OrderRequestCandidateContext<'a> { order_id: &'a str, seller_pubkey: Option<&'a str>, } -#[derive(Debug, Clone, Copy)] -struct OrderStatusContext<'a> { - order_id: &'a str, - buyer_pubkey: Option<&'a str>, - seller_pubkey: Option<&'a str>, - selected_account_pubkey: Option<&'a str>, - actor_context_source: &'static str, -} - -#[cfg(test)] -fn order_status_from_receipt(order_id: &str, receipt: DirectRelayFetchReceipt) -> OrderStatusView { - order_status_from_receipt_with_context( - OrderStatusContext { - order_id, - buyer_pubkey: None, - seller_pubkey: None, - selected_account_pubkey: None, - actor_context_source: ORDER_ACTOR_CONTEXT_NETWORK_ONLY, - }, - receipt, - ) -} - -fn order_status_from_receipt_with_context( - context: OrderStatusContext<'_>, - receipt: DirectRelayFetchReceipt, -) -> OrderStatusView { - order_status_reduction_from_receipt_with_context(context, receipt).view -} - -fn order_status_reduction_from_receipt_with_context( - context: OrderStatusContext<'_>, - receipt: DirectRelayFetchReceipt, -) -> OrderStatusReduction { - order_status_reduction_from_receipt_inner(context, receipt) -} - -fn order_status_reduction_from_receipt_inner( - context: OrderStatusContext<'_>, - receipt: DirectRelayFetchReceipt, -) -> OrderStatusReduction { - let DirectRelayFetchReceipt { - target_relays, - connected_relays, - failed_relays, - events, - } = receipt; - let fetched_count = events.len(); - let mut decoded_count = 0usize; - let mut skipped_count = 0usize; - let mut requests = Vec::new(); - let mut decisions = Vec::new(); - let mut revision_proposals = Vec::new(); - let mut revision_decisions = Vec::new(); - let mut cancellations = Vec::new(); - let mut request_listing_events = Vec::new(); - let mut candidate_issues = Vec::new(); - - for event in events { - match order_status_record_from_event(&event) { - Ok(OrderStatusRecord::Request { - listing_event_id, - record, - }) => { - if !order_status_request_matches_context(&record, context) { - skipped_count += 1; - continue; - } - decoded_count += 1; - request_listing_events.push((record.event_id.clone(), listing_event_id)); - requests.push(record); - } - Ok(OrderStatusRecord::Decision(record)) => { - decoded_count += 1; - decisions.push(record); - } - Ok(OrderStatusRecord::RevisionProposal(record)) => { - decoded_count += 1; - revision_proposals.push(record); - } - Ok(OrderStatusRecord::RevisionDecision(record)) => { - decoded_count += 1; - revision_decisions.push(record); - } - Ok(OrderStatusRecord::Cancellation(record)) => { - decoded_count += 1; - cancellations.push(record); - } - Err(error) => { - skipped_count += 1; - if order_status_request_candidate(&event, context) { - let event_id = event.id.to_string(); - candidate_issues.push(issue_with_events( - "invalid_request_candidate", - "request_event_id", - format!( - "request event `{event_id}` failed order status validation: {error}" - ), - vec![event_id], - )); - } - } - } - } - candidate_issues.sort_by(|left, right| { - left.event_ids - .cmp(&right.event_ids) - .then_with(|| left.message.cmp(&right.message)) - }); - - let reducer_order_id = match protocol_order_id(context.order_id, "order_id") { - Ok(order_id) => order_id, - Err(error) => { - let message = error.to_string(); - let view = OrderStatusView { - state: "invalid".to_owned(), - source: LEGACY_ORDER_PREFLIGHT_STATUS_SOURCE.to_owned(), - order_id: context.order_id.to_owned(), - actor_context_source: context.actor_context_source.to_owned(), - request_event_id: None, - decision_event_id: None, - agreement_event_id: None, - listing_event_id: None, - listing_addr: None, - buyer_pubkey: None, - seller_pubkey: None, - economics: None, - last_event_id: None, - revision: None, - inventory: None, - lifecycle: None, - sdk_receipt: None, - reducer_issues: vec![issue("order_id", message.clone())], - target_relays, - connected_relays, - failed_relays: relay_failures(failed_relays), - fetched_count, - decoded_count, - skipped_count, - reason: Some(message), - actions: Vec::new(), - }; - return OrderStatusReduction { view }; - } - }; - let order_id = context.order_id; - let revision_proposal_records = revision_proposals.clone(); - let revision_decision_records = revision_decisions.clone(); - let cancellation_records = cancellations.clone(); - let projection = reduce_order_events( - &reducer_order_id, - RadrootsOrderReductionInputs { - requests, - decisions: decisions.clone(), - revision_proposals, - revision_decisions, - cancellations, - }, - ); - let cancellation_root_event_id = - projection - .cancellation_event_id - .as_ref() - .and_then(|event_id| { - cancellation_records - .iter() - .find(|record| &record.event_id == event_id) - .map(|record| record.root_event_id.clone()) - }); - let cancellation_prev_event_id = - projection - .cancellation_event_id - .as_ref() - .and_then(|event_id| { - cancellation_records - .iter() - .find(|record| &record.event_id == event_id) - .map(|record| record.prev_event_id.clone()) - }); - let cancellation_reason = projection - .cancellation_event_id - .as_ref() - .and_then(|event_id| { - cancellation_records - .iter() - .find(|record| &record.event_id == event_id) - .map(|record| record.payload.reason.clone()) - }); - let listing_event_id = projection - .request_event_id - .as_ref() - .and_then(|request_event_id| { - request_listing_events - .iter() - .find(|(event_id, _)| event_id == request_event_id) - .and_then(|(_, listing_event_id)| listing_event_id.clone()) - }); - let mut state = active_order_status_state(&projection.status).to_owned(); - let mut reason = active_order_status_reason(&projection.status, order_id); - let mut reducer_issues = projection - .issues - .into_iter() - .map(active_order_reducer_issue_view) - .collect::<Vec<_>>(); - if !candidate_issues.is_empty() { - state = "invalid".to_owned(); - reason = Some(format!( - "active order request candidates for `{order_id}` failed status validation" - )); - reducer_issues.extend(candidate_issues); - } - let inventory = order_status_inventory_view( - &projection.status, - listing_event_id.clone(), - projection.decision_event_id.as_ref(), - &decisions, - reducer_issues.as_slice(), - ); - let lifecycle = order_status_lifecycle_view( - &projection.status, - optional_string(projection.request_event_id.clone()), - optional_string(projection.last_event_id.clone()), - optional_string(projection.cancellation_event_id.clone()), - optional_string(cancellation_root_event_id), - optional_string(cancellation_prev_event_id), - cancellation_reason, - reducer_issues.as_slice(), - ); - let revision = order_status_revision_view( - projection.last_event_id.as_ref(), - projection.agreement_event_id.as_ref(), - &revision_proposal_records, - &revision_decision_records, - ); - let view = OrderStatusView { - state, - source: LEGACY_ORDER_PREFLIGHT_STATUS_SOURCE.to_owned(), - order_id: projection.order_id.to_string(), - actor_context_source: context.actor_context_source.to_owned(), - request_event_id: optional_string(projection.request_event_id), - decision_event_id: optional_string(projection.decision_event_id), - agreement_event_id: optional_string(projection.agreement_event_id), - listing_event_id, - listing_addr: optional_string(projection.listing_addr), - buyer_pubkey: optional_string(projection.buyer_pubkey), - seller_pubkey: optional_string(projection.seller_pubkey), - economics: projection.economics, - last_event_id: optional_string(projection.last_event_id), - revision, - inventory, - lifecycle: Some(lifecycle), - sdk_receipt: None, - reducer_issues, - target_relays, - connected_relays, - failed_relays: relay_failures(failed_relays), - fetched_count, - decoded_count, - skipped_count, - reason, - actions: Vec::new(), - }; - OrderStatusReduction { view } -} - -fn order_status_request_matches_context( - record: &RadrootsOrderRequestRecord, - context: OrderStatusContext<'_>, -) -> bool { - if record.payload.order_id.to_string() != context.order_id { - return false; - } - order_status_context_is_network_only(context) - || context.buyer_pubkey.is_some_and(|pubkey| { - record - .payload - .buyer_pubkey - .to_string() - .eq_ignore_ascii_case(pubkey) - }) - || context.seller_pubkey.is_some_and(|pubkey| { - record - .payload - .seller_pubkey - .to_string() - .eq_ignore_ascii_case(pubkey) - }) - || context.selected_account_pubkey.is_some_and(|pubkey| { - record - .payload - .buyer_pubkey - .to_string() - .eq_ignore_ascii_case(pubkey) - || record - .payload - .seller_pubkey - .to_string() - .eq_ignore_ascii_case(pubkey) - }) -} - fn enrich_order_status_inventory( config: &RuntimeConfig, view: &mut OrderStatusView, @@ -2504,34 +2161,6 @@ fn listing_inventory_issue_involves_order( } } -fn order_status_request_candidate( - event: &RadrootsNostrEvent, - context: OrderStatusContext<'_>, -) -> bool { - if event_kind_u32(event) != KIND_ORDER_REQUEST - || !event_matches_tag_value(event, "d", context.order_id) - { - return false; - } - order_status_context_is_network_only(context) - || context - .buyer_pubkey - .is_some_and(|pubkey| event.pubkey.to_string().eq_ignore_ascii_case(pubkey)) - || context - .seller_pubkey - .is_some_and(|pubkey| event_matches_tag_value(event, "p", pubkey)) - || context.selected_account_pubkey.is_some_and(|pubkey| { - event.pubkey.to_string().eq_ignore_ascii_case(pubkey) - || event_matches_tag_value(event, "p", pubkey) - }) -} - -fn order_status_context_is_network_only(context: OrderStatusContext<'_>) -> bool { - context.buyer_pubkey.is_none() - && context.seller_pubkey.is_none() - && context.selected_account_pubkey.is_none() -} - fn order_request_candidate_matches( event: &RadrootsNostrEvent, context: OrderRequestCandidateContext<'_>, @@ -2634,7 +2263,7 @@ fn order_status_record_from_event( let author_pubkey = protocol_pubkey(event.author.as_str(), "revision_author_pubkey")?; let envelope = order_revision_proposal_from_event(&event).map_err(|error| { RuntimeError::Config(format!( - "decode active order revision proposal event: {error}" + "decode active trade revision proposal event: {error}" )) })?; let context = order_event_context_from_tags( @@ -2643,7 +2272,7 @@ fn order_status_record_from_event( ) .map_err(|error| { RuntimeError::Config(format!( - "decode active order revision proposal tags: {error}" + "decode active trade revision proposal tags: {error}" )) })?; Ok(OrderStatusRecord::RevisionProposal( @@ -2654,12 +2283,12 @@ fn order_status_record_from_event( root_event_id: required_order_context_event_id( context.root_event_id, "e_root", - "active order revision proposal", + "active trade revision proposal", )?, prev_event_id: required_order_context_event_id( context.prev_event_id, "e_prev", - "active order revision proposal", + "active trade revision proposal", )?, payload: envelope.payload, }, @@ -2672,7 +2301,7 @@ fn order_status_record_from_event( protocol_pubkey(event.author.as_str(), "revision_decision_author_pubkey")?; let envelope = order_revision_decision_from_event(&event).map_err(|error| { RuntimeError::Config(format!( - "decode active order revision decision event: {error}" + "decode active trade revision decision event: {error}" )) })?; let context = order_event_context_from_tags( @@ -2681,7 +2310,7 @@ fn order_status_record_from_event( ) .map_err(|error| { RuntimeError::Config(format!( - "decode active order revision decision tags: {error}" + "decode active trade revision decision tags: {error}" )) })?; Ok(OrderStatusRecord::RevisionDecision( @@ -2692,12 +2321,12 @@ fn order_status_record_from_event( root_event_id: required_order_context_event_id( context.root_event_id, "e_root", - "active order revision decision", + "active trade revision decision", )?, prev_event_id: required_order_context_event_id( context.prev_event_id, "e_prev", - "active order revision decision", + "active trade revision decision", )?, payload: envelope.payload, }, @@ -2709,13 +2338,13 @@ fn order_status_record_from_event( let author_pubkey = protocol_pubkey(event.author.as_str(), "cancellation_author_pubkey")?; let envelope = order_cancellation_from_event(&event).map_err(|error| { - RuntimeError::Config(format!("decode active order cancellation event: {error}")) + RuntimeError::Config(format!("decode active trade cancellation event: {error}")) })?; let context = order_event_context_from_tags(RadrootsOrderEventType::OrderCancelled, &event.tags) .map_err(|error| { RuntimeError::Config(format!( - "decode active order cancellation tags: {error}" + "decode active trade cancellation tags: {error}" )) })?; Ok(OrderStatusRecord::Cancellation( @@ -2726,12 +2355,12 @@ fn order_status_record_from_event( root_event_id: required_order_context_event_id( context.root_event_id, "e_root", - "active order cancellation", + "active trade cancellation", )?, prev_event_id: required_order_context_event_id( context.prev_event_id, "e_prev", - "active order cancellation", + "active trade cancellation", )?, payload: envelope.payload, }, @@ -2777,647 +2406,6 @@ fn order_revision_proposals_from_events( OrderRevisionProposalCandidates { records, issues } } -fn active_order_status_state(status: &RadrootsTradeWorkflowState) -> &'static str { - match status { - RadrootsTradeWorkflowState::Missing => "missing", - RadrootsTradeWorkflowState::Requested => "requested", - RadrootsTradeWorkflowState::RevisionProposed => "revision_proposed", - RadrootsTradeWorkflowState::AgreedPendingRhi => "pending_rhi", - RadrootsTradeWorkflowState::Committed => "committed", - RadrootsTradeWorkflowState::Declined => "declined", - RadrootsTradeWorkflowState::Cancelled => "cancelled", - RadrootsTradeWorkflowState::Invalid => "invalid", - } -} - -fn active_order_status_reason( - status: &RadrootsTradeWorkflowState, - order_id: &str, -) -> Option<String> { - match status { - RadrootsTradeWorkflowState::Missing => { - Some(format!("no active order events matched `{order_id}`")) - } - RadrootsTradeWorkflowState::Invalid => Some(format!( - "active order events for `{order_id}` failed reducer validation" - )), - _ => None, - } -} - -fn order_status_inventory_view( - status: &RadrootsTradeWorkflowState, - listing_event_id: Option<String>, - decision_event_id: Option<&RadrootsEventId>, - decisions: &[RadrootsOrderDecisionRecord], - reducer_issues: &[OrderIssueView], -) -> Option<OrderInventoryView> { - let inventory_issues = reducer_issues - .iter() - .filter(|issue| { - matches!( - issue.code.as_str(), - "missing_decision_inventory_commitments" - | "decision_inventory_commitment_mismatch" - | "decision_counterparty_mismatch" - | "listing_inventory_arithmetic_overflow" - | "unknown_inventory_bin" - | "listing_inventory_over_reserved" - | "invalid_inventory_order" - ) - }) - .cloned() - .collect::<Vec<_>>(); - - match status { - RadrootsTradeWorkflowState::AgreedPendingRhi | RadrootsTradeWorkflowState::Committed => { - let bins = decision_event_id - .and_then(|event_id| { - decisions - .iter() - .find(|decision| decision.event_id == *event_id) - }) - .map(|decision| inventory_bins_from_decision(&decision.payload.decision)) - .unwrap_or_default(); - Some(OrderInventoryView { - state: if inventory_issues.is_empty() { - match status { - RadrootsTradeWorkflowState::Committed => "committed".to_owned(), - _ => "reserved".to_owned(), - } - } else { - "invalid".to_owned() - }, - listing_event_id, - commitment_valid: inventory_issues.is_empty(), - bins, - issues: inventory_issues, - }) - } - RadrootsTradeWorkflowState::Cancelled => Some(OrderInventoryView { - state: if decision_event_id.is_some() { - "released".to_owned() - } else { - "not_reserved".to_owned() - }, - listing_event_id, - commitment_valid: inventory_issues.is_empty(), - bins: Vec::new(), - issues: inventory_issues, - }), - RadrootsTradeWorkflowState::Declined => Some(OrderInventoryView { - state: "not_reserved".to_owned(), - listing_event_id, - commitment_valid: true, - bins: Vec::new(), - issues: inventory_issues, - }), - RadrootsTradeWorkflowState::Invalid if !inventory_issues.is_empty() => { - Some(OrderInventoryView { - state: "invalid".to_owned(), - listing_event_id, - commitment_valid: false, - bins: Vec::new(), - issues: inventory_issues, - }) - } - _ => None, - } -} - -fn order_status_lifecycle_view( - status: &RadrootsTradeWorkflowState, - request_event_id: Option<String>, - last_event_id: Option<String>, - cancellation_event_id: Option<String>, - cancellation_root_event_id: Option<String>, - cancellation_prev_event_id: Option<String>, - cancellation_reason: Option<String>, - reducer_issues: &[OrderIssueView], -) -> OrderStatusLifecycleView { - let phase = order_status_lifecycle_phase(status).to_owned(); - let terminal = matches!( - status, - RadrootsTradeWorkflowState::Committed - | RadrootsTradeWorkflowState::Declined - | RadrootsTradeWorkflowState::Cancelled - | RadrootsTradeWorkflowState::Invalid - ); - let cancellation = - cancellation_event_id - .as_ref() - .map(|event_id| OrderStatusLifecycleCancellationView { - event_id: event_id.clone(), - root_event_id: cancellation_root_event_id - .clone() - .or(request_event_id.clone()), - prev_event_id: cancellation_prev_event_id.clone(), - reason: cancellation_reason.clone(), - }); - let event_id = cancellation_event_id; - let prev_event_id = cancellation_prev_event_id.or(last_event_id); - OrderStatusLifecycleView { - phase, - terminal, - event_id, - root_event_id: request_event_id, - prev_event_id, - cancellation, - issues: reducer_issues.to_vec(), - } -} - -fn order_status_revision_view( - last_event_id: Option<&RadrootsEventId>, - agreement_event_id: Option<&RadrootsEventId>, - proposals: &[RadrootsOrderRevisionProposalRecord], - decisions: &[RadrootsOrderRevisionDecisionRecord], -) -> Option<OrderStatusRevisionView> { - if let Some(proposal) = last_event_id - .and_then(|event_id| proposals.iter().find(|record| record.event_id == *event_id)) - { - return Some(OrderStatusRevisionView { - state: "pending".to_owned(), - revision_id: Some(proposal.payload.revision_id.to_string()), - proposal_event_id: Some(proposal.event_id.to_string()), - decision_event_id: None, - root_event_id: Some(proposal.root_event_id.to_string()), - prev_event_id: Some(proposal.prev_event_id.to_string()), - agreement_event_id: None, - reason: Some(proposal.payload.reason.clone()), - }); - } - - if let Some(decision) = last_event_id - .and_then(|event_id| decisions.iter().find(|record| record.event_id == *event_id)) - { - return Some(order_status_revision_view_from_decision( - decision, - agreement_event_id, - )); - } - - agreement_event_id - .and_then(|event_id| decisions.iter().find(|record| record.event_id == *event_id)) - .map(|decision| order_status_revision_view_from_decision(decision, agreement_event_id)) -} - -fn order_status_revision_view_from_decision( - decision: &RadrootsOrderRevisionDecisionRecord, - agreement_event_id: Option<&RadrootsEventId>, -) -> OrderStatusRevisionView { - let (state, reason) = match &decision.payload.decision { - RadrootsOrderRevisionOutcome::Accepted => ("accepted", None), - RadrootsOrderRevisionOutcome::Declined { reason } => ("declined", Some(reason.clone())), - }; - OrderStatusRevisionView { - state: state.to_owned(), - revision_id: Some(decision.payload.revision_id.to_string()), - proposal_event_id: Some(decision.prev_event_id.to_string()), - decision_event_id: Some(decision.event_id.to_string()), - root_event_id: Some(decision.root_event_id.to_string()), - prev_event_id: Some(decision.prev_event_id.to_string()), - agreement_event_id: agreement_event_id.map(ToString::to_string), - reason, - } -} - -fn order_status_lifecycle_phase(status: &RadrootsTradeWorkflowState) -> &'static str { - match status { - RadrootsTradeWorkflowState::Missing => "missing", - RadrootsTradeWorkflowState::Requested => "requested", - RadrootsTradeWorkflowState::RevisionProposed => "revision_proposed", - RadrootsTradeWorkflowState::AgreedPendingRhi => "pending_rhi", - RadrootsTradeWorkflowState::Committed => "committed", - RadrootsTradeWorkflowState::Declined => "declined", - RadrootsTradeWorkflowState::Cancelled => "cancelled", - RadrootsTradeWorkflowState::Invalid => "invalid", - } -} - -fn inventory_bins_from_decision( - decision: &RadrootsOrderDecisionOutcome, -) -> Vec<OrderInventoryBinView> { - match decision { - RadrootsOrderDecisionOutcome::Accepted { - inventory_commitments, - } => { - let mut bins = inventory_commitments - .iter() - .map(|commitment| OrderInventoryBinView { - bin_id: commitment.bin_id.to_string(), - committed_count: u64::from(commitment.bin_count), - available_count: None, - remaining_count: None, - over_reserved: false, - }) - .collect::<Vec<_>>(); - bins.sort_by(|left, right| left.bin_id.cmp(&right.bin_id)); - bins - } - RadrootsOrderDecisionOutcome::Declined { .. } => Vec::new(), - } -} - -fn active_order_reducer_issue_view(issue_value: RadrootsOrderIssue) -> OrderIssueView { - match issue_value { - RadrootsOrderIssue::MissingRequest => issue_with_code( - "missing_request", - "request_event_id", - "active order reducer reported missing request", - ), - RadrootsOrderIssue::MultipleRequests { event_ids } => issue_with_events( - "multiple_requests", - "request_event_id", - "active order reducer reported multiple request events", - event_ids, - ), - RadrootsOrderIssue::RequestPayloadInvalid { event_id } => issue_with_events( - "invalid_request_payload", - "request_payload", - "active order reducer reported invalid request payload", - vec![event_id], - ), - RadrootsOrderIssue::RequestOrderIdMismatch { event_id } => issue_with_events( - "request_order_id_mismatch", - "order_id", - "active order reducer reported request order id mismatch", - vec![event_id], - ), - RadrootsOrderIssue::RequestAuthorMismatch { event_id } => issue_with_events( - "request_author_mismatch", - "buyer_pubkey", - "active order reducer reported request author mismatch", - vec![event_id], - ), - RadrootsOrderIssue::RequestListingAddressInvalid { event_id } => issue_with_events( - "invalid_request_listing_address", - "listing_addr", - "active order reducer reported invalid request listing address", - vec![event_id], - ), - RadrootsOrderIssue::RequestSellerListingMismatch { event_id } => issue_with_events( - "request_seller_listing_mismatch", - "seller_pubkey", - "active order reducer reported request seller/listing mismatch", - vec![event_id], - ), - RadrootsOrderIssue::DecisionPayloadInvalid { event_id } => issue_with_events( - "invalid_decision_payload", - "decision_payload", - "active order reducer reported invalid decision payload", - vec![event_id], - ), - RadrootsOrderIssue::DecisionOrderIdMismatch { event_id } => issue_with_events( - "decision_order_id_mismatch", - "order_id", - "active order reducer reported decision order id mismatch", - vec![event_id], - ), - RadrootsOrderIssue::DecisionAuthorMismatch { event_id } => issue_with_events( - "decision_author_mismatch", - "seller_pubkey", - "active order reducer reported decision author mismatch", - vec![event_id], - ), - RadrootsOrderIssue::DecisionCounterpartyMismatch { event_id } => issue_with_events( - "decision_counterparty_mismatch", - "buyer_pubkey", - "active order reducer reported decision counterparty mismatch", - vec![event_id], - ), - RadrootsOrderIssue::DecisionBuyerMismatch { event_id } => issue_with_events( - "decision_buyer_mismatch", - "buyer_pubkey", - "active order reducer reported decision buyer mismatch", - vec![event_id], - ), - RadrootsOrderIssue::DecisionSellerMismatch { event_id } => issue_with_events( - "decision_seller_mismatch", - "seller_pubkey", - "active order reducer reported decision seller mismatch", - vec![event_id], - ), - RadrootsOrderIssue::DecisionListingAddressInvalid { event_id } => issue_with_events( - "invalid_decision_listing_address", - "listing_addr", - "active order reducer reported invalid decision listing address", - vec![event_id], - ), - RadrootsOrderIssue::DecisionListingMismatch { event_id } => issue_with_events( - "decision_listing_mismatch", - "listing_addr", - "active order reducer reported decision listing mismatch", - vec![event_id], - ), - RadrootsOrderIssue::DecisionRootMismatch { event_id } => issue_with_events( - "decision_root_mismatch", - "root_event_id", - "active order reducer reported decision root mismatch", - vec![event_id], - ), - RadrootsOrderIssue::DecisionPreviousMismatch { event_id } => issue_with_events( - "decision_previous_mismatch", - "prev_event_id", - "active order reducer reported decision previous mismatch", - vec![event_id], - ), - RadrootsOrderIssue::DecisionMissingInventoryCommitments { event_id } => issue_with_events( - "missing_decision_inventory_commitments", - "inventory_commitments", - "active order reducer reported missing decision inventory commitments", - vec![event_id], - ), - RadrootsOrderIssue::DecisionInventoryCommitmentMismatch { event_id } => issue_with_events( - "decision_inventory_commitment_mismatch", - "inventory_commitments", - "active order reducer reported decision inventory commitment mismatch", - vec![event_id], - ), - RadrootsOrderIssue::DecisionMissingReason { event_id } => issue_with_events( - "missing_decision_decline_reason", - "reason", - "active order reducer reported missing decision decline reason", - vec![event_id], - ), - RadrootsOrderIssue::ConflictingDecisions { event_ids } => issue_with_events( - "conflicting_decisions", - "decision_event_id", - "active order reducer reported conflicting decisions", - event_ids, - ), - RadrootsOrderIssue::RevisionProposalPayloadInvalid { event_id } => issue_with_events( - "invalid_revision_proposal_payload", - "revision_payload", - "active order reducer reported invalid revision proposal payload", - vec![event_id], - ), - RadrootsOrderIssue::RevisionProposalOrderIdMismatch { event_id } => issue_with_events( - "revision_proposal_order_id_mismatch", - "order_id", - "active order reducer reported revision proposal order id mismatch", - vec![event_id], - ), - RadrootsOrderIssue::RevisionProposalAuthorMismatch { event_id } => issue_with_events( - "revision_proposal_author_mismatch", - "seller_pubkey", - "active order reducer reported revision proposal author mismatch", - vec![event_id], - ), - RadrootsOrderIssue::RevisionProposalCounterpartyMismatch { event_id } => issue_with_events( - "revision_proposal_counterparty_mismatch", - "buyer_pubkey", - "active order reducer reported revision proposal counterparty mismatch", - vec![event_id], - ), - RadrootsOrderIssue::RevisionProposalBuyerMismatch { event_id } => issue_with_events( - "revision_proposal_buyer_mismatch", - "buyer_pubkey", - "active order reducer reported revision proposal buyer mismatch", - vec![event_id], - ), - RadrootsOrderIssue::RevisionProposalSellerMismatch { event_id } => issue_with_events( - "revision_proposal_seller_mismatch", - "seller_pubkey", - "active order reducer reported revision proposal seller mismatch", - vec![event_id], - ), - RadrootsOrderIssue::RevisionProposalListingAddressInvalid { event_id } => { - issue_with_events( - "invalid_revision_proposal_listing_address", - "listing_addr", - "active order reducer reported invalid revision proposal listing address", - vec![event_id], - ) - } - RadrootsOrderIssue::RevisionProposalListingMismatch { event_id } => issue_with_events( - "revision_proposal_listing_mismatch", - "listing_addr", - "active order reducer reported revision proposal listing mismatch", - vec![event_id], - ), - RadrootsOrderIssue::RevisionProposalRootMismatch { event_id } => issue_with_events( - "revision_proposal_root_mismatch", - "root_event_id", - "active order reducer reported revision proposal root mismatch", - vec![event_id], - ), - RadrootsOrderIssue::RevisionProposalPreviousMismatch { event_id } => issue_with_events( - "revision_proposal_previous_mismatch", - "prev_event_id", - "active order reducer reported revision proposal previous mismatch", - vec![event_id], - ), - RadrootsOrderIssue::RevisionDecisionWithoutProposal { event_id } => issue_with_events( - "revision_decision_without_proposal", - "revision_decision_event_id", - "active order reducer reported revision decision without proposal", - vec![event_id], - ), - RadrootsOrderIssue::RevisionDecisionPayloadInvalid { event_id } => issue_with_events( - "invalid_revision_decision_payload", - "revision_decision_payload", - "active order reducer reported invalid revision decision payload", - vec![event_id], - ), - RadrootsOrderIssue::RevisionDecisionOrderIdMismatch { event_id } => issue_with_events( - "revision_decision_order_id_mismatch", - "order_id", - "active order reducer reported revision decision order id mismatch", - vec![event_id], - ), - RadrootsOrderIssue::RevisionDecisionAuthorMismatch { event_id } => issue_with_events( - "revision_decision_author_mismatch", - "buyer_pubkey", - "active order reducer reported revision decision author mismatch", - vec![event_id], - ), - RadrootsOrderIssue::RevisionDecisionCounterpartyMismatch { event_id } => issue_with_events( - "revision_decision_counterparty_mismatch", - "seller_pubkey", - "active order reducer reported revision decision counterparty mismatch", - vec![event_id], - ), - RadrootsOrderIssue::RevisionDecisionBuyerMismatch { event_id } => issue_with_events( - "revision_decision_buyer_mismatch", - "buyer_pubkey", - "active order reducer reported revision decision buyer mismatch", - vec![event_id], - ), - RadrootsOrderIssue::RevisionDecisionSellerMismatch { event_id } => issue_with_events( - "revision_decision_seller_mismatch", - "seller_pubkey", - "active order reducer reported revision decision seller mismatch", - vec![event_id], - ), - RadrootsOrderIssue::RevisionDecisionListingAddressInvalid { event_id } => { - issue_with_events( - "invalid_revision_decision_listing_address", - "listing_addr", - "active order reducer reported invalid revision decision listing address", - vec![event_id], - ) - } - RadrootsOrderIssue::RevisionDecisionListingMismatch { event_id } => issue_with_events( - "revision_decision_listing_mismatch", - "listing_addr", - "active order reducer reported revision decision listing mismatch", - vec![event_id], - ), - RadrootsOrderIssue::RevisionDecisionRootMismatch { event_id } => issue_with_events( - "revision_decision_root_mismatch", - "root_event_id", - "active order reducer reported revision decision root mismatch", - vec![event_id], - ), - RadrootsOrderIssue::RevisionDecisionPreviousMismatch { event_id } => issue_with_events( - "revision_decision_previous_mismatch", - "prev_event_id", - "active order reducer reported revision decision previous mismatch", - vec![event_id], - ), - RadrootsOrderIssue::RevisionDecisionRevisionIdMismatch { event_id } => issue_with_events( - "revision_decision_revision_id_mismatch", - "revision_id", - "active order reducer reported revision decision revision id mismatch", - vec![event_id], - ), - RadrootsOrderIssue::CancellationWithoutCancellableOrder { event_id } => issue_with_events( - "cancellation_without_cancellable_order", - "cancellation_event_id", - "active order reducer reported cancellation without cancellable order", - vec![event_id], - ), - RadrootsOrderIssue::CancellationPayloadInvalid { event_id } => issue_with_events( - "invalid_cancellation_payload", - "cancellation_payload", - "active order reducer reported invalid cancellation payload", - vec![event_id], - ), - RadrootsOrderIssue::CancellationOrderIdMismatch { event_id } => issue_with_events( - "cancellation_order_id_mismatch", - "order_id", - "active order reducer reported cancellation order id mismatch", - vec![event_id], - ), - RadrootsOrderIssue::CancellationAuthorMismatch { event_id } => issue_with_events( - "cancellation_author_mismatch", - "buyer_pubkey", - "active order reducer reported cancellation author mismatch", - vec![event_id], - ), - RadrootsOrderIssue::CancellationCounterpartyMismatch { event_id } => issue_with_events( - "cancellation_counterparty_mismatch", - "seller_pubkey", - "active order reducer reported cancellation counterparty mismatch", - vec![event_id], - ), - RadrootsOrderIssue::CancellationBuyerMismatch { event_id } => issue_with_events( - "cancellation_buyer_mismatch", - "buyer_pubkey", - "active order reducer reported cancellation buyer mismatch", - vec![event_id], - ), - RadrootsOrderIssue::CancellationSellerMismatch { event_id } => issue_with_events( - "cancellation_seller_mismatch", - "seller_pubkey", - "active order reducer reported cancellation seller mismatch", - vec![event_id], - ), - RadrootsOrderIssue::CancellationListingAddressInvalid { event_id } => issue_with_events( - "invalid_cancellation_listing_address", - "listing_addr", - "active order reducer reported invalid cancellation listing address", - vec![event_id], - ), - RadrootsOrderIssue::CancellationListingMismatch { event_id } => issue_with_events( - "cancellation_listing_mismatch", - "listing_addr", - "active order reducer reported cancellation listing mismatch", - vec![event_id], - ), - RadrootsOrderIssue::CancellationRootMismatch { event_id } => issue_with_events( - "cancellation_root_mismatch", - "root_event_id", - "active order reducer reported cancellation root mismatch", - vec![event_id], - ), - RadrootsOrderIssue::CancellationPreviousMismatch { event_id } => issue_with_events( - "cancellation_previous_mismatch", - "prev_event_id", - "active order reducer reported cancellation previous mismatch", - vec![event_id], - ), - RadrootsOrderIssue::ValidationReceiptWithoutPendingAgreement { event_id } => { - issue_with_events( - "validation_receipt_without_pending_agreement", - "validation_receipt_event_id", - "active order reducer reported validation receipt without pending agreement", - vec![event_id], - ) - } - RadrootsOrderIssue::ValidationReceiptOrderIdMismatch { event_id } => issue_with_events( - "validation_receipt_order_id_mismatch", - "order_id", - "active order reducer reported validation receipt order id mismatch", - vec![event_id], - ), - RadrootsOrderIssue::ValidationReceiptTypeMismatch { event_id } => issue_with_events( - "validation_receipt_type_mismatch", - "validation_receipt_type", - "active order reducer reported validation receipt type mismatch", - vec![event_id], - ), - RadrootsOrderIssue::ValidationReceiptRootMismatch { event_id } => issue_with_events( - "validation_receipt_root_mismatch", - "root_event_id", - "active order reducer reported validation receipt root mismatch", - vec![event_id], - ), - RadrootsOrderIssue::ValidationReceiptTargetMismatch { event_id } => issue_with_events( - "validation_receipt_target_mismatch", - "target_event_id", - "active order reducer reported validation receipt target mismatch", - vec![event_id], - ), - RadrootsOrderIssue::ValidationReceiptListingMismatch { event_id } => issue_with_events( - "validation_receipt_listing_mismatch", - "listing_event_id", - "active order reducer reported validation receipt listing mismatch", - vec![event_id], - ), - RadrootsOrderIssue::ConflictingValidationReceipts { event_ids } => issue_with_events( - "conflicting_validation_receipts", - "validation_receipt_event_id", - "active order reducer reported conflicting validation receipts", - event_ids, - ), - RadrootsOrderIssue::DeterministicValidationFailure { event_id, reason } => { - issue_with_events( - "deterministic_validation_failure", - "validation_event_id", - format!("active order reducer reported deterministic validation failure: {reason}"), - vec![event_id], - ) - } - RadrootsOrderIssue::StaleListingEvent { - expected_event_id, - current_event_id, - } => issue_with_events( - "stale_listing_event", - "listing_event_id", - "active order reducer reported stale listing event evidence", - vec![expected_event_id, current_event_id], - ), - RadrootsOrderIssue::ForkedLifecycle { event_ids } => issue_with_events( - "forked_lifecycle", - "event_id", - "active order reducer reported forked lifecycle events", - event_ids, - ), - } -} - fn order_event_list_unconfigured( seller_pubkey: Option<String>, actor_context_source: &'static str, @@ -3535,7 +2523,7 @@ fn order_event_list_from_receipt( fn order_decision_base_view( config: &RuntimeConfig, - args: &OrderDecisionArgs, + args: &TradeDecisionArgs, state: &str, dry_run: bool, ) -> OrderDecisionView { @@ -3572,7 +2560,7 @@ fn order_decision_base_view( fn order_revision_base_view( config: &RuntimeConfig, - args: &OrderRevisionProposeArgs, + args: &TradeRevisionProposeArgs, state: &str, dry_run: bool, ) -> OrderRevisionProposalView { @@ -3611,7 +2599,7 @@ fn order_revision_base_view( fn order_revision_decision_base_view( config: &RuntimeConfig, - args: &OrderRevisionDecisionArgs, + args: &TradeRevisionDecisionArgs, state: &str, dry_run: bool, ) -> OrderRevisionDecisionView { @@ -3651,7 +2639,7 @@ fn order_revision_decision_base_view( fn order_cancellation_base_view( config: &RuntimeConfig, - args: &OrderCancelArgs, + args: &TradeCancelArgs, state: &str, dry_run: bool, ) -> OrderCancellationView { @@ -3706,7 +2694,7 @@ fn apply_order_cancellation_status(view: &mut OrderCancellationView, status: &Or fn order_cancellation_prev_event_id(status: &OrderStatusView) -> Option<String> { match status.state.as_str() { "requested" => status.request_event_id.clone(), - "accepted" => status + "pending_rhi" => status .last_event_id .clone() .or(status.decision_event_id.clone()), @@ -3716,7 +2704,7 @@ fn order_cancellation_prev_event_id(status: &OrderStatusView) -> Option<String> fn order_cancellation_preflight_view_from_status( config: &RuntimeConfig, - args: &OrderCancelArgs, + args: &TradeCancelArgs, status: &OrderStatusView, selected_pubkey: &str, ) -> Option<OrderCancellationView> { @@ -3726,8 +2714,8 @@ fn order_cancellation_preflight_view_from_status( .is_some_and(|buyer| buyer.eq_ignore_ascii_case(selected_pubkey)); let state = match status.state.as_str() { "requested" if buyer_matches => return None, - "accepted" if buyer_matches => "finalized", - "cancelled" => "terminal", + "pending_rhi" if buyer_matches => "finalized", + "committed" | "cancelled" => "terminal", "missing" | "declined" | "invalid" | "unavailable" | "unconfigured" => { status.state.as_str() } @@ -3743,45 +2731,45 @@ fn order_cancellation_preflight_view_from_status( view.event_kind = Some(KIND_ORDER_CANCELLATION); } view.reason = Some(match state { - "missing" => format!("no active order events matched `{}`", args.key), + "missing" => format!("no active trade events matched `{}`", args.key), "declined" => format!( - "order cancel refused because order `{}` was declined", + "trade cancel refused because order `{}` was declined", args.key ), "terminal" => { format!( - "order cancel refused because order `{}` is already terminal", + "trade cancel refused because order `{}` is already terminal", args.key ) } "finalized" => format!( - "order cancel refused because order `{}` already has an accepted agreement", + "trade cancel refused because order `{}` already has an accepted agreement", args.key ), "invalid" if !buyer_matches && status.buyer_pubkey.is_some() => format!( - "order cancel refused because selected account is not buyer for order `{}`", + "trade cancel refused because selected account is not buyer for order `{}`", args.key ), "invalid" => status.reason.clone().unwrap_or_else(|| { format!( - "order cancel refused because active order events for `{}` are invalid", + "trade cancel refused because active trade events for `{}` are invalid", args.key ) }), _ => status.reason.clone().unwrap_or_else(|| { format!( - "order cancel status preflight failed with state `{}`", + "trade cancel status preflight failed with state `{}`", status.state ) }), }); - view.actions = vec![format!("radroots order status get {}", args.key)]; + view.actions = vec![format!("radroots trade status get {}", args.key)]; Some(view) } fn order_decision_view_from_resolution( config: &RuntimeConfig, - args: &OrderDecisionArgs, + args: &TradeDecisionArgs, seller_pubkey: String, resolution: SellerOrderRequestResolution, ) -> OrderDecisionView { @@ -3811,7 +2799,7 @@ fn order_decision_view_from_resolution( "seller order request preflight found invalid request candidates for `{}`", args.key )); - view.actions = vec![format!("radroots order status get {}", args.key)]; + view.actions = vec![format!("radroots trade status get {}", args.key)]; return view; } match requests.as_slice() { @@ -3842,7 +2830,7 @@ fn order_decision_view_from_resolution( ), event_ids, )]; - view.actions = vec![format!("radroots order status get {}", args.key)]; + view.actions = vec![format!("radroots trade status get {}", args.key)]; view } } @@ -3931,14 +2919,14 @@ fn apply_order_revision_decision_status( fn order_decision_preflight_view_from_status( config: &RuntimeConfig, - args: &OrderDecisionArgs, + args: &TradeDecisionArgs, request: &ResolvedSellerOrderRequest, resolution: &SellerOrderRequestResolution, status: &OrderStatusView, ) -> Option<OrderDecisionView> { let state = match status.state.as_str() { - "accepted" | "declined" => "already_decided", - "cancelled" => "terminal", + "pending_rhi" | "declined" => "already_decided", + "committed" | "cancelled" => "terminal", "invalid" => "invalid", "unavailable" => "unavailable", "unconfigured" => "unconfigured", @@ -3953,20 +2941,20 @@ fn order_decision_preflight_view_from_status( view.event_kind = Some(KIND_ORDER_DECISION); } view.reason = Some(match status.state.as_str() { - "accepted" | "declined" => format!( + "pending_rhi" | "declined" => format!( "order {} refused because order `{}` already has a visible `{}` seller decision", args.decision.command(), request.order_id, status.state ), - "cancelled" => format!( + "committed" | "cancelled" => format!( "order {} refused because order `{}` is already terminal", args.decision.command(), request.order_id ), "invalid" => status.reason.clone().unwrap_or_else(|| { format!( - "order {} refused because active order events for `{}` are invalid", + "order {} refused because active trade events for `{}` are invalid", args.decision.command(), request.order_id ) @@ -3979,13 +2967,13 @@ fn order_decision_preflight_view_from_status( ) }), }); - view.actions = vec![format!("radroots order status get {}", request.order_id)]; + view.actions = vec![format!("radroots trade status get {}", request.order_id)]; Some(view) } fn order_revision_args_preflight_view( config: &RuntimeConfig, - args: &OrderRevisionProposeArgs, + args: &TradeRevisionProposeArgs, ) -> Option<OrderRevisionProposalView> { let mut issues = Vec::new(); let has_bin_id = args.bin_id.as_deref().and_then(non_empty_ref).is_some(); @@ -4030,7 +3018,7 @@ fn order_revision_args_preflight_view( issues.push(issue_with_code( "revision_no_changes", "revision", - "order revision propose requires a bin-count change or revision adjustment", + "trade revision propose requires a bin-count change or revision adjustment", )); } @@ -4039,7 +3027,7 @@ fn order_revision_args_preflight_view( } let mut view = order_revision_base_view(config, args, "invalid", config.output.dry_run); view.reason = Some(format!( - "order revision propose inputs for `{}` failed validation", + "trade revision propose inputs for `{}` failed validation", args.key )); view.issues = issues; @@ -4048,17 +3036,17 @@ fn order_revision_args_preflight_view( fn order_revision_decision_args_preflight_view( config: &RuntimeConfig, - args: &OrderRevisionDecisionArgs, + args: &TradeRevisionDecisionArgs, ) -> Option<OrderRevisionDecisionView> { let mut issues = Vec::new(); if args.revision_id.trim().is_empty() { issues.push(issue_with_code( "revision_id_required", "revision_id", - "order revision decision requires --revision-id", + "trade revision decision requires --revision-id", )); } - if args.decision == OrderRevisionDecisionArg::Decline + if args.decision == TradeRevisionDecisionArg::Decline && args .reason .as_deref() @@ -4069,7 +3057,7 @@ fn order_revision_decision_args_preflight_view( issues.push(issue_with_code( "revision_decline_reason_required", "reason", - "order revision decline requires a non-empty reason", + "trade revision decline requires a non-empty reason", )); } @@ -4079,7 +3067,7 @@ fn order_revision_decision_args_preflight_view( let mut view = order_revision_decision_base_view(config, args, "invalid", config.output.dry_run); view.reason = Some(format!( - "order revision {} inputs for `{}` failed validation", + "trade revision {} inputs for `{}` failed validation", args.decision.command(), args.key )); @@ -4089,7 +3077,7 @@ fn order_revision_decision_args_preflight_view( fn order_revision_preflight_view_from_status( config: &RuntimeConfig, - args: &OrderRevisionProposeArgs, + args: &TradeRevisionProposeArgs, status: &OrderStatusView, selected_pubkey: &str, candidates: &OrderRevisionProposalCandidates, @@ -4100,15 +3088,15 @@ fn order_revision_preflight_view_from_status( .as_deref() .is_some_and(|seller| seller.eq_ignore_ascii_case(selected_pubkey)); let state = match status.state.as_str() { - "accepted" + "pending_rhi" if seller_matches && candidates.issues.is_empty() && pending_revision.is_none() => { return None; } - "accepted" if !seller_matches => "invalid", - "accepted" if !candidates.issues.is_empty() => "invalid", - "accepted" if pending_revision.is_some() => "forked", - "cancelled" => "terminal", + "pending_rhi" if !seller_matches => "invalid", + "pending_rhi" if !candidates.issues.is_empty() => "invalid", + "pending_rhi" if pending_revision.is_some() => "forked", + "committed" | "cancelled" => "terminal", "missing" | "requested" | "declined" | "invalid" | "unavailable" | "unconfigured" => { status.state.as_str() } @@ -4122,40 +3110,40 @@ fn order_revision_preflight_view_from_status( view.revision_id = Some(record.payload.revision_id.to_string()); } view.reason = Some(match state { - "missing" => format!("no active order events matched `{}`", args.key), + "missing" => format!("no active trade events matched `{}`", args.key), "requested" => format!( - "order revision propose refused because order `{}` has no accepted seller decision", + "trade revision propose refused because order `{}` has no accepted seller decision", args.key ), "declined" => format!( - "order revision propose refused because order `{}` was declined", + "trade revision propose refused because order `{}` was declined", args.key ), "terminal" => format!( - "order revision propose refused because order `{}` is already terminal", + "trade revision propose refused because order `{}` is already terminal", args.key ), "forked" => format!( - "order revision propose refused because order `{}` already has a pending revision proposal", + "trade revision propose refused because order `{}` already has a pending revision proposal", args.key ), "invalid" if !seller_matches && status.seller_pubkey.is_some() => format!( - "order revision propose refused because selected account is not seller for order `{}`", + "trade revision propose refused because selected account is not seller for order `{}`", args.key ), "invalid" if !candidates.issues.is_empty() => format!( - "order revision propose refused because revision proposal candidates for `{}` are invalid", + "trade revision propose refused because revision proposal candidates for `{}` are invalid", args.key ), "invalid" => status.reason.clone().unwrap_or_else(|| { format!( - "order revision propose refused because active order events for `{}` are invalid", + "trade revision propose refused because active trade events for `{}` are invalid", args.key ) }), _ => status.reason.clone().unwrap_or_else(|| { format!( - "order revision propose status preflight failed with state `{}`", + "trade revision propose status preflight failed with state `{}`", status.state ) }), @@ -4174,13 +3162,13 @@ fn order_revision_preflight_view_from_status( )); } view.issues.extend(candidates.issues.clone()); - view.actions = vec![format!("radroots order status get {}", args.key)]; + view.actions = vec![format!("radroots trade status get {}", args.key)]; Some(view) } fn order_revision_decision_preflight_view_from_status( config: &RuntimeConfig, - args: &OrderRevisionDecisionArgs, + args: &TradeRevisionDecisionArgs, status: &OrderStatusView, selected_pubkey: &str, candidates: &OrderRevisionProposalCandidates, @@ -4191,17 +3179,17 @@ fn order_revision_decision_preflight_view_from_status( .as_deref() .is_some_and(|buyer| buyer.eq_ignore_ascii_case(selected_pubkey)); let state = match status.state.as_str() { - "accepted" + "revision_proposed" if buyer_matches && candidates.issues.is_empty() && pending_revision.is_some() => { return None; } - "accepted" if !buyer_matches => "invalid", - "accepted" if !candidates.issues.is_empty() => "invalid", - "accepted" => "missing", - "cancelled" => "terminal", + "revision_proposed" if !buyer_matches => "invalid", + "revision_proposed" if !candidates.issues.is_empty() => "invalid", + "revision_proposed" => "missing", + "committed" | "cancelled" => "terminal", "declined" => "order_declined", - "missing" | "requested" | "invalid" | "unavailable" | "unconfigured" => { + "missing" | "requested" | "pending_rhi" | "invalid" | "unavailable" | "unconfigured" => { status.state.as_str() } _ => "invalid", @@ -4214,54 +3202,54 @@ fn order_revision_decision_preflight_view_from_status( view.event_kind = Some(KIND_ORDER_REVISION_PROPOSAL); } view.reason = Some(match state { - "missing" if status.state == "accepted" => format!( - "order revision {} refused because order `{}` has no pending revision proposal", + "missing" if status.state == "revision_proposed" => format!( + "trade revision {} refused because order `{}` has no pending revision proposal", args.decision.command(), args.key ), - "missing" => format!("no active order events matched `{}`", args.key), + "missing" => format!("no active trade events matched `{}`", args.key), "requested" => format!( - "order revision {} refused because order `{}` has no accepted seller decision", + "trade revision {} refused because order `{}` has no accepted seller decision", args.decision.command(), args.key ), "order_declined" => format!( - "order revision {} refused because order `{}` was declined", + "trade revision {} refused because order `{}` was declined", args.decision.command(), args.key ), "terminal" => format!( - "order revision {} refused because order `{}` is already terminal", + "trade revision {} refused because order `{}` is already terminal", args.decision.command(), args.key ), "invalid" if !buyer_matches && status.buyer_pubkey.is_some() => format!( - "order revision {} refused because selected account is not buyer for order `{}`", + "trade revision {} refused because selected account is not buyer for order `{}`", args.decision.command(), args.key ), "invalid" if !candidates.issues.is_empty() => format!( - "order revision {} refused because revision proposal candidates for `{}` are invalid", + "trade revision {} refused because revision proposal candidates for `{}` are invalid", args.decision.command(), args.key ), "invalid" => status.reason.clone().unwrap_or_else(|| { format!( - "order revision {} refused because active order events for `{}` are invalid", + "trade revision {} refused because active trade events for `{}` are invalid", args.decision.command(), args.key ) }), _ => status.reason.clone().unwrap_or_else(|| { format!( - "order revision {} status preflight failed with state `{}`", + "trade revision {} status preflight failed with state `{}`", args.decision.command(), status.state ) }), }); view.issues.extend(candidates.issues.clone()); - view.actions = vec![format!("radroots order status get {}", args.key)]; + view.actions = vec![format!("radroots trade status get {}", args.key)]; Some(view) } @@ -4278,12 +3266,12 @@ fn pending_revision_proposal_candidate<'a>( fn order_accept_inventory_preflight_view( config: &RuntimeConfig, - args: &OrderDecisionArgs, + args: &TradeDecisionArgs, request: &ResolvedSellerOrderRequest, resolution: &SellerOrderRequestResolution, status: &OrderStatusView, ) -> Result<OrderDecisionInventoryPreflight, RuntimeError> { - if args.decision != OrderDecisionArg::Accept { + if args.decision != TradeDecisionArg::Accept { return Ok(OrderDecisionInventoryPreflight { invalid_view: None, inventory: Some(order_declined_inventory_view(request)), @@ -4400,7 +3388,7 @@ fn order_accept_inventory_preflight_view( fn order_accept_inventory_preflight_view_from_projection( config: &RuntimeConfig, - args: &OrderDecisionArgs, + args: &TradeDecisionArgs, request: &ResolvedSellerOrderRequest, resolution: &SellerOrderRequestResolution, status: &OrderStatusView, @@ -4479,19 +3467,19 @@ fn order_declined_inventory_view(request: &ResolvedSellerOrderRequest) -> OrderI } fn order_decision_inventory_for_view( - args: &OrderDecisionArgs, + args: &TradeDecisionArgs, request: &ResolvedSellerOrderRequest, inventory: Option<OrderInventoryView>, ) -> Option<OrderInventoryView> { match args.decision { - OrderDecisionArg::Accept => inventory, - OrderDecisionArg::Decline => Some(order_declined_inventory_view(request)), + TradeDecisionArg::Accept => inventory, + TradeDecisionArg::Decline => Some(order_declined_inventory_view(request)), } } fn fetch_current_inventory_listing( config: &RuntimeConfig, - args: &OrderDecisionArgs, + args: &TradeDecisionArgs, request: &ResolvedSellerOrderRequest, resolution: &SellerOrderRequestResolution, status: &OrderStatusView, @@ -4748,7 +3736,7 @@ fn proposed_accept_decision_record( fn order_decision_inventory_invalid_view( config: &RuntimeConfig, - args: &OrderDecisionArgs, + args: &TradeDecisionArgs, request: &ResolvedSellerOrderRequest, resolution: &SellerOrderRequestResolution, status: &OrderStatusView, @@ -4761,7 +3749,7 @@ fn order_decision_inventory_invalid_view( apply_order_decision_status(&mut view, status); view.reason = Some(reason.into()); view.issues.extend(issues); - view.actions = vec![format!("radroots order status get {}", request.order_id)]; + view.actions = vec![format!("radroots trade status get {}", request.order_id)]; view } @@ -4812,7 +3800,7 @@ fn listing_inventory_accounting_issue_view( fn order_decision_dry_run_view( config: &RuntimeConfig, - args: &OrderDecisionArgs, + args: &TradeDecisionArgs, request: &ResolvedSellerOrderRequest, status: &OrderStatusView, inventory: Option<OrderInventoryView>, @@ -4832,13 +3820,13 @@ fn order_decision_dry_run_view( ), None => "dry run requested; seller order decision publication skipped".to_owned(), }); - view.actions = vec![format!("radroots order status get {}", request.order_id)]; + view.actions = vec![format!("radroots trade status get {}", request.order_id)]; view } fn order_revision_invalid_view( config: &RuntimeConfig, - args: &OrderRevisionProposeArgs, + args: &TradeRevisionProposeArgs, status: &OrderStatusView, reason: impl Into<String>, issues: Vec<OrderIssueView>, @@ -4847,13 +3835,13 @@ fn order_revision_invalid_view( apply_order_revision_status(&mut view, status); view.reason = Some(reason.into()); view.issues.extend(issues); - view.actions = vec![format!("radroots order status get {}", args.key)]; + view.actions = vec![format!("radroots trade status get {}", args.key)]; view } fn order_revision_decision_invalid_view( config: &RuntimeConfig, - args: &OrderRevisionDecisionArgs, + args: &TradeRevisionDecisionArgs, status: &OrderStatusView, reason: impl Into<String>, issues: Vec<OrderIssueView>, @@ -4863,13 +3851,13 @@ fn order_revision_decision_invalid_view( apply_order_revision_decision_status(&mut view, status); view.reason = Some(reason.into()); view.issues.extend(issues); - view.actions = vec![format!("radroots order status get {}", args.key)]; + view.actions = vec![format!("radroots trade status get {}", args.key)]; view } fn order_revision_dry_run_view( config: &RuntimeConfig, - args: &OrderRevisionProposeArgs, + args: &TradeRevisionProposeArgs, status: &OrderStatusView, payload: &RadrootsOrderRevisionProposal, ) -> OrderRevisionProposalView { @@ -4878,13 +3866,13 @@ fn order_revision_dry_run_view( apply_order_revision_payload(&mut view, payload); view.reason = Some("dry run requested; seller revision proposal publication skipped".to_owned()); - view.actions = vec![format!("radroots order status get {}", status.order_id)]; + view.actions = vec![format!("radroots trade status get {}", status.order_id)]; view } fn order_revision_decision_dry_run_view( config: &RuntimeConfig, - args: &OrderRevisionDecisionArgs, + args: &TradeRevisionDecisionArgs, status: &OrderStatusView, proposal: &OrderRevisionProposalRecord, payload: &RadrootsOrderRevisionDecision, @@ -4896,25 +3884,25 @@ fn order_revision_decision_dry_run_view( "dry run requested; buyer revision {} publication skipped", args.decision.command() )); - view.actions = vec![format!("radroots order status get {}", status.order_id)]; + view.actions = vec![format!("radroots trade status get {}", status.order_id)]; view } fn order_cancellation_dry_run_view( config: &RuntimeConfig, - args: &OrderCancelArgs, + args: &TradeCancelArgs, status: &OrderStatusView, ) -> OrderCancellationView { let mut view = order_cancellation_base_view(config, args, "dry_run", true); apply_order_cancellation_status(&mut view, status); view.reason = - Some("dry run requested; buyer order cancellation publication skipped".to_owned()); - view.actions = vec![format!("radroots order status get {}", status.order_id)]; + Some("dry run requested; buyer trade cancellation publication skipped".to_owned()); + view.actions = vec![format!("radroots trade status get {}", status.order_id)]; view } fn order_cancellation_payload_from_status( - args: &OrderCancelArgs, + args: &TradeCancelArgs, status: &OrderStatusView, ) -> Result<RadrootsOrderCancellation, RuntimeError> { Ok(RadrootsOrderCancellation { @@ -4942,7 +3930,7 @@ fn order_cancellation_payload_from_status( } fn order_revision_payload_from_status( - args: &OrderRevisionProposeArgs, + args: &TradeRevisionProposeArgs, status: &OrderStatusView, ) -> Result<RadrootsOrderRevisionProposal, RuntimeError> { let revision_id = protocol_revision_id(next_revision_id().as_str(), "revision_id")?; @@ -5002,7 +3990,7 @@ fn order_revision_payload_from_status( } fn revised_order_economics( - args: &OrderRevisionProposeArgs, + args: &TradeRevisionProposeArgs, revision_id: &str, current: &RadrootsOrderEconomics, ) -> Result<RadrootsOrderEconomics, RuntimeError> { @@ -5061,14 +4049,14 @@ fn revised_order_economics( .map_err(|error| RuntimeError::Config(format!("build revision economics: {error}")))?; if !changed { return Err(RuntimeError::Config( - "order revision propose requires a changed item count or adjustment".to_owned(), + "trade revision propose requires a changed item count or adjustment".to_owned(), )); } Ok(economics) } fn revision_adjustment_line( - args: &OrderRevisionProposeArgs, + args: &TradeRevisionProposeArgs, expected_currency: RadrootsCoreCurrency, ) -> Result<Option<RadrootsOrderEconomicLine>, RuntimeError> { let Some(id) = args.adjustment_id.as_deref().and_then(non_empty_ref) else { @@ -5149,17 +4137,17 @@ fn order_revision_event_parts( let prev_event_id = protocol_event_id(prev_event_id, "prev_event_id")?; if payload.root_event_id != root_event_id || payload.prev_event_id != prev_event_id { return Err(RuntimeError::Config( - "order revision proposal payload chain does not match order status".to_owned(), + "trade revision proposal payload chain does not match order status".to_owned(), )); } order_revision_proposal_event_build(&root_event_id, &prev_event_id, payload).map_err(|error| { - RuntimeError::Config(format!("encode order revision proposal event: {error}")) + RuntimeError::Config(format!("encode trade revision proposal event: {error}")) }) } fn order_revision_inventory_preflight_view( config: &RuntimeConfig, - args: &OrderRevisionProposeArgs, + args: &TradeRevisionProposeArgs, status: &OrderStatusView, payload: &RadrootsOrderRevisionProposal, ) -> Option<OrderRevisionProposalView> { @@ -5171,7 +4159,7 @@ fn order_revision_inventory_preflight_view( config, args, status, - "order revision propose refused because visible inventory is unavailable for the revised items", + "trade revision propose refused because visible inventory is unavailable for the revised items", issues, ); apply_order_revision_payload(&mut view, payload); @@ -5302,12 +4290,12 @@ fn apply_order_revision_decision_payload( } fn order_revision_decision_payload_from_proposal( - args: &OrderRevisionDecisionArgs, + args: &TradeRevisionDecisionArgs, proposal: &OrderRevisionProposalRecord, ) -> Result<RadrootsOrderRevisionDecision, RuntimeError> { let decision = match args.decision { - OrderRevisionDecisionArg::Accept => RadrootsOrderRevisionOutcome::Accepted, - OrderRevisionDecisionArg::Decline => { + TradeRevisionDecisionArg::Accept => RadrootsOrderRevisionOutcome::Accepted, + TradeRevisionDecisionArg::Decline => { let reason = args .reason .as_deref() @@ -5315,7 +4303,7 @@ fn order_revision_decision_payload_from_proposal( .filter(|reason| !reason.is_empty()) .ok_or_else(|| { RuntimeError::Config( - "order revision decline requires a non-empty reason".to_owned(), + "trade revision decline requires a non-empty reason".to_owned(), ) })?; RadrootsOrderRevisionOutcome::Declined { @@ -5340,13 +4328,13 @@ fn order_revision_decision_event_parts( ) -> Result<WireEventParts, RuntimeError> { order_revision_decision_event_build(&payload.root_event_id, &payload.prev_event_id, payload) .map_err(|error| { - RuntimeError::Config(format!("encode order revision decision event: {error}")) + RuntimeError::Config(format!("encode trade revision decision event: {error}")) }) } fn publish_order_revision( config: &RuntimeConfig, - args: &OrderRevisionProposeArgs, + args: &TradeRevisionProposeArgs, status: OrderStatusView, signing: account::AccountSigningIdentity, payload: RadrootsOrderRevisionProposal, @@ -5358,7 +4346,7 @@ fn publish_order_revision( fn publish_order_revision_decision( config: &RuntimeConfig, - args: &OrderRevisionDecisionArgs, + args: &TradeRevisionDecisionArgs, status: OrderStatusView, proposal: &OrderRevisionProposalRecord, signing: account::AccountSigningIdentity, @@ -5379,7 +4367,7 @@ fn publish_order_revision_decision( fn publish_order_cancellation( config: &RuntimeConfig, - args: &OrderCancelArgs, + args: &TradeCancelArgs, status: OrderStatusView, signing: account::AccountSigningIdentity, payload: RadrootsOrderCancellation, @@ -5469,7 +4457,7 @@ fn prepare_order_cancellation_dry_run_via_sdk( fn enqueue_order_revision_proposal_via_sdk( config: &RuntimeConfig, - args: &OrderRevisionProposeArgs, + args: &TradeRevisionProposeArgs, status: OrderStatusView, signing: account::AccountSigningIdentity, payload: RadrootsOrderRevisionProposal, @@ -5513,7 +4501,7 @@ fn enqueue_order_revision_proposal_via_sdk( fn enqueue_order_revision_decision_via_sdk( config: &RuntimeConfig, - args: &OrderRevisionDecisionArgs, + args: &TradeRevisionDecisionArgs, status: OrderStatusView, proposal: &OrderRevisionProposalRecord, signing: account::AccountSigningIdentity, @@ -5559,7 +4547,7 @@ fn enqueue_order_revision_decision_via_sdk( fn enqueue_order_cancellation_via_sdk( config: &RuntimeConfig, - args: &OrderCancelArgs, + args: &TradeCancelArgs, status: OrderStatusView, signing: account::AccountSigningIdentity, payload: RadrootsOrderCancellation, @@ -5682,7 +4670,7 @@ fn push_one_sdk_outbox_event( fn sdk_enqueued_order_revision_view( config: &RuntimeConfig, - args: &OrderRevisionProposeArgs, + args: &TradeRevisionProposeArgs, status: &OrderStatusView, payload: &RadrootsOrderRevisionProposal, enqueue: OrderRevisionProposalReceipt, @@ -5711,14 +4699,14 @@ fn sdk_enqueued_order_revision_view( .unwrap_or_default(); view.failed_relays = push_event.map(sdk_push_failed_relays).unwrap_or_default(); view.reason = - sdk_order_lifecycle_reason("order revision proposal", &enqueue.workflow, push_event); + sdk_order_lifecycle_reason("trade revision proposal", &enqueue.workflow, push_event); view.actions = sdk_order_lifecycle_actions(push_event); view } fn sdk_enqueued_order_revision_decision_view( config: &RuntimeConfig, - args: &OrderRevisionDecisionArgs, + args: &TradeRevisionDecisionArgs, status: &OrderStatusView, proposal: &OrderRevisionProposalRecord, payload: &RadrootsOrderRevisionDecision, @@ -5755,14 +4743,14 @@ fn sdk_enqueued_order_revision_decision_view( .unwrap_or_default(); view.failed_relays = push_event.map(sdk_push_failed_relays).unwrap_or_default(); view.reason = - sdk_order_lifecycle_reason("order revision decision", &enqueue.workflow, push_event); + sdk_order_lifecycle_reason("trade revision decision", &enqueue.workflow, push_event); view.actions = sdk_order_lifecycle_actions(push_event); view } fn sdk_enqueued_order_cancellation_view( config: &RuntimeConfig, - args: &OrderCancelArgs, + args: &TradeCancelArgs, status: &OrderStatusView, enqueue: OrderCancellationReceipt, push: PushOutboxReceipt, @@ -5788,7 +4776,7 @@ fn sdk_enqueued_order_cancellation_view( .map(sdk_push_acknowledged_relays) .unwrap_or_default(); view.failed_relays = push_event.map(sdk_push_failed_relays).unwrap_or_default(); - view.reason = sdk_order_lifecycle_reason("order cancellation", &enqueue.workflow, push_event); + view.reason = sdk_order_lifecycle_reason("trade cancellation", &enqueue.workflow, push_event); view.actions = sdk_order_lifecycle_actions(push_event); view } @@ -5914,7 +4902,7 @@ fn order_actor_write_binding_error_parts( fn order_revision_binding_error_view( config: &RuntimeConfig, - args: &OrderRevisionProposeArgs, + args: &TradeRevisionProposeArgs, status: &OrderStatusView, error: ActorWriteBindingError, ) -> OrderRevisionProposalView { @@ -5928,7 +4916,7 @@ fn order_revision_binding_error_view( fn order_revision_decision_binding_error_view( config: &RuntimeConfig, - args: &OrderRevisionDecisionArgs, + args: &TradeRevisionDecisionArgs, status: &OrderStatusView, error: ActorWriteBindingError, ) -> OrderRevisionDecisionView { @@ -5943,7 +4931,7 @@ fn order_revision_decision_binding_error_view( fn order_cancellation_binding_error_view( config: &RuntimeConfig, - args: &OrderCancelArgs, + args: &TradeCancelArgs, status: &OrderStatusView, error: ActorWriteBindingError, ) -> OrderCancellationView { @@ -6084,7 +5072,7 @@ fn seller_order_request_from_event( fn publish_order_decision( config: &RuntimeConfig, - args: &OrderDecisionArgs, + args: &TradeDecisionArgs, request: ResolvedSellerOrderRequest, resolution: SellerOrderRequestResolution, signing: account::AccountSigningIdentity, @@ -6167,7 +5155,7 @@ fn order_decision_relay_url_policy(target_relays: &[String]) -> SdkRelayUrlPolic fn enqueue_order_decision_via_sdk( config: &RuntimeConfig, - args: &OrderDecisionArgs, + args: &TradeDecisionArgs, request_context: ResolvedSellerOrderRequest, resolution: SellerOrderRequestResolution, signing: account::AccountSigningIdentity, @@ -6230,7 +5218,7 @@ fn cli_sdk_error_to_runtime(error: CliSdkAdapterError) -> RuntimeError { } fn canonical_order_decision_payload( - args: &OrderDecisionArgs, + args: &TradeDecisionArgs, request: &ResolvedSellerOrderRequest, signer_pubkey: &str, ) -> Result<RadrootsOrderDecision, RuntimeError> { @@ -6240,12 +5228,12 @@ fn canonical_order_decision_payload( } fn order_decision_payload_from_request( - args: &OrderDecisionArgs, + args: &TradeDecisionArgs, request: &ResolvedSellerOrderRequest, ) -> Result<RadrootsOrderDecision, RuntimeError> { match args.decision { - OrderDecisionArg::Accept => Ok(accepted_order_decision_payload_from_request(request)), - OrderDecisionArg::Decline => { + TradeDecisionArg::Accept => Ok(accepted_order_decision_payload_from_request(request)), + TradeDecisionArg::Decline => { let reason = args .reason .as_deref() @@ -6299,7 +5287,7 @@ fn declined_order_decision_payload_from_request( fn sdk_enqueued_order_decision_view( config: &RuntimeConfig, - args: &OrderDecisionArgs, + args: &TradeDecisionArgs, request: ResolvedSellerOrderRequest, resolution: SellerOrderRequestResolution, enqueue: OrderDecisionReceipt, @@ -6346,7 +5334,7 @@ fn sdk_push_event_for_order_decision<'a>( } fn sdk_order_decision_state( - decision: OrderDecisionArg, + decision: TradeDecisionArg, push_event: Option<&PushOutboxEventReceipt>, ) -> String { match push_event.map(|event| event.final_state) { @@ -6400,7 +5388,7 @@ fn sdk_order_decision_actions(push_event: Option<&PushOutboxEventReceipt>) -> Ve fn order_decision_binding_error_view( config: &RuntimeConfig, - args: &OrderDecisionArgs, + args: &TradeDecisionArgs, request: ResolvedSellerOrderRequest, resolution: SellerOrderRequestResolution, error: ActorWriteBindingError, @@ -6421,7 +5409,7 @@ fn order_event_list_entry_from_event( let event_kind = event_kind_u32(event); if event_kind != KIND_ORDER_REQUEST { return Err(RuntimeError::Config(format!( - "order event list received unexpected kind `{event_kind}`" + "trade event list received unexpected kind `{event_kind}`" ))); } @@ -6566,7 +5554,7 @@ fn order_evidence_from_relay_events(events: &[RadrootsNostrEvent]) -> Vec<SdkRad fn validate_scaffold_args(args: &OrderDraftCreateArgs) -> Result<(), RuntimeError> { match (normalize_optional(args.bin_id.as_deref()), args.bin_count) { (None, Some(_)) => Err(RuntimeError::Config( - "`--qty` requires `--bin` when creating an order draft".to_owned(), + "`--qty` requires `--bin` when creating an trade draft".to_owned(), )), (Some(_), Some(0)) => Err(RuntimeError::Config( "`--qty` must be greater than zero".to_owned(), @@ -6624,7 +5612,7 @@ fn resolve_order_listing( if !config.local.replica_db_path.exists() { return Err(RuntimeError::Config(format!( - "order listing lookup `{listing_lookup}` requires local market data; run `radroots store init` and `radroots market refresh` before creating an order from a listing" + "trade listing lookup `{listing_lookup}` requires local market data; run `radroots store init` and `radroots market refresh` before creating a trade from a listing" ))); } @@ -6660,7 +5648,7 @@ fn resolve_order_listing( )? .ok_or_else(|| { RuntimeError::Config(format!( - "listing `{listing_lookup}` is missing the latest listing event pointer; run `radroots market refresh` before creating an order from this listing" + "listing `{listing_lookup}` is missing the latest listing event pointer; run `radroots market refresh` before creating a trade from this listing" )) })?; let shared_provenance = resolve_shared_signed_listing_provenance( @@ -7452,7 +6440,7 @@ fn load_app_order_record_from_record( source_issues.push(issue_with_code( "invalid_app_order_record", "document", - "app-authored order record is missing document", + "app-authored trade record is missing document", )); placeholder_app_order_document(&record) } @@ -7480,14 +6468,14 @@ fn app_order_record_source_issues( issues.push(issue_with_code( "app_order_unsupported", "source_runtime", - "order record must come from radroots_studio_app", + "trade record must come from radroots_studio_app", )); } if record.family != LocalRecordFamily::LocalWork { issues.push(issue_with_code( "app_order_unsupported", "family", - "order record must be shared local work", + "trade record must be shared local work", )); } if record.status != LocalRecordStatus::LocalSaved { @@ -7495,7 +6483,7 @@ fn app_order_record_source_issues( "app_order_unsupported", "status", format!( - "order record status `{}` is not consumable as local saved work", + "trade record status `{}` is not consumable as local saved work", record.status.as_str() ), )); @@ -7504,7 +6492,7 @@ fn app_order_record_source_issues( issues.push(issue_with_code( "invalid_app_order_record", "local_work_json", - "app-authored order record is missing local work payload", + "app-authored trade record is missing local work payload", )); return Ok(issues); }; @@ -7513,14 +6501,14 @@ fn app_order_record_source_issues( issues.push(issue_with_code( "app_order_stale", "currentness.current", - "app-authored order record is not marked current", + "app-authored trade record is not marked current", )); } if payload["currentness"]["record_id"].as_str() != Some(record.record_id.as_str()) { issues.push(issue_with_code( "invalid_app_order_record", "currentness.record_id", - "app-authored order record currentness id does not match the shared record id", + "app-authored trade record currentness id does not match the shared record id", )); } if current { @@ -7536,7 +6524,7 @@ fn app_order_record_source_issues( issues.push(issue_with_code( "app_order_unsupported", "support_status.state", - "app-authored order record is not marked supported", + "app-authored trade record is not marked supported", )); for support_issue in support_issues { if let Some(support_issue) = support_issue.as_str() { @@ -7564,7 +6552,7 @@ fn app_order_record_source_issues( "app_order_stale", "record_id", format!( - "app-authored local order record `{}` was superseded by `{}`", + "app-authored local trade record `{}` was superseded by `{}`", record.record_id, current_record.record_id ), )); @@ -7794,22 +6782,22 @@ fn app_order_record_summary( }; let actions = if exportable { vec![ - format!("radroots order get {}", document.order.order_id), - format!("radroots order app export {}", record.record_id), + format!("radroots trade get {}", document.order.order_id), + format!("radroots trade app export {}", record.record_id), format!( - "radroots --relay wss://relay.example.com order submit {}", + "radroots --relay wss://relay.example.com trade submit {}", document.order.order_id ), ] } else if app_order_issue_present(&issues, APP_ORDER_ALREADY_SUBMITTED_ISSUE) { vec![format!( - "radroots order status get {}", + "radroots trade status get {}", document.order.order_id )] } else if app_order_issue_present(&issues, APP_ORDER_SIGNED_EVIDENCE_CONFLICT_ISSUE) { vec![ - format!("radroots order status get {}", document.order.order_id), - "radroots order app list".to_owned(), + format!("radroots trade status get {}", document.order.order_id), + "radroots trade app list".to_owned(), ] } else { Vec::new() @@ -7913,16 +6901,16 @@ fn app_order_export_failure_actions( ) -> Vec<String> { if app_order_issue_present(issues, APP_ORDER_ALREADY_SUBMITTED_ISSUE) { vec![format!( - "radroots order status get {}", + "radroots trade status get {}", document.order.order_id )] } else if app_order_issue_present(issues, APP_ORDER_SIGNED_EVIDENCE_CONFLICT_ISSUE) { vec![ - format!("radroots order status get {}", document.order.order_id), - "radroots order app list".to_owned(), + format!("radroots trade status get {}", document.order.order_id), + "radroots trade app list".to_owned(), ] } else { - vec!["radroots order app list".to_owned()] + vec!["radroots trade app list".to_owned()] } } @@ -7939,14 +6927,14 @@ fn order_export_output_path( fn validate_order_export_output_target(output_path: &Path) -> Result<(), RuntimeError> { if output_path.exists() { return Err(RuntimeError::Config(format!( - "order draft output {} must not already exist", + "trade draft output {} must not already exist", output_path.display() ))); } if let Some(parent) = output_path.parent() { if parent.exists() && !parent.is_dir() { return Err(RuntimeError::Config(format!( - "order draft parent {} is not a directory", + "trade draft parent {} is not a directory", parent.display() ))); } @@ -8102,7 +7090,7 @@ fn collect_issues(document: &OrderDraftDocument) -> Vec<OrderIssueView> { Ok(parsed) => { if parsed.kind != KIND_LISTING { issues.push(issue( - "order.listing_addr", + "trade.listing_addr", "listing_addr must reference a public NIP-99 listing", )); } @@ -8117,13 +7105,13 @@ fn collect_issues(document: &OrderDraftDocument) -> Vec<OrderIssueView> { } } Err(error) => issues.push(issue( - "order.listing_addr", + "trade.listing_addr", format!("listing_addr is invalid: {error}"), )), }, None => issues.push(issue( - "order.listing_addr", - "listing_addr is required before order submit", + "trade.listing_addr", + "listing_addr is required before trade submit", )), } @@ -8131,27 +7119,27 @@ fn collect_issues(document: &OrderDraftDocument) -> Vec<OrderIssueView> { Some(listing_event_id) => { if !is_valid_event_id(listing_event_id.as_str()) { issues.push(issue( - "order.listing_event_id", + "trade.listing_event_id", "listing_event_id must be a 64-character hex Nostr event id", )); } } None => issues.push(issue( - "order.listing_event_id", - "latest active listing event id is required before order submit; run `radroots market refresh` and create the order from local market data", + "trade.listing_event_id", + "latest active listing event id is required before trade submit; run `radroots market refresh` and create the trade from local market data", )), } match normalize_listing_relay_set(document.order.listing_relays.iter()) { Ok(listing_relays) if listing_relays.is_empty() => issues.push(issue_with_code( "listing_provenance_missing", - "order.listing_relays", - "listing relay provenance is required before order submit; run `radroots market refresh` and create the order from current local market data", + "trade.listing_relays", + "listing relay provenance is required before trade submit; run `radroots market refresh` and create the trade from current local market data", )), Ok(_) => {} Err(error) => issues.push(issue_with_code( "listing_provenance_invalid", - "order.listing_relays", + "trade.listing_relays", format!("listing relay provenance is invalid: {error}"), )), } @@ -8159,7 +7147,7 @@ fn collect_issues(document: &OrderDraftDocument) -> Vec<OrderIssueView> { if document.order.items.is_empty() { issues.push(issue( "order.items", - "at least one order item is required before order submit", + "at least one order item is required before trade submit", )); } for (index, item) in document.order.items.iter().enumerate() { @@ -8194,26 +7182,26 @@ fn collect_issues(document: &OrderDraftDocument) -> Vec<OrderIssueView> { } None => issues.push(issue( "order.economics", - "quote economics is required before order submit; run `radroots basket quote create` from current local market data", + "quote economics is required before trade submit; run `radroots basket quote create` from current local market data", )), } if document.buyer_actor.account_id.trim().is_empty() { issues.push(issue( "buyer_actor.account_id", - "buyer_actor account_id is required before order submit", + "buyer_actor account_id is required before trade submit", )); } if document.buyer_actor.pubkey.trim().is_empty() { issues.push(issue( "buyer_actor.pubkey", - "buyer_actor pubkey is required before order submit", + "buyer_actor pubkey is required before trade submit", )); } if document.buyer_actor.source.trim().is_empty() { issues.push(issue( "buyer_actor.source", - "buyer_actor source is required before order submit", + "buyer_actor source is required before trade submit", )); } else if !matches!( document.buyer_actor.source.as_str(), @@ -8230,7 +7218,7 @@ fn collect_issues(document: &OrderDraftDocument) -> Vec<OrderIssueView> { if document.order.buyer_pubkey.trim().is_empty() { issues.push(issue( "order.buyer_pubkey", - "order buyer_pubkey is required before order submit", + "order buyer_pubkey is required before trade submit", )); } else if !document .order @@ -8271,14 +7259,14 @@ fn actions_for_document( ) -> Vec<String> { if app_order_issue_present(issues, APP_ORDER_ALREADY_SUBMITTED_ISSUE) { return vec![format!( - "radroots order status get {}", + "radroots trade status get {}", document.order.order_id )]; } if app_order_issue_present(issues, APP_ORDER_SIGNED_EVIDENCE_CONFLICT_ISSUE) { return vec![ - format!("radroots order status get {}", document.order.order_id), - "radroots order app list".to_owned(), + format!("radroots trade status get {}", document.order.order_id), + "radroots trade app list".to_owned(), ]; } @@ -8296,7 +7284,7 @@ fn actions_for_document( .eq_ignore_ascii_case(document.buyer_actor.pubkey.as_str()) { actions.push(format!( - "radroots order rebind {} <selector>", + "radroots trade rebind {} <selector>", document.order.order_id )); } @@ -8306,7 +7294,7 @@ fn actions_for_document( { actions.push("radroots account import <path>".to_owned()); actions.push(format!( - "radroots order rebind {} <selector>", + "radroots trade rebind {} <selector>", document.order.order_id )); } @@ -8318,11 +7306,11 @@ fn actions_for_document( "radroots account attach-secret {} <path>", document.buyer_actor.account_id )); - actions.push(format!("radroots order get {}", document.order.order_id)); + actions.push(format!("radroots trade get {}", document.order.order_id)); } if issues.iter().any(|issue| issue.code == "account_mismatch") { actions.push(format!( - "radroots order rebind {} <selector>", + "radroots trade rebind {} <selector>", document.order.order_id )); } @@ -8331,7 +7319,7 @@ fn actions_for_document( .iter() .any(|issue| issue.field.starts_with("order.items[")) { - actions.push(format!("radroots order get {}", document.order.order_id)); + actions.push(format!("radroots trade get {}", document.order.order_id)); } let mut deduped = Vec::new(); for action in actions { @@ -8496,7 +7484,7 @@ fn order_event_list_actor_context( let seller_pubkey = non_empty_string(loaded.document.order.seller_pubkey).ok_or_else(|| { RuntimeError::Config(format!( - "local order draft `{order_id}` is missing seller_pubkey" + "local trade draft `{order_id}` is missing seller_pubkey" )) })?; return Ok(Some(OrderEventListActorContext { @@ -8556,16 +7544,16 @@ fn order_buyer_write_actor_context( fn order_submit_listing_freshness_view( config: &RuntimeConfig, loaded: &LoadedOrderDraft, - args: &OrderSubmitArgs, + args: &TradeSubmitArgs, ) -> Result<Option<OrderSubmitView>, RuntimeError> { if !config.local.replica_db_path.exists() { return Ok(Some(order_submit_unconfigured_view( config, loaded, args, - "order submit requires local market data to confirm the listing is still active; run `radroots store init` and `radroots market refresh` before submitting", + "trade submit requires local market data to confirm the listing is still active; run `radroots store init` and `radroots market refresh` before submitting", vec![issue( - "order.listing_addr", + "trade.listing_addr", "local replica database is missing; run `radroots store init` and `radroots market refresh` before submitting", )], vec![ @@ -8577,7 +7565,7 @@ fn order_submit_listing_freshness_view( let listing_addr = loaded.document.order.listing_addr.as_str(); let parsed = parse_listing_addr(listing_addr) - .map_err(|error| RuntimeError::Config(format!("order listing_addr is invalid: {error}")))?; + .map_err(|error| RuntimeError::Config(format!("trade listing_addr is invalid: {error}")))?; let active_event_id = match resolve_active_listing_event_id(config, listing_addr, &parsed)? { Some(event_id) => event_id, None => { @@ -8585,9 +7573,9 @@ fn order_submit_listing_freshness_view( config, loaded, args, - "order listing is not active in the local replica; run `radroots market refresh` and create a new order from current market data", + "trade listing is not active in the local replica; run `radroots market refresh` and create a new trade from current market data", vec![issue( - "order.listing_addr", + "trade.listing_addr", "listing is missing, archived, or superseded in the local replica", )], vec!["radroots market refresh".to_owned()], @@ -8600,9 +7588,9 @@ fn order_submit_listing_freshness_view( config, loaded, args, - "order listing event is no longer current in the local replica; run `radroots market refresh` and create a new order from current market data", + "trade listing event is no longer current in the local replica; run `radroots market refresh` and create a new trade from current market data", vec![issue( - "order.listing_event_id", + "trade.listing_event_id", format!( "draft listing_event_id does not match latest local listing event `{active_event_id}`" ), @@ -8617,16 +7605,16 @@ fn order_submit_listing_freshness_view( fn order_submit_quantity_preflight_view( config: &RuntimeConfig, loaded: &LoadedOrderDraft, - args: &OrderSubmitArgs, + args: &TradeSubmitArgs, ) -> Result<Option<OrderSubmitView>, RuntimeError> { if !config.local.replica_db_path.exists() { return Ok(Some(order_submit_unconfigured_view( config, loaded, args, - "order submit requires local market data to confirm current listing availability; run `radroots store init` and `radroots market refresh` before submitting", + "trade submit requires local market data to confirm current listing availability; run `radroots store init` and `radroots market refresh` before submitting", vec![issue( - "order.listing_addr", + "trade.listing_addr", "local replica database is missing; run `radroots store init` and `radroots market refresh` before submitting", )], vec![ @@ -8673,9 +7661,9 @@ fn order_submit_quantity_preflight_view( config, loaded, args, - "order listing is not active in the local replica; run `radroots market refresh` and create a new order from current market data", + "trade listing is not active in the local replica; run `radroots market refresh` and create a new trade from current market data", vec![issue( - "order.listing_addr", + "trade.listing_addr", "listing is missing, archived, or superseded in the local replica", )], vec!["radroots market refresh".to_owned()], @@ -8695,7 +7683,7 @@ fn order_submit_quantity_preflight_view( config, loaded, args, - "order listing bin identity is missing in the local replica", + "trade listing bin identity is missing in the local replica", vec![issue_with_code( "listing_primary_bin_missing", "inventory.primary_bin_id", @@ -8712,7 +7700,7 @@ fn order_submit_quantity_preflight_view( config, loaded, args, - "order listing bin identity is not verified in the local replica", + "trade listing bin identity is not verified in the local replica", vec![issue_with_code( "listing_primary_bin_invalid", "inventory.primary_bin_id", @@ -8725,7 +7713,7 @@ fn order_submit_quantity_preflight_view( config, loaded, args, - "order listing bin identity is invalid in the local replica", + "trade listing bin identity is invalid in the local replica", vec![issue_with_code( "listing_primary_bin_invalid", "inventory.primary_bin_id", @@ -8754,7 +7742,7 @@ fn order_submit_quantity_preflight_view( config, loaded, args, - "order draft references a bin outside the current local listing", + "trade draft references a bin outside the current local listing", bin_issues, ))); } @@ -8766,7 +7754,7 @@ fn order_submit_quantity_preflight_view( config, loaded, args, - "order listing availability is invalid in the local replica", + "trade listing availability is invalid in the local replica", vec![issue_with_code( "listing_inventory_availability_invalid", "inventory.available", @@ -8779,7 +7767,7 @@ fn order_submit_quantity_preflight_view( config, loaded, args, - "order listing availability is missing in the local replica", + "trade listing availability is missing in the local replica", vec![issue_with_code( "listing_inventory_availability_missing", "inventory.available", @@ -8811,13 +7799,13 @@ fn order_submit_quantity_preflight_view( fn order_submit_unconfigured_view( config: &RuntimeConfig, loaded: &LoadedOrderDraft, - args: &OrderSubmitArgs, + args: &TradeSubmitArgs, reason: impl Into<String>, issues: Vec<OrderIssueView>, mut actions: Vec<String>, ) -> OrderSubmitView { actions.push(format!( - "radroots order get {}", + "radroots trade get {}", loaded.document.order.order_id )); @@ -8856,7 +7844,7 @@ fn order_submit_unconfigured_view( fn order_submit_app_signed_evidence_view( config: &RuntimeConfig, loaded: &LoadedOrderDraft, - args: &OrderSubmitArgs, + args: &TradeSubmitArgs, issues: &[OrderIssueView], ) -> Option<OrderSubmitView> { if let Some(issue) = app_order_issue(issues, APP_ORDER_ALREADY_SUBMITTED_ISSUE) { @@ -8927,7 +7915,7 @@ fn order_submit_app_signed_evidence_view( job: None, issues: issues.to_vec(), actions: vec![format!( - "radroots order status get {}", + "radroots trade status get {}", loaded.document.order.order_id )], }); @@ -8939,7 +7927,7 @@ fn order_submit_app_signed_evidence_view( fn order_submit_invalid_quantity_view( config: &RuntimeConfig, loaded: &LoadedOrderDraft, - args: &OrderSubmitArgs, + args: &TradeSubmitArgs, reason: impl Into<String>, issues: Vec<OrderIssueView>, ) -> OrderSubmitView { @@ -8973,7 +7961,7 @@ fn order_submit_invalid_quantity_view( issues, actions: vec![ "radroots market refresh".to_owned(), - format!("radroots order get {}", loaded.document.order.order_id), + format!("radroots trade get {}", loaded.document.order.order_id), ], } } @@ -8981,7 +7969,7 @@ fn order_submit_invalid_quantity_view( fn order_submit_listing_provenance_preflight_view( config: &RuntimeConfig, loaded: &LoadedOrderDraft, - args: &OrderSubmitArgs, + args: &TradeSubmitArgs, ) -> Result<Option<OrderSubmitView>, RuntimeError> { let listing_relays = normalize_listing_relay_set(loaded.document.order.listing_relays.iter()) @@ -9004,13 +7992,13 @@ fn order_submit_listing_provenance_preflight_view( .iter() .map(|relay| { format!( - "radroots --relay {} order submit {}", + "radroots --relay {} trade submit {}", relay, loaded.document.order.order_id ) }) .collect::<Vec<_>>(); actions.push(format!( - "radroots order get {}", + "radroots trade get {}", loaded.document.order.order_id )); Ok(Some(OrderSubmitView { @@ -9039,13 +8027,13 @@ fn order_submit_listing_provenance_preflight_view( idempotency_key: args.idempotency_key.clone(), signer_mode: Some(config.signer.backend.as_str().to_owned()), reason: Some( - "order submit requires at least one configured relay that is known to carry the listing" + "trade submit requires at least one configured relay that is known to carry the listing" .to_owned(), ), job: None, issues: vec![issue_with_code( "listing_relay_target_mismatch", - "order.listing_relays", + "trade.listing_relays", format!( "configured relays must include one of the listing provenance relays: {}", loaded.document.order.listing_relays.join(", ") @@ -9058,7 +8046,7 @@ fn order_submit_listing_provenance_preflight_view( fn order_submit_market_freshness_view( config: &RuntimeConfig, loaded: &LoadedOrderDraft, - args: &OrderSubmitArgs, + args: &TradeSubmitArgs, ) -> Result<Option<OrderSubmitView>, RuntimeError> { if config.output.dry_run || config.relay.urls.is_empty() { return Ok(None); @@ -9077,11 +8065,11 @@ fn order_submit_market_freshness_view( config, loaded, args, - "order submit requires a current market refresh before signing; run `radroots market refresh` with the relays you trust, then submit again", + "trade submit requires a current market refresh before signing; run `radroots market refresh` with the relays you trust, then submit again", vec![issue( - "order.listing_addr", + "trade.listing_addr", format!( - "local market freshness is `{}`; current listing state must be refreshed before order submit", + "local market freshness is `{}`; current listing state must be refreshed before trade submit", freshness.state ), )], @@ -9092,7 +8080,7 @@ fn order_submit_market_freshness_view( fn order_submit_existing_request_view_from_receipt( config: &RuntimeConfig, loaded: &LoadedOrderDraft, - args: &OrderSubmitArgs, + args: &TradeSubmitArgs, payload: &RadrootsOrderRequest, receipt: DirectRelayFetchReceipt, ) -> Result<Option<OrderSubmitView>, RuntimeError> { @@ -9119,7 +8107,7 @@ fn order_submit_existing_request_view_from_receipt( Err(error) => candidate_issues.push(issue_with_events( "invalid_request_candidate", "request_event_id", - format!("request event `{event_id}` failed order submit preflight: {error}"), + format!("request event `{event_id}` failed trade submit preflight: {error}"), vec![event_id], )), } @@ -9165,12 +8153,12 @@ fn order_submit_existing_request_view_from_receipt( config, loaded, args, - "visible order request event conflicts with the local order draft; refusing to publish a second request for the same order id", + "visible order request event conflicts with the local trade draft; refusing to publish a second request for the same order id", vec![issue_with_events( "existing_request_conflict", "request_event_id", format!( - "request event `{}` does not match the local order draft", + "request event `{}` does not match the local trade draft", request.request_event_id ), vec![request.request_event_id.clone()], @@ -9254,7 +8242,7 @@ fn order_submit_request_matches_draft( fn order_submit_deduplicated_view( config: &RuntimeConfig, loaded: &LoadedOrderDraft, - args: &OrderSubmitArgs, + args: &TradeSubmitArgs, request: &ResolvedOrderSubmitRequest, target_relays: Vec<String>, connected_relays: Vec<String>, @@ -9298,7 +8286,7 @@ fn order_submit_deduplicated_view( fn order_submit_dry_run_view( config: &RuntimeConfig, loaded: &LoadedOrderDraft, - args: &OrderSubmitArgs, + args: &TradeSubmitArgs, plan: OrderSubmitPlan, target_relays: Vec<String>, ) -> OrderSubmitView { @@ -9331,7 +8319,7 @@ fn order_submit_dry_run_view( job: None, issues: Vec::new(), actions: vec![format!( - "radroots order submit {}", + "radroots trade submit {}", loaded.document.order.order_id )], } @@ -9340,7 +8328,7 @@ fn order_submit_dry_run_view( fn order_submit_invalid_existing_request_view( config: &RuntimeConfig, loaded: &LoadedOrderDraft, - args: &OrderSubmitArgs, + args: &TradeSubmitArgs, reason: impl Into<String>, issues: Vec<OrderIssueView>, target_relays: Vec<String>, @@ -9375,7 +8363,7 @@ fn order_submit_invalid_existing_request_view( job: None, issues, actions: vec![format!( - "radroots order status get {}", + "radroots trade status get {}", loaded.document.order.order_id )], } @@ -9387,7 +8375,7 @@ fn canonical_order_request_payload_from_loaded( ) -> Result<RadrootsOrderRequest, RuntimeError> { let economics = loaded.document.order.economics.clone().ok_or_else(|| { - RuntimeError::Config("order draft is missing quote economics".to_owned()) + RuntimeError::Config("trade draft is missing quote economics".to_owned()) })?; let items = loaded .document @@ -9499,7 +8487,7 @@ fn order_submit_relay_url_policy(target_relays: &[String]) -> SdkRelayUrlPolicy fn prepare_order_submit_via_sdk( config: &RuntimeConfig, loaded: &LoadedOrderDraft, - args: &OrderSubmitArgs, + args: &TradeSubmitArgs, input: SdkOrderSubmitInput, ) -> Result<OrderSubmitView, CliSdkAdapterError> { let target_relays = input.target_relays.clone(); @@ -9524,7 +8512,7 @@ fn prepare_order_submit_via_sdk( fn submit_via_sdk( config: &RuntimeConfig, loaded: &LoadedOrderDraft, - args: &OrderSubmitArgs, + args: &TradeSubmitArgs, signing: account::AccountSigningIdentity, input: SdkOrderSubmitInput, ) -> Result<OrderSubmitView, CliSdkAdapterError> { @@ -9579,7 +8567,7 @@ fn enqueue_target_relays( fn sdk_enqueued_order_submit_view( config: &RuntimeConfig, loaded: &LoadedOrderDraft, - args: &OrderSubmitArgs, + args: &TradeSubmitArgs, enqueue: OrderSubmitReceipt, push: PushOutboxReceipt, ) -> OrderSubmitView { @@ -9664,7 +8652,7 @@ fn sdk_order_submit_reason( sdk_order_enqueue_retry_summary(enqueue) )), None => Some(format!( - "{}; order submit queued in SDK outbox; no ready SDK outbox event was pushed; {}", + "{}; trade submit queued in SDK outbox; no ready SDK outbox event was pushed; {}", sdk_order_enqueue_summary(enqueue), sdk_order_enqueue_retry_summary(enqueue) )), @@ -9755,14 +8743,14 @@ fn sdk_relay_outcome_kind(kind: PushOutboxRelayOutcomeKind) -> &'static str { fn order_binding_error_view( config: &RuntimeConfig, loaded: &LoadedOrderDraft, - args: &OrderSubmitArgs, + args: &TradeSubmitArgs, error: ActorWriteBindingError, ) -> OrderSubmitView { let (state, reason, actions) = order_actor_write_binding_error_parts(error); let mut actions = actions; actions.push(format!( - "radroots order get {}", + "radroots trade get {}", loaded.document.order.order_id )); @@ -9821,8 +8809,8 @@ fn validate_bound_order_buyer_account( json!({ "actions": [ "radroots account import <path>", - format!("radroots order rebind {} <selector>", document.order.order_id), - format!("radroots order get {}", document.order.order_id), + format!("radroots trade rebind {} <selector>", document.order.order_id), + format!("radroots trade get {}", document.order.order_id), ], }), ), @@ -9847,8 +8835,8 @@ fn validate_bound_order_buyer_account( "attempted_buyer_account_id": account_id, "attempted_buyer_pubkey": account_pubkey, "actions": [ - format!("radroots order rebind {} <selector>", document.order.order_id), - format!("radroots order get {}", document.order.order_id), + format!("radroots trade rebind {} <selector>", document.order.order_id), + format!("radroots trade get {}", document.order.order_id), ], }), ), @@ -9864,7 +8852,7 @@ fn validate_bound_order_buyer_account( json!({ "actions": [ format!("radroots account attach-secret {account_id} <path>"), - format!("radroots order get {}", document.order.order_id), + format!("radroots trade get {}", document.order.order_id), ], }), ), @@ -9882,7 +8870,7 @@ fn validate_bound_order_buyer_account( "attempted_buyer_account_id": selector, "actions": [ "radroots account list", - format!("radroots order get {}", document.order.order_id), + format!("radroots trade get {}", document.order.order_id), ], }), ), @@ -9901,9 +8889,9 @@ fn validate_bound_order_buyer_account( "attempted_buyer_account_id": attempted.record.account_id.to_string(), "attempted_buyer_pubkey": attempted_pubkey, "actions": [ - format!("radroots --account-id {account_id} order submit {}", document.order.order_id), - format!("radroots order rebind {} <selector>", document.order.order_id), - format!("radroots order get {}", document.order.order_id), + format!("radroots --account-id {account_id} trade submit {}", document.order.order_id), + format!("radroots trade rebind {} <selector>", document.order.order_id), + format!("radroots trade get {}", document.order.order_id), ], }), ), @@ -9924,7 +8912,7 @@ fn order_buyer_failure_detail( "order_buyer_account_id": loaded.document.buyer_actor.account_id.as_str(), "order_buyer_pubkey": loaded.document.buyer_actor.pubkey.as_str(), "order_file": loaded.file.display().to_string(), - "order_id": loaded.document.order.order_id.as_str(), + "trade_id": loaded.document.order.order_id.as_str(), }); if let (Some(detail), Some(extra)) = (detail.as_object_mut(), extra.as_object_mut()) { for (key, value) in std::mem::take(extra) { @@ -9938,7 +8926,7 @@ fn resolve_local_order_signing_identity( config: &RuntimeConfig, loaded: &LoadedOrderDraft, ) -> Result<account::AccountSigningIdentity, ActorWriteBindingError> { - resolve_local_order_bound_buyer_signing_identity(config, loaded, "order submit") + resolve_local_order_bound_buyer_signing_identity(config, loaded, "trade submit") } fn resolve_local_order_bound_buyer_signing_identity( @@ -9973,8 +8961,8 @@ fn resolve_local_order_bound_buyer_signing_identity( "attempted_buyer_account_id": signing.account.record.account_id.to_string(), "attempted_buyer_pubkey": selected_pubkey, "actions": [ - format!("radroots order rebind {} <selector>", loaded.document.order.order_id), - format!("radroots order get {}", loaded.document.order.order_id), + format!("radroots trade rebind {} <selector>", loaded.document.order.order_id), + format!("radroots trade get {}", loaded.document.order.order_id), ], }), ), @@ -9987,7 +8975,7 @@ fn resolve_local_order_bound_buyer_signing_identity( fn resolve_local_order_decision_signing_identity( config: &RuntimeConfig, seller_pubkey: &str, - decision: OrderDecisionArg, + decision: TradeDecisionArg, ) -> Result<account::AccountSigningIdentity, ActorWriteBindingError> { if !matches!(config.signer.backend, SignerBackend::Local) { return Err(ActorWriteBindingError::Unconfigured(format!( @@ -10019,7 +9007,7 @@ fn resolve_local_order_revision_signing_identity( ) -> Result<account::AccountSigningIdentity, ActorWriteBindingError> { if !matches!(config.signer.backend, SignerBackend::Local) { return Err(ActorWriteBindingError::Unconfigured( - "order revision propose requires signer mode `local`".to_owned(), + "trade revision propose requires signer mode `local`".to_owned(), )); } let signing = account::resolve_local_signing_identity(config) @@ -10046,7 +9034,7 @@ fn resolve_local_order_cancellation_signing_identity( ) -> Result<account::AccountSigningIdentity, ActorWriteBindingError> { if !matches!(config.signer.backend, SignerBackend::Local) { return Err(ActorWriteBindingError::Unconfigured( - "order cancel requires signer mode `local`".to_owned(), + "trade cancel requires signer mode `local`".to_owned(), )); } let signing = account::resolve_local_signing_identity(config) @@ -10070,11 +9058,11 @@ fn resolve_local_order_cancellation_signing_identity( fn resolve_local_order_revision_decision_signing_identity( config: &RuntimeConfig, buyer_pubkey: &str, - args: &OrderRevisionDecisionArgs, + args: &TradeRevisionDecisionArgs, ) -> Result<account::AccountSigningIdentity, ActorWriteBindingError> { if !matches!(config.signer.backend, SignerBackend::Local) { return Err(ActorWriteBindingError::Unconfigured(format!( - "order revision {} requires signer mode `local`", + "trade revision {} requires signer mode `local`", args.decision.command() ))); } @@ -10108,9 +9096,9 @@ fn relay_failures(failures: Vec<DirectRelayFailure>) -> Vec<RelayFailureView> { fn load_draft(path: &Path) -> Result<LoadedOrderDraft, String> { let contents = fs::read_to_string(path) - .map_err(|error| format!("read order draft {}: {error}", path.display()))?; + .map_err(|error| format!("read trade draft {}: {error}", path.display()))?; let document = toml::from_str::<OrderDraftDocument>(contents.as_str()) - .map_err(|error| format!("parse order draft {}: {error}", path.display()))?; + .map_err(|error| format!("parse trade draft {}: {error}", path.display()))?; Ok(LoadedOrderDraft { file: path.to_path_buf(), updated_at_unix: modified_unix(path).unwrap_or_default(), @@ -10128,9 +9116,9 @@ fn save_draft(path: &Path, draft: &OrderDraftDocument) -> Result<(), RuntimeErro fn scaffold_contents(draft: &OrderDraftDocument) -> Result<String, RuntimeError> { let toml = toml::to_string_pretty(draft) - .map_err(|error| RuntimeError::Config(format!("render order draft: {error}")))?; + .map_err(|error| RuntimeError::Config(format!("render trade draft: {error}")))?; Ok(format!( - "# radroots order draft v1\n# fill listing_addr and any missing order items before submit\n\n{toml}" + "# radroots trade draft v1\n# fill listing_addr and any missing order items before submit\n\n{toml}" )) } diff --git a/src/runtime/order/sdk_status.rs b/src/runtime/order/sdk_status.rs @@ -113,9 +113,9 @@ fn sdk_order_status_state(status: OrderStatusKind) -> &'static str { fn sdk_order_status_reason(status: OrderStatusKind, order_id: &str) -> Option<String> { match status { - OrderStatusKind::Missing => Some(format!("no local SDK order events matched `{order_id}`")), + OrderStatusKind::Missing => Some(format!("no local SDK trade events matched `{order_id}`")), OrderStatusKind::Invalid => Some(format!( - "local SDK order events for `{order_id}` failed reducer validation" + "local SDK trade events for `{order_id}` failed reducer validation" )), _ => None, } @@ -167,7 +167,7 @@ fn sdk_order_status_issue_view(issue: &SdkOrderStatusIssue) -> OrderIssueView { OrderIssueView { code: code.clone(), field: "sdk_order_status".to_owned(), - message: format!("SDK order status reported `{code}`"), + message: format!("SDK trade status reported `{code}`"), event_ids: issue .event_ids .iter() diff --git a/src/runtime/paths.rs b/src/runtime/paths.rs @@ -35,6 +35,7 @@ pub struct PathsConfig { pub app_config_path: PathBuf, pub workspace_config_path: Option<PathBuf>, pub app_data_root: PathBuf, + pub shared_cache_root: PathBuf, pub app_logs_root: PathBuf, pub shared_accounts_data_root: PathBuf, pub shared_accounts_secrets_root: PathBuf, @@ -93,6 +94,7 @@ pub(crate) fn resolve_paths( app_config_path: app_paths.config.join(DEFAULT_CONFIG_FILE_NAME), workspace_config_path, app_data_root: app_paths.data, + shared_cache_root: resolved.cache, app_logs_root: app_paths.logs, shared_accounts_data_root: shared_accounts_paths.data, shared_accounts_secrets_root: shared_accounts_paths.secrets, diff --git a/src/runtime/provider.rs b/src/runtime/provider.rs @@ -300,6 +300,7 @@ mod tests { app_config_path: PathBuf::from("/tmp/config/apps/cli/config.toml"), workspace_config_path: None, app_data_root: PathBuf::from("/tmp/data"), + shared_cache_root: PathBuf::from("/tmp/cache"), app_logs_root: PathBuf::from("/tmp/logs"), shared_accounts_data_root: PathBuf::from("/tmp/shared/accounts"), shared_accounts_secrets_root: PathBuf::from("/tmp/shared/accounts-secrets"), diff --git a/src/runtime/sdk.rs b/src/runtime/sdk.rs @@ -52,6 +52,7 @@ pub enum CliSdkAdapterError { #[derive(Debug, Clone, PartialEq, Eq)] pub struct CliSdkConfig { pub storage_root: PathBuf, + pub geonames_cache_root: PathBuf, pub relay_url_policy: SdkRelayUrlPolicy, pub relay_urls: Vec<String>, pub publish_transport: SdkPublishTransport, @@ -61,6 +62,7 @@ impl CliSdkConfig { pub fn from_runtime_config(config: &RuntimeConfig) -> Result<Self, RuntimeError> { Ok(Self { storage_root: sdk_storage_root(config), + geonames_cache_root: config.paths.shared_cache_root.clone(), relay_url_policy: sdk_relay_url_policy(config), relay_urls: config.relay.urls.clone(), publish_transport: sdk_publish_transport(config)?, @@ -73,6 +75,7 @@ impl CliSdkConfig { .storage(RadrootsSdkStorageConfig::Directory( self.storage_root.clone(), )) + .geonames_cache_root(self.geonames_cache_root.clone()) .relay_url_policy(self.relay_url_policy) .publish_transport(self.publish_transport.clone()), |builder, relay_url| builder.relay_url(relay_url.clone()), @@ -590,6 +593,7 @@ pub(crate) fn sdk_runtime() -> Result<Runtime, RuntimeError> { fn memory_builder(config: &CliSdkConfig) -> RadrootsClientBuilder { config.relay_urls.iter().fold( RadrootsClient::builder() + .geonames_cache_root(config.geonames_cache_root.clone()) .relay_url_policy(config.relay_url_policy) .publish_transport(config.publish_transport.clone()), |builder, relay_url| builder.relay_url(relay_url.clone()), @@ -797,7 +801,7 @@ mod tests { section: "dependencies", name: "radroots_replica_db", owner: "legacy-replica-and-market-projection", - reason: "legacy derived replica status, export, market reads, sync pull, basket lookup, and order draft preflight", + reason: "legacy derived replica status, export, market reads, sync pull, basket lookup, and trade draft preflight", lifecycle: "transitional until those derived projection surfaces migrate", }, DirectRrRsDependency { @@ -869,12 +873,12 @@ mod tests { LegacyDirectRelayConsumer { path: "src/runtime/order.rs", required_tokens: &[ - "legacy_order_preflight_relay_status", + "sdk_order_status_from_relay_receipt", "fetch_events_from_relays", ], - owner: "order.status.relay-read", - reason: "bounded order status and preflight reads still inspect configured relays outside SDK local storage", - lifecycle: "retain until order relay reads migrate to SDK-backed query APIs", + owner: "trade.relay-evidence-acquisition", + reason: "bounded trade commands acquire configured-relay evidence before SDK local ingest and projection", + lifecycle: "retain until trade relay acquisition moves behind SDK query APIs", }, LegacyDirectRelayConsumer { path: "src/runtime/sync.rs", @@ -933,7 +937,7 @@ mod tests { label: "order status", path: "src/runtime/order.rs", start: "pub fn status(\n config: &RuntimeConfig", - end: "fn legacy_order_preflight_relay_status(", + end: "fn sdk_order_status_from_relay_receipt(", required_tokens: &["OrderStatusRequest::parse", "session.sdk().trades().status"], }, MigratedCliPathGuard { @@ -949,7 +953,7 @@ mod tests { ], }, MigratedCliPathGuard { - label: "order submit", + label: "trade submit", path: "src/runtime/order.rs", start: "fn prepare_order_submit_via_sdk(", end: "fn enqueue_target_relays(", @@ -1167,7 +1171,7 @@ mod tests { ("radroots store", "CLI command string"), ("radroots sync", "CLI command string"), ("radroots listing", "CLI command string"), - ("radroots order", "CLI command string"), + ("radroots trade", "CLI command string"), ]; for file in files { @@ -1340,6 +1344,7 @@ mod tests { fn sample_config(root: &Path, relays: Vec<String>) -> RuntimeConfig { let data = root.join("data"); + let cache = root.join("cache"); let logs = root.join("logs"); let secrets = root.join("secrets"); RuntimeConfig { @@ -1371,6 +1376,7 @@ mod tests { app_config_path: root.join("config/apps/cli/config.toml"), workspace_config_path: None, app_data_root: data.join("apps/cli"), + shared_cache_root: cache.clone(), app_logs_root: logs.join("apps/cli"), shared_accounts_data_root: data.join("shared/accounts"), shared_accounts_secrets_root: secrets.join("shared/accounts"), diff --git a/src/runtime/signer.rs b/src/runtime/signer.rs @@ -320,31 +320,31 @@ fn cli_write_kinds() -> [CliWriteKind; 12] { event_kind: KIND_LISTING, }, CliWriteKind { - command: "order.submit", + command: "trade.submit", event_kind: KIND_ORDER_REQUEST, }, CliWriteKind { - command: "order.accept", + command: "trade.accept", event_kind: KIND_ORDER_DECISION, }, CliWriteKind { - command: "order.decline", + command: "trade.decline", event_kind: KIND_ORDER_DECISION, }, CliWriteKind { - command: "order.cancel", + command: "trade.cancel", event_kind: KIND_ORDER_CANCELLATION, }, CliWriteKind { - command: "order.revision.propose", + command: "trade.revision.propose", event_kind: KIND_ORDER_REVISION_PROPOSAL, }, CliWriteKind { - command: "order.revision.accept", + command: "trade.revision.accept", event_kind: KIND_ORDER_REVISION_DECISION, }, CliWriteKind { - command: "order.revision.decline", + command: "trade.revision.decline", event_kind: KIND_ORDER_REVISION_DECISION, }, ] @@ -623,13 +623,13 @@ mod tests { "listing.publish", "listing.update", "listing.archive", - "order.submit", - "order.accept", - "order.decline", - "order.cancel", - "order.revision.propose", - "order.revision.accept", - "order.revision.decline", + "trade.submit", + "trade.accept", + "trade.decline", + "trade.cancel", + "trade.revision.propose", + "trade.revision.accept", + "trade.revision.decline", ] ); assert!(!commands.contains(&"signer.status.get")); @@ -639,7 +639,7 @@ mod tests { fn order_submit_readiness_uses_active_order_request_kind() { let write_kind = cli_write_kinds() .into_iter() - .find(|kind| kind.command == "order.submit") + .find(|kind| kind.command == "trade.submit") .expect("order submit readiness"); assert_eq!(write_kind.event_kind, KIND_ORDER_REQUEST); @@ -648,7 +648,7 @@ mod tests { #[test] fn order_decision_readiness_uses_active_order_decision_kind() { - for command in ["order.accept", "order.decline"] { + for command in ["trade.accept", "trade.decline"] { let write_kind = cli_write_kinds() .into_iter() .find(|kind| kind.command == command) @@ -663,13 +663,13 @@ mod tests { fn order_revision_readiness_uses_active_revision_kinds() { let proposal = cli_write_kinds() .into_iter() - .find(|kind| kind.command == "order.revision.propose") + .find(|kind| kind.command == "trade.revision.propose") .expect("order revision propose readiness"); assert_eq!(proposal.event_kind, KIND_ORDER_REVISION_PROPOSAL); assert_ne!(proposal.event_kind, RESERVED_ORDER_KIND_3431); - for command in ["order.revision.accept", "order.revision.decline"] { + for command in ["trade.revision.accept", "trade.revision.decline"] { let write_kind = cli_write_kinds() .into_iter() .find(|kind| kind.command == command) @@ -684,7 +684,7 @@ mod tests { fn order_cancel_readiness_uses_order_cancellation_kind() { let cancel = cli_write_kinds() .into_iter() - .find(|kind| kind.command == "order.cancel") + .find(|kind| kind.command == "trade.cancel") .expect("order cancel readiness"); assert_eq!(cancel.event_kind, KIND_ORDER_CANCELLATION); assert_ne!(cancel.event_kind, RESERVED_ORDER_KIND_3431); @@ -709,7 +709,7 @@ mod tests { for (command, event_kind) in [ ("farm.publish", KIND_FARM), ("listing.publish", KIND_LISTING), - ("order.submit", KIND_ORDER_REQUEST), + ("trade.submit", KIND_ORDER_REQUEST), ] { let entry = readiness .iter() diff --git a/src/runtime/sync.rs b/src/runtime/sync.rs @@ -2216,6 +2216,7 @@ mod tests { fn sample_config(root: &Path, relays: Vec<String>) -> RuntimeConfig { let data = root.join("data"); + let cache = root.join("cache"); let logs = root.join("logs"); let secrets = root.join("secrets"); RuntimeConfig { @@ -2247,6 +2248,7 @@ mod tests { app_config_path: root.join("config/apps/cli/config.toml"), workspace_config_path: None, app_data_root: data.join("apps/cli"), + shared_cache_root: cache.clone(), app_logs_root: logs.join("apps/cli"), shared_accounts_data_root: data.join("shared/accounts"), shared_accounts_secrets_root: secrets.join("shared/accounts"), diff --git a/src/view/runtime.rs b/src/view/runtime.rs @@ -248,6 +248,7 @@ pub struct PathsRuntimeView { pub workspace_config_enabled: bool, pub workspace_config_path: Option<String>, pub app_data_root: String, + pub shared_cache_root: String, pub app_logs_root: String, pub shared_accounts_data_root: String, pub shared_accounts_secrets_root: String, @@ -811,6 +812,60 @@ impl FarmSetView { } #[derive(Debug, Clone, Serialize)] +pub struct FarmPrivateLocationView { + pub state: String, + pub source: String, + pub farm_addr: Option<String>, + pub farm_d_tag: Option<String>, + pub seller_account_id: Option<String>, + pub seller_pubkey: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub exact_location: Option<FarmPrivateExactLocationView>, + #[serde(skip_serializing_if = "Option::is_none")] + pub public_locality: Option<FarmPrivatePublicLocalityView>, + #[serde(skip_serializing_if = "Option::is_none")] + pub geonames_feature_id: Option<i64>, + #[serde(skip_serializing_if = "Option::is_none")] + pub geonames_country_id: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub geonames_database_path: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub cleared: Option<bool>, + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option<String>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub actions: Vec<String>, +} + +#[derive(Debug, Clone, Serialize)] +pub struct FarmPrivateExactLocationView { + pub lat: f64, + pub lng: f64, +} + +#[derive(Debug, Clone, Serialize)] +pub struct FarmPrivatePublicLocalityView { + pub primary: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub city: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub region: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub country: Option<String>, + pub geohash5: String, +} + +impl FarmPrivateLocationView { + pub fn disposition(&self) -> CommandDisposition { + match self.state.as_str() { + "unconfigured" => CommandDisposition::Unconfigured, + "missing" => CommandDisposition::NotFound, + _ => CommandDisposition::Success, + } + } +} + +#[derive(Debug, Clone, Serialize)] pub struct FarmRebindView { pub state: String, pub source: String, @@ -1114,7 +1169,7 @@ pub struct FindHyfView { pub struct MarketReadinessView { pub protocol_valid: bool, pub marketplace_eligible: bool, - pub order_request_enabled: bool, + pub checkout_enabled: bool, pub primary_bin_verified: bool, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub reason_codes: Vec<String>, @@ -1125,7 +1180,7 @@ impl MarketReadinessView { Self { protocol_valid: false, marketplace_eligible: false, - order_request_enabled: false, + checkout_enabled: false, primary_bin_verified: false, reason_codes: vec![reason_code.into()], } @@ -1169,7 +1224,7 @@ impl MarketReadinessView { && !price_currency.trim().is_empty() && price_per_amount.is_finite() && price_per_amount > 0.0; - let order_request_enabled = + let checkout_enabled = marketplace_eligible && inventory_available && primary_bin_verified && price_available; let mut reason_codes = Vec::new(); if !protocol_valid { @@ -1178,8 +1233,8 @@ impl MarketReadinessView { if protocol_valid && !marketplace_eligible { reason_codes.push("listing_marketplace_ineligible".to_owned()); } - if marketplace_eligible && !order_request_enabled { - reason_codes.push("listing_order_request_disabled".to_owned()); + if marketplace_eligible && !checkout_enabled { + reason_codes.push("listing_checkout_disabled".to_owned()); if !inventory_available { reason_codes.push("listing_inventory_unavailable".to_owned()); } @@ -1195,7 +1250,7 @@ impl MarketReadinessView { Self { protocol_valid, marketplace_eligible, - order_request_enabled, + checkout_enabled, primary_bin_verified, reason_codes, } @@ -1223,7 +1278,7 @@ mod market_readiness_tests { ); assert!(enabled.protocol_valid); assert!(enabled.marketplace_eligible); - assert!(enabled.order_request_enabled); + assert!(enabled.checkout_enabled); assert!(enabled.primary_bin_verified); assert!(enabled.reason_codes.is_empty()); @@ -1240,7 +1295,7 @@ mod market_readiness_tests { ); assert!(!invalid.protocol_valid); assert!(!invalid.marketplace_eligible); - assert!(!invalid.order_request_enabled); + assert!(!invalid.checkout_enabled); assert!(!invalid.primary_bin_verified); assert_eq!(invalid.reason_codes, vec!["listing_protocol_invalid"]); @@ -1257,7 +1312,7 @@ mod market_readiness_tests { ); assert!(ineligible.protocol_valid); assert!(!ineligible.marketplace_eligible); - assert!(!ineligible.order_request_enabled); + assert!(!ineligible.checkout_enabled); assert!(ineligible.primary_bin_verified); assert_eq!( ineligible.reason_codes, @@ -1277,14 +1332,11 @@ mod market_readiness_tests { ); assert!(order_request_disabled.protocol_valid); assert!(order_request_disabled.marketplace_eligible); - assert!(!order_request_disabled.order_request_enabled); + assert!(!order_request_disabled.checkout_enabled); assert!(order_request_disabled.primary_bin_verified); assert_eq!( order_request_disabled.reason_codes, - vec![ - "listing_order_request_disabled", - "listing_inventory_unavailable" - ] + vec!["listing_checkout_disabled", "listing_inventory_unavailable"] ); let primary_bin_missing = MarketReadinessView::from_market_projection( @@ -1300,14 +1352,11 @@ mod market_readiness_tests { ); assert!(primary_bin_missing.protocol_valid); assert!(primary_bin_missing.marketplace_eligible); - assert!(!primary_bin_missing.order_request_enabled); + assert!(!primary_bin_missing.checkout_enabled); assert!(!primary_bin_missing.primary_bin_verified); assert_eq!( primary_bin_missing.reason_codes, - vec![ - "listing_order_request_disabled", - "listing_primary_bin_missing" - ] + vec!["listing_checkout_disabled", "listing_primary_bin_missing"] ); let primary_bin_blank = MarketReadinessView::from_market_projection( @@ -1323,10 +1372,7 @@ mod market_readiness_tests { ); assert_eq!( primary_bin_blank.reason_codes, - vec![ - "listing_order_request_disabled", - "listing_primary_bin_missing" - ] + vec!["listing_checkout_disabled", "listing_primary_bin_missing"] ); let primary_bin_invalid = MarketReadinessView::from_market_projection( @@ -1342,14 +1388,11 @@ mod market_readiness_tests { ); assert!(primary_bin_invalid.protocol_valid); assert!(primary_bin_invalid.marketplace_eligible); - assert!(!primary_bin_invalid.order_request_enabled); + assert!(!primary_bin_invalid.checkout_enabled); assert!(!primary_bin_invalid.primary_bin_verified); assert_eq!( primary_bin_invalid.reason_codes, - vec![ - "listing_order_request_disabled", - "listing_primary_bin_invalid" - ] + vec!["listing_checkout_disabled", "listing_primary_bin_invalid"] ); } } @@ -1449,6 +1492,7 @@ pub struct JobWatchFrameView { pub struct OrderNewView { pub state: String, pub source: String, + #[serde(rename = "trade_id")] pub order_id: String, pub file: String, #[serde(skip_serializing_if = "Option::is_none")] @@ -1497,6 +1541,7 @@ pub struct OrderGetView { pub source: String, pub lookup: String, #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "trade_id")] pub order_id: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub file: Option<String>, @@ -1554,6 +1599,7 @@ pub struct OrderListView { pub source: String, pub count: usize, #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(rename = "trades")] pub orders: Vec<OrderSummaryView>, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub actions: Vec<String>, @@ -1614,6 +1660,7 @@ pub struct OrderAppRecordSummaryView { #[serde(default, skip_serializing_if = "Vec::is_empty")] pub listing_relays: Vec<String>, #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "trade_id")] pub order_id: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub buyer_account_id: Option<String>, @@ -1638,6 +1685,7 @@ pub struct OrderAppRecordExportView { pub file: String, pub valid: bool, #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "trade_id")] pub order_id: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub listing_addr: Option<String>, @@ -1678,6 +1726,7 @@ impl OrderAppRecordExportView { pub struct OrderSubmitView { pub state: String, pub source: String, + #[serde(rename = "trade_id")] pub order_id: String, pub file: String, #[serde(skip_serializing_if = "Option::is_none")] @@ -1750,8 +1799,11 @@ pub struct OrderRebindView { pub lookup: String, pub file: String, pub dry_run: bool, + #[serde(rename = "from_trade_id")] pub from_order_id: String, + #[serde(rename = "to_trade_id")] pub to_order_id: String, + #[serde(rename = "trade_id_changed")] pub order_id_changed: bool, #[serde(skip_serializing_if = "Option::is_none")] pub from_buyer_account_id: Option<String>, @@ -1787,6 +1839,7 @@ impl OrderRebindView { pub struct OrderDecisionView { pub state: String, pub source: String, + #[serde(rename = "trade_id")] pub order_id: String, #[serde(skip_serializing_if = "Option::is_none")] pub listing_addr: Option<String>, @@ -1854,6 +1907,7 @@ impl OrderDecisionView { pub struct OrderCancellationView { pub state: String, pub source: String, + #[serde(rename = "trade_id")] pub order_id: String, #[serde(skip_serializing_if = "Option::is_none")] pub listing_addr: Option<String>, @@ -1922,6 +1976,7 @@ impl OrderCancellationView { pub struct OrderRevisionProposalView { pub state: String, pub source: String, + #[serde(rename = "trade_id")] pub order_id: String, #[serde(skip_serializing_if = "Option::is_none")] pub revision_id: Option<String>, @@ -1995,6 +2050,7 @@ impl OrderRevisionProposalView { pub struct OrderRevisionDecisionView { pub state: String, pub source: String, + #[serde(rename = "trade_id")] pub order_id: String, #[serde(skip_serializing_if = "Option::is_none")] pub revision_id: Option<String>, @@ -2071,6 +2127,7 @@ impl OrderRevisionDecisionView { pub struct OrderStatusView { pub state: String, pub source: String, + #[serde(rename = "trade_id")] pub order_id: String, pub actor_context_source: String, #[serde(skip_serializing_if = "Option::is_none")] @@ -2233,6 +2290,7 @@ impl OrderStatusView { pub struct OrderWorkflowView { pub state: String, pub source: String, + #[serde(rename = "trade_id")] pub order_id: String, #[serde(skip_serializing_if = "Option::is_none")] pub listing_addr: Option<String>, @@ -2269,6 +2327,7 @@ pub struct OrderEventListView { #[serde(skip_serializing_if = "Option::is_none")] pub reason: Option<String>, #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(rename = "trades")] pub orders: Vec<OrderEventListEntryView>, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub actions: Vec<String>, diff --git a/tests/signer_runtime_modes.rs b/tests/signer_runtime_modes.rs @@ -2134,7 +2134,7 @@ fn local_seller_publish_commands_attempt_configured_relay() { "create", "direct_order", ]); - let order_id = quote["result"]["quote"]["order_id"] + let order_id = quote["result"]["quote"]["trade_id"] .as_str() .expect("order id"); let (order_output, order_value) = sandbox.json_output(&[ @@ -2144,24 +2144,24 @@ fn local_seller_publish_commands_attempt_configured_relay() { relay, "--approval-token", "approve", - "order", + "trade", "submit", order_id, ]); assert!(!order_output.status.success()); - assert_eq!(order_value["operation_id"], "order.submit"); + assert_eq!(order_value["operation_id"], "trade.submit"); assert_eq!(order_value["result"], serde_json::Value::Null); assert_eq!(order_value["errors"][0]["code"], "operation_unavailable"); assert_eq!( order_value["errors"][0]["detail"]["issues"][0]["field"], - "order.listing_addr" + "trade.listing_addr" ); assert_contains( &order_value["errors"][0]["detail"]["issues"][0]["message"], "local market freshness", ); - assert_no_removed_command_reference(&order_value, &["order", "submit"]); - assert_no_daemon_runtime_reference(&order_value, &["order", "submit"]); + assert_no_removed_command_reference(&order_value, &["trade", "submit"]); + assert_no_daemon_runtime_reference(&order_value, &["trade", "submit"]); } #[test] @@ -2171,11 +2171,11 @@ fn local_order_event_list_attempts_configured_direct_relay() { let relay = "ws://127.0.0.1:9"; let (output, value) = sandbox.json_output(&[ - "--format", "json", "--relay", relay, "order", "event", "list", + "--format", "json", "--relay", relay, "trade", "event", "list", ]); assert!(!output.status.success()); - assert_direct_relay_connection_failure(&value, "order.event.list", &["order", "event", "list"]); + assert_direct_relay_connection_failure(&value, "trade.event.list", &["trade", "event", "list"]); assert_eq!(value["errors"][0]["detail"]["state"], "unavailable"); assert_eq!(value["errors"][0]["detail"]["target_relays"][0], relay); assert_eq!( @@ -2204,17 +2204,17 @@ fn local_order_event_list_attempts_configured_direct_relay() { #[test] fn local_order_failure_envelopes_are_structured_and_actionable() { let sandbox = RadrootsCliSandbox::new(); - let watch_args = ["--format", "json", "order", "event", "watch", "ord_missing"]; + let watch_args = ["--format", "json", "trade", "event", "watch", "ord_missing"]; let (watch_output, watch) = sandbox.json_output(&watch_args); assert!(!watch_output.status.success()); - assert_eq!(watch["operation_id"], "order.event.watch"); + assert_eq!(watch["operation_id"], "trade.event.watch"); assert_eq!(watch["result"], Value::Null); assert_eq!(watch["errors"][0]["code"], "not_implemented"); assert_eq!(watch["errors"][0]["detail"]["state"], "not_implemented"); - assert_eq!(watch["errors"][0]["detail"]["order_id"], "ord_missing"); + assert_eq!(watch["errors"][0]["detail"]["trade_id"], "ord_missing"); assert_eq!( watch["next_actions"][0]["command"], - "radroots order status get ord_missing" + "radroots trade status get ord_missing" ); assert_no_daemon_runtime_reference(&watch, &watch_args); @@ -2224,7 +2224,7 @@ fn local_order_failure_envelopes_are_structured_and_actionable() { "--publish-transport", "direct_nostr_relay", "--dry-run", - "order", + "trade", "submit", "ord_missing", ]; @@ -2232,33 +2232,33 @@ fn local_order_failure_envelopes_are_structured_and_actionable() { assert!(!submit_output.status.success()); assert_eq!(submit["errors"][0]["code"], "not_found"); assert_eq!(submit["errors"][0]["detail"]["state"], "missing"); - assert_eq!(submit["errors"][0]["detail"]["order_id"], "ord_missing"); - assert_eq!(submit["next_actions"][0]["command"], "radroots order list"); + assert_eq!(submit["errors"][0]["detail"]["trade_id"], "ord_missing"); + assert_eq!(submit["next_actions"][0]["command"], "radroots trade list"); assert_eq!( submit["next_actions"][1]["command"], "radroots basket create" ); assert_no_daemon_runtime_reference(&submit, &submit_args); - let status_args = ["--format", "json", "order", "status", "get", "ord_missing"]; + let status_args = ["--format", "json", "trade", "status", "get", "ord_missing"]; let status = sandbox.json_success(&status_args); - assert_eq!(status["operation_id"], "order.status.get"); + assert_eq!(status["operation_id"], "trade.status.get"); assert_eq!(status["result"]["state"], "missing"); - assert_eq!(status["result"]["source"], "SDK local order projection"); + assert_eq!(status["result"]["source"], "SDK local trade projection"); assert_eq!( status["result"]["actor_context_source"], "sdk_local_projection" ); - assert_eq!(status["result"]["order_id"], "ord_missing"); + assert_eq!(status["result"]["trade_id"], "ord_missing"); assert_eq!(status["result"]["fetched_count"], 0); assert_eq!(status["result"]["decoded_count"], 0); assert_eq!( status["result"]["reason"], - "no local SDK order events matched `ord_missing`" + "no local SDK trade events matched `ord_missing`" ); assert_no_daemon_runtime_reference(&status, &status_args); - let event_list_no_relay_args = ["--format", "json", "order", "event", "list"]; + let event_list_no_relay_args = ["--format", "json", "trade", "event", "list"]; let (event_list_no_relay_output, event_list_no_relay) = sandbox.json_output(&event_list_no_relay_args); assert!(!event_list_no_relay_output.status.success()); @@ -2272,7 +2272,7 @@ fn local_order_failure_envelopes_are_structured_and_actionable() { ); assert_eq!( event_list_no_relay["next_actions"][0]["command"], - "radroots --relay wss://relay.example.com order event list" + "radroots --relay wss://relay.example.com trade event list" ); assert_no_daemon_runtime_reference(&event_list_no_relay, &event_list_no_relay_args); @@ -2281,7 +2281,7 @@ fn local_order_failure_envelopes_are_structured_and_actionable() { "json", "--relay", "ws://127.0.0.1:9", - "order", + "trade", "event", "list", ]; @@ -2308,7 +2308,7 @@ fn local_order_failure_envelopes_are_structured_and_actionable() { "--publish-transport", "direct_nostr_relay", "--dry-run", - "order", + "trade", "accept", "ord_missing", ]; @@ -2316,7 +2316,7 @@ fn local_order_failure_envelopes_are_structured_and_actionable() { assert!(!accept_output.status.success()); assert_eq!(accept["errors"][0]["code"], "operation_unavailable"); assert_eq!(accept["errors"][0]["detail"]["state"], "unconfigured"); - assert_eq!(accept["errors"][0]["detail"]["order_id"], "ord_missing"); + assert_eq!(accept["errors"][0]["detail"]["trade_id"], "ord_missing"); assert_eq!(accept["errors"][0]["detail"]["decision"], "accepted"); assert_no_daemon_runtime_reference(&accept, &accept_args); @@ -2326,7 +2326,7 @@ fn local_order_failure_envelopes_are_structured_and_actionable() { "--publish-transport", "direct_nostr_relay", "--dry-run", - "order", + "trade", "decline", "ord_missing", "--reason", @@ -2336,7 +2336,7 @@ fn local_order_failure_envelopes_are_structured_and_actionable() { assert!(!decline_output.status.success()); assert_eq!(decline["errors"][0]["code"], "operation_unavailable"); assert_eq!(decline["errors"][0]["detail"]["state"], "unconfigured"); - assert_eq!(decline["errors"][0]["detail"]["order_id"], "ord_missing"); + assert_eq!(decline["errors"][0]["detail"]["trade_id"], "ord_missing"); assert_eq!(decline["errors"][0]["detail"]["decision"], "declined"); assert_no_daemon_runtime_reference(&decline, &decline_args); } diff --git a/tests/support/mod.rs b/tests/support/mod.rs @@ -193,11 +193,11 @@ pub fn assert_no_removed_command_reference(value: &Value, args: &[&str]) { "radroots market search", "radroots market view", "radroots market update", - "radroots order ls", - "radroots order history", - "radroots order watch", - "radroots order new", - "radroots order create", + "radroots trade ls", + "radroots trade history", + "radroots trade watch", + "radroots trade new", + "radroots trade create", "radroots farm init", "radroots farm check", "radroots relay ls", diff --git a/tests/target_cli.rs b/tests/target_cli.rs @@ -1262,7 +1262,7 @@ fn root_help_exposes_only_target_namespaces() { "listing", "market", "basket", - "order", + "trade", ] { assert!( help_lists(&stdout, namespace), @@ -2352,9 +2352,9 @@ fn listing_update_publish_attempts_direct_relay_with_approval() { #[test] fn removed_order_submit_watch_flag_is_rejected_publicly() { let output = radroots() - .args(["order", "submit", "--watch"]) + .args(["trade", "submit", "--watch"]) .output() - .expect("run removed order submit watch flag"); + .expect("run removed trade submit watch flag"); assert!(!output.status.success()); let stderr = String::from_utf8(output.stderr).expect("utf8 stderr"); @@ -2382,24 +2382,24 @@ fn removed_command_families_are_rejected_publicly() { fn seller_order_decision_and_status_commands_are_public() { for (operation_id, args) in [ ( - "order.accept", + "trade.accept", [ "--format", "json", "--dry-run", - "order", + "trade", "accept", "ord_public", ] .as_slice(), ), ( - "order.decline", + "trade.decline", [ "--format", "json", "--dry-run", - "order", + "trade", "decline", "ord_public", "--reason", @@ -2408,12 +2408,12 @@ fn seller_order_decision_and_status_commands_are_public() { .as_slice(), ), ( - "order.cancel", + "trade.cancel", [ "--format", "json", "--dry-run", - "order", + "trade", "cancel", "ord_public", "--reason", @@ -2422,8 +2422,8 @@ fn seller_order_decision_and_status_commands_are_public() { .as_slice(), ), ( - "order.status.get", - ["--format", "json", "order", "status", "get", "ord_public"].as_slice(), + "trade.status.get", + ["--format", "json", "trade", "status", "get", "ord_public"].as_slice(), ), ] { let output = radroots() @@ -2440,7 +2440,7 @@ fn seller_order_decision_and_status_commands_are_public() { } let output = radroots() - .args(["order", "decision", "accept", "ord_deferred"]) + .args(["trade", "decision", "accept", "ord_deferred"]) .output() .expect("run removed nested decision command"); @@ -2452,11 +2452,11 @@ fn seller_order_decision_and_status_commands_are_public() { #[test] fn removed_order_post_agreement_subcommands_are_rejected_publicly() { for args in [ - ["order", "fulfillment", "update", "ord_public"].as_slice(), - ["order", "receipt", "record", "ord_public"].as_slice(), - ["order", "payment", "record", "ord_public"].as_slice(), - ["order", "settlement", "accept", "ord_public"].as_slice(), - ["order", "settlement", "reject", "ord_public"].as_slice(), + ["trade", "fulfillment", "update", "ord_public"].as_slice(), + ["trade", "receipt", "record", "ord_public"].as_slice(), + ["trade", "payment", "record", "ord_public"].as_slice(), + ["trade", "settlement", "accept", "ord_public"].as_slice(), + ["trade", "settlement", "reject", "ord_public"].as_slice(), ] { let output = radroots() .args(args) @@ -2481,7 +2481,7 @@ fn target_outputs_do_not_suggest_removed_command_families() { [ "--format", "json", - "order", + "trade", "get", "ord_AAAAAAAAAAAAAAAAAAAAAA", ] @@ -2840,15 +2840,15 @@ fn human_market_refresh_missing_store_shows_action() { #[test] fn human_failure_output_preserves_error_code_and_message() { let output = radroots() - .args(["--format", "human", "order", "submit"]) + .args(["--format", "human", "trade", "submit"]) .output() - .expect("run order submit"); + .expect("run trade submit"); assert_eq!(output.status.code(), Some(6)); let stdout = String::from_utf8(output.stdout).expect("utf8 stdout"); - assert!(stdout.starts_with("order.submit: error\n")); - assert!(stdout.contains("request_id: req_order_submit_")); + assert!(stdout.starts_with("trade.submit: error\n")); + assert!(stdout.contains("request_id: req_trade_submit_")); assert!(stdout.contains("error: approval_required")); assert!(stdout.contains("message: missing required `approval_token` input")); assert!(serde_json::from_str::<Value>(&stdout).is_err()); @@ -2860,7 +2860,7 @@ fn human_failure_output_renders_structured_error_detail() { .args([ "--format", "human", - "order", + "trade", "event", "watch", "ord_missing", @@ -2871,12 +2871,12 @@ fn human_failure_output_renders_structured_error_detail() { assert_eq!(output.status.code(), Some(3)); let stdout = String::from_utf8(output.stdout).expect("utf8 stdout"); - assert!(stdout.starts_with("order.event.watch: error\n")); - assert!(stdout.contains("request_id: req_order_event_watch_")); + assert!(stdout.starts_with("trade.event.watch: error\n")); + assert!(stdout.contains("request_id: req_trade_event_watch_")); assert!(stdout.contains("error: not_implemented")); assert!(stdout.contains("state: not_implemented")); - assert!(stdout.contains("reason: relay-backed order event watch is not implemented")); - assert!(stdout.contains("- radroots order status get ord_missing")); + assert!(stdout.contains("reason: relay-backed trade event watch is not implemented")); + assert!(stdout.contains("- radroots trade status get ord_missing")); assert!(serde_json::from_str::<Value>(&stdout).is_err()); } @@ -2973,7 +2973,7 @@ fn unsupported_ndjson_returns_structured_invalid_input() { .args([ "--format", "ndjson", - "order", + "trade", "event", "watch", "ord_missing", @@ -2984,9 +2984,9 @@ fn unsupported_ndjson_returns_structured_invalid_input() { assert_eq!(watch_output.status.code(), Some(2)); let watch_frames = ndjson_from_stdout(&watch_output); assert_eq!(watch_frames.len(), 2); - assert_eq!(watch_frames[0]["operation_id"], "order.event.watch"); + assert_eq!(watch_frames[0]["operation_id"], "trade.event.watch"); assert_eq!(watch_frames[0]["frame_type"], "started"); - assert_eq!(watch_frames[1]["operation_id"], "order.event.watch"); + assert_eq!(watch_frames[1]["operation_id"], "trade.event.watch"); assert_eq!(watch_frames[1]["frame_type"], "error"); assert_eq!(watch_frames[1]["errors"][0]["code"], "invalid_input"); assert_eq!(watch_frames[1]["errors"][0]["exit_code"], 2); @@ -3048,16 +3048,16 @@ fn offline_forbids_external_network_operations() { ["--format", "json", "--offline", "market", "refresh"].as_slice(), ), ( - "order.submit", - ["--format", "json", "--offline", "order", "submit"].as_slice(), + "trade.submit", + ["--format", "json", "--offline", "trade", "submit"].as_slice(), ), ( - "order.cancel", + "trade.cancel", [ "--format", "json", "--offline", - "order", + "trade", "cancel", "ord_offline_cancel", "--reason", @@ -3066,14 +3066,14 @@ fn offline_forbids_external_network_operations() { .as_slice(), ), ( - "order.revision.propose", + "trade.revision.propose", [ "--format", "json", "--offline", "--approval-token", "approve", - "order", + "trade", "revision", "propose", "ord_offline_revision", @@ -3087,14 +3087,14 @@ fn offline_forbids_external_network_operations() { .as_slice(), ), ( - "order.revision.accept", + "trade.revision.accept", [ "--format", "json", "--offline", "--approval-token", "approve", - "order", + "trade", "revision", "accept", "ord_offline_revision", @@ -3104,14 +3104,14 @@ fn offline_forbids_external_network_operations() { .as_slice(), ), ( - "order.revision.decline", + "trade.revision.decline", [ "--format", "json", "--offline", "--approval-token", "approve", - "order", + "trade", "revision", "decline", "ord_offline_revision", @@ -3328,26 +3328,26 @@ fn listing_publish_idempotency_conflict_maps_sdk_partial_mutation_recovery() { fn offline_rejects_order_decision_dry_run() { for (operation_id, args) in [ ( - "order.accept", + "trade.accept", [ "--format", "json", "--offline", "--dry-run", - "order", + "trade", "accept", "ord_offline_decision", ] .as_slice(), ), ( - "order.decline", + "trade.decline", [ "--format", "json", "--offline", "--dry-run", - "order", + "trade", "decline", "ord_offline_decision", "--reason", @@ -3356,13 +3356,13 @@ fn offline_rejects_order_decision_dry_run() { .as_slice(), ), ( - "order.cancel", + "trade.cancel", [ "--format", "json", "--offline", "--dry-run", - "order", + "trade", "cancel", "ord_offline_decision", "--reason", @@ -3371,13 +3371,13 @@ fn offline_rejects_order_decision_dry_run() { .as_slice(), ), ( - "order.revision.propose", + "trade.revision.propose", [ "--format", "json", "--offline", "--dry-run", - "order", + "trade", "revision", "propose", "ord_offline_revision", @@ -3391,13 +3391,13 @@ fn offline_rejects_order_decision_dry_run() { .as_slice(), ), ( - "order.revision.accept", + "trade.revision.accept", [ "--format", "json", "--offline", "--dry-run", - "order", + "trade", "revision", "accept", "ord_offline_revision", @@ -3407,13 +3407,13 @@ fn offline_rejects_order_decision_dry_run() { .as_slice(), ), ( - "order.revision.decline", + "trade.revision.decline", [ "--format", "json", "--offline", "--dry-run", - "order", + "trade", "revision", "decline", "ord_offline_revision", @@ -3502,16 +3502,16 @@ fn online_requires_relay_for_external_network_operations() { ["--format", "json", "--online", "market", "refresh"].as_slice(), ), ( - "order.event.list", - ["--format", "json", "--online", "order", "event", "list"].as_slice(), + "trade.event.list", + ["--format", "json", "--online", "trade", "event", "list"].as_slice(), ), ( - "order.cancel", + "trade.cancel", [ "--format", "json", "--online", - "order", + "trade", "cancel", "ord_missing", "--reason", @@ -3520,14 +3520,14 @@ fn online_requires_relay_for_external_network_operations() { .as_slice(), ), ( - "order.revision.propose", + "trade.revision.propose", [ "--format", "json", "--online", "--approval-token", "approve", - "order", + "trade", "revision", "propose", "ord_missing", @@ -3541,13 +3541,13 @@ fn online_requires_relay_for_external_network_operations() { .as_slice(), ), ( - "order.revision.accept", + "trade.revision.accept", [ "--format", "json", "--online", "--dry-run", - "order", + "trade", "revision", "accept", "ord_missing", @@ -3557,13 +3557,13 @@ fn online_requires_relay_for_external_network_operations() { .as_slice(), ), ( - "order.revision.decline", + "trade.revision.decline", [ "--format", "json", "--online", "--dry-run", - "order", + "trade", "revision", "decline", "ord_missing", @@ -3603,15 +3603,15 @@ fn order_status_get_uses_sdk_local_projection_without_relay_fetch() { "--format", "json", "--online", - "order", + "trade", "status", "get", "ord_missing", ]); - assert_eq!(local["operation_id"], "order.status.get"); + assert_eq!(local["operation_id"], "trade.status.get"); assert_eq!(local["result"]["state"], "missing"); - assert_eq!(local["result"]["source"], "SDK local order projection"); + assert_eq!(local["result"]["source"], "SDK local trade projection"); assert_eq!( local["result"]["actor_context_source"], "sdk_local_projection" @@ -3627,17 +3627,17 @@ fn order_status_get_uses_sdk_local_projection_without_relay_fetch() { "json", "--relay", closed_relay.as_str(), - "order", + "trade", "status", "get", "ord_missing", ]); - assert_eq!(with_closed_relay["operation_id"], "order.status.get"); + assert_eq!(with_closed_relay["operation_id"], "trade.status.get"); assert_eq!(with_closed_relay["result"]["state"], "missing"); assert_eq!( with_closed_relay["result"]["source"], - "SDK local order projection" + "SDK local trade projection" ); assert_eq!(with_closed_relay["result"]["fetched_count"], 0); assert_eq!(with_closed_relay["result"]["decoded_count"], 0); @@ -3647,10 +3647,10 @@ fn order_status_get_uses_sdk_local_projection_without_relay_fetch() { fn order_status_get_invalid_order_id_uses_sdk_error_contract() { let sandbox = RadrootsCliSandbox::new(); let (output, value) = - sandbox.json_output(&["--format", "json", "order", "status", "get", "bad order id"]); + sandbox.json_output(&["--format", "json", "trade", "status", "get", "bad order id"]); assert!(!output.status.success()); - assert_eq!(value["operation_id"], "order.status.get"); + assert_eq!(value["operation_id"], "trade.status.get"); assert_eq!(value["result"], Value::Null); assert_eq!(value["errors"][0]["code"], "invalid_order_id"); assert_eq!(value["errors"][0]["exit_code"], 2); @@ -3684,7 +3684,7 @@ fn online_order_event_watch_returns_deferred_without_relay_preflight() { "--format", "json", "--online", - "order", + "trade", "event", "watch", "ord_missing", @@ -3692,14 +3692,14 @@ fn online_order_event_watch_returns_deferred_without_relay_preflight() { assert!(!output.status.success()); assert_eq!(output.status.code(), Some(3)); - assert_eq!(value["operation_id"], "order.event.watch"); + assert_eq!(value["operation_id"], "trade.event.watch"); assert_eq!(value["result"], Value::Null); assert_eq!(value["errors"][0]["code"], "not_implemented"); assert_eq!(value["errors"][0]["detail"]["state"], "not_implemented"); - assert_eq!(value["errors"][0]["detail"]["order_id"], "ord_missing"); + assert_eq!(value["errors"][0]["detail"]["trade_id"], "ord_missing"); assert_eq!( value["next_actions"][0]["command"], - "radroots order status get ord_missing" + "radroots trade status get ord_missing" ); assert!( !value["errors"][0]["message"] @@ -3707,7 +3707,7 @@ fn online_order_event_watch_returns_deferred_without_relay_preflight() { .expect("message") .contains("configured relay") ); - assert_no_daemon_runtime_reference(&value, &["order", "event", "watch"]); + assert_no_daemon_runtime_reference(&value, &["trade", "event", "watch"]); } #[test] @@ -4984,43 +4984,43 @@ fn order_app_records_list_export_get_and_submit_supported_app_order() { listing_event_id.as_str(), ); - let app_list = sandbox.json_success(&["--format", "json", "order", "app", "list"]); - assert_eq!(app_list["operation_id"], "order.app.list"); + let app_list = sandbox.json_success(&["--format", "json", "trade", "app", "list"]); + assert_eq!(app_list["operation_id"], "trade.app.list"); assert_eq!(app_list["result"]["state"], "ready"); assert_eq!(app_list["result"]["count"], 1); assert_eq!(app_list["result"]["records"][0]["record_id"], record_id); - assert_eq!(app_list["result"]["records"][0]["order_id"], order_id); + assert_eq!(app_list["result"]["records"][0]["trade_id"], order_id); assert_eq!(app_list["result"]["records"][0]["ready_for_submit"], true); assert_eq!(app_list["result"]["records"][0]["exportable"], true); - assert_no_removed_command_reference(&app_list, &["order", "app", "list"]); - assert_no_daemon_runtime_reference(&app_list, &["order", "app", "list"]); + assert_no_removed_command_reference(&app_list, &["trade", "app", "list"]); + assert_no_daemon_runtime_reference(&app_list, &["trade", "app", "list"]); - let orders = sandbox.json_success(&["--format", "json", "order", "list"]); - assert_eq!(orders["operation_id"], "order.list"); + let orders = sandbox.json_success(&["--format", "json", "trade", "list"]); + assert_eq!(orders["operation_id"], "trade.list"); assert_eq!(orders["result"]["state"], "ready"); assert_eq!(orders["result"]["count"], 1); - assert_eq!(orders["result"]["orders"][0]["id"], order_id); - assert_eq!(orders["result"]["orders"][0]["ready_for_submit"], true); + assert_eq!(orders["result"]["trades"][0]["id"], order_id); + assert_eq!(orders["result"]["trades"][0]["ready_for_submit"], true); assert_eq!( - orders["result"]["orders"][0]["listing_event_id"], + orders["result"]["trades"][0]["listing_event_id"], listing_event_id ); assert_eq!( - orders["result"]["orders"][0]["buyer_account_id"], + orders["result"]["trades"][0]["buyer_account_id"], account_id ); assert_eq!( - orders["result"]["orders"][0]["file"], + orders["result"]["trades"][0]["file"], format!("shared-local-events/{record_id}") ); - assert_no_removed_command_reference(&orders, &["order", "list"]); - assert_no_daemon_runtime_reference(&orders, &["order", "list"]); + assert_no_removed_command_reference(&orders, &["trade", "list"]); + assert_no_daemon_runtime_reference(&orders, &["trade", "list"]); let get_by_record = - sandbox.json_success(&["--format", "json", "order", "get", record_id.as_str()]); - assert_eq!(get_by_record["operation_id"], "order.get"); + sandbox.json_success(&["--format", "json", "trade", "get", record_id.as_str()]); + assert_eq!(get_by_record["operation_id"], "trade.get"); assert_eq!(get_by_record["result"]["state"], "ready"); - assert_eq!(get_by_record["result"]["order_id"], order_id); + assert_eq!(get_by_record["result"]["trade_id"], order_id); assert_eq!(get_by_record["result"]["ready_for_submit"], true); let export_path = sandbox.root().join("app-order.toml"); @@ -5029,14 +5029,14 @@ fn order_app_records_list_export_get_and_submit_supported_app_order() { "--format", "json", "--dry-run", - "order", + "trade", "app", "export", record_id.as_str(), "--output", export_path_arg.as_ref(), ]); - assert_eq!(dry_run["operation_id"], "order.app.export"); + assert_eq!(dry_run["operation_id"], "trade.app.export"); assert_eq!(dry_run["result"]["state"], "dry_run"); assert_eq!(dry_run["result"]["valid"], true); assert!(!export_path.exists()); @@ -5044,18 +5044,18 @@ fn order_app_records_list_export_get_and_submit_supported_app_order() { let export = sandbox.json_success(&[ "--format", "json", - "order", + "trade", "app", "export", record_id.as_str(), "--output", export_path_arg.as_ref(), ]); - assert_eq!(export["operation_id"], "order.app.export"); + assert_eq!(export["operation_id"], "trade.app.export"); assert_eq!(export["result"]["state"], "exported"); - assert_eq!(export["result"]["order_id"], order_id); + assert_eq!(export["result"]["trade_id"], order_id); assert!(export_path.exists()); - let exported_contents = fs::read_to_string(&export_path).expect("exported order draft"); + let exported_contents = fs::read_to_string(&export_path).expect("exported trade draft"); assert!(exported_contents.contains("kind = \"order_draft_v1\"")); assert!(exported_contents.contains(format!("order_id = \"{order_id}\"").as_str())); assert!(exported_contents.contains("source = \"resolved_account\"")); @@ -5064,13 +5064,13 @@ fn order_app_records_list_export_get_and_submit_supported_app_order() { "--format", "json", "--dry-run", - "order", + "trade", "submit", record_id.as_str(), ]); - assert_eq!(submit["operation_id"], "order.submit"); + assert_eq!(submit["operation_id"], "trade.submit"); assert_eq!(submit["result"]["state"], "dry_run"); - assert_eq!(submit["result"]["source"], "SDK order submit · local key"); + assert_eq!(submit["result"]["source"], "SDK trade submit · local key"); assert_eq!(submit["result"]["event_kind"], 3422); assert_eq!( submit["result"]["target_relays"][0], @@ -5148,7 +5148,7 @@ fn order_app_records_treat_matching_signed_evidence_as_submitted() { &signed_event, ); - let app_list = sandbox.json_success(&["--format", "json", "order", "app", "list"]); + let app_list = sandbox.json_success(&["--format", "json", "trade", "app", "list"]); let listed = &app_list["result"]["records"][0]; assert_eq!(listed["record_id"], record_id); assert_eq!(listed["status"], "submitted"); @@ -5156,16 +5156,16 @@ fn order_app_records_treat_matching_signed_evidence_as_submitted() { assert_eq!(listed["exportable"], false); assert_eq!( listed["actions"].as_array().expect("actions"), - &vec![json!(format!("radroots order status get {order_id}"))] + &vec![json!(format!("radroots trade status get {order_id}"))] ); - let orders = sandbox.json_success(&["--format", "json", "order", "list"]); + let orders = sandbox.json_success(&["--format", "json", "trade", "list"]); assert_eq!(orders["result"]["state"], "ready"); - assert_eq!(orders["result"]["orders"][0]["state"], "submitted"); - assert_eq!(orders["result"]["orders"][0]["ready_for_submit"], false); + assert_eq!(orders["result"]["trades"][0]["state"], "submitted"); + assert_eq!(orders["result"]["trades"][0]["ready_for_submit"], false); let get_by_record = - sandbox.json_success(&["--format", "json", "order", "get", record_id.as_str()]); + sandbox.json_success(&["--format", "json", "trade", "get", record_id.as_str()]); assert_eq!(get_by_record["result"]["state"], "submitted"); assert_eq!(get_by_record["result"]["ready_for_submit"], false); assert_eq!( @@ -5180,7 +5180,7 @@ fn order_app_records_treat_matching_signed_evidence_as_submitted() { get_by_record["result"]["actions"] .as_array() .expect("actions"), - &vec![json!(format!("radroots order status get {order_id}"))] + &vec![json!(format!("radroots trade status get {order_id}"))] ); let export_path = sandbox.root().join("submitted-app-order.toml"); @@ -5188,7 +5188,7 @@ fn order_app_records_treat_matching_signed_evidence_as_submitted() { let (export_output, export) = sandbox.json_output(&[ "--format", "json", - "order", + "trade", "app", "export", record_id.as_str(), @@ -5196,7 +5196,7 @@ fn order_app_records_treat_matching_signed_evidence_as_submitted() { export_path_arg.as_ref(), ]); assert!(!export_output.status.success()); - assert_eq!(export["operation_id"], "order.app.export"); + assert_eq!(export["operation_id"], "trade.app.export"); assert_eq!(export["errors"][0]["detail"]["state"], "already_submitted"); assert_eq!(export["errors"][0]["detail"]["valid"], false); assert!(!export_path.exists()); @@ -5205,11 +5205,11 @@ fn order_app_records_treat_matching_signed_evidence_as_submitted() { "--format", "json", "--dry-run", - "order", + "trade", "submit", record_id.as_str(), ]); - assert_eq!(submit["operation_id"], "order.submit"); + assert_eq!(submit["operation_id"], "trade.submit"); assert_eq!(submit["result"]["state"], "submitted"); assert_eq!(submit["result"]["deduplicated"], true); assert_eq!(submit["result"]["event_id"], signed_event_id); @@ -5282,7 +5282,7 @@ fn order_app_records_fail_closed_when_signed_evidence_conflicts() { &signed_event, ); - let app_list = sandbox.json_success(&["--format", "json", "order", "app", "list"]); + let app_list = sandbox.json_success(&["--format", "json", "trade", "app", "list"]); let listed = &app_list["result"]["records"][0]; assert_eq!(listed["record_id"], record_id); assert_eq!(listed["status"], "conflict"); @@ -5310,7 +5310,7 @@ fn order_app_records_fail_closed_when_signed_evidence_conflicts() { let (export_output, export) = sandbox.json_output(&[ "--format", "json", - "order", + "trade", "app", "export", record_id.as_str(), @@ -5318,7 +5318,7 @@ fn order_app_records_fail_closed_when_signed_evidence_conflicts() { export_path_arg.as_ref(), ]); assert!(!export_output.status.success()); - assert_eq!(export["operation_id"], "order.app.export"); + assert_eq!(export["operation_id"], "trade.app.export"); assert_eq!(export["errors"][0]["detail"]["state"], "conflict"); assert_eq!( export["errors"][0]["detail"]["issues"][0]["code"], @@ -5330,12 +5330,12 @@ fn order_app_records_fail_closed_when_signed_evidence_conflicts() { "--format", "json", "--dry-run", - "order", + "trade", "submit", record_id.as_str(), ]); assert!(!submit_output.status.success()); - assert_eq!(submit["operation_id"], "order.submit"); + assert_eq!(submit["operation_id"], "trade.submit"); assert_eq!(submit["errors"][0]["detail"]["state"], "invalid"); assert_eq!( submit["errors"][0]["detail"]["issues"][0]["code"], @@ -5371,7 +5371,7 @@ fn order_app_records_fail_closed_when_not_current_or_supported() { Vec::new(), ); - let app_list = sandbox.json_success(&["--format", "json", "order", "app", "list"]); + let app_list = sandbox.json_success(&["--format", "json", "trade", "app", "list"]); assert_eq!( app_list["result"]["records"][0]["record_id"], stale_record_id @@ -5390,7 +5390,7 @@ fn order_app_records_fail_closed_when_not_current_or_supported() { let (output, stale_export) = sandbox.json_output(&[ "--format", "json", - "order", + "trade", "app", "export", stale_record_id.as_str(), @@ -5398,7 +5398,7 @@ fn order_app_records_fail_closed_when_not_current_or_supported() { export_path_arg.as_ref(), ]); assert!(!output.status.success()); - assert_eq!(stale_export["operation_id"], "order.app.export"); + assert_eq!(stale_export["operation_id"], "trade.app.export"); assert_eq!(stale_export["result"], Value::Null); assert_eq!(stale_export["errors"][0]["detail"]["state"], "stale"); assert_eq!(stale_export["errors"][0]["detail"]["valid"], false); @@ -5408,13 +5408,13 @@ fn order_app_records_fail_closed_when_not_current_or_supported() { "--format", "json", "--dry-run", - "order", + "trade", "submit", stale_record_id.as_str(), ]); assert!(!submit_output.status.success()); assert_eq!(submit_output.status.code(), Some(3)); - assert_eq!(submit["operation_id"], "order.submit"); + assert_eq!(submit["operation_id"], "trade.submit"); assert_eq!(submit["errors"][0]["code"], "operation_unavailable"); assert_eq!( submit["errors"][0]["detail"]["issues"][0]["code"], @@ -5450,7 +5450,7 @@ fn order_app_records_fail_closed_when_unsupported() { vec!["seller_pubkey_required"], ); - let app_list = sandbox.json_success(&["--format", "json", "order", "app", "list"]); + let app_list = sandbox.json_success(&["--format", "json", "trade", "app", "list"]); assert_eq!(app_list["result"]["records"][0]["record_id"], record_id); assert_eq!(app_list["result"]["records"][0]["ready_for_submit"], false); assert_eq!(app_list["result"]["records"][0]["exportable"], false); @@ -5466,7 +5466,7 @@ fn order_app_records_fail_closed_when_unsupported() { let (export_output, export) = sandbox.json_output(&[ "--format", "json", - "order", + "trade", "app", "export", record_id.as_str(), @@ -5474,7 +5474,7 @@ fn order_app_records_fail_closed_when_unsupported() { export_path_arg.as_ref(), ]); assert!(!export_output.status.success()); - assert_eq!(export["operation_id"], "order.app.export"); + assert_eq!(export["operation_id"], "trade.app.export"); assert_eq!(export["errors"][0]["detail"]["state"], "unsupported"); assert_eq!( export["errors"][0]["detail"]["issues"][0]["code"], @@ -5483,7 +5483,7 @@ fn order_app_records_fail_closed_when_unsupported() { assert!(!export_path.exists()); let (submit_output, submit) = - sandbox.json_output(&["--format", "json", "--dry-run", "order", "submit", order_id]); + sandbox.json_output(&["--format", "json", "--dry-run", "trade", "submit", order_id]); assert!(!submit_output.status.success()); assert_eq!( submit["errors"][0]["detail"]["issues"][0]["code"], @@ -5519,7 +5519,7 @@ fn order_app_records_fail_closed_when_supported_record_is_malformed() { vec!["unit_price_required"], ); - let app_list = sandbox.json_success(&["--format", "json", "order", "app", "list"]); + let app_list = sandbox.json_success(&["--format", "json", "trade", "app", "list"]); assert_eq!(app_list["result"]["records"][0]["record_id"], record_id); assert_eq!(app_list["result"]["records"][0]["ready_for_submit"], false); assert_eq!(app_list["result"]["records"][0]["exportable"], false); @@ -5535,7 +5535,7 @@ fn order_app_records_fail_closed_when_supported_record_is_malformed() { let (export_output, export) = sandbox.json_output(&[ "--format", "json", - "order", + "trade", "app", "export", record_id.as_str(), @@ -5543,7 +5543,7 @@ fn order_app_records_fail_closed_when_supported_record_is_malformed() { export_path_arg.as_ref(), ]); assert!(!export_output.status.success()); - assert_eq!(export["operation_id"], "order.app.export"); + assert_eq!(export["operation_id"], "trade.app.export"); assert_eq!(export["errors"][0]["detail"]["state"], "invalid"); assert_eq!( export["errors"][0]["detail"]["issues"][0]["code"], @@ -5552,9 +5552,9 @@ fn order_app_records_fail_closed_when_supported_record_is_malformed() { assert!(!export_path.exists()); let (submit_output, submit) = - sandbox.json_output(&["--format", "json", "--dry-run", "order", "submit", order_id]); + sandbox.json_output(&["--format", "json", "--dry-run", "trade", "submit", order_id]); assert!(!submit_output.status.success()); - assert_eq!(submit["operation_id"], "order.submit"); + assert_eq!(submit["operation_id"], "trade.submit"); assert_eq!( submit["errors"][0]["detail"]["issues"][0]["code"], "invalid_app_order_record" @@ -5600,7 +5600,7 @@ fn order_app_records_fail_closed_when_order_id_conflicts() { Vec::new(), ); - let app_list = sandbox.json_success(&["--format", "json", "order", "app", "list"]); + let app_list = sandbox.json_success(&["--format", "json", "trade", "app", "list"]); assert_eq!(app_list["result"]["count"], 1); assert_eq!( app_list["result"]["records"][0]["record_id"], @@ -5620,7 +5620,7 @@ fn order_app_records_fail_closed_when_order_id_conflicts() { let (export_output, export) = sandbox.json_output(&[ "--format", "json", - "order", + "trade", "app", "export", conflicting_record_id.as_str(), @@ -5628,7 +5628,7 @@ fn order_app_records_fail_closed_when_order_id_conflicts() { export_path_arg.as_ref(), ]); assert!(!export_output.status.success()); - assert_eq!(export["operation_id"], "order.app.export"); + assert_eq!(export["operation_id"], "trade.app.export"); assert_eq!(export["errors"][0]["detail"]["state"], "conflict"); assert_eq!( export["errors"][0]["detail"]["issues"][0]["code"], @@ -5637,7 +5637,7 @@ fn order_app_records_fail_closed_when_order_id_conflicts() { assert!(!export_path.exists()); let (submit_output, submit) = - sandbox.json_output(&["--format", "json", "--dry-run", "order", "submit", order_id]); + sandbox.json_output(&["--format", "json", "--dry-run", "trade", "submit", order_id]); assert!(!submit_output.status.success()); assert_eq!( submit["errors"][0]["detail"]["issues"][0]["code"], @@ -6099,7 +6099,7 @@ fn market_order_request_readiness_gates_buyer_intent_actions() { let result = &search["result"]["results"][0]; assert_eq!(result["protocol_valid"], true); assert_eq!(result["marketplace_eligible"], true); - assert_eq!(result["order_request_enabled"], true); + assert_eq!(result["checkout_enabled"], true); assert_eq!(result["primary_bin_verified"], true); assert!(result.get("reason_codes").is_none()); assert!( @@ -6121,7 +6121,7 @@ fn market_order_request_readiness_gates_buyer_intent_actions() { assert_eq!(listing["operation_id"], "market.listing.get"); assert_eq!(listing["result"]["protocol_valid"], true); assert_eq!(listing["result"]["marketplace_eligible"], true); - assert_eq!(listing["result"]["order_request_enabled"], true); + assert_eq!(listing["result"]["checkout_enabled"], true); assert_eq!(listing["result"]["primary_bin_verified"], true); assert!( listing["result"]["actions"] @@ -6138,11 +6138,11 @@ fn market_order_request_readiness_gates_buyer_intent_actions() { let disabled_result = &disabled_search["result"]["results"][0]; assert_eq!(disabled_result["protocol_valid"], true); assert_eq!(disabled_result["marketplace_eligible"], true); - assert_eq!(disabled_result["order_request_enabled"], false); + assert_eq!(disabled_result["checkout_enabled"], false); assert_eq!(disabled_result["primary_bin_verified"], true); assert_eq!( disabled_result["reason_codes"][0], - "listing_order_request_disabled" + "listing_checkout_disabled" ); assert_eq!( disabled_result["reason_codes"][1], @@ -6166,11 +6166,11 @@ fn market_order_request_readiness_gates_buyer_intent_actions() { ]); assert_eq!(disabled_listing["result"]["protocol_valid"], true); assert_eq!(disabled_listing["result"]["marketplace_eligible"], true); - assert_eq!(disabled_listing["result"]["order_request_enabled"], false); + assert_eq!(disabled_listing["result"]["checkout_enabled"], false); assert_eq!(disabled_listing["result"]["primary_bin_verified"], true); assert_eq!( disabled_listing["result"]["reason_codes"][0], - "listing_order_request_disabled" + "listing_checkout_disabled" ); assert!( disabled_listing["result"] @@ -6186,11 +6186,11 @@ fn market_order_request_readiness_gates_buyer_intent_actions() { sandbox.json_success(&["--format", "json", "market", "product", "search", "eggs"]); let no_bin_result = &no_bin_search["result"]["results"][0]; assert_eq!(no_bin_result["primary_bin_id"], Value::Null); - assert_eq!(no_bin_result["order_request_enabled"], false); + assert_eq!(no_bin_result["checkout_enabled"], false); assert_eq!(no_bin_result["primary_bin_verified"], false); assert_eq!( no_bin_result["reason_codes"][0], - "listing_order_request_disabled" + "listing_checkout_disabled" ); assert_eq!( no_bin_result["reason_codes"][1], @@ -6213,7 +6213,7 @@ fn market_order_request_readiness_gates_buyer_intent_actions() { "pasture-eggs", ]); assert_eq!(no_bin_listing["result"]["primary_bin_id"], Value::Null); - assert_eq!(no_bin_listing["result"]["order_request_enabled"], false); + assert_eq!(no_bin_listing["result"]["checkout_enabled"], false); assert_eq!(no_bin_listing["result"]["primary_bin_verified"], false); assert_eq!( no_bin_listing["result"]["reason_codes"][1], @@ -6232,7 +6232,7 @@ fn market_order_request_readiness_gates_buyer_intent_actions() { sandbox.json_success(&["--format", "json", "market", "product", "search", "eggs"]); let invalid_bin_result = &invalid_bin_search["result"]["results"][0]; assert_eq!(invalid_bin_result["primary_bin_id"], "missing-bin"); - assert_eq!(invalid_bin_result["order_request_enabled"], false); + assert_eq!(invalid_bin_result["checkout_enabled"], false); assert_eq!(invalid_bin_result["primary_bin_verified"], false); assert_eq!( invalid_bin_result["reason_codes"][1], @@ -6258,10 +6258,7 @@ fn market_order_request_readiness_gates_buyer_intent_actions() { invalid_bin_listing["result"]["primary_bin_id"], "missing-bin" ); - assert_eq!( - invalid_bin_listing["result"]["order_request_enabled"], - false - ); + assert_eq!(invalid_bin_listing["result"]["checkout_enabled"], false); assert_eq!(invalid_bin_listing["result"]["primary_bin_verified"], false); assert_eq!( invalid_bin_listing["result"]["reason_codes"][1], @@ -6285,7 +6282,7 @@ fn market_order_request_readiness_gates_buyer_intent_actions() { "pasture-eggs", ]); assert_eq!(restored_listing["result"]["primary_bin_id"], "bin-1"); - assert_eq!(restored_listing["result"]["order_request_enabled"], true); + assert_eq!(restored_listing["result"]["checkout_enabled"], true); assert_eq!(restored_listing["result"]["primary_bin_verified"], true); assert!( restored_listing["result"] @@ -6345,28 +6342,28 @@ fn required_approval_token_rejects_absent_empty_and_whitespace_values() { "sync.push", &["--relay", "ws://127.0.0.1:9", "sync", "push"], ); - assert_required_approval_token_rejected(&sandbox, "order.submit", &["order", "submit"]); + assert_required_approval_token_rejected(&sandbox, "trade.submit", &["trade", "submit"]); assert_required_approval_token_rejected( &sandbox, - "order.rebind", - &["order", "rebind", "ord_missing", "acct_missing"], + "trade.rebind", + &["trade", "rebind", "ord_missing", "acct_missing"], ); - assert_required_approval_token_rejected(&sandbox, "order.accept", &["order", "accept"]); + assert_required_approval_token_rejected(&sandbox, "trade.accept", &["trade", "accept"]); assert_required_approval_token_rejected( &sandbox, - "order.decline", - &["order", "decline", "--reason", "out_of_stock"], + "trade.decline", + &["trade", "decline", "--reason", "out_of_stock"], ); assert_required_approval_token_rejected( &sandbox, - "order.cancel", - &["order", "cancel", "--reason", "changed plans"], + "trade.cancel", + &["trade", "cancel", "--reason", "changed plans"], ); assert_required_approval_token_rejected( &sandbox, - "order.revision.accept", + "trade.revision.accept", &[ - "order", + "trade", "revision", "accept", "ord_pending", @@ -6376,9 +6373,9 @@ fn required_approval_token_rejects_absent_empty_and_whitespace_values() { ); assert_required_approval_token_rejected( &sandbox, - "order.revision.decline", + "trade.revision.decline", &[ - "order", + "trade", "revision", "decline", "ord_pending", @@ -6420,11 +6417,11 @@ fn order_submit_missing_order_returns_not_found_while_read_view_stays_successful let get = sandbox.json_success(&[ "--format", "json", - "order", + "trade", "get", "ord_missing_submit_target", ]); - assert_eq!(get["operation_id"], "order.get"); + assert_eq!(get["operation_id"], "trade.get"); assert_eq!(get["result"]["state"], "missing"); assert_eq!(get["errors"].as_array().expect("errors").len(), 0); @@ -6433,18 +6430,18 @@ fn order_submit_missing_order_returns_not_found_while_read_view_stays_successful "json", "--approval-token", "approve", - "order", + "trade", "submit", "ord_missing_submit_target", ]); assert_eq!(output.status.code(), Some(4)); - assert_eq!(submit["operation_id"], "order.submit"); + assert_eq!(submit["operation_id"], "trade.submit"); assert_eq!(submit["errors"][0]["code"], "not_found"); assert_eq!(submit["errors"][0]["exit_code"], 4); assert_eq!(submit["errors"][0]["detail"]["class"], "resource"); - assert_no_removed_command_reference(&submit, &["order", "submit"]); - assert_no_daemon_runtime_reference(&submit, &["order", "submit"]); + assert_no_removed_command_reference(&submit, &["trade", "submit"]); + assert_no_daemon_runtime_reference(&submit, &["trade", "submit"]); } fn create_ready_order(sandbox: &RadrootsCliSandbox, basket_id: &str) -> String { @@ -6466,7 +6463,7 @@ fn create_ready_order(sandbox: &RadrootsCliSandbox, basket_id: &str) -> String { "2", ]); let quote = sandbox.json_success(&["--format", "json", "basket", "quote", "create", basket_id]); - quote["result"]["quote"]["order_id"] + quote["result"]["quote"]["trade_id"] .as_str() .expect("order id") .to_owned() @@ -6477,13 +6474,13 @@ fn rewrite_order_bin(sandbox: &RadrootsCliSandbox, order_id: &str, bin_id: &str) .root() .join("data/apps/cli/orders/drafts") .join(format!("{order_id}.toml")); - let contents = fs::read_to_string(&path).expect("read order draft"); + let contents = fs::read_to_string(&path).expect("read trade draft"); let updated = contents.replace( "bin_id = \"bin-1\"", format!("bin_id = \"{bin_id}\"").as_str(), ); assert_ne!(updated, contents); - fs::write(path, updated).expect("rewrite order draft bin"); + fs::write(path, updated).expect("rewrite trade draft bin"); } fn rewrite_order_buyer_actor_pubkey(sandbox: &RadrootsCliSandbox, order_id: &str, pubkey: &str) { @@ -6491,7 +6488,7 @@ fn rewrite_order_buyer_actor_pubkey(sandbox: &RadrootsCliSandbox, order_id: &str .root() .join("data/apps/cli/orders/drafts") .join(format!("{order_id}.toml")); - let contents = fs::read_to_string(&path).expect("read order draft"); + let contents = fs::read_to_string(&path).expect("read trade draft"); let mut in_buyer_actor = false; let mut replaced = false; let updated = contents @@ -6511,7 +6508,7 @@ fn rewrite_order_buyer_actor_pubkey(sandbox: &RadrootsCliSandbox, order_id: &str .collect::<Vec<_>>() .join("\n"); assert!(replaced, "buyer_actor pubkey field"); - fs::write(path, format!("{updated}\n")).expect("rewrite order draft buyer actor"); + fs::write(path, format!("{updated}\n")).expect("rewrite trade draft buyer actor"); } fn line_indent(line: &str) -> &str { @@ -6614,11 +6611,11 @@ fn buyer_target_flow_acceptance_uses_target_operations() { assert_eq!(quote["operation_id"], "basket.quote.create"); assert_eq!(quote["result"]["state"], "quoted"); assert_no_removed_command_reference(&quote, &["basket", "quote", "create"]); - let order_id = quote["result"]["quote"]["order_id"] + let order_id = quote["result"]["quote"]["trade_id"] .as_str() .expect("order id"); let quote_economics = &quote["result"]["quote"]["economics"]; - let order_file = quote["result"]["order"]["file"] + let order_file = quote["result"]["trade"]["file"] .as_str() .expect("order file"); assert_eq!(quote["result"]["quote"]["ready_for_submit"], true); @@ -6635,56 +6632,56 @@ fn buyer_target_flow_acceptance_uses_target_operations() { assert_eq!(quote_economics["discounts"], Value::Array(Vec::new())); assert_eq!(quote_economics["adjustments"], Value::Array(Vec::new())); assert_eq!( - quote["result"]["order"]["economics"], + quote["result"]["trade"]["economics"], quote_economics.clone() ); - let order_draft = fs::read_to_string(order_file).expect("read order draft"); + let order_draft = fs::read_to_string(order_file).expect("read trade draft"); assert!(order_draft.contains("[buyer_actor]")); assert!(order_draft.contains("source = \"resolved_account\"")); assert!(order_draft.contains("[order.economics]")); assert!(order_draft.contains("pricing_basis = \"listing_event\"")); - assert_eq!(quote["result"]["order"]["buyer_account_id"], account_id); + assert_eq!(quote["result"]["trade"]["buyer_account_id"], account_id); assert_eq!( - quote["result"]["order"]["buyer_actor_source"], + quote["result"]["trade"]["buyer_actor_source"], "resolved_account" ); assert_eq!( - quote["result"]["order"]["listing_event_id"], + quote["result"]["trade"]["listing_event_id"], listing_event_id ); - let orders = sandbox.json_success(&["--format", "json", "order", "list"]); - assert_eq!(orders["operation_id"], "order.list"); + let orders = sandbox.json_success(&["--format", "json", "trade", "list"]); + assert_eq!(orders["operation_id"], "trade.list"); assert_eq!(orders["result"]["state"], "ready"); assert_eq!(orders["result"]["count"], 1); - assert_eq!(orders["result"]["orders"][0]["id"], order_id); - assert_eq!(orders["result"]["orders"][0]["ready_for_submit"], true); + assert_eq!(orders["result"]["trades"][0]["id"], order_id); + assert_eq!(orders["result"]["trades"][0]["ready_for_submit"], true); assert_eq!( - orders["result"]["orders"][0]["listing_event_id"], + orders["result"]["trades"][0]["listing_event_id"], listing_event_id ); assert_eq!( - orders["result"]["orders"][0]["buyer_account_id"], + orders["result"]["trades"][0]["buyer_account_id"], account_id ); assert_eq!( - orders["result"]["orders"][0]["buyer_actor_source"], + orders["result"]["trades"][0]["buyer_actor_source"], "resolved_account" ); assert_eq!( - orders["result"]["orders"][0]["economics"], + orders["result"]["trades"][0]["economics"], quote_economics.clone() ); - assert_eq!(orders["result"]["orders"][0]["issues"], Value::Null); - assert_no_removed_command_reference(&orders, &["order", "list"]); - assert_no_daemon_runtime_reference(&orders, &["order", "list"]); + assert_eq!(orders["result"]["trades"][0]["issues"], Value::Null); + assert_no_removed_command_reference(&orders, &["trade", "list"]); + assert_no_daemon_runtime_reference(&orders, &["trade", "list"]); let submit = - sandbox.json_success(&["--format", "json", "--dry-run", "order", "submit", order_id]); - assert_eq!(submit["operation_id"], "order.submit"); + sandbox.json_success(&["--format", "json", "--dry-run", "trade", "submit", order_id]); + assert_eq!(submit["operation_id"], "trade.submit"); assert_eq!(submit["dry_run"], true); assert_eq!(submit["result"]["state"], "dry_run"); - assert_eq!(submit["result"]["source"], "SDK order submit · local key"); + assert_eq!(submit["result"]["source"], "SDK trade submit · local key"); assert_eq!(submit["result"]["event_kind"], 3422); assert_eq!( submit["result"]["target_relays"][0], @@ -6697,21 +6694,21 @@ fn buyer_target_flow_acceptance_uses_target_operations() { .len(), 64 ); - assert_no_removed_command_reference(&submit, &["order", "submit", "--dry-run"]); - assert_no_daemon_runtime_reference(&submit, &["order", "submit", "--dry-run"]); + assert_no_removed_command_reference(&submit, &["trade", "submit", "--dry-run"]); + assert_no_daemon_runtime_reference(&submit, &["trade", "submit", "--dry-run"]); let (output, unavailable_submit) = sandbox.json_output(&[ "--format", "json", "--approval-token", "approve", - "order", + "trade", "submit", order_id, ]); assert!(!output.status.success()); assert_eq!(output.status.code(), Some(3), "{unavailable_submit}"); - assert_eq!(unavailable_submit["operation_id"], "order.submit"); + assert_eq!(unavailable_submit["operation_id"], "trade.submit"); assert_eq!(unavailable_submit["result"], Value::Null); assert_eq!( unavailable_submit["errors"][0]["code"], @@ -6731,11 +6728,11 @@ fn buyer_target_flow_acceptance_uses_target_operations() { .expect("message") .contains("SDK relay publish") ); - assert_no_removed_command_reference(&unavailable_submit, &["order", "submit"]); - assert_no_daemon_runtime_reference(&unavailable_submit, &["order", "submit"]); + assert_no_removed_command_reference(&unavailable_submit, &["trade", "submit"]); + assert_no_daemon_runtime_reference(&unavailable_submit, &["trade", "submit"]); - let order_after_submit = sandbox.json_success(&["--format", "json", "order", "get", order_id]); - assert_eq!(order_after_submit["operation_id"], "order.get"); + let order_after_submit = sandbox.json_success(&["--format", "json", "trade", "get", order_id]); + assert_eq!(order_after_submit["operation_id"], "trade.get"); assert_eq!(order_after_submit["result"]["state"], "ready"); assert_eq!( order_after_submit["result"]["economics"], @@ -6743,26 +6740,26 @@ fn buyer_target_flow_acceptance_uses_target_operations() { ); assert_eq!(order_after_submit["result"]["job"], Value::Null); assert_eq!(order_after_submit["result"]["workflow"], Value::Null); - assert_no_daemon_runtime_reference(&order_after_submit, &["order", "get"]); + assert_no_daemon_runtime_reference(&order_after_submit, &["trade", "get"]); let (watch_output, watch) = - sandbox.json_output(&["--format", "json", "order", "event", "watch", order_id]); + sandbox.json_output(&["--format", "json", "trade", "event", "watch", order_id]); assert!(!watch_output.status.success()); assert_eq!(watch_output.status.code(), Some(3)); - assert_eq!(watch["operation_id"], "order.event.watch"); + assert_eq!(watch["operation_id"], "trade.event.watch"); assert_eq!(watch["result"], Value::Null); assert_eq!(watch["errors"][0]["code"], "not_implemented"); assert_eq!(watch["errors"][0]["detail"]["state"], "not_implemented"); - assert_eq!(watch["errors"][0]["detail"]["order_id"], order_id); + assert_eq!(watch["errors"][0]["detail"]["trade_id"], order_id); assert_eq!( watch["next_actions"][0]["command"], - format!("radroots order status get {order_id}") + format!("radroots trade status get {order_id}") ); - assert_no_daemon_runtime_reference(&watch, &["order", "event", "watch"]); + assert_no_daemon_runtime_reference(&watch, &["trade", "event", "watch"]); assert!( !serde_json::to_string(&watch) .expect("watch json") - .contains("local order drafts") + .contains("local trade drafts") ); } @@ -6771,7 +6768,7 @@ fn order_get_and_list_report_missing_bound_buyer_account() { let sandbox = RadrootsCliSandbox::new(); let order_id = create_ready_order(&sandbox, "missing_buyer_account"); - let ready = sandbox.json_success(&["--format", "json", "order", "get", order_id.as_str()]); + let ready = sandbox.json_success(&["--format", "json", "trade", "get", order_id.as_str()]); let account_id = ready["result"]["buyer_account_id"] .as_str() .expect("buyer account id"); @@ -6790,8 +6787,8 @@ fn order_get_and_list_report_missing_bound_buyer_account() { account_id, ]); - let missing = sandbox.json_success(&["--format", "json", "order", "get", order_id.as_str()]); - assert_eq!(missing["operation_id"], "order.get"); + let missing = sandbox.json_success(&["--format", "json", "trade", "get", order_id.as_str()]); + assert_eq!(missing["operation_id"], "trade.get"); assert_eq!(missing["result"]["state"], "draft"); assert_eq!(missing["result"]["ready_for_submit"], false); assert_eq!(missing["result"]["buyer_account_id"], account_id); @@ -6816,14 +6813,14 @@ fn order_get_and_list_report_missing_bound_buyer_account() { .expect("actions") .iter() .any(|action| action - == &Value::String(format!("radroots order rebind {order_id} <selector>"))) + == &Value::String(format!("radroots trade rebind {order_id} <selector>"))) ); - let list = sandbox.json_success(&["--format", "json", "order", "list"]); + let list = sandbox.json_success(&["--format", "json", "trade", "list"]); assert_eq!(list["result"]["state"], "degraded"); - assert_eq!(list["result"]["orders"][0]["ready_for_submit"], false); + assert_eq!(list["result"]["trades"][0]["ready_for_submit"], false); assert!( - list["result"]["orders"][0]["issues"] + list["result"]["trades"][0]["issues"] .as_array() .expect("issues") .iter() @@ -6834,15 +6831,15 @@ fn order_get_and_list_report_missing_bound_buyer_account() { "--format", "json", "--dry-run", - "order", + "trade", "submit", order_id.as_str(), ]); assert!(!submit_output.status.success()); assert_eq!(submit_output.status.code(), Some(5)); - assert_eq!(submit["operation_id"], "order.submit"); + assert_eq!(submit["operation_id"], "trade.submit"); assert_eq!(submit["errors"][0]["code"], "account_unresolved"); - assert_eq!(submit["errors"][0]["detail"]["order_id"], order_id); + assert_eq!(submit["errors"][0]["detail"]["trade_id"], order_id); } #[test] @@ -6890,11 +6887,11 @@ fn order_get_marks_watch_only_bound_buyer_unready() { "create", "watch_buyer", ]); - let order_id = quote["result"]["quote"]["order_id"] + let order_id = quote["result"]["quote"]["trade_id"] .as_str() .expect("order id"); - let get = sandbox.json_success(&["--format", "json", "order", "get", order_id]); + let get = sandbox.json_success(&["--format", "json", "trade", "get", order_id]); assert_eq!(get["result"]["state"], "draft"); assert_eq!(get["result"]["ready_for_submit"], false); assert_eq!(get["result"]["buyer_account_id"], account_id); @@ -6919,10 +6916,10 @@ fn order_get_marks_watch_only_bound_buyer_unready() { ); let (submit_output, submit) = - sandbox.json_output(&["--format", "json", "--dry-run", "order", "submit", order_id]); + sandbox.json_output(&["--format", "json", "--dry-run", "trade", "submit", order_id]); assert!(!submit_output.status.success()); assert_eq!(submit_output.status.code(), Some(7)); - assert_eq!(submit["operation_id"], "order.submit"); + assert_eq!(submit["operation_id"], "trade.submit"); assert_eq!(submit["errors"][0]["code"], "account_watch_only"); assert_eq!( submit["errors"][0]["detail"]["order_buyer_account_id"], @@ -6937,7 +6934,7 @@ fn order_get_marks_bound_buyer_pubkey_mismatch_unready() { let other_pubkey = identity_public(93).public_key_hex; rewrite_order_buyer_actor_pubkey(&sandbox, order_id.as_str(), other_pubkey.as_str()); - let get = sandbox.json_success(&["--format", "json", "order", "get", order_id.as_str()]); + let get = sandbox.json_success(&["--format", "json", "trade", "get", order_id.as_str()]); assert_eq!(get["result"]["state"], "draft"); assert_eq!(get["result"]["ready_for_submit"], false); assert_eq!(get["result"]["buyer_custody"], "secret_backed"); @@ -6955,7 +6952,7 @@ fn order_get_marks_bound_buyer_pubkey_mismatch_unready() { .expect("actions") .iter() .any(|action| action - == &Value::String(format!("radroots order rebind {order_id} <selector>"))) + == &Value::String(format!("radroots trade rebind {order_id} <selector>"))) ); } @@ -6977,15 +6974,15 @@ fn order_rebind_previews_and_writes_bound_buyer_actor_updates() { "--format", "json", "--dry-run", - "order", + "trade", "rebind", order_id.as_str(), second_account_id, ]); - assert_eq!(dry_run["operation_id"], "order.rebind"); + assert_eq!(dry_run["operation_id"], "trade.rebind"); assert_eq!(dry_run["result"]["state"], "dry_run"); - assert_eq!(dry_run["result"]["from_order_id"], order_id); - assert_eq!(dry_run["result"]["order_id_changed"], true); + assert_eq!(dry_run["result"]["from_trade_id"], order_id); + assert_eq!(dry_run["result"]["trade_id_changed"], true); assert_eq!(dry_run["result"]["buyer_pubkey_changed"], true); assert_eq!(dry_run["result"]["to_buyer_account_id"], second_account_id); assert_eq!( @@ -7000,13 +6997,13 @@ fn order_rebind_previews_and_writes_bound_buyer_actor_updates() { let (unapproved_output, unapproved) = sandbox.json_output(&[ "--format", "json", - "order", + "trade", "rebind", order_id.as_str(), second_account_id, ]); assert!(!unapproved_output.status.success()); - assert_eq!(unapproved["operation_id"], "order.rebind"); + assert_eq!(unapproved["operation_id"], "trade.rebind"); assert_eq!(unapproved["errors"][0]["code"], "approval_required"); let rebound = sandbox.json_success(&[ @@ -7014,16 +7011,16 @@ fn order_rebind_previews_and_writes_bound_buyer_actor_updates() { "json", "--approval-token", "approve", - "order", + "trade", "rebind", order_id.as_str(), second_account_id, ]); - assert_eq!(rebound["operation_id"], "order.rebind"); + assert_eq!(rebound["operation_id"], "trade.rebind"); assert_eq!(rebound["result"]["state"], "rebound"); - assert_eq!(rebound["result"]["from_order_id"], order_id); - assert_eq!(rebound["result"]["order_id_changed"], true); - let rebound_order_id = rebound["result"]["to_order_id"] + assert_eq!(rebound["result"]["from_trade_id"], order_id); + assert_eq!(rebound["result"]["trade_id_changed"], true); + let rebound_order_id = rebound["result"]["to_trade_id"] .as_str() .expect("rebound order id"); assert_ne!(rebound_order_id, order_id); @@ -7035,7 +7032,7 @@ fn order_rebind_previews_and_writes_bound_buyer_actor_updates() { assert!(after.contains(format!("order_id = \"{rebound_order_id}\"").as_str())); assert!(after.contains(format!("quote_id = \"quote_{rebound_order_id}\"").as_str())); - let get = sandbox.json_success(&["--format", "json", "order", "get", rebound_order_id]); + let get = sandbox.json_success(&["--format", "json", "trade", "get", rebound_order_id]); assert_eq!(get["result"]["state"], "ready"); assert_eq!(get["result"]["buyer_account_id"], second_account_id); assert_eq!(get["result"]["buyer_actor_source"], "order_rebind"); @@ -7045,14 +7042,14 @@ fn order_rebind_previews_and_writes_bound_buyer_actor_updates() { "json", "--approval-token", "approve", - "order", + "trade", "rebind", rebound_order_id, second_account_id, ]); assert_eq!(same["result"]["state"], "rebound"); - assert_eq!(same["result"]["order_id_changed"], false); - assert_eq!(same["result"]["to_order_id"], rebound_order_id); + assert_eq!(same["result"]["trade_id_changed"], false); + assert_eq!(same["result"]["to_trade_id"], rebound_order_id); } #[test] @@ -7096,7 +7093,7 @@ fn order_rebind_refuses_visible_published_request() { "create", "visible_rebind", ]); - let order_id = quote["result"]["quote"]["order_id"] + let order_id = quote["result"]["quote"]["trade_id"] .as_str() .expect("order id"); let economics: RadrootsOrderEconomics = @@ -7120,7 +7117,7 @@ fn order_rebind_refuses_visible_published_request() { "--dry-run", "--relay", relay.endpoint(), - "order", + "trade", "rebind", order_id, target_account_id, @@ -7129,7 +7126,7 @@ fn order_rebind_refuses_visible_published_request() { assert!(!output.status.success()); assert_eq!(output.status.code(), Some(10)); - assert_eq!(value["operation_id"], "order.rebind"); + assert_eq!(value["operation_id"], "trade.rebind"); assert_eq!(value["errors"][0]["code"], "validation_failed"); assert_eq!( value["errors"][0]["detail"]["existing_request_check"], @@ -7184,7 +7181,7 @@ fn order_status_and_event_list_use_draft_context_after_account_override_drift() "create", "draft_status", ]); - let order_id = quote["result"]["quote"]["order_id"] + let order_id = quote["result"]["quote"]["trade_id"] .as_str() .expect("order id"); let economics: RadrootsOrderEconomics = @@ -7206,14 +7203,14 @@ fn order_status_and_event_list_use_draft_context_after_account_override_drift() "json", "--account-id", drift_account_id, - "order", + "trade", "status", "get", order_id, ]); - assert_eq!(status["operation_id"], "order.status.get"); - assert_eq!(status["result"]["source"], "SDK local order projection"); + assert_eq!(status["operation_id"], "trade.status.get"); + assert_eq!(status["result"]["source"], "SDK local trade projection"); assert_eq!( status["result"]["actor_context_source"], "sdk_local_projection" @@ -7230,18 +7227,18 @@ fn order_status_and_event_list_use_draft_context_after_account_override_drift() drift_account_id, "--relay", event_list_relay.endpoint(), - "order", + "trade", "event", "list", order_id, ]); event_list_relay.join(); - assert_eq!(events["operation_id"], "order.event.list"); + assert_eq!(events["operation_id"], "trade.event.list"); assert_eq!(events["result"]["actor_context_source"], "order_draft"); assert_eq!(events["result"]["seller_pubkey"], "1".repeat(64)); assert_eq!(events["result"]["count"], 1); - assert_eq!(events["result"]["orders"][0]["id"], order_id); + assert_eq!(events["result"]["trades"][0]["id"], order_id); } #[test] @@ -7299,7 +7296,7 @@ fn order_cancel_uses_bound_buyer_after_default_account_drift() { "create", "bound_cancel", ]); - let order_id = quote["result"]["quote"]["order_id"] + let order_id = quote["result"]["quote"]["trade_id"] .as_str() .expect("order id"); let economics: RadrootsOrderEconomics = @@ -7331,7 +7328,7 @@ fn order_cancel_uses_bound_buyer_after_default_account_drift() { "--dry-run", "--relay", relay.endpoint(), - "order", + "trade", "cancel", order_id, "--reason", @@ -7339,7 +7336,7 @@ fn order_cancel_uses_bound_buyer_after_default_account_drift() { ]); relay.join(); - assert_eq!(cancel["operation_id"], "order.cancel"); + assert_eq!(cancel["operation_id"], "trade.cancel"); assert_eq!(cancel["result"]["state"], "dry_run"); assert_eq!(cancel["result"]["buyer_pubkey"], buyer.public_key_hex()); assert_eq!(cancel["result"]["signer_mode"], "local"); @@ -7356,7 +7353,7 @@ fn buyer_side_order_writes_reject_conflicting_account_override_for_local_draft() for (operation_id, command) in [ ( - "order.revision.accept", + "trade.revision.accept", vec![ "--format", "json", @@ -7365,7 +7362,7 @@ fn buyer_side_order_writes_reject_conflicting_account_override_for_local_draft() drift_account_id, "--relay", "ws://127.0.0.1:9", - "order", + "trade", "revision", "accept", order_id.as_str(), @@ -7374,7 +7371,7 @@ fn buyer_side_order_writes_reject_conflicting_account_override_for_local_draft() ], ), ( - "order.cancel", + "trade.cancel", vec![ "--format", "json", @@ -7383,7 +7380,7 @@ fn buyer_side_order_writes_reject_conflicting_account_override_for_local_draft() drift_account_id, "--relay", "ws://127.0.0.1:9", - "order", + "trade", "cancel", order_id.as_str(), "--reason", @@ -7398,7 +7395,7 @@ fn buyer_side_order_writes_reject_conflicting_account_override_for_local_draft() assert_eq!(value["operation_id"], operation_id); assert_eq!(value["result"], Value::Null); assert_eq!(value["errors"][0]["code"], "account_mismatch"); - assert_eq!(value["errors"][0]["detail"]["order_id"], order_id); + assert_eq!(value["errors"][0]["detail"]["trade_id"], order_id); assert_eq!( value["errors"][0]["detail"]["attempted_buyer_account_id"], drift_account_id @@ -7419,19 +7416,19 @@ fn order_submit_requires_local_replica_freshness_before_signing() { "ws://127.0.0.1:9", "--approval-token", "approve", - "order", + "trade", "submit", order_id.as_str(), ]); assert!(!output.status.success()); assert_eq!(output.status.code(), Some(3)); - assert_eq!(value["operation_id"], "order.submit"); + assert_eq!(value["operation_id"], "trade.submit"); assert_eq!(value["errors"][0]["code"], "operation_unavailable"); assert_eq!(value["errors"][0]["detail"]["state"], "unconfigured"); assert_eq!( value["errors"][0]["detail"]["issues"][0]["field"], - "order.listing_addr" + "trade.listing_addr" ); assert!( value["errors"][0]["message"] @@ -7451,20 +7448,20 @@ fn order_submit_dry_run_requires_local_replica_freshness() { "--format", "json", "--dry-run", - "order", + "trade", "submit", order_id.as_str(), ]); assert!(!output.status.success()); assert_eq!(output.status.code(), Some(3)); - assert_eq!(value["operation_id"], "order.submit"); + assert_eq!(value["operation_id"], "trade.submit"); assert_eq!(value["dry_run"], true); assert_eq!(value["errors"][0]["code"], "operation_unavailable"); assert_eq!(value["errors"][0]["detail"]["state"], "unconfigured"); assert_eq!( value["errors"][0]["detail"]["issues"][0]["field"], - "order.listing_addr" + "trade.listing_addr" ); } @@ -7481,18 +7478,18 @@ fn order_submit_rejects_missing_or_archived_local_listing_before_publish() { "ws://127.0.0.1:9", "--approval-token", "approve", - "order", + "trade", "submit", order_id.as_str(), ]); assert!(!output.status.success()); assert_eq!(output.status.code(), Some(3)); - assert_eq!(value["operation_id"], "order.submit"); + assert_eq!(value["operation_id"], "trade.submit"); assert_eq!(value["errors"][0]["code"], "operation_unavailable"); assert_eq!( value["errors"][0]["detail"]["issues"][0]["field"], - "order.listing_addr" + "trade.listing_addr" ); assert!( value["errors"][0]["message"] @@ -7516,18 +7513,18 @@ fn order_submit_rejects_superseded_local_listing_event_before_publish() { "ws://127.0.0.1:9", "--approval-token", "approve", - "order", + "trade", "submit", order_id.as_str(), ]); assert!(!output.status.success()); assert_eq!(output.status.code(), Some(3)); - assert_eq!(value["operation_id"], "order.submit"); + assert_eq!(value["operation_id"], "trade.submit"); assert_eq!(value["errors"][0]["code"], "operation_unavailable"); assert_eq!( value["errors"][0]["detail"]["issues"][0]["field"], - "order.listing_event_id" + "trade.listing_event_id" ); assert!( value["errors"][0]["detail"]["issues"][0]["message"] @@ -7565,7 +7562,7 @@ fn order_submit_rejects_over_available_quantity_before_publish() { "create", "over_quantity", ]); - let order_id = quote["result"]["quote"]["order_id"] + let order_id = quote["result"]["quote"]["trade_id"] .as_str() .expect("order id"); @@ -7576,14 +7573,14 @@ fn order_submit_rejects_over_available_quantity_before_publish() { "ws://127.0.0.1:9", "--approval-token", "approve", - "order", + "trade", "submit", order_id, ]); assert!(!output.status.success()); assert_eq!(output.status.code(), Some(10)); - assert_eq!(value["operation_id"], "order.submit"); + assert_eq!(value["operation_id"], "trade.submit"); assert_eq!(value["errors"][0]["code"], "validation_failed"); assert_eq!( value["errors"][0]["detail"]["issues"][0]["code"], @@ -7595,8 +7592,8 @@ fn order_submit_rejects_over_available_quantity_before_publish() { .expect("issue message") .contains("available quantity 5") ); - assert_no_removed_command_reference(&value, &["order", "submit"]); - assert_no_daemon_runtime_reference(&value, &["order", "submit"]); + assert_no_removed_command_reference(&value, &["trade", "submit"]); + assert_no_daemon_runtime_reference(&value, &["trade", "submit"]); } #[test] @@ -7612,14 +7609,14 @@ fn order_submit_rejects_unknown_local_listing_bin_before_publish() { "ws://127.0.0.1:9", "--approval-token", "approve", - "order", + "trade", "submit", order_id.as_str(), ]); assert!(!output.status.success()); assert_eq!(output.status.code(), Some(10)); - assert_eq!(value["operation_id"], "order.submit"); + assert_eq!(value["operation_id"], "trade.submit"); assert_eq!(value["errors"][0]["code"], "validation_failed"); assert_eq!( value["errors"][0]["detail"]["issues"][0]["code"], @@ -7635,8 +7632,8 @@ fn order_submit_rejects_unknown_local_listing_bin_before_publish() { .expect("issue message") .contains("expected primary bin `bin-1`") ); - assert_no_removed_command_reference(&value, &["order", "submit"]); - assert_no_daemon_runtime_reference(&value, &["order", "submit"]); + assert_no_removed_command_reference(&value, &["trade", "submit"]); + assert_no_daemon_runtime_reference(&value, &["trade", "submit"]); } #[test] @@ -7842,14 +7839,14 @@ fn order_submit_rejects_stale_invalid_verified_primary_bin_before_relay_prefligh "--format", "json", "--dry-run", - "order", + "trade", "submit", &order_id, ]); assert!(!output.status.success()); assert_eq!(output.status.code(), Some(10)); - assert_eq!(value["operation_id"], "order.submit"); + assert_eq!(value["operation_id"], "trade.submit"); assert_eq!(value["dry_run"], true); assert_eq!(value["errors"][0]["code"], "validation_failed"); assert_eq!( @@ -7860,8 +7857,8 @@ fn order_submit_rejects_stale_invalid_verified_primary_bin_before_relay_prefligh value["errors"][0]["detail"]["issues"][0]["field"], "inventory.primary_bin_id" ); - assert_no_removed_command_reference(&value, &["order", "submit", "--dry-run"]); - assert_no_daemon_runtime_reference(&value, &["order", "submit", "--dry-run"]); + assert_no_removed_command_reference(&value, &["trade", "submit", "--dry-run"]); + assert_no_daemon_runtime_reference(&value, &["trade", "submit", "--dry-run"]); } #[test] @@ -7892,16 +7889,16 @@ fn order_submit_dry_run_rejects_over_available_quantity_before_relay_preflight() "create", "dry_over_quantity", ]); - let order_id = quote["result"]["quote"]["order_id"] + let order_id = quote["result"]["quote"]["trade_id"] .as_str() .expect("order id"); let (output, value) = - sandbox.json_output(&["--format", "json", "--dry-run", "order", "submit", order_id]); + sandbox.json_output(&["--format", "json", "--dry-run", "trade", "submit", order_id]); assert!(!output.status.success()); assert_eq!(output.status.code(), Some(10)); - assert_eq!(value["operation_id"], "order.submit"); + assert_eq!(value["operation_id"], "trade.submit"); assert_eq!(value["dry_run"], true); assert_eq!(value["errors"][0]["code"], "validation_failed"); assert_eq!( @@ -7941,31 +7938,31 @@ fn ready_order_submit_dry_run_validates_local_buyer_authority() { "create", "ready_order", ]); - let order_id = quote["result"]["quote"]["order_id"] + let order_id = quote["result"]["quote"]["trade_id"] .as_str() .expect("order id"); assert_eq!(quote["result"]["quote"]["ready_for_submit"], true); assert_eq!( - quote["result"]["order"]["buyer_account_id"], + quote["result"]["trade"]["buyer_account_id"], first_account_id ); assert_eq!( - quote["result"]["order"]["listing_event_id"], + quote["result"]["trade"]["listing_event_id"], listing_event_id ); let dry_run = - sandbox.json_success(&["--format", "json", "--dry-run", "order", "submit", order_id]); - assert_eq!(dry_run["operation_id"], "order.submit"); + sandbox.json_success(&["--format", "json", "--dry-run", "trade", "submit", order_id]); + assert_eq!(dry_run["operation_id"], "trade.submit"); assert_eq!(dry_run["dry_run"], true); assert_eq!(dry_run["result"]["state"], "dry_run"); - assert_eq!(dry_run["result"]["source"], "SDK order submit · local key"); + assert_eq!(dry_run["result"]["source"], "SDK trade submit · local key"); assert_eq!(dry_run["result"]["event_kind"], 3422); assert_eq!( dry_run["result"]["target_relays"][0], ORDERABLE_LISTING_RELAY ); - assert_no_daemon_runtime_reference(&dry_run, &["order", "submit", "--dry-run"]); + assert_no_daemon_runtime_reference(&dry_run, &["trade", "submit", "--dry-run"]); let second = sandbox.json_success(&["--format", "json", "account", "create"]); let second_account_id = second["result"]["account"]["id"] @@ -7980,8 +7977,8 @@ fn ready_order_submit_dry_run_validates_local_buyer_authority() { second_account_id, ]); let drift = - sandbox.json_success(&["--format", "json", "--dry-run", "order", "submit", order_id]); - assert_eq!(drift["operation_id"], "order.submit"); + sandbox.json_success(&["--format", "json", "--dry-run", "trade", "submit", order_id]); + assert_eq!(drift["operation_id"], "trade.submit"); assert_eq!(drift["result"]["state"], "dry_run"); assert_eq!(drift["result"]["buyer_account_id"], first_account_id); @@ -7991,18 +7988,18 @@ fn ready_order_submit_dry_run_validates_local_buyer_authority() { "--account-id", second_account_id, "--dry-run", - "order", + "trade", "submit", order_id, ]); assert!(!output.status.success()); assert_eq!(output.status.code(), Some(5)); - assert_eq!(mismatch["operation_id"], "order.submit"); + assert_eq!(mismatch["operation_id"], "trade.submit"); assert_eq!(mismatch["errors"][0]["code"], "account_mismatch"); assert_eq!(mismatch["errors"][0]["detail"]["class"], "account"); - assert_no_removed_command_reference(&mismatch, &["order", "submit", "--dry-run"]); - assert_no_daemon_runtime_reference(&mismatch, &["order", "submit", "--dry-run"]); + assert_no_removed_command_reference(&mismatch, &["trade", "submit", "--dry-run"]); + assert_no_daemon_runtime_reference(&mismatch, &["trade", "submit", "--dry-run"]); let (network_output, network_mismatch) = sandbox.json_output(&[ "--format", @@ -8013,18 +8010,18 @@ fn ready_order_submit_dry_run_validates_local_buyer_authority() { "ws://127.0.0.1:9", "--approval-token", "approve", - "order", + "trade", "submit", order_id, ]); assert!(!network_output.status.success()); assert_eq!(network_output.status.code(), Some(5)); - assert_eq!(network_mismatch["operation_id"], "order.submit"); + assert_eq!(network_mismatch["operation_id"], "trade.submit"); assert_eq!(network_mismatch["result"], Value::Null); assert_eq!(network_mismatch["errors"][0]["code"], "account_mismatch"); assert_eq!(network_mismatch["errors"][0]["detail"]["class"], "account"); - assert_no_daemon_runtime_reference(&network_mismatch, &["order", "submit"]); + assert_no_daemon_runtime_reference(&network_mismatch, &["trade", "submit"]); } #[test]