diff --git a/Cargo.lock b/Cargo.lock index cea13305..07d513ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1970,9 +1970,9 @@ dependencies = [ [[package]] name = "mostro-core" -version = "0.6.24" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d09cc9a5dc67cbdda958e53f72fe6c8a7e3a036d31b84d54f79ae06e28fb0fa2" +checksum = "e6a7c10b8900e96717edc31b621e1d07204a18b001c22d128fd3573a9f882585" dependencies = [ "anyhow", "bitcoin", diff --git a/Cargo.toml b/Cargo.toml index 624529f1..1d6b9978 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ uuid = { version = "1.8.0", features = [ "serde", ] } reqwest = { version = "0.12.1", features = ["json"] } -mostro-core = { version = "0.6.24", features = ["sqlx"] } +mostro-core = { version = "0.6.25", features = ["sqlx"] } tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } config = "0.15.4" diff --git a/migrations/20221222153301_orders.sql b/migrations/20221222153301_orders.sql index 885bb396..83056e49 100644 --- a/migrations/20221222153301_orders.sql +++ b/migrations/20221222153301_orders.sql @@ -37,5 +37,7 @@ CREATE TABLE IF NOT EXISTS orders ( failed_payment integer default 0, expires_at integer not null, trade_index_seller integer default 0, - trade_index_buyer integer default 0 + trade_index_buyer integer default 0, + next_trade_pubkey char(64), + next_trade_index integer default 0 ); diff --git a/src/app/fiat_sent.rs b/src/app/fiat_sent.rs index 690e167e..066073d0 100644 --- a/src/app/fiat_sent.rs +++ b/src/app/fiat_sent.rs @@ -24,7 +24,7 @@ pub async fn fiat_sent_action( } else { return Err(Error::msg("No order id")); }; - let order = match Order::by_id(pool, order_id).await? { + let mut order = match Order::by_id(pool, order_id).await? { Some(order) => order, None => { error!("Order Id {order_id} not found!"); @@ -54,6 +54,10 @@ pub async fn fiat_sent_action( .await; return Ok(()); } + let next_trade: Option<(String, u32)> = match &msg.get_inner_message_kind().payload { + Some(Payload::NextTrade(pubkey, index)) => Some((pubkey.clone(), *index)), + _ => None, + }; // We publish a new replaceable kind nostr event with the status updated // and update on local database the status and new event id @@ -93,5 +97,15 @@ pub async fn fiat_sent_action( ) .await; + // Update next trade fields only when the buyer is the maker of a range order + // These fields will be used to create the next child order in the range + if order.creator_pubkey == event.rumor.pubkey.to_string() && next_trade.is_some() { + if let Some((pubkey, index)) = next_trade { + order.next_trade_pubkey = Some(pubkey.clone()); + order.next_trade_index = Some(index as i64); + order.update(pool).await?; + } + } + Ok(()) } diff --git a/src/app/release.rs b/src/app/release.rs index 5e25abec..749d1140 100644 --- a/src/app/release.rs +++ b/src/app/release.rs @@ -72,39 +72,30 @@ pub async fn release_action( _ => None, }; - // Check if order id is ok - let order_id = if let Some(order_id) = msg.get_inner_message_kind().id { + let order_id = msg + .get_inner_message_kind() + .id + .ok_or(Error::msg("Order ID is required but was not provided"))?; + + let mut order = Order::by_id(pool, order_id) + .await? + .ok_or(Error::msg(format!( + "Order {} not found in database", + order_id + )))?; + + let seller_pubkey_hex = order.seller_pubkey.as_ref().ok_or(Error::msg(format!( + "Seller public key not found for order {}", order_id - } else { - return Err(Error::msg("No order id")); - }; + )))?; - let order = match Order::by_id(pool, order_id).await? { - Some(order) => order, - None => { - error!("Order Id {order_id} not found!"); - return Ok(()); - } - }; - let seller_pubkey_hex = match order.seller_pubkey { - Some(ref pk) => pk, - None => { - error!("Order Id {}: Seller pubkey not found!", order.id); - return Ok(()); - } - }; - let seller_pubkey = event.rumor.pubkey; + let current_status = + Status::from_str(&order.status).map_err(|_| Error::msg("Wrong order status"))?; - let current_status = if let Ok(current_status) = Status::from_str(&order.status) { - current_status - } else { - return Err(Error::msg("Wrong order status")); - }; - - if current_status != Status::Active - && current_status != Status::FiatSent - && current_status != Status::Dispute - { + if !matches!( + current_status, + Status::Active | Status::FiatSent | Status::Dispute + ) { send_cant_do_msg( request_id, Some(order.id), @@ -112,11 +103,10 @@ pub async fn release_action( &event.rumor.pubkey, ) .await; - return Ok(()); } - if &seller_pubkey.to_string() != seller_pubkey_hex { + if &event.rumor.pubkey.to_string() != seller_pubkey_hex { send_cant_do_msg( request_id, Some(order.id), @@ -137,69 +127,93 @@ pub async fn release_action( ) .await?; - let mut order_updated = update_order_event(my_keys, Status::SettledHoldInvoice, &order).await?; - - let (is_range, child_order) = get_child_order(&mut order_updated, request_id, my_keys).await?; - // If we have the next trade data and the order is a range order we create a new order - if is_range && next_trade.is_some() { - if let Some((next_trade_pubkey, next_trade_index)) = next_trade.clone() { - // As we are creating a new order from Mostro to user, - // we need save the trade_pubkey and the last_trade_index for the next trade - let mut child_order = child_order.clone(); - child_order.seller_pubkey = Some(next_trade_pubkey.clone()); - child_order.creator_pubkey = next_trade_pubkey.clone(); - let next_trade_index = Some(next_trade_index as i64); - child_order.trade_index_seller = next_trade_index; - let pool = db::connect().await?; - // We create the new order to send to the user - let new_order = child_order.as_new_order(); - // We save the new order as a child of the parent range order - child_order.update(&pool).await?; - let next_trade_pubkey = PublicKey::from_str(&next_trade_pubkey)?; - - // We send a message to the order creator with the new order - send_new_order_msg( - request_id, - new_order.id, - Action::NewOrder, - Some(Payload::Order(new_order)), - &next_trade_pubkey, - next_trade_index, - ) - .await; - } - } + // We send a message to buyer indicating seller released funds + let buyer_pubkey = PublicKey::from_str( + order + .buyer_pubkey + .as_ref() + .ok_or(Error::msg("Missing buyer pubkey"))? + .as_str(), + )?; - // We send a HoldInvoicePaymentSettled message to seller, the client should - // indicate *funds released* message to seller send_new_order_msg( - request_id, + None, Some(order_id), - Action::HoldInvoicePaymentSettled, + Action::Released, None, - &seller_pubkey, + &buyer_pubkey, None, ) .await; + order = update_order_event(my_keys, Status::SettledHoldInvoice, &order).await?; - // We send a message to buyer indicating seller released funds - let buyer_pubkey = match &order.buyer_pubkey { - Some(buyer) => PublicKey::from_str(buyer.as_str())?, - _ => return Err(Error::msg("Missing buyer pubkeys")), - }; + // Handle child order for range orders + if let Ok((true, mut child_order)) = get_child_order(&mut order, request_id, my_keys).await { + handle_child_order(&mut child_order, &order, next_trade, pool, request_id).await?; + } + + // We send a HoldInvoicePaymentSettled message to seller, the client should + // indicate *funds released* message to seller send_new_order_msg( - None, + request_id, Some(order_id), - Action::Released, + Action::HoldInvoicePaymentSettled, None, - &buyer_pubkey, + &event.rumor.pubkey, None, ) .await; // Finally we try to pay buyer's invoice - let _ = do_payment(order_updated, request_id).await; + let _ = do_payment(order, request_id).await; + + Ok(()) +} +/// Manages the creation and update of child orders in a range order sequence +/// +/// # Arguments +/// * `child_order` - The child order to be created/updated +/// * `order` - The parent order +/// * `next_trade` - Optional tuple of (pubkey, index) for the next trade +/// * `pool` - Database connection pool +/// * `request_id` - Optional request ID for messaging +/// +/// # Returns +/// Result indicating success or failure of the operation +async fn handle_child_order( + child_order: &mut Order, + order: &Order, + next_trade: Option<(String, u32)>, + pool: &Pool, + request_id: Option, +) -> Result<()> { + if let Some((next_trade_pubkey, next_trade_index)) = next_trade { + if &order.creator_pubkey == order.seller_pubkey.as_ref().unwrap() { + child_order.seller_pubkey = Some(next_trade_pubkey.clone()); + child_order.creator_pubkey = next_trade_pubkey.clone(); + child_order.trade_index_seller = Some(next_trade_index as i64); + } else if &order.creator_pubkey == order.buyer_pubkey.as_ref().unwrap() { + child_order.buyer_pubkey = Some(next_trade_pubkey.clone()); + child_order.creator_pubkey = next_trade_pubkey.clone(); + child_order.trade_index_buyer = order.next_trade_index; + } + + let new_order = child_order.as_new_order(); + let next_trade_pubkey = PublicKey::from_str(&next_trade_pubkey)?; + send_new_order_msg( + request_id, + new_order.id, + Action::NewOrder, + Some(Payload::Order(new_order)), + &next_trade_pubkey, + child_order + .trade_index_buyer + .or(child_order.trade_index_seller), + ) + .await; + child_order.clone().update(pool).await?; + } Ok(()) } diff --git a/src/db.rs b/src/db.rs index d0a15378..b6a7d3fa 100644 --- a/src/db.rs +++ b/src/db.rs @@ -33,12 +33,13 @@ pub async fn connect() -> Result> { })?; match SqlitePool::connect(&db_url).await { Ok(pool) => { - tracing::info!( - "Successfully created Mostro database file at {}", - db_path.display(), - ); match sqlx::migrate!().run(&pool).await { - Ok(_) => (), + Ok(_) => { + tracing::info!( + "Successfully created Mostro database file at {}", + db_path.display(), + ); + } Err(e) => { // Clean up the created file on migration failure if let Err(cleanup_err) = std::fs::remove_file(db_path) {