From 5b090d9f97ea23bd1f8d563df610a54458ddbb09 Mon Sep 17 00:00:00 2001 From: arkanoider Date: Mon, 29 Sep 2025 00:02:58 +0200 Subject: [PATCH 01/23] feat: improving parsing functions --- src/cli.rs | 4 +- src/cli/get_dm.rs | 4 +- src/cli/list_orders.rs | 1 + src/cli/send_msg.rs | 59 ++++++++++-------- src/parser/dms.rs | 137 ++++++++++++++++++++++++++++++++++++++--- src/util.rs | 43 +++++++++++-- 6 files changed, 206 insertions(+), 42 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 8ee84d6..d3408f2 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -489,11 +489,11 @@ impl Commands { // DM retrieval commands Commands::GetDm { since, from_user } => { - execute_get_dm(Some(since), false, from_user, ctx).await + execute_get_dm(since, false, from_user, ctx).await } Commands::GetDmUser { since } => execute_get_dm_user(since, ctx).await, Commands::GetAdminDm { since, from_user } => { - execute_get_dm(Some(since), true, from_user, ctx).await + execute_get_dm(since, true, from_user, ctx).await } // Admin commands diff --git a/src/cli/get_dm.rs b/src/cli/get_dm.rs index d5edf38..0442915 100644 --- a/src/cli/get_dm.rs +++ b/src/cli/get_dm.rs @@ -8,7 +8,7 @@ use crate::{ }; pub async fn execute_get_dm( - since: Option<&i64>, + since: &i64, admin: bool, from_user: &bool, ctx: &Context, @@ -22,7 +22,7 @@ pub async fn execute_get_dm( }; // Fetch the requested events - let all_fetched_events = { fetch_events_list(list_kind, None, None, None, ctx, since).await? }; + let all_fetched_events = fetch_events_list(list_kind, None, None, None, ctx, None, Some(since)).await?; // Extract (Message, u64) tuples from Event::MessageTuple variants let mut dm_events: Vec<(Message, u64)> = Vec::new(); diff --git a/src/cli/list_orders.rs b/src/cli/list_orders.rs index 56891c3..880f38f 100644 --- a/src/cli/list_orders.rs +++ b/src/cli/list_orders.rs @@ -63,6 +63,7 @@ pub async fn execute_list_orders( kind_checked, ctx, None, + None, ) .await?; let table = print_orders_table(table_of_orders)?; diff --git a/src/cli/send_msg.rs b/src/cli/send_msg.rs index 6701b59..c9fb124 100644 --- a/src/cli/send_msg.rs +++ b/src/cli/send_msg.rs @@ -1,6 +1,6 @@ use crate::cli::{Commands, Context}; use crate::db::{Order, User}; -use crate::util::{send_dm, wait_for_dm}; +use crate::util::{fetch_events_list, send_dm, wait_for_dm, ListKind}; use anyhow::Result; use mostro_core::prelude::*; @@ -79,29 +79,40 @@ pub async fn execute_send_msg( let message_json = message .as_json() .map_err(|e| anyhow::anyhow!("Failed to serialize message: {e}"))?; - send_dm( - &ctx.client, - Some(&idkey), - &trade_keys, - &ctx.mostro_pubkey, - message_json, - None, - false, - ) - .await - .map_err(|e| anyhow::anyhow!("Failed to send DM: {e}"))?; - - // Wait for the DM to be sent from mostro - wait_for_dm( - &ctx.client, - &trade_keys, - request_id, - None, - Some(order), - &ctx.pool, - ) - .await - .map_err(|e| anyhow::anyhow!("Failed to wait for DM: {e}"))?; + + // Clone the keys and client for the async call + let trade_keys_clone = trade_keys.clone(); + let client_clone = ctx.client.clone(); + let mostro_pubkey_clone = ctx.mostro_pubkey; + let idkey_clone = idkey.clone(); + + // Spawn a new task to send the DM + tokio::spawn(async move { + let _ = send_dm( + &client_clone, + Some(&idkey_clone), + &trade_keys_clone, + &mostro_pubkey_clone, + message_json, + None, + false, + ) + .await; + }); + + let new_incoming_message = fetch_events_list(ListKind::WaitForUpdate, None, None, None, ctx, Some(&trade_keys),None).await?; + + // // Wait for the DM to be sent from mostro + // wait_for_dm( + // &ctx.client, + // &trade_keys, + // request_id, + // None, + // Some(order), + // &ctx.pool, + // ) + // .await + // .map_err(|e| anyhow::anyhow!("Failed to wait for DM: {e}"))?; } } diff --git a/src/parser/dms.rs b/src/parser/dms.rs index f4a1466..6bfdcb8 100644 --- a/src/parser/dms.rs +++ b/src/parser/dms.rs @@ -11,7 +11,130 @@ use nostr_sdk::prelude::*; use crate::db::{Order, User}; use sqlx::SqlitePool; -pub async fn parse_dm_events(events: Events, pubkey: &Keys) -> Vec<(Message, u64, PublicKey)> { +pub async fn print_commands_results(event){ + let message = message.get_inner_message_kind(); +if message.request_id == Some(request_id) { + match message.action { + Action::NewOrder => { + if let Some(Payload::Order(order)) = message.payload.as_ref() { + if let Err(e) = save_order(order.clone(), trade_keys, request_id, trade_index, pool).await { + println!("Failed to save order: {}", e); + return Err(()); + } + return Ok(()); + } + } + // this is the case where the buyer adds an invoice to a takesell order + Action::WaitingSellerToPay => { + println!("Now we should wait for the seller to pay the invoice"); + if let Some(mut order) = order.take() { + match order + .set_status(Status::WaitingPayment.to_string()) + .save(pool) + .await + { + Ok(_) => println!("Order status updated"), + Err(e) => println!("Failed to update order status: {}", e), + } + return Ok(()); + } + } + // this is the case where the buyer adds an invoice to a takesell order + Action::AddInvoice => { + if let Some(Payload::Order(order)) = &message.payload { + println!( + "Please add a lightning invoice with amount of {}", + order.amount + ); + // Save the order + if let Err(e) = save_order(order.clone(), trade_keys, request_id, trade_index, pool).await { + println!("Failed to save order: {}", e); + return Err(()); + } + return Ok(()); + } + } + // this is the case where the buyer pays the invoice coming from a takebuy + Action::PayInvoice => { + if let Some(Payload::PaymentRequest(order, invoice, _)) = &message.payload { + println!( + "Mostro sent you this hold invoice for order id: {}", + order + .as_ref() + .and_then(|o| o.id) + .map_or("unknown".to_string(), |id| id.to_string()) + ); + println!(); + println!("Pay this invoice to continue --> {}", invoice); + println!(); + if let Some(order) = order { + let store_order = order.clone(); + // Save the order + if let Err(e) = save_order(store_order, trade_keys, request_id, trade_index, pool).await { + println!("Failed to save order: {}", e); + return Err(()); + } + } + return Ok(()); + } + } + Action::CantDo => { + match message.payload { + Some(Payload::CantDo(Some(CantDoReason::OutOfRangeFiatAmount | CantDoReason::OutOfRangeSatsAmount))) => { + println!("Error: Amount is outside the allowed range. Please check the order's min/max limits."); + return Err(()); + } + Some(Payload::CantDo(Some(CantDoReason::PendingOrderExists))) => { + println!("Error: A pending order already exists. Please wait for it to be filled or canceled."); + return Err(()); + } + Some(Payload::CantDo(Some(CantDoReason::InvalidTradeIndex))) => { + println!("Error: Invalid trade index. Please synchronize the trade index with mostro"); + return Err(()); + } + _ => { + println!("Unknown reason: {:?}", message.payload); + return Err(()); + } + } + } + // this is the case where the user cancels the order + Action::Canceled => { + if let Some(order_id) = &message.id { + // Acquire database connection + // Verify order exists before deletion + if Order::get_by_id(pool, &order_id.to_string()).await.is_ok() { + if let Err(e) = Order::delete_by_id(pool, &order_id.to_string()).await { + println!("Failed to delete order: {}", e); + return Err(()); + } + // Release database connection + println!("Order {} canceled!", order_id); + return Ok(()); + } else { + println!("Order not found: {}", order_id); + return Err(()); + } + } + } + Action::Rate => { + println!("Sats released!"); + println!("You can rate the counterpart now"); + return Ok(()); + } + Action::FiatSentOk => { + if let Some(order_id) = &message.id { + println!("Fiat sent message for order {} received", order_id); + println!("Waiting for sats release from seller"); + return Ok(()); + } + } + _ => {} + } + } +} + +pub async fn parse_dm_events(events: Events, pubkey: &Keys, since: Option<&i64>) -> Vec<(Message, u64, PublicKey)> { let mut id_set = HashSet::::new(); let mut direct_messages: Vec<(Message, u64, PublicKey)> = Vec::new(); @@ -75,17 +198,11 @@ pub async fn parse_dm_events(events: Events, pubkey: &Keys) -> Vec<(Message, u64 } _ => continue, }; - - let since_time = match chrono::Utc::now().checked_sub_signed(chrono::Duration::minutes(30)) - { - Some(dt) => dt.timestamp() as u64, - None => { - println!("Error: Unable to calculate time 30 minutes ago"); + // check if the message is older than the since time if it is, skip it + if let Some(since_time) = since { + if created_at.as_u64() < *since_time as u64 { continue; } - }; - if created_at.as_u64() < since_time { - continue; } direct_messages.push((message, created_at.as_u64(), dm.pubkey)); } diff --git a/src/util.rs b/src/util.rs index 1f4fe50..b0749ae 100644 --- a/src/util.rs +++ b/src/util.rs @@ -32,6 +32,7 @@ pub enum ListKind { DirectMessagesUser, DirectMessagesAdmin, PrivateDirectMessagesUser, + WaitForUpdate, } async fn send_gift_wrap_dm_internal( @@ -263,6 +264,18 @@ pub async fn wait_for_dm( } } } + Action::Rate => { + println!("Sats released!"); + println!("You can rate the counterpart now"); + return Ok(()); + } + Action::FiatSentOk => { + if let Some(order_id) = &message.id { + println!("Fiat sent message for order {} received", order_id); + println!("Waiting for sats release from seller"); + return Ok(()); + } + } _ => {} } } @@ -525,6 +538,13 @@ pub fn create_filter( .pubkey(pubkey) .since(fake_timestamp)) } + ListKind::WaitForUpdate => { + Ok(Filter::new() + .kind(nostr_sdk::Kind::GiftWrap) + .pubkey(pubkey) + .limit(0) + .since(Timestamp::from(chrono::Utc::now().timestamp() as u64))) + } ListKind::PrivateDirectMessagesUser => { // Get since from cli or use 30 minutes default let since = if let Some(mins) = since { @@ -554,7 +574,8 @@ pub async fn fetch_events_list( currency: Option, kind: Option, ctx: &Context, - _since: Option<&i64>, + specific_trade_key: Option<&Keys>, + since: Option<&i64> ) -> Result> { match list_kind { ListKind::Orders => { @@ -566,13 +587,26 @@ pub async fn fetch_events_list( let orders = parse_orders_events(fetched_events, currency, status, kind); Ok(orders.into_iter().map(Event::SmallOrder).collect()) } + ListKind::WaitForUpdate => { + if let Some(trade_key) = specific_trade_key { + let filters = create_filter(list_kind, trade_key.public_key(), None)?; + let fetched_event = ctx + .client + .fetch_events(filters, FETCH_EVENTS_TIMEOUT) + .await?; + let message = parse_dm_events(fetched_event, trade_key, None).await; + Ok(message.into_iter().map(|(message, timestamp, _)| Event::MessageTuple(Box::new((message, timestamp)))).collect()) + } else { + Err(anyhow::anyhow!("Specific trade key is required for this command!")) + } + } ListKind::DirectMessagesAdmin => { let filters = create_filter(list_kind, ctx.mostro_pubkey, None)?; let fetched_events = ctx .client .fetch_events(filters, FETCH_EVENTS_TIMEOUT) .await?; - let direct_messages_mostro = parse_dm_events(fetched_events, &ctx.context_keys).await; + let direct_messages_mostro = parse_dm_events(fetched_events, &ctx.context_keys, since).await; Ok(direct_messages_mostro .into_iter() .map(|(message, timestamp, _)| Event::MessageTuple(Box::new((message, timestamp)))) @@ -592,7 +626,7 @@ pub async fn fetch_events_list( .fetch_events(filter, FETCH_EVENTS_TIMEOUT) .await?; let direct_messages_for_trade_key = - parse_dm_events(fetched_user_messages, &trade_key).await; + parse_dm_events(fetched_user_messages, &trade_key, since).await; direct_messages.extend( direct_messages_for_trade_key .into_iter() @@ -606,6 +640,7 @@ pub async fn fetch_events_list( } ListKind::DirectMessagesUser => { let mut direct_messages: Vec<(Message, u64)> = Vec::new(); + for index in 1..=ctx.trade_index { let trade_key = User::get_trade_keys(&ctx.pool, index).await?; let filter = @@ -615,7 +650,7 @@ pub async fn fetch_events_list( .fetch_events(filter, FETCH_EVENTS_TIMEOUT) .await?; let direct_messages_for_trade_key = - parse_dm_events(fetched_user_messages, &trade_key).await; + parse_dm_events(fetched_user_messages, &trade_key, since).await; direct_messages.extend( direct_messages_for_trade_key .into_iter() From 5ad663a6c45097e9efa49ca2f5dda9a041c400a1 Mon Sep 17 00:00:00 2001 From: arkanoider Date: Mon, 29 Sep 2025 15:30:14 +0200 Subject: [PATCH 02/23] chore: going on to uniform message fetching calls --- src/cli/add_invoice.rs | 21 +++--- src/cli/get_dm.rs | 3 +- src/cli/list_disputes.rs | 2 +- src/cli/send_msg.rs | 52 +++++++-------- src/parser/dms.rs | 137 +++++++++++++++++++++++++++------------ src/util.rs | 30 +++++---- 6 files changed, 153 insertions(+), 92 deletions(-) diff --git a/src/cli/add_invoice.rs b/src/cli/add_invoice.rs index ebf4d61..59cc218 100644 --- a/src/cli/add_invoice.rs +++ b/src/cli/add_invoice.rs @@ -1,4 +1,4 @@ -use crate::util::{send_dm, wait_for_dm}; +use crate::util::{fetch_events_list, send_dm, wait_for_dm, ListKind}; use crate::{cli::Context, db::Order, lightning::is_valid_invoice}; use anyhow::Result; use lnurl::lightning_address::LightningAddress; @@ -82,15 +82,20 @@ pub async fn execute_add_invoice(order_id: &Uuid, invoice: &str, ctx: &Context) .await; }); - // Wait for the DM to be sent from mostro and update the order - wait_for_dm( - &ctx.client, - &order_trade_keys_clone, - request_id, + let events = fetch_events_list( + ListKind::WaitForUpdate, + None, + None, + None, + ctx, + Some(&order_trade_keys_clone), None, - Some(order), - &ctx.pool, ) .await?; + + // We just need the first event + let recv_event = events.get(0).unwrap(); + + Ok(()) } diff --git a/src/cli/get_dm.rs b/src/cli/get_dm.rs index 0442915..b5c1430 100644 --- a/src/cli/get_dm.rs +++ b/src/cli/get_dm.rs @@ -22,7 +22,8 @@ pub async fn execute_get_dm( }; // Fetch the requested events - let all_fetched_events = fetch_events_list(list_kind, None, None, None, ctx, None, Some(since)).await?; + let all_fetched_events = + fetch_events_list(list_kind, None, None, None, ctx, None, Some(since)).await?; // Extract (Message, u64) tuples from Event::MessageTuple variants let mut dm_events: Vec<(Message, u64)> = Vec::new(); diff --git a/src/cli/list_disputes.rs b/src/cli/list_disputes.rs index feff25b..bb750c7 100644 --- a/src/cli/list_disputes.rs +++ b/src/cli/list_disputes.rs @@ -13,7 +13,7 @@ pub async fn execute_list_disputes(ctx: &Context) -> Result<()> { // Get orders from relays let table_of_disputes = - fetch_events_list(ListKind::Disputes, None, None, None, ctx, None).await?; + fetch_events_list(ListKind::Disputes, None, None, None, ctx, None, None).await?; let table = print_disputes_table(table_of_disputes)?; println!("{table}"); diff --git a/src/cli/send_msg.rs b/src/cli/send_msg.rs index c9fb124..470f4a9 100644 --- a/src/cli/send_msg.rs +++ b/src/cli/send_msg.rs @@ -1,6 +1,7 @@ use crate::cli::{Commands, Context}; use crate::db::{Order, User}; -use crate::util::{fetch_events_list, send_dm, wait_for_dm, ListKind}; +use crate::parser::dms::print_commands_results; +use crate::util::{fetch_events_list, send_dm, Event, ListKind}; use anyhow::Result; use mostro_core::prelude::*; @@ -65,16 +66,7 @@ pub async fn execute_send_msg( if let Some(trade_keys_str) = order.trade_keys.clone() { let trade_keys = Keys::parse(&trade_keys_str)?; - // Subscribe to gift wrap events - ONLY NEW ONES WITH LIMIT 0 - let subscription = Filter::new() - .pubkey(trade_keys.public_key()) - .kind(nostr_sdk::Kind::GiftWrap) - .limit(0); - let opts = - SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::WaitForEvents(1)); - // Subscribe to gift wrap events - ctx.client.subscribe(subscription, Some(opts)).await?; // Send DM let message_json = message .as_json() @@ -95,27 +87,33 @@ pub async fn execute_send_msg( &mostro_pubkey_clone, message_json, None, - false, - ) - .await; + false, + ) + .await; }); - let new_incoming_message = fetch_events_list(ListKind::WaitForUpdate, None, None, None, ctx, Some(&trade_keys),None).await?; - - // // Wait for the DM to be sent from mostro - // wait_for_dm( - // &ctx.client, - // &trade_keys, - // request_id, - // None, - // Some(order), - // &ctx.pool, - // ) - // .await - // .map_err(|e| anyhow::anyhow!("Failed to wait for DM: {e}"))?; + let events = fetch_events_list( + ListKind::WaitForUpdate, + None, + None, + None, + ctx, + Some(&trade_keys), + None, + ) + .await?; + + // Extract (Message, u64) tuples from Event::MessageTuple variants + for event in events { + if let Event::MessageTuple(tuple) = event { + let message = tuple.0.get_inner_message_kind(); + if message.request_id == Some(request_id) { + print_commands_results(&message, Some(order.clone()), &ctx).await; + } + } + } } } - Ok(()) } diff --git a/src/parser/dms.rs b/src/parser/dms.rs index 6bfdcb8..ab9ed88 100644 --- a/src/parser/dms.rs +++ b/src/parser/dms.rs @@ -8,20 +8,41 @@ use mostro_core::prelude::*; use nip44::v2::{decrypt_to_bytes, ConversationKey}; use nostr_sdk::prelude::*; -use crate::db::{Order, User}; +use crate::{ + cli::Context, + db::{Order, User}, + util::save_order, +}; use sqlx::SqlitePool; -pub async fn print_commands_results(event){ - let message = message.get_inner_message_kind(); -if message.request_id == Some(request_id) { +/// Execute logic of command answer +pub async fn print_commands_results( + message: &MessageKind, + mut order: Option, + ctx: &Context, +) -> Result<()> { + // Do the logic for the message response match message.action { Action::NewOrder => { if let Some(Payload::Order(order)) = message.payload.as_ref() { - if let Err(e) = save_order(order.clone(), trade_keys, request_id, trade_index, pool).await { - println!("Failed to save order: {}", e); - return Err(()); + if let Some(req_id) = message.request_id { + if let Err(e) = save_order( + order.clone(), + &ctx.trade_keys, + req_id, + Some(ctx.trade_index), + &ctx.pool, + ) + .await + { + return Err(anyhow::anyhow!("Failed to save order: {}", e)); + } + return Ok(()); + } else { + return Err(anyhow::anyhow!("No request id found in message")); } - return Ok(()); + } else { + return Err(anyhow::anyhow!("No order found in message")); } } // this is the case where the buyer adds an invoice to a takesell order @@ -29,14 +50,16 @@ if message.request_id == Some(request_id) { println!("Now we should wait for the seller to pay the invoice"); if let Some(mut order) = order.take() { match order - .set_status(Status::WaitingPayment.to_string()) - .save(pool) - .await + .set_status(Status::WaitingPayment.to_string()) + .save(&ctx.pool) + .await { Ok(_) => println!("Order status updated"), Err(e) => println!("Failed to update order status: {}", e), } return Ok(()); + } else { + return Err(anyhow::anyhow!("No order found in message")); } } // this is the case where the buyer adds an invoice to a takesell order @@ -46,12 +69,25 @@ if message.request_id == Some(request_id) { "Please add a lightning invoice with amount of {}", order.amount ); - // Save the order - if let Err(e) = save_order(order.clone(), trade_keys, request_id, trade_index, pool).await { - println!("Failed to save order: {}", e); - return Err(()); + if let Some(req_id) = message.request_id { + // Save the order + if let Err(e) = save_order( + order.clone(), + &ctx.trade_keys, + req_id, + None, + &ctx.pool, + ) + .await + { + return Err(anyhow::anyhow!("Failed to save order: {}", e)); + } + } else { + return Err(anyhow::anyhow!("No request id found in message")); } return Ok(()); + } else { + return Err(anyhow::anyhow!("No order found in message")); } } // this is the case where the buyer pays the invoice coming from a takebuy @@ -68,53 +104,61 @@ if message.request_id == Some(request_id) { println!("Pay this invoice to continue --> {}", invoice); println!(); if let Some(order) = order { + if let Some(req_id) = message.request_id { let store_order = order.clone(); // Save the order - if let Err(e) = save_order(store_order, trade_keys, request_id, trade_index, pool).await { + if let Err(e) = + save_order(store_order, &ctx.trade_keys, req_id, Some(ctx.trade_index), &ctx.pool).await + { println!("Failed to save order: {}", e); - return Err(()); + return Err(anyhow::anyhow!("Failed to save order: {}", e)); } } + else { + return Err(anyhow::anyhow!("No request id found in message")); + } + } + else { + return Err(anyhow::anyhow!("No request id found in message")); + } + } return Ok(()); - } } Action::CantDo => { match message.payload { - Some(Payload::CantDo(Some(CantDoReason::OutOfRangeFiatAmount | CantDoReason::OutOfRangeSatsAmount))) => { - println!("Error: Amount is outside the allowed range. Please check the order's min/max limits."); - return Err(()); + Some(Payload::CantDo(Some( + CantDoReason::OutOfRangeFiatAmount | CantDoReason::OutOfRangeSatsAmount, + ))) => { + return Err(anyhow::anyhow!("Amount is outside the allowed range. Please check the order's min/max limits.")); } Some(Payload::CantDo(Some(CantDoReason::PendingOrderExists))) => { - println!("Error: A pending order already exists. Please wait for it to be filled or canceled."); - return Err(()); - } + return Err(anyhow::anyhow!("A pending order already exists. Please wait for it to be filled or canceled.")); + } Some(Payload::CantDo(Some(CantDoReason::InvalidTradeIndex))) => { - println!("Error: Invalid trade index. Please synchronize the trade index with mostro"); - return Err(()); + return Err(anyhow::anyhow!("Invalid trade index. Please synchronize the trade index with mostro")); } _ => { - println!("Unknown reason: {:?}", message.payload); - return Err(()); + return Err(anyhow::anyhow!("Unknown reason: {:?}", message.payload)); } } } // this is the case where the user cancels the order Action::Canceled => { if let Some(order_id) = &message.id { - // Acquire database connection - // Verify order exists before deletion - if Order::get_by_id(pool, &order_id.to_string()).await.is_ok() { - if let Err(e) = Order::delete_by_id(pool, &order_id.to_string()).await { - println!("Failed to delete order: {}", e); - return Err(()); + // Acquire database connection + // Verify order exists before deletion + if Order::get_by_id(&ctx.pool, &order_id.to_string()).await.is_ok() { + if let Err(e) = Order::delete_by_id(&ctx.pool, &order_id.to_string()).await { + return Err(anyhow::anyhow!("Failed to delete order: {}", e)); + } + // Release database connection + println!("Order {} canceled!", order_id); + return Ok(()); + } else { + return Err(anyhow::anyhow!("Order not found: {}", order_id)); } - // Release database connection - println!("Order {} canceled!", order_id); - return Ok(()); } else { - println!("Order not found: {}", order_id); - return Err(()); - } + return Err(anyhow::anyhow!("No order id found in message")); } } Action::Rate => { @@ -127,14 +171,21 @@ if message.request_id == Some(request_id) { println!("Fiat sent message for order {} received", order_id); println!("Waiting for sats release from seller"); return Ok(()); + } else { + return Err(anyhow::anyhow!("No order id found in message")); } } - _ => {} - } + _ => return Err(anyhow::anyhow!("Unknown action: {:?}", message.action)), } } -pub async fn parse_dm_events(events: Events, pubkey: &Keys, since: Option<&i64>) -> Vec<(Message, u64, PublicKey)> { + + +pub async fn parse_dm_events( + events: Events, + pubkey: &Keys, + since: Option<&i64>, +) -> Vec<(Message, u64, PublicKey)> { let mut id_set = HashSet::::new(); let mut direct_messages: Vec<(Message, u64, PublicKey)> = Vec::new(); diff --git a/src/util.rs b/src/util.rs index b0749ae..2fb6b3b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -538,13 +538,11 @@ pub fn create_filter( .pubkey(pubkey) .since(fake_timestamp)) } - ListKind::WaitForUpdate => { - Ok(Filter::new() - .kind(nostr_sdk::Kind::GiftWrap) - .pubkey(pubkey) - .limit(0) - .since(Timestamp::from(chrono::Utc::now().timestamp() as u64))) - } + ListKind::WaitForUpdate => Ok(Filter::new() + .kind(nostr_sdk::Kind::GiftWrap) + .pubkey(pubkey) + .limit(0) + .since(Timestamp::from(chrono::Utc::now().timestamp() as u64))), ListKind::PrivateDirectMessagesUser => { // Get since from cli or use 30 minutes default let since = if let Some(mins) = since { @@ -575,7 +573,7 @@ pub async fn fetch_events_list( kind: Option, ctx: &Context, specific_trade_key: Option<&Keys>, - since: Option<&i64> + since: Option<&i64>, ) -> Result> { match list_kind { ListKind::Orders => { @@ -595,9 +593,16 @@ pub async fn fetch_events_list( .fetch_events(filters, FETCH_EVENTS_TIMEOUT) .await?; let message = parse_dm_events(fetched_event, trade_key, None).await; - Ok(message.into_iter().map(|(message, timestamp, _)| Event::MessageTuple(Box::new((message, timestamp)))).collect()) + Ok(message + .into_iter() + .map(|(message, timestamp, _)| { + Event::MessageTuple(Box::new((message, timestamp))) + }) + .collect()) } else { - Err(anyhow::anyhow!("Specific trade key is required for this command!")) + Err(anyhow::anyhow!( + "Specific trade key is required for this command!" + )) } } ListKind::DirectMessagesAdmin => { @@ -606,7 +611,8 @@ pub async fn fetch_events_list( .client .fetch_events(filters, FETCH_EVENTS_TIMEOUT) .await?; - let direct_messages_mostro = parse_dm_events(fetched_events, &ctx.context_keys, since).await; + let direct_messages_mostro = + parse_dm_events(fetched_events, &ctx.context_keys, since).await; Ok(direct_messages_mostro .into_iter() .map(|(message, timestamp, _)| Event::MessageTuple(Box::new((message, timestamp)))) @@ -640,7 +646,7 @@ pub async fn fetch_events_list( } ListKind::DirectMessagesUser => { let mut direct_messages: Vec<(Message, u64)> = Vec::new(); - + for index in 1..=ctx.trade_index { let trade_key = User::get_trade_keys(&ctx.pool, index).await?; let filter = From fbe1c9a9bb46c1bd97a76f379a29f9abca4b7513 Mon Sep 17 00:00:00 2001 From: arkanoider Date: Mon, 29 Sep 2025 23:49:44 +0200 Subject: [PATCH 03/23] chore: almost remove wait for dm function with fetch event --- src/util.rs | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/util.rs b/src/util.rs index 2fb6b3b..b97e492 100644 --- a/src/util.rs +++ b/src/util.rs @@ -586,24 +586,22 @@ pub async fn fetch_events_list( Ok(orders.into_iter().map(Event::SmallOrder).collect()) } ListKind::WaitForUpdate => { - if let Some(trade_key) = specific_trade_key { - let filters = create_filter(list_kind, trade_key.public_key(), None)?; - let fetched_event = ctx - .client - .fetch_events(filters, FETCH_EVENTS_TIMEOUT) - .await?; - let message = parse_dm_events(fetched_event, trade_key, None).await; - Ok(message - .into_iter() - .map(|(message, timestamp, _)| { - Event::MessageTuple(Box::new((message, timestamp))) - }) - .collect()) - } else { - Err(anyhow::anyhow!( - "Specific trade key is required for this command!" - )) - } + // get trade key from previous order if specic tradey is Some or get trade key from ctx + let trade_key = match specific_trade_key { + Some(key) => key.clone(), + None => ctx.trade_keys.clone(), + }; + + let filters = create_filter(list_kind, trade_key.public_key(), None)?; + let fetched_event = ctx + .client + .fetch_events(filters, FETCH_EVENTS_TIMEOUT) + .await?; + let message = parse_dm_events(fetched_event, &trade_key, None).await; + Ok(message + .into_iter() + .map(|(message, timestamp, _)| Event::MessageTuple(Box::new((message, timestamp)))) + .collect()) } ListKind::DirectMessagesAdmin => { let filters = create_filter(list_kind, ctx.mostro_pubkey, None)?; From ebe1bfc54c59269b0046fa7adb97b663afeb386f Mon Sep 17 00:00:00 2001 From: arkanoider Date: Mon, 29 Sep 2025 23:50:17 +0200 Subject: [PATCH 04/23] chore: almost remove wait for dm function with fetch event --- src/cli/add_invoice.rs | 21 ++++---- src/cli/send_msg.rs | 2 +- src/cli/take_order.rs | 43 +++++------------ src/parser/dms.rs | 107 ++++++++++++++++++++--------------------- tests/parser_dms.rs | 2 +- 5 files changed, 74 insertions(+), 101 deletions(-) diff --git a/src/cli/add_invoice.rs b/src/cli/add_invoice.rs index 59cc218..4a31d6a 100644 --- a/src/cli/add_invoice.rs +++ b/src/cli/add_invoice.rs @@ -1,4 +1,5 @@ -use crate::util::{fetch_events_list, send_dm, wait_for_dm, ListKind}; +use crate::parser::dms::print_commands_results; +use crate::util::{fetch_events_list, send_dm, Event, ListKind}; use crate::{cli::Context, db::Order, lightning::is_valid_invoice}; use anyhow::Result; use lnurl::lightning_address::LightningAddress; @@ -52,15 +53,6 @@ pub async fn execute_add_invoice(order_id: &Uuid, invoice: &str, ctx: &Context) .as_json() .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?; - // Subscribe to gift wrap events - ONLY NEW ONES WITH LIMIT 0 - let subscription = Filter::new() - .pubkey(order_trade_keys.clone().public_key()) - .kind(nostr_sdk::Kind::GiftWrap) - .limit(0); - - let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::WaitForEvents(1)); - ctx.client.subscribe(subscription, Some(opts)).await?; - // Clone the keys and client for the async call let identity_keys_clone = ctx.identity_keys.clone(); let client_clone = ctx.client.clone(); @@ -94,8 +86,13 @@ pub async fn execute_add_invoice(order_id: &Uuid, invoice: &str, ctx: &Context) .await?; // We just need the first event - let recv_event = events.get(0).unwrap(); - + let recv_event = events.first().unwrap(); + if let Event::MessageTuple(tuple) = recv_event { + let message = tuple.0.get_inner_message_kind(); + if message.request_id == Some(request_id) { + let _ = print_commands_results(message, Some(order.clone()), ctx).await; + } + } Ok(()) } diff --git a/src/cli/send_msg.rs b/src/cli/send_msg.rs index 470f4a9..3af1ef5 100644 --- a/src/cli/send_msg.rs +++ b/src/cli/send_msg.rs @@ -108,7 +108,7 @@ pub async fn execute_send_msg( if let Event::MessageTuple(tuple) = event { let message = tuple.0.get_inner_message_kind(); if message.request_id == Some(request_id) { - print_commands_results(&message, Some(order.clone()), &ctx).await; + let _ = print_commands_results(message, Some(order.clone()), ctx).await; } } } diff --git a/src/cli/take_order.rs b/src/cli/take_order.rs index 62fe4f5..a8f6975 100644 --- a/src/cli/take_order.rs +++ b/src/cli/take_order.rs @@ -1,13 +1,13 @@ use anyhow::Result; use lnurl::lightning_address::LightningAddress; use mostro_core::prelude::*; -use nostr_sdk::prelude::*; use std::str::FromStr; use uuid::Uuid; use crate::cli::Context; use crate::lightning::is_valid_invoice; -use crate::util::{send_dm, wait_for_dm}; +use crate::parser::dms::print_commands_results; +use crate::util::{fetch_events_list, send_dm, Event, ListKind}; /// Create payload based on action type and parameters fn create_take_order_payload( @@ -98,15 +98,6 @@ pub async fn execute_take_order( let client_clone = ctx.client.clone(); let mostro_pubkey_clone = ctx.mostro_pubkey; - // Subscribe to gift wrap events - ONLY NEW ONES WITH LIMIT 0 - let subscription = Filter::new() - .pubkey(ctx.trade_keys.public_key()) - .kind(nostr_sdk::Kind::GiftWrap) - .limit(0); - - let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::WaitForEvents(1)); - ctx.client.subscribe(subscription, Some(opts)).await?; - // Spawn a new task to send the DM // This is so we can wait for the gift wrap event in the main thread tokio::spawn(async move { @@ -122,27 +113,17 @@ pub async fn execute_take_order( .await; }); - // For take_sell, add an additional subscription with timestamp filtering - if action == Action::TakeSell { - let subscription = Filter::new() - .pubkey(ctx.trade_keys.public_key()) - .kind(nostr_sdk::Kind::GiftWrap) - .since(Timestamp::from(chrono::Utc::now().timestamp() as u64)) - .limit(0); + let events = + fetch_events_list(ListKind::WaitForUpdate, None, None, None, ctx, None, None).await?; - ctx.client.subscribe(subscription, None).await?; + // Extract (Message, u64) tuples from Event::MessageTuple variants + for event in events { + if let Event::MessageTuple(tuple) = event { + let message = tuple.0.get_inner_message_kind(); + if message.request_id == Some(request_id) { + let _ = print_commands_results(message, None, ctx).await; + } + } } - - // Wait for the DM to be sent from mostro - wait_for_dm( - &ctx.client, - &ctx.trade_keys, - request_id, - Some(ctx.trade_index), - None, - &ctx.pool, - ) - .await?; - Ok(()) } diff --git a/src/parser/dms.rs b/src/parser/dms.rs index ab9ed88..30dd479 100644 --- a/src/parser/dms.rs +++ b/src/parser/dms.rs @@ -37,12 +37,12 @@ pub async fn print_commands_results( { return Err(anyhow::anyhow!("Failed to save order: {}", e)); } - return Ok(()); + Ok(()) } else { - return Err(anyhow::anyhow!("No request id found in message")); + Err(anyhow::anyhow!("No request id found in message")) } } else { - return Err(anyhow::anyhow!("No order found in message")); + Err(anyhow::anyhow!("No order found in message")) } } // this is the case where the buyer adds an invoice to a takesell order @@ -57,9 +57,9 @@ pub async fn print_commands_results( Ok(_) => println!("Order status updated"), Err(e) => println!("Failed to update order status: {}", e), } - return Ok(()); + Ok(()) } else { - return Err(anyhow::anyhow!("No order found in message")); + Err(anyhow::anyhow!("No order found in message")) } } // this is the case where the buyer adds an invoice to a takesell order @@ -71,23 +71,17 @@ pub async fn print_commands_results( ); if let Some(req_id) = message.request_id { // Save the order - if let Err(e) = save_order( - order.clone(), - &ctx.trade_keys, - req_id, - None, - &ctx.pool, - ) - .await + if let Err(e) = + save_order(order.clone(), &ctx.trade_keys, req_id, None, &ctx.pool).await { return Err(anyhow::anyhow!("Failed to save order: {}", e)); } } else { return Err(anyhow::anyhow!("No request id found in message")); } - return Ok(()); + Ok(()) } else { - return Err(anyhow::anyhow!("No order found in message")); + Err(anyhow::anyhow!("No order found in message")) } } // this is the case where the buyer pays the invoice coming from a takebuy @@ -105,82 +99,83 @@ pub async fn print_commands_results( println!(); if let Some(order) = order { if let Some(req_id) = message.request_id { - let store_order = order.clone(); - // Save the order - if let Err(e) = - save_order(store_order, &ctx.trade_keys, req_id, Some(ctx.trade_index), &ctx.pool).await - { - println!("Failed to save order: {}", e); - return Err(anyhow::anyhow!("Failed to save order: {}", e)); - } - } - else { + let store_order = order.clone(); + // Save the order + if let Err(e) = save_order( + store_order, + &ctx.trade_keys, + req_id, + Some(ctx.trade_index), + &ctx.pool, + ) + .await + { + println!("Failed to save order: {}", e); + return Err(anyhow::anyhow!("Failed to save order: {}", e)); + } + } else { return Err(anyhow::anyhow!("No request id found in message")); } - } - else { + } else { return Err(anyhow::anyhow!("No request id found in message")); } - } - return Ok(()); - } - Action::CantDo => { - match message.payload { - Some(Payload::CantDo(Some( - CantDoReason::OutOfRangeFiatAmount | CantDoReason::OutOfRangeSatsAmount, - ))) => { - return Err(anyhow::anyhow!("Amount is outside the allowed range. Please check the order's min/max limits.")); - } - Some(Payload::CantDo(Some(CantDoReason::PendingOrderExists))) => { - return Err(anyhow::anyhow!("A pending order already exists. Please wait for it to be filled or canceled.")); - } - Some(Payload::CantDo(Some(CantDoReason::InvalidTradeIndex))) => { - return Err(anyhow::anyhow!("Invalid trade index. Please synchronize the trade index with mostro")); - } - _ => { - return Err(anyhow::anyhow!("Unknown reason: {:?}", message.payload)); - } } + Ok(()) } + Action::CantDo => match message.payload { + Some(Payload::CantDo(Some( + CantDoReason::OutOfRangeFiatAmount | CantDoReason::OutOfRangeSatsAmount, + ))) => Err(anyhow::anyhow!( + "Amount is outside the allowed range. Please check the order's min/max limits." + )), + Some(Payload::CantDo(Some(CantDoReason::PendingOrderExists))) => Err(anyhow::anyhow!( + "A pending order already exists. Please wait for it to be filled or canceled." + )), + Some(Payload::CantDo(Some(CantDoReason::InvalidTradeIndex))) => Err(anyhow::anyhow!( + "Invalid trade index. Please synchronize the trade index with mostro" + )), + _ => Err(anyhow::anyhow!("Unknown reason: {:?}", message.payload)), + }, // this is the case where the user cancels the order Action::Canceled => { if let Some(order_id) = &message.id { // Acquire database connection // Verify order exists before deletion - if Order::get_by_id(&ctx.pool, &order_id.to_string()).await.is_ok() { + if Order::get_by_id(&ctx.pool, &order_id.to_string()) + .await + .is_ok() + { if let Err(e) = Order::delete_by_id(&ctx.pool, &order_id.to_string()).await { return Err(anyhow::anyhow!("Failed to delete order: {}", e)); } // Release database connection println!("Order {} canceled!", order_id); - return Ok(()); + Ok(()) } else { - return Err(anyhow::anyhow!("Order not found: {}", order_id)); + Err(anyhow::anyhow!("Order not found: {}", order_id)) } } else { - return Err(anyhow::anyhow!("No order id found in message")); + Err(anyhow::anyhow!("No order id found in message")) } } Action::Rate => { println!("Sats released!"); println!("You can rate the counterpart now"); - return Ok(()); + Ok(()) } Action::FiatSentOk => { if let Some(order_id) = &message.id { println!("Fiat sent message for order {} received", order_id); println!("Waiting for sats release from seller"); - return Ok(()); + Ok(()) } else { - return Err(anyhow::anyhow!("No order id found in message")); + Err(anyhow::anyhow!("No order id found in message")) } } - _ => return Err(anyhow::anyhow!("Unknown action: {:?}", message.action)), + _ => Err(anyhow::anyhow!("Unknown action: {:?}", message.action)), } } - - pub async fn parse_dm_events( events: Events, pubkey: &Keys, diff --git a/tests/parser_dms.rs b/tests/parser_dms.rs index ebd590c..7ce9e6e 100644 --- a/tests/parser_dms.rs +++ b/tests/parser_dms.rs @@ -6,7 +6,7 @@ use nostr_sdk::prelude::*; async fn parse_dm_empty() { let keys = Keys::generate(); let events = Events::new(&Filter::new()); - let out = parse_dm_events(events, &keys).await; + let out = parse_dm_events(events, &keys, None).await; assert!(out.is_empty()); } From 55c6d3f71eb85992920d5207b93cab5acdbc88d0 Mon Sep 17 00:00:00 2001 From: arkanoider Date: Wed, 1 Oct 2025 00:21:14 +0200 Subject: [PATCH 05/23] chore: testing all patterns with new code --- src/cli/add_invoice.rs | 52 ++++------ src/cli/get_dm.rs | 2 +- src/cli/list_disputes.rs | 2 +- src/cli/list_orders.rs | 1 - src/cli/new_order.rs | 59 ++++------- src/cli/send_msg.rs | 59 ++++------- src/cli/take_order.rs | 51 ++++----- src/parser/dms.rs | 2 +- src/util.rs | 216 +++++++-------------------------------- 9 files changed, 122 insertions(+), 322 deletions(-) diff --git a/src/cli/add_invoice.rs b/src/cli/add_invoice.rs index 4a31d6a..a69f2af 100644 --- a/src/cli/add_invoice.rs +++ b/src/cli/add_invoice.rs @@ -1,5 +1,6 @@ use crate::parser::dms::print_commands_results; -use crate::util::{fetch_events_list, send_dm, Event, ListKind}; +use crate::parser::parse_dm_events; +use crate::util::{send_dm, wait_for_dm}; use crate::{cli::Context, db::Order, lightning::is_valid_invoice}; use anyhow::Result; use lnurl::lightning_address::LightningAddress; @@ -53,42 +54,25 @@ pub async fn execute_add_invoice(order_id: &Uuid, invoice: &str, ctx: &Context) .as_json() .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?; - // Clone the keys and client for the async call - let identity_keys_clone = ctx.identity_keys.clone(); - let client_clone = ctx.client.clone(); - let mostro_pubkey_clone = ctx.mostro_pubkey; - let order_trade_keys_clone = order_trade_keys.clone(); - - // Spawn a new task to send the DM - // This is so we can wait for the gift wrap event in the main thread - tokio::spawn(async move { - let _ = send_dm( - &client_clone, - Some(&identity_keys_clone), - &order_trade_keys, - &mostro_pubkey_clone, - message_json, - None, - false, - ) - .await; - }); - - let events = fetch_events_list( - ListKind::WaitForUpdate, - None, - None, - None, - ctx, - Some(&order_trade_keys_clone), + // Send the DM + let _ = send_dm( + &ctx.client, + Some(&ctx.identity_keys), + &order_trade_keys, + &ctx.mostro_pubkey, + message_json, None, + false, ) - .await?; + .await; + + // Wait for the DM to be sent from mostro + let recv_event = wait_for_dm(ctx).await?; - // We just need the first event - let recv_event = events.first().unwrap(); - if let Event::MessageTuple(tuple) = recv_event { - let message = tuple.0.get_inner_message_kind(); + // Parse the incoming DM + let messages = parse_dm_events(recv_event, &order_trade_keys, None).await; + if let Some(message) = messages.first() { + let message = message.0.get_inner_message_kind(); if message.request_id == Some(request_id) { let _ = print_commands_results(message, Some(order.clone()), ctx).await; } diff --git a/src/cli/get_dm.rs b/src/cli/get_dm.rs index b5c1430..fce1133 100644 --- a/src/cli/get_dm.rs +++ b/src/cli/get_dm.rs @@ -23,7 +23,7 @@ pub async fn execute_get_dm( // Fetch the requested events let all_fetched_events = - fetch_events_list(list_kind, None, None, None, ctx, None, Some(since)).await?; + fetch_events_list(list_kind, None, None, None, ctx, Some(since)).await?; // Extract (Message, u64) tuples from Event::MessageTuple variants let mut dm_events: Vec<(Message, u64)> = Vec::new(); diff --git a/src/cli/list_disputes.rs b/src/cli/list_disputes.rs index bb750c7..feff25b 100644 --- a/src/cli/list_disputes.rs +++ b/src/cli/list_disputes.rs @@ -13,7 +13,7 @@ pub async fn execute_list_disputes(ctx: &Context) -> Result<()> { // Get orders from relays let table_of_disputes = - fetch_events_list(ListKind::Disputes, None, None, None, ctx, None, None).await?; + fetch_events_list(ListKind::Disputes, None, None, None, ctx, None).await?; let table = print_disputes_table(table_of_disputes)?; println!("{table}"); diff --git a/src/cli/list_orders.rs b/src/cli/list_orders.rs index 880f38f..56891c3 100644 --- a/src/cli/list_orders.rs +++ b/src/cli/list_orders.rs @@ -63,7 +63,6 @@ pub async fn execute_list_orders( kind_checked, ctx, None, - None, ) .await?; let table = print_orders_table(table_of_orders)?; diff --git a/src/cli/new_order.rs b/src/cli/new_order.rs index 4bf5ed7..3ae1b3e 100644 --- a/src/cli/new_order.rs +++ b/src/cli/new_order.rs @@ -1,9 +1,10 @@ use crate::cli::Context; +use crate::parser::dms::print_commands_results; use crate::parser::orders::print_order_preview; +use crate::parser::parse_dm_events; use crate::util::{send_dm, uppercase_first, wait_for_dm}; use anyhow::Result; use mostro_core::prelude::*; -use nostr_sdk::prelude::*; use std::collections::HashMap; use std::io::{stdin, stdout, BufRead, Write}; use std::process; @@ -128,47 +129,29 @@ pub async fn execute_new_order( .as_json() .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?; - // Clone the keys and client for the async call - let identity_keys_clone = ctx.identity_keys.clone(); - let trade_keys_clone = ctx.trade_keys.clone(); - let client_clone = ctx.client.clone(); - let mostro_pubkey_clone = ctx.mostro_pubkey; - - // Subscribe to gift wrap events - ONLY NEW ONES WITH LIMIT 0 - let subscription = Filter::new() - .pubkey(ctx.trade_keys.public_key()) - .kind(nostr_sdk::Kind::GiftWrap) - .limit(0); - - let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::WaitForEvents(1)); - - ctx.client.subscribe(subscription, Some(opts)).await?; - - // Spawn a new task to send the DM - // This is so we can wait for the gift wrap event in the main thread - tokio::spawn(async move { - let _ = send_dm( - &client_clone, - Some(&identity_keys_clone), - &trade_keys_clone, - &mostro_pubkey_clone, - message_json, - None, - false, - ) - .await; - }); - - // Wait for the DM to be sent from mostro - wait_for_dm( + // Send the DM + let _ = send_dm( &ctx.client, + Some(&ctx.identity_keys), &ctx.trade_keys, - request_id, - Some(ctx.trade_index), + &ctx.mostro_pubkey, + message_json, None, - &ctx.pool, + false, ) - .await?; + .await; + + // Wait for the DM to be sent from mostro + let recv_event = wait_for_dm(ctx).await?; + + // Parse the incoming DM + let messages = parse_dm_events(recv_event, &ctx.trade_keys, None).await; + if let Some(message) = messages.first() { + let message = message.0.get_inner_message_kind(); + if message.request_id == Some(request_id) { + let _ = print_commands_results(message, None, ctx).await; + } + } Ok(()) } diff --git a/src/cli/send_msg.rs b/src/cli/send_msg.rs index 3af1ef5..202b5c9 100644 --- a/src/cli/send_msg.rs +++ b/src/cli/send_msg.rs @@ -1,7 +1,8 @@ use crate::cli::{Commands, Context}; use crate::db::{Order, User}; use crate::parser::dms::print_commands_results; -use crate::util::{fetch_events_list, send_dm, Event, ListKind}; +use crate::parser::parse_dm_events; +use crate::util::{send_dm, wait_for_dm}; use anyhow::Result; use mostro_core::prelude::*; @@ -29,7 +30,7 @@ pub async fn execute_send_msg( }; println!( - "Sending {} command for order {:?} to mostro pubId {}", + "Sending {} command for order {:#?} to mostro pubId {}", requested_action, order_id.as_ref(), &ctx.mostro_pubkey @@ -59,7 +60,6 @@ pub async fn execute_send_msg( // Create and send the message let message = Message::new_order(order_id, Some(request_id), None, requested_action, payload); - let idkey = ctx.identity_keys.to_owned(); if let Some(order_id) = order_id { let order = Order::get_by_id(&ctx.pool, &order_id.to_string()).await?; @@ -72,44 +72,27 @@ pub async fn execute_send_msg( .as_json() .map_err(|e| anyhow::anyhow!("Failed to serialize message: {e}"))?; - // Clone the keys and client for the async call - let trade_keys_clone = trade_keys.clone(); - let client_clone = ctx.client.clone(); - let mostro_pubkey_clone = ctx.mostro_pubkey; - let idkey_clone = idkey.clone(); - - // Spawn a new task to send the DM - tokio::spawn(async move { - let _ = send_dm( - &client_clone, - Some(&idkey_clone), - &trade_keys_clone, - &mostro_pubkey_clone, - message_json, - None, - false, - ) - .await; - }); - - let events = fetch_events_list( - ListKind::WaitForUpdate, - None, - None, - None, - ctx, - Some(&trade_keys), + // Send DM + let _ = send_dm( + &ctx.client, + Some(&ctx.identity_keys), + &trade_keys, + &ctx.mostro_pubkey, + message_json, None, + false, ) - .await?; + .await; - // Extract (Message, u64) tuples from Event::MessageTuple variants - for event in events { - if let Event::MessageTuple(tuple) = event { - let message = tuple.0.get_inner_message_kind(); - if message.request_id == Some(request_id) { - let _ = print_commands_results(message, Some(order.clone()), ctx).await; - } + // Wait for incoming DM + let recv_event = wait_for_dm(ctx).await?; + println!("Recv event: {:?}", recv_event); + let messages = parse_dm_events(recv_event, &trade_keys, None).await; + if let Some(message) = messages.first() { + let message = message.0.get_inner_message_kind(); + println!("Message: {:?}", message); + if message.request_id == Some(request_id) { + let _ = print_commands_results(message, None, ctx).await; } } } diff --git a/src/cli/take_order.rs b/src/cli/take_order.rs index a8f6975..b4e5242 100644 --- a/src/cli/take_order.rs +++ b/src/cli/take_order.rs @@ -7,7 +7,8 @@ use uuid::Uuid; use crate::cli::Context; use crate::lightning::is_valid_invoice; use crate::parser::dms::print_commands_results; -use crate::util::{fetch_events_list, send_dm, Event, ListKind}; +use crate::parser::parse_dm_events; +use crate::util::{send_dm, wait_for_dm}; /// Create payload based on action type and parameters fn create_take_order_payload( @@ -92,38 +93,30 @@ pub async fn execute_take_order( .as_json() .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?; - // Clone the keys and client for the async call - let identity_keys_clone = ctx.identity_keys.clone(); - let trade_keys_clone = ctx.trade_keys.clone(); - let client_clone = ctx.client.clone(); - let mostro_pubkey_clone = ctx.mostro_pubkey; - - // Spawn a new task to send the DM + // Send the DM // This is so we can wait for the gift wrap event in the main thread - tokio::spawn(async move { - let _ = send_dm( - &client_clone, - Some(&identity_keys_clone), - &trade_keys_clone, - &mostro_pubkey_clone, - message_json, - None, - false, - ) - .await; - }); + let _ = send_dm( + &ctx.client, + Some(&ctx.identity_keys), + &ctx.trade_keys, + &ctx.mostro_pubkey, + message_json, + None, + false, + ) + .await; - let events = - fetch_events_list(ListKind::WaitForUpdate, None, None, None, ctx, None, None).await?; + // Wait for the DM to be sent from mostro + let recv_event = wait_for_dm(ctx).await?; - // Extract (Message, u64) tuples from Event::MessageTuple variants - for event in events { - if let Event::MessageTuple(tuple) = event { - let message = tuple.0.get_inner_message_kind(); - if message.request_id == Some(request_id) { - let _ = print_commands_results(message, None, ctx).await; - } + // Parse the incoming DM + let messages = parse_dm_events(recv_event, &ctx.trade_keys, None).await; + if let Some(message) = messages.first() { + let message = message.0.get_inner_message_kind(); + if message.request_id == Some(request_id) { + let _ = print_commands_results(message, None, ctx).await; } } + Ok(()) } diff --git a/src/parser/dms.rs b/src/parser/dms.rs index 30dd479..68134ea 100644 --- a/src/parser/dms.rs +++ b/src/parser/dms.rs @@ -165,7 +165,7 @@ pub async fn print_commands_results( } Action::FiatSentOk => { if let Some(order_id) = &message.id { - println!("Fiat sent message for order {} received", order_id); + println!("Fiat sent message for order {:?} received", order_id); println!("Waiting for sats release from seller"); Ok(()) } else { diff --git a/src/util.rs b/src/util.rs index b97e492..31d1d2d 100644 --- a/src/util.rs +++ b/src/util.rs @@ -32,7 +32,6 @@ pub enum ListKind { DirectMessagesUser, DirectMessagesAdmin, PrivateDirectMessagesUser, - WaitForUpdate, } async fn send_gift_wrap_dm_internal( @@ -131,166 +130,49 @@ pub async fn save_order( } /// Wait for incoming gift wraps or events coming in -pub async fn wait_for_dm( - client: &Client, - trade_keys: &Keys, - request_id: u64, - trade_index: Option, - mut order: Option, - pool: &SqlitePool, -) -> anyhow::Result<()> { - let mut notifications = client.notifications(); - - match tokio::time::timeout(FETCH_EVENTS_TIMEOUT, async move { - while let Ok(notification) = notifications.recv().await { - if let RelayPoolNotification::Event { event, .. } = notification { - if event.kind == nostr_sdk::Kind::GiftWrap { - let gift = match nip59::extract_rumor(trade_keys, &event).await { - Ok(gift) => gift, - Err(e) => { - println!("Failed to extract rumor: {}", e); - continue; - } - }; - let (message, _): (Message, Option) = match serde_json::from_str(&gift.rumor.content) { - Ok(msg) => msg, - Err(e) => { - println!("Failed to deserialize message: {}", e); - continue; - } - }; - let message = message.get_inner_message_kind(); - if message.request_id == Some(request_id) { - match message.action { - Action::NewOrder => { - if let Some(Payload::Order(order)) = message.payload.as_ref() { - if let Err(e) = save_order(order.clone(), trade_keys, request_id, trade_index, pool).await { - println!("Failed to save order: {}", e); - return Err(()); - } - return Ok(()); - } - } - // this is the case where the buyer adds an invoice to a takesell order - Action::WaitingSellerToPay => { - println!("Now we should wait for the seller to pay the invoice"); - if let Some(mut order) = order.take() { - match order - .set_status(Status::WaitingPayment.to_string()) - .save(pool) - .await - { - Ok(_) => println!("Order status updated"), - Err(e) => println!("Failed to update order status: {}", e), - } - return Ok(()); - } - } - // this is the case where the buyer adds an invoice to a takesell order - Action::AddInvoice => { - if let Some(Payload::Order(order)) = &message.payload { - println!( - "Please add a lightning invoice with amount of {}", - order.amount - ); - // Save the order - if let Err(e) = save_order(order.clone(), trade_keys, request_id, trade_index, pool).await { - println!("Failed to save order: {}", e); - return Err(()); - } - return Ok(()); - } - } - // this is the case where the buyer pays the invoice coming from a takebuy - Action::PayInvoice => { - if let Some(Payload::PaymentRequest(order, invoice, _)) = &message.payload { - println!( - "Mostro sent you this hold invoice for order id: {}", - order - .as_ref() - .and_then(|o| o.id) - .map_or("unknown".to_string(), |id| id.to_string()) - ); - println!(); - println!("Pay this invoice to continue --> {}", invoice); - println!(); - if let Some(order) = order { - let store_order = order.clone(); - // Save the order - if let Err(e) = save_order(store_order, trade_keys, request_id, trade_index, pool).await { - println!("Failed to save order: {}", e); - return Err(()); - } - } - return Ok(()); - } - } - Action::CantDo => { - match message.payload { - Some(Payload::CantDo(Some(CantDoReason::OutOfRangeFiatAmount | CantDoReason::OutOfRangeSatsAmount))) => { - println!("Error: Amount is outside the allowed range. Please check the order's min/max limits."); - return Err(()); - } - Some(Payload::CantDo(Some(CantDoReason::PendingOrderExists))) => { - println!("Error: A pending order already exists. Please wait for it to be filled or canceled."); - return Err(()); - } - Some(Payload::CantDo(Some(CantDoReason::InvalidTradeIndex))) => { - println!("Error: Invalid trade index. Please synchronize the trade index with mostro"); - return Err(()); - } - _ => { - println!("Unknown reason: {:?}", message.payload); - return Err(()); - } - } - } - // this is the case where the user cancels the order - Action::Canceled => { - if let Some(order_id) = &message.id { - // Acquire database connection - // Verify order exists before deletion - if Order::get_by_id(pool, &order_id.to_string()).await.is_ok() { - if let Err(e) = Order::delete_by_id(pool, &order_id.to_string()).await { - println!("Failed to delete order: {}", e); - return Err(()); - } - // Release database connection - println!("Order {} canceled!", order_id); - return Ok(()); - } else { - println!("Order not found: {}", order_id); - return Err(()); - } - } +pub async fn wait_for_dm(ctx: &Context) -> anyhow::Result { + // Create subscription + let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::WaitForEvents(1)); + // Subscribe to gift wrap events - ONLY NEW ONES WITH LIMIT 0 + let subscription = Filter::new() + .pubkey(ctx.trade_keys.public_key()) + .kind(nostr_sdk::Kind::GiftWrap) + .limit(0); + // Subscribe to subscription with exit policy of just waiting for 1 event + ctx.client.subscribe(subscription, Some(opts)).await?; + + // Get notifications from client + let mut notifications = ctx.client.notifications(); + + // Wait for event + let event = tokio::time::timeout(FETCH_EVENTS_TIMEOUT, async move { + loop { + match notifications.recv().await { + Ok(notification) => { + match notification { + RelayPoolNotification::Event { event, .. } => { + // Return event + return Ok(*event); } - Action::Rate => { - println!("Sats released!"); - println!("You can rate the counterpart now"); - return Ok(()); + _ => { + // Continue waiting for a valid event + continue; } - Action::FiatSentOk => { - if let Some(order_id) = &message.id { - println!("Fiat sent message for order {} received", order_id); - println!("Waiting for sats release from seller"); - return Ok(()); - } - } - _ => {} - } } } + Err(e) => { + return Err(anyhow::anyhow!("Error receiving notification: {:?}", e)); + } + } } - } - Ok(()) }) - .await { - Ok(result) => match result { - Ok(()) => Ok(()), - Err(()) => Err(anyhow::anyhow!("Error in timeout closure")), - }, - Err(_) => Err(anyhow::anyhow!("Timeout waiting for DM or gift wrap event")) - } + .await? + .map_err(|_| anyhow::anyhow!("Timeout waiting for DM or gift wrap event"))?; + + // Convert event to events + let mut events = Events::default(); + events.insert(event); + Ok(events) } #[derive(Debug, Clone, Copy)] @@ -538,11 +420,6 @@ pub fn create_filter( .pubkey(pubkey) .since(fake_timestamp)) } - ListKind::WaitForUpdate => Ok(Filter::new() - .kind(nostr_sdk::Kind::GiftWrap) - .pubkey(pubkey) - .limit(0) - .since(Timestamp::from(chrono::Utc::now().timestamp() as u64))), ListKind::PrivateDirectMessagesUser => { // Get since from cli or use 30 minutes default let since = if let Some(mins) = since { @@ -572,7 +449,6 @@ pub async fn fetch_events_list( currency: Option, kind: Option, ctx: &Context, - specific_trade_key: Option<&Keys>, since: Option<&i64>, ) -> Result> { match list_kind { @@ -585,24 +461,6 @@ pub async fn fetch_events_list( let orders = parse_orders_events(fetched_events, currency, status, kind); Ok(orders.into_iter().map(Event::SmallOrder).collect()) } - ListKind::WaitForUpdate => { - // get trade key from previous order if specic tradey is Some or get trade key from ctx - let trade_key = match specific_trade_key { - Some(key) => key.clone(), - None => ctx.trade_keys.clone(), - }; - - let filters = create_filter(list_kind, trade_key.public_key(), None)?; - let fetched_event = ctx - .client - .fetch_events(filters, FETCH_EVENTS_TIMEOUT) - .await?; - let message = parse_dm_events(fetched_event, &trade_key, None).await; - Ok(message - .into_iter() - .map(|(message, timestamp, _)| Event::MessageTuple(Box::new((message, timestamp)))) - .collect()) - } ListKind::DirectMessagesAdmin => { let filters = create_filter(list_kind, ctx.mostro_pubkey, None)?; let fetched_events = ctx From 4fcb3bf16caf73e45af5cf58f19e82ca26f0547d Mon Sep 17 00:00:00 2001 From: arkanoider Date: Sun, 5 Oct 2025 23:17:03 +0200 Subject: [PATCH 06/23] fix: some fixes on message management --- src/cli/add_invoice.rs | 6 ++++-- src/cli/new_order.rs | 6 ++++-- src/cli/send_msg.rs | 16 ++++++++-------- src/cli/take_order.rs | 7 ++++--- src/parser/dms.rs | 39 ++++++++++++++++++++++++++++++++------- src/util.rs | 16 +++++----------- 6 files changed, 57 insertions(+), 33 deletions(-) diff --git a/src/cli/add_invoice.rs b/src/cli/add_invoice.rs index a69f2af..6ea9687 100644 --- a/src/cli/add_invoice.rs +++ b/src/cli/add_invoice.rs @@ -67,14 +67,16 @@ pub async fn execute_add_invoice(order_id: &Uuid, invoice: &str, ctx: &Context) .await; // Wait for the DM to be sent from mostro - let recv_event = wait_for_dm(ctx).await?; + let recv_event = wait_for_dm(ctx, Some(&order_trade_keys)).await?; // Parse the incoming DM let messages = parse_dm_events(recv_event, &order_trade_keys, None).await; if let Some(message) = messages.first() { let message = message.0.get_inner_message_kind(); if message.request_id == Some(request_id) { - let _ = print_commands_results(message, Some(order.clone()), ctx).await; + if let Err(e) = print_commands_results(message, Some(order.clone()), ctx).await { + println!("Error in print_commands_results: {}", e); + } } } diff --git a/src/cli/new_order.rs b/src/cli/new_order.rs index 3ae1b3e..06faa21 100644 --- a/src/cli/new_order.rs +++ b/src/cli/new_order.rs @@ -142,14 +142,16 @@ pub async fn execute_new_order( .await; // Wait for the DM to be sent from mostro - let recv_event = wait_for_dm(ctx).await?; + let recv_event = wait_for_dm(ctx, None).await?; // Parse the incoming DM let messages = parse_dm_events(recv_event, &ctx.trade_keys, None).await; if let Some(message) = messages.first() { let message = message.0.get_inner_message_kind(); if message.request_id == Some(request_id) { - let _ = print_commands_results(message, None, ctx).await; + if let Err(e) = print_commands_results(message, None, ctx).await { + println!("Error in print_commands_results: {}", e); + } } } diff --git a/src/cli/send_msg.rs b/src/cli/send_msg.rs index 202b5c9..2d2441f 100644 --- a/src/cli/send_msg.rs +++ b/src/cli/send_msg.rs @@ -29,12 +29,13 @@ pub async fn execute_send_msg( } }; - println!( - "Sending {} command for order {:#?} to mostro pubId {}", - requested_action, - order_id.as_ref(), - &ctx.mostro_pubkey - ); + match order_id { + Some(id) => println!( + "Sending {} command for order {} to mostro pubId {}", + requested_action, id, ctx.mostro_pubkey + ), + None => return Err(anyhow::anyhow!("Missing order id!")), + }; // Determine payload let payload = match requested_action { @@ -85,8 +86,7 @@ pub async fn execute_send_msg( .await; // Wait for incoming DM - let recv_event = wait_for_dm(ctx).await?; - println!("Recv event: {:?}", recv_event); + let recv_event = wait_for_dm(ctx, Some(&trade_keys)).await?; let messages = parse_dm_events(recv_event, &trade_keys, None).await; if let Some(message) = messages.first() { let message = message.0.get_inner_message_kind(); diff --git a/src/cli/take_order.rs b/src/cli/take_order.rs index b4e5242..d94ff5c 100644 --- a/src/cli/take_order.rs +++ b/src/cli/take_order.rs @@ -107,16 +107,17 @@ pub async fn execute_take_order( .await; // Wait for the DM to be sent from mostro - let recv_event = wait_for_dm(ctx).await?; + let recv_event = wait_for_dm(ctx, None).await?; // Parse the incoming DM let messages = parse_dm_events(recv_event, &ctx.trade_keys, None).await; if let Some(message) = messages.first() { let message = message.0.get_inner_message_kind(); if message.request_id == Some(request_id) { - let _ = print_commands_results(message, None, ctx).await; + if let Err(e) = print_commands_results(message, None, ctx).await { + println!("Error in print_commands_results: {}", e); + } } } - Ok(()) } diff --git a/src/parser/dms.rs b/src/parser/dms.rs index 68134ea..5dd256c 100644 --- a/src/parser/dms.rs +++ b/src/parser/dms.rs @@ -30,7 +30,7 @@ pub async fn print_commands_results( order.clone(), &ctx.trade_keys, req_id, - Some(ctx.trade_index), + ctx.trade_index, &ctx.pool, ) .await @@ -71,8 +71,14 @@ pub async fn print_commands_results( ); if let Some(req_id) = message.request_id { // Save the order - if let Err(e) = - save_order(order.clone(), &ctx.trade_keys, req_id, None, &ctx.pool).await + if let Err(e) = save_order( + order.clone(), + &ctx.trade_keys, + req_id, + ctx.trade_index, + &ctx.pool, + ) + .await { return Err(anyhow::anyhow!("Failed to save order: {}", e)); } @@ -105,7 +111,7 @@ pub async fn print_commands_results( store_order, &ctx.trade_keys, req_id, - Some(ctx.trade_index), + ctx.trade_index, &ctx.pool, ) .await @@ -131,9 +137,28 @@ pub async fn print_commands_results( Some(Payload::CantDo(Some(CantDoReason::PendingOrderExists))) => Err(anyhow::anyhow!( "A pending order already exists. Please wait for it to be filled or canceled." )), - Some(Payload::CantDo(Some(CantDoReason::InvalidTradeIndex))) => Err(anyhow::anyhow!( - "Invalid trade index. Please synchronize the trade index with mostro" - )), + Some(Payload::CantDo(Some(CantDoReason::InvalidTradeIndex))) => { + if let Some(order_id) = message.id { + let _ = Order::delete_by_id(&ctx.pool, &order_id.to_string()).await; + } + // Workaround to update the trade index if mostro is sending this error + match User::get(&ctx.pool).await { + Ok(mut user) => { + let new_trade_index = ctx.trade_index + 1; + user.set_last_trade_index(new_trade_index); + if let Err(e) = user.save(&ctx.pool).await { + println!("Failed to update user trade index to continue: {}", e); + } + } + Err(e) => println!( + "Failed to get user to update trade index to continue: {}", + e + ), + } + Err(anyhow::anyhow!( + "Invalid trade index. I have incremented the trade index to the next one to continue - try again to repeat command!" + )) + } _ => Err(anyhow::anyhow!("Unknown reason: {:?}", message.payload)), }, // this is the case where the user cancels the order diff --git a/src/util.rs b/src/util.rs index 31d1d2d..f1f03f2 100644 --- a/src/util.rs +++ b/src/util.rs @@ -97,7 +97,7 @@ pub async fn save_order( order: SmallOrder, trade_keys: &Keys, request_id: u64, - trade_index: Option, + trade_index: i64, pool: &SqlitePool, ) -> Result<()> { if let Ok(order) = Order::new(pool, order, trade_keys, Some(request_id as i64)).await { @@ -106,14 +106,6 @@ pub async fn save_order( } else { println!("Warning: The newly created order has no ID."); } - // Get trade index - we must have it - let trade_index = if let Some(trade_index) = trade_index { - trade_index - } else { - return Err(anyhow::anyhow!( - "No trade index found for new order, this should never happen" - )); - }; // Update last trade index to be used in next trade match User::get(pool).await { @@ -130,12 +122,14 @@ pub async fn save_order( } /// Wait for incoming gift wraps or events coming in -pub async fn wait_for_dm(ctx: &Context) -> anyhow::Result { +pub async fn wait_for_dm(ctx: &Context, order_trade_keys: Option<&Keys>) -> anyhow::Result { + // Get correct trade keys to wait for + let trade_keys = order_trade_keys.unwrap_or(&ctx.trade_keys); // Create subscription let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::WaitForEvents(1)); // Subscribe to gift wrap events - ONLY NEW ONES WITH LIMIT 0 let subscription = Filter::new() - .pubkey(ctx.trade_keys.public_key()) + .pubkey(trade_keys.public_key()) .kind(nostr_sdk::Kind::GiftWrap) .limit(0); // Subscribe to subscription with exit policy of just waiting for 1 event From 29baccbdf8d77f8a177d0b1b2ee52e235685fcc1 Mon Sep 17 00:00:00 2001 From: arkanoider Date: Tue, 7 Oct 2025 11:57:42 +0200 Subject: [PATCH 07/23] fix: added a check to avoid UNIQUE constraint error - now if an order is yet present in db we do an UPDATE sql and not INSERT --- src/cli/take_order.rs | 1 + src/db.rs | 50 +++++++++++++++++++++++++++++++++++++++++-- src/parser/dms.rs | 25 +++------------------- 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/src/cli/take_order.rs b/src/cli/take_order.rs index d94ff5c..6675e78 100644 --- a/src/cli/take_order.rs +++ b/src/cli/take_order.rs @@ -72,6 +72,7 @@ pub async fn execute_take_order( // Create payload based on action type let payload = create_take_order_payload(action.clone(), invoice, amount)?; + // Create request id let request_id = Uuid::new_v4().as_u128() as u64; // Create message diff --git a/src/db.rs b/src/db.rs index f5d9e04..f18dc33 100644 --- a/src/db.rs +++ b/src/db.rs @@ -306,7 +306,8 @@ impl Order { expires_at: None, }; - sqlx::query( + // Try insert; if id already exists, perform an update instead + let insert_result = sqlx::query( r#" INSERT INTO orders (id, kind, status, amount, min_amount, max_amount, fiat_code, fiat_amount, payment_method, premium, trade_keys, @@ -332,7 +333,52 @@ impl Order { .bind(order.created_at) .bind(order.expires_at) .execute(pool) - .await?; + .await; + + if let Err(e) = insert_result { + // If the error is due to unique constraint (id already present), update instead + // SQLite uses error code 1555 (constraint failed) or 2067 (unique constraint failed) + let is_unique_violation = match e.as_database_error() { + Some(db_err) => { + let code = db_err.code().map(|c| c.to_string()).unwrap_or_default(); + code == "1555" || code == "2067" + } + None => false, + }; + + if is_unique_violation { + sqlx::query( + r#" + UPDATE orders + SET kind = ?, status = ?, amount = ?, min_amount = ?, max_amount = ?, + fiat_code = ?, fiat_amount = ?, payment_method = ?, premium = ?, trade_keys = ?, + counterparty_pubkey = ?, is_mine = ?, buyer_invoice = ?, request_id = ?, created_at = ?, expires_at = ? + WHERE id = ? + "#, + ) + .bind(&order.kind) + .bind(&order.status) + .bind(order.amount) + .bind(order.min_amount) + .bind(order.max_amount) + .bind(&order.fiat_code) + .bind(order.fiat_amount) + .bind(&order.payment_method) + .bind(order.premium) + .bind(&order.trade_keys) + .bind(&order.counterparty_pubkey) + .bind(order.is_mine) + .bind(&order.buyer_invoice) + .bind(order.request_id) + .bind(order.created_at) + .bind(order.expires_at) + .bind(&order.id) + .execute(pool) + .await?; + } else { + return Err(e.into()); + } + } Ok(order) } diff --git a/src/parser/dms.rs b/src/parser/dms.rs index 5dd256c..92591ef 100644 --- a/src/parser/dms.rs +++ b/src/parser/dms.rs @@ -137,28 +137,9 @@ pub async fn print_commands_results( Some(Payload::CantDo(Some(CantDoReason::PendingOrderExists))) => Err(anyhow::anyhow!( "A pending order already exists. Please wait for it to be filled or canceled." )), - Some(Payload::CantDo(Some(CantDoReason::InvalidTradeIndex))) => { - if let Some(order_id) = message.id { - let _ = Order::delete_by_id(&ctx.pool, &order_id.to_string()).await; - } - // Workaround to update the trade index if mostro is sending this error - match User::get(&ctx.pool).await { - Ok(mut user) => { - let new_trade_index = ctx.trade_index + 1; - user.set_last_trade_index(new_trade_index); - if let Err(e) = user.save(&ctx.pool).await { - println!("Failed to update user trade index to continue: {}", e); - } - } - Err(e) => println!( - "Failed to get user to update trade index to continue: {}", - e - ), - } - Err(anyhow::anyhow!( - "Invalid trade index. I have incremented the trade index to the next one to continue - try again to repeat command!" - )) - } + Some(Payload::CantDo(Some(CantDoReason::InvalidTradeIndex))) => Err(anyhow::anyhow!( + "Invalid trade index. Please synchronize the trade index with mostro" + )), _ => Err(anyhow::anyhow!("Unknown reason: {:?}", message.payload)), }, // this is the case where the user cancels the order From ac3f69b9fe19e4d1579f743b1139abbc41aac57b Mon Sep 17 00:00:00 2001 From: arkanoider Date: Tue, 7 Oct 2025 23:09:55 +0200 Subject: [PATCH 08/23] feat: add invalid fiat currency managed --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- src/parser/dms.rs | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 121be6f..7f04176 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1770,9 +1770,9 @@ dependencies = [ [[package]] name = "mostro-core" -version = "0.6.50" +version = "0.6.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8bf32904269e30059c5354a3f379a90ebb6afa7355995d624ec6403062ccd47" +checksum = "86d4f49998d23c7168145336513292a82a85f92ba98a59c55196417fbf40d864" dependencies = [ "argon2", "base64 0.22.1", diff --git a/Cargo.toml b/Cargo.toml index a60b21c..3900ebc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ uuid = { version = "1.18.1", features = [ dotenvy = "0.15.6" lightning-invoice = { version = "0.33.2", features = ["std"] } reqwest = { version = "0.12.23", features = ["json"] } -mostro-core = "0.6.50" +mostro-core = "0.6.55" lnurl-rs = "0.9.0" pretty_env_logger = "0.5.0" openssl = { version = "0.10.73", features = ["vendored"] } diff --git a/src/parser/dms.rs b/src/parser/dms.rs index 92591ef..3dd45fb 100644 --- a/src/parser/dms.rs +++ b/src/parser/dms.rs @@ -140,6 +140,10 @@ pub async fn print_commands_results( Some(Payload::CantDo(Some(CantDoReason::InvalidTradeIndex))) => Err(anyhow::anyhow!( "Invalid trade index. Please synchronize the trade index with mostro" )), + Some(Payload::CantDo(Some(CantDoReason::InvalidFiatCurrency))) => Err(anyhow::anyhow!( + " + Invalid currency" + )), _ => Err(anyhow::anyhow!("Unknown reason: {:?}", message.payload)), }, // this is the case where the user cancels the order From 60e7638f32d61d5062abe2f26c271c9605db7a20 Mon Sep 17 00:00:00 2001 From: arkanoider Date: Tue, 7 Oct 2025 23:49:37 +0200 Subject: [PATCH 09/23] chore: rabbit fixes --- src/cli/add_invoice.rs | 4 ++-- src/cli/send_msg.rs | 4 ++-- src/cli/take_order.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/cli/add_invoice.rs b/src/cli/add_invoice.rs index 6ea9687..b32641f 100644 --- a/src/cli/add_invoice.rs +++ b/src/cli/add_invoice.rs @@ -55,7 +55,7 @@ pub async fn execute_add_invoice(order_id: &Uuid, invoice: &str, ctx: &Context) .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?; // Send the DM - let _ = send_dm( + send_dm( &ctx.client, Some(&ctx.identity_keys), &order_trade_keys, @@ -64,7 +64,7 @@ pub async fn execute_add_invoice(order_id: &Uuid, invoice: &str, ctx: &Context) None, false, ) - .await; + .await?; // Wait for the DM to be sent from mostro let recv_event = wait_for_dm(ctx, Some(&order_trade_keys)).await?; diff --git a/src/cli/send_msg.rs b/src/cli/send_msg.rs index 2d2441f..27852ad 100644 --- a/src/cli/send_msg.rs +++ b/src/cli/send_msg.rs @@ -74,7 +74,7 @@ pub async fn execute_send_msg( .map_err(|e| anyhow::anyhow!("Failed to serialize message: {e}"))?; // Send DM - let _ = send_dm( + send_dm( &ctx.client, Some(&ctx.identity_keys), &trade_keys, @@ -83,7 +83,7 @@ pub async fn execute_send_msg( None, false, ) - .await; + .await?; // Wait for incoming DM let recv_event = wait_for_dm(ctx, Some(&trade_keys)).await?; diff --git a/src/cli/take_order.rs b/src/cli/take_order.rs index 6675e78..ce6f089 100644 --- a/src/cli/take_order.rs +++ b/src/cli/take_order.rs @@ -96,7 +96,7 @@ pub async fn execute_take_order( // Send the DM // This is so we can wait for the gift wrap event in the main thread - let _ = send_dm( + send_dm( &ctx.client, Some(&ctx.identity_keys), &ctx.trade_keys, @@ -105,7 +105,7 @@ pub async fn execute_take_order( None, false, ) - .await; + .await?; // Wait for the DM to be sent from mostro let recv_event = wait_for_dm(ctx, None).await?; From 47ff00e0bd6bdaeaf2b7431b024517e1f27e6eb5 Mon Sep 17 00:00:00 2001 From: arkanoider Date: Tue, 7 Oct 2025 23:50:54 +0200 Subject: [PATCH 10/23] chore: rabbit fixes --- src/cli/new_order.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/new_order.rs b/src/cli/new_order.rs index 06faa21..6411b59 100644 --- a/src/cli/new_order.rs +++ b/src/cli/new_order.rs @@ -130,7 +130,7 @@ pub async fn execute_new_order( .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?; // Send the DM - let _ = send_dm( + send_dm( &ctx.client, Some(&ctx.identity_keys), &ctx.trade_keys, @@ -139,7 +139,7 @@ pub async fn execute_new_order( None, false, ) - .await; + .await?; // Wait for the DM to be sent from mostro let recv_event = wait_for_dm(ctx, None).await?; From d9d356a86300ebb53d5206ff1a845c5538840581 Mon Sep 17 00:00:00 2001 From: arkanoider Date: Wed, 8 Oct 2025 11:35:40 +0200 Subject: [PATCH 11/23] feat: added correct since time calculation for get dm command - improved code readability for new order management in db.rs --- src/db.rs | 122 +++++++++++++++++++++++++--------------------- src/parser/dms.rs | 8 ++- 2 files changed, 74 insertions(+), 56 deletions(-) diff --git a/src/db.rs b/src/db.rs index f18dc33..4493af7 100644 --- a/src/db.rs +++ b/src/db.rs @@ -307,33 +307,7 @@ impl Order { }; // Try insert; if id already exists, perform an update instead - let insert_result = sqlx::query( - r#" - INSERT INTO orders (id, kind, status, amount, min_amount, max_amount, - fiat_code, fiat_amount, payment_method, premium, trade_keys, - counterparty_pubkey, is_mine, buyer_invoice, request_id, created_at, expires_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - "#, - ) - .bind(&order.id) - .bind(&order.kind) - .bind(&order.status) - .bind(order.amount) - .bind(order.min_amount) - .bind(order.max_amount) - .bind(&order.fiat_code) - .bind(order.fiat_amount) - .bind(&order.payment_method) - .bind(order.premium) - .bind(&order.trade_keys) - .bind(&order.counterparty_pubkey) - .bind(order.is_mine) - .bind(&order.buyer_invoice) - .bind(order.request_id) - .bind(order.created_at) - .bind(order.expires_at) - .execute(pool) - .await; + let insert_result = order.insert_db(pool).await; if let Err(e) = insert_result { // If the error is due to unique constraint (id already present), update instead @@ -347,34 +321,7 @@ impl Order { }; if is_unique_violation { - sqlx::query( - r#" - UPDATE orders - SET kind = ?, status = ?, amount = ?, min_amount = ?, max_amount = ?, - fiat_code = ?, fiat_amount = ?, payment_method = ?, premium = ?, trade_keys = ?, - counterparty_pubkey = ?, is_mine = ?, buyer_invoice = ?, request_id = ?, created_at = ?, expires_at = ? - WHERE id = ? - "#, - ) - .bind(&order.kind) - .bind(&order.status) - .bind(order.amount) - .bind(order.min_amount) - .bind(order.max_amount) - .bind(&order.fiat_code) - .bind(order.fiat_amount) - .bind(&order.payment_method) - .bind(order.premium) - .bind(&order.trade_keys) - .bind(&order.counterparty_pubkey) - .bind(order.is_mine) - .bind(&order.buyer_invoice) - .bind(order.request_id) - .bind(order.created_at) - .bind(order.expires_at) - .bind(&order.id) - .execute(pool) - .await?; + order.update_db(pool).await?; } else { return Err(e.into()); } @@ -383,6 +330,71 @@ impl Order { Ok(order) } + async fn insert_db(&self, pool: &SqlitePool) -> Result<(), sqlx::Error> { + sqlx::query( + r#" + INSERT INTO orders (id, kind, status, amount, min_amount, max_amount, + fiat_code, fiat_amount, payment_method, premium, trade_keys, + counterparty_pubkey, is_mine, buyer_invoice, request_id, created_at, expires_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + "#, + ) + .bind(&self.id) + .bind(&self.kind) + .bind(&self.status) + .bind(self.amount) + .bind(self.min_amount) + .bind(self.max_amount) + .bind(&self.fiat_code) + .bind(self.fiat_amount) + .bind(&self.payment_method) + .bind(self.premium) + .bind(&self.trade_keys) + .bind(&self.counterparty_pubkey) + .bind(self.is_mine) + .bind(&self.buyer_invoice) + .bind(self.request_id) + .bind(self.created_at) + .bind(self.expires_at) + .execute(pool) + .await? + .rows_affected(); + Ok(()) + } + + async fn update_db(&self, pool: &SqlitePool) -> Result<(), sqlx::Error> { + sqlx::query( + r#" + UPDATE orders + SET kind = ?, status = ?, amount = ?, min_amount = ?, max_amount = ?, + fiat_code = ?, fiat_amount = ?, payment_method = ?, premium = ?, trade_keys = ?, + counterparty_pubkey = ?, is_mine = ?, buyer_invoice = ?, request_id = ?, created_at = ?, expires_at = ? + WHERE id = ? + "#, + ) + .bind(&self.kind) + .bind(&self.status) + .bind(self.amount) + .bind(self.min_amount) + .bind(self.max_amount) + .bind(&self.fiat_code) + .bind(self.fiat_amount) + .bind(&self.payment_method) + .bind(self.premium) + .bind(&self.trade_keys) + .bind(&self.counterparty_pubkey) + .bind(self.is_mine) + .bind(&self.buyer_invoice) + .bind(self.request_id) + .bind(self.created_at) + .bind(self.expires_at) + .bind(&self.id) + .execute(pool) + .await? + .rows_affected(); + Ok(()) + } + // Setters encadenables pub fn set_kind(&mut self, kind: String) -> &mut Self { self.kind = Some(kind); diff --git a/src/parser/dms.rs b/src/parser/dms.rs index 3dd45fb..75cca98 100644 --- a/src/parser/dms.rs +++ b/src/parser/dms.rs @@ -256,7 +256,13 @@ pub async fn parse_dm_events( }; // check if the message is older than the since time if it is, skip it if let Some(since_time) = since { - if created_at.as_u64() < *since_time as u64 { + // Calculate since time from now in minutes subtracting the since time + let since_time = chrono::Utc::now() + .checked_sub_signed(chrono::Duration::minutes(*since_time)) + .unwrap() + .timestamp() as u64; + + if created_at.as_u64() < since_time { continue; } } From 807ce792d71e9e597c66b59ca109fd3a14bdcc55 Mon Sep 17 00:00:00 2001 From: arkanoider Date: Wed, 8 Oct 2025 11:44:06 +0200 Subject: [PATCH 12/23] chore: added trade index number printout in new order and take order commands --- src/cli/new_order.rs | 3 ++- src/cli/take_order.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/cli/new_order.rs b/src/cli/new_order.rs index 6411b59..cb4aff9 100644 --- a/src/cli/new_order.rs +++ b/src/cli/new_order.rs @@ -120,7 +120,8 @@ pub async fn execute_new_order( // Send dm to receiver pubkey println!( - "SENDING DM with trade keys: {:?}", + "SENDING DM with trade index: {} and trade keys: {:?}", + ctx.trade_index, ctx.trade_keys.public_key().to_hex() ); diff --git a/src/cli/take_order.rs b/src/cli/take_order.rs index ce6f089..30b56a4 100644 --- a/src/cli/take_order.rs +++ b/src/cli/take_order.rs @@ -86,7 +86,8 @@ pub async fn execute_take_order( // Send dm to receiver pubkey println!( - "SENDING DM with trade keys: {:?}", + "SENDING DM with trade index: {} and trade keys: {:?}", + ctx.trade_index, ctx.trade_keys.public_key().to_hex() ); From 08ea97e58547cad92e04abb394eac969bed57439 Mon Sep 17 00:00:00 2001 From: arkanoider <113362043+arkanoider@users.noreply.github.com> Date: Wed, 8 Oct 2025 11:54:28 +0200 Subject: [PATCH 13/23] chore: rabbit improvement Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/cli/take_order.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/cli/take_order.rs b/src/cli/take_order.rs index 30b56a4..7a7baa9 100644 --- a/src/cli/take_order.rs +++ b/src/cli/take_order.rs @@ -113,13 +113,21 @@ pub async fn execute_take_order( // Parse the incoming DM let messages = parse_dm_events(recv_event, &ctx.trade_keys, None).await; - if let Some(message) = messages.first() { + if let Some((message, _, _)) = messages.first() { let message = message.0.get_inner_message_kind(); if message.request_id == Some(request_id) { - if let Err(e) = print_commands_results(message, None, ctx).await { - println!("Error in print_commands_results: {}", e); - } + print_commands_results(message, None, ctx).await?; + } else { + return Err(anyhow::anyhow!( + "Received response with mismatched request_id. Expected: {}, Got: {:?}", + request_id, + message.request_id + )); } + } else { + return Err(anyhow::anyhow!( + "No valid response received from Mostro after taking order" + )); } Ok(()) } From 07e416566f9d2fee3d5eb497540e52cf46997b78 Mon Sep 17 00:00:00 2001 From: arkanoider <113362043+arkanoider@users.noreply.github.com> Date: Wed, 8 Oct 2025 11:54:57 +0200 Subject: [PATCH 14/23] chore: rabbit improvement Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/cli/new_order.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/cli/new_order.rs b/src/cli/new_order.rs index cb4aff9..0d8d282 100644 --- a/src/cli/new_order.rs +++ b/src/cli/new_order.rs @@ -147,13 +147,21 @@ pub async fn execute_new_order( // Parse the incoming DM let messages = parse_dm_events(recv_event, &ctx.trade_keys, None).await; - if let Some(message) = messages.first() { + if let Some((message, _, _)) = messages.first() { let message = message.0.get_inner_message_kind(); if message.request_id == Some(request_id) { - if let Err(e) = print_commands_results(message, None, ctx).await { - println!("Error in print_commands_results: {}", e); - } + print_commands_results(message, None, ctx).await?; + } else { + return Err(anyhow::anyhow!( + "Received response with mismatched request_id. Expected: {}, Got: {:?}", + request_id, + message.request_id + )); } + } else { + return Err(anyhow::anyhow!( + "No valid response received from Mostro after sending new order" + )); } Ok(()) From c55069ec41c5b95a44fd9239e60546d49e3649df Mon Sep 17 00:00:00 2001 From: arkanoider Date: Wed, 8 Oct 2025 11:58:38 +0200 Subject: [PATCH 15/23] chore: fix on rabbit improvements --- src/cli/new_order.rs | 2 +- src/cli/take_order.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/new_order.rs b/src/cli/new_order.rs index 0d8d282..2fda1e2 100644 --- a/src/cli/new_order.rs +++ b/src/cli/new_order.rs @@ -148,7 +148,7 @@ pub async fn execute_new_order( // Parse the incoming DM let messages = parse_dm_events(recv_event, &ctx.trade_keys, None).await; if let Some((message, _, _)) = messages.first() { - let message = message.0.get_inner_message_kind(); + let message = message.get_inner_message_kind(); if message.request_id == Some(request_id) { print_commands_results(message, None, ctx).await?; } else { diff --git a/src/cli/take_order.rs b/src/cli/take_order.rs index 7a7baa9..f77da09 100644 --- a/src/cli/take_order.rs +++ b/src/cli/take_order.rs @@ -114,7 +114,7 @@ pub async fn execute_take_order( // Parse the incoming DM let messages = parse_dm_events(recv_event, &ctx.trade_keys, None).await; if let Some((message, _, _)) = messages.first() { - let message = message.0.get_inner_message_kind(); + let message = message.get_inner_message_kind(); if message.request_id == Some(request_id) { print_commands_results(message, None, ctx).await?; } else { From 275facfe65211388736fc2314d76e164b2d8aa68 Mon Sep 17 00:00:00 2001 From: arkanoider Date: Thu, 9 Oct 2025 08:16:13 +0200 Subject: [PATCH 16/23] fix: moved some logic parts in wait dm function to avoid issues with unresponsive relays --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- src/cli/add_invoice.rs | 21 ++++++++++++++++----- src/util.rs | 7 +++---- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f04176..4534eea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1770,9 +1770,9 @@ dependencies = [ [[package]] name = "mostro-core" -version = "0.6.55" +version = "0.6.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d4f49998d23c7168145336513292a82a85f92ba98a59c55196417fbf40d864" +checksum = "10d9108f750c13c52c13c8cc1c447022f404222fa7cd06e576f398c38624bb55" dependencies = [ "argon2", "base64 0.22.1", @@ -1980,9 +1980,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.5.2+3.5.2" +version = "300.5.3+3.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d270b79e2926f5150189d475bc7e9d2c69f9c4697b185fa917d5a32b792d21b4" +checksum = "dc6bad8cd0233b63971e232cc9c5e83039375b8586d2312f31fda85db8f888c2" dependencies = [ "cc", ] diff --git a/Cargo.toml b/Cargo.toml index 3900ebc..3344a6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ uuid = { version = "1.18.1", features = [ dotenvy = "0.15.6" lightning-invoice = { version = "0.33.2", features = ["std"] } reqwest = { version = "0.12.23", features = ["json"] } -mostro-core = "0.6.55" +mostro-core = "0.6.56" lnurl-rs = "0.9.0" pretty_env_logger = "0.5.0" openssl = { version = "0.10.73", features = ["vendored"] } diff --git a/src/cli/add_invoice.rs b/src/cli/add_invoice.rs index b32641f..5c7595e 100644 --- a/src/cli/add_invoice.rs +++ b/src/cli/add_invoice.rs @@ -10,11 +10,14 @@ use std::str::FromStr; use uuid::Uuid; pub async fn execute_add_invoice(order_id: &Uuid, invoice: &str, ctx: &Context) -> Result<()> { + // Get order from order id let order = Order::get_by_id(&ctx.pool, &order_id.to_string()).await?; + // Get trade keys of specific order let trade_keys = order .trade_keys .clone() .ok_or(anyhow::anyhow!("Missing trade keys"))?; + let order_trade_keys = Keys::parse(&trade_keys)?; println!( "Order trade keys: {:?}", @@ -71,13 +74,21 @@ pub async fn execute_add_invoice(order_id: &Uuid, invoice: &str, ctx: &Context) // Parse the incoming DM let messages = parse_dm_events(recv_event, &order_trade_keys, None).await; - if let Some(message) = messages.first() { - let message = message.0.get_inner_message_kind(); + if let Some((message, _, _)) = messages.first() { + let message = message.get_inner_message_kind(); if message.request_id == Some(request_id) { - if let Err(e) = print_commands_results(message, Some(order.clone()), ctx).await { - println!("Error in print_commands_results: {}", e); - } + print_commands_results(message, None, ctx).await?; + } else { + return Err(anyhow::anyhow!( + "Received response with mismatched request_id. Expected: {}, Got: {:?}", + request_id, + message.request_id + )); } + } else { + return Err(anyhow::anyhow!( + "No valid response received from Mostro after adding invoice to order" + )); } Ok(()) diff --git a/src/util.rs b/src/util.rs index f1f03f2..73fe0ca 100644 --- a/src/util.rs +++ b/src/util.rs @@ -125,8 +125,10 @@ pub async fn save_order( pub async fn wait_for_dm(ctx: &Context, order_trade_keys: Option<&Keys>) -> anyhow::Result { // Get correct trade keys to wait for let trade_keys = order_trade_keys.unwrap_or(&ctx.trade_keys); + // Get notifications from client + let mut notifications = ctx.client.notifications(); // Create subscription - let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::WaitForEvents(1)); + let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::WaitForEventsAfterEOSE(1)); // Subscribe to gift wrap events - ONLY NEW ONES WITH LIMIT 0 let subscription = Filter::new() .pubkey(trade_keys.public_key()) @@ -135,9 +137,6 @@ pub async fn wait_for_dm(ctx: &Context, order_trade_keys: Option<&Keys>) -> anyh // Subscribe to subscription with exit policy of just waiting for 1 event ctx.client.subscribe(subscription, Some(opts)).await?; - // Get notifications from client - let mut notifications = ctx.client.notifications(); - // Wait for event let event = tokio::time::timeout(FETCH_EVENTS_TIMEOUT, async move { loop { From 61636b1fd3996ff19ae86f7b5f610a220ca042df Mon Sep 17 00:00:00 2001 From: arkanoider Date: Thu, 9 Oct 2025 10:56:49 +0200 Subject: [PATCH 17/23] feat: first testable mostro cli version with last_trade_index command to be tested - improved some part of code where messsage are received - tried to improve message reception with not responsive relays ( catrya issue ) --- src/cli.rs | 11 +++++-- src/cli/add_invoice.rs | 24 ++-------------- src/cli/last_trade_index.rs | 57 +++++++++++++++++++++++++++++++++++++ src/cli/new_order.rs | 22 ++------------ src/cli/send_msg.rs | 29 +++++++------------ src/cli/take_order.rs | 23 ++------------- src/parser/dms.rs | 17 +++++++++++ src/util.rs | 32 +++++++++++++++++++-- 8 files changed, 131 insertions(+), 84 deletions(-) create mode 100644 src/cli/last_trade_index.rs diff --git a/src/cli.rs b/src/cli.rs index d3408f2..db4a644 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -4,6 +4,7 @@ pub mod conversation_key; pub mod dm_to_user; pub mod get_dm; pub mod get_dm_user; +pub mod last_trade_index; pub mod list_disputes; pub mod list_orders; pub mod new_order; @@ -20,6 +21,7 @@ use crate::cli::conversation_key::execute_conversation_key; use crate::cli::dm_to_user::execute_dm_to_user; use crate::cli::get_dm::execute_get_dm; use crate::cli::get_dm_user::execute_get_dm_user; +use crate::cli::last_trade_index::execute_last_trade_index; use crate::cli::list_disputes::execute_list_disputes; use crate::cli::list_orders::execute_list_orders; use crate::cli::new_order::execute_new_order; @@ -291,6 +293,8 @@ pub enum Commands { #[arg(short, long)] pubkey: String, }, + /// Get last trade index of user + GetLastTradeIndex {}, } fn get_env_var(cli: &Cli) { @@ -415,9 +419,12 @@ impl Commands { | Commands::Release { order_id } | Commands::Dispute { order_id } | Commands::Cancel { order_id } => { - crate::util::run_simple_order_msg(self.clone(), order_id, ctx).await + crate::util::run_simple_order_msg(self.clone(), Some(*order_id), ctx).await + } + // Last trade index commands + Commands::GetLastTradeIndex {} => { + execute_last_trade_index(&ctx.identity_keys, ctx.mostro_pubkey, ctx).await } - // DM commands with pubkey parsing Commands::SendDm { pubkey, diff --git a/src/cli/add_invoice.rs b/src/cli/add_invoice.rs index 5c7595e..d7181d4 100644 --- a/src/cli/add_invoice.rs +++ b/src/cli/add_invoice.rs @@ -1,6 +1,4 @@ -use crate::parser::dms::print_commands_results; -use crate::parser::parse_dm_events; -use crate::util::{send_dm, wait_for_dm}; +use crate::util::{print_dm_events, send_dm, wait_for_dm}; use crate::{cli::Context, db::Order, lightning::is_valid_invoice}; use anyhow::Result; use lnurl::lightning_address::LightningAddress; @@ -17,7 +15,7 @@ pub async fn execute_add_invoice(order_id: &Uuid, invoice: &str, ctx: &Context) .trade_keys .clone() .ok_or(anyhow::anyhow!("Missing trade keys"))?; - + let order_trade_keys = Keys::parse(&trade_keys)?; println!( "Order trade keys: {:?}", @@ -73,23 +71,7 @@ pub async fn execute_add_invoice(order_id: &Uuid, invoice: &str, ctx: &Context) let recv_event = wait_for_dm(ctx, Some(&order_trade_keys)).await?; // Parse the incoming DM - let messages = parse_dm_events(recv_event, &order_trade_keys, None).await; - if let Some((message, _, _)) = messages.first() { - let message = message.get_inner_message_kind(); - if message.request_id == Some(request_id) { - print_commands_results(message, None, ctx).await?; - } else { - return Err(anyhow::anyhow!( - "Received response with mismatched request_id. Expected: {}, Got: {:?}", - request_id, - message.request_id - )); - } - } else { - return Err(anyhow::anyhow!( - "No valid response received from Mostro after adding invoice to order" - )); - } + print_dm_events(recv_event, request_id, ctx).await?; Ok(()) } diff --git a/src/cli/last_trade_index.rs b/src/cli/last_trade_index.rs new file mode 100644 index 0000000..f4895c4 --- /dev/null +++ b/src/cli/last_trade_index.rs @@ -0,0 +1,57 @@ +use anyhow::Result; +use mostro_core::prelude::*; +use nostr_sdk::prelude::*; + +use crate::{ + cli::Context, + parser::{dms::print_commands_results, parse_dm_events}, + util::{send_dm, wait_for_dm}, +}; + +pub async fn execute_last_trade_index( + identity_keys: &Keys, + mostro_key: PublicKey, + ctx: &Context, +) -> Result<()> { + let kind = MessageKind::new(None, None, None, Action::LastTradeIndex, None); + let last_trade_index_message = Message::Restore(kind); + let message_json = last_trade_index_message + .as_json() + .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?; + + // Send the last trade index message to Mostro server + send_dm( + &ctx.client, + Some(identity_keys), + identity_keys, + &mostro_key, + message_json, + None, + false, + ) + .await?; + + // Wait for incoming DM + let recv_event = wait_for_dm(ctx, Some(identity_keys)).await?; + + // Parse the incoming DM + let messages = parse_dm_events(recv_event, &ctx.trade_keys, None).await; + if let Some((message, _, _)) = messages.first() { + let message = message.get_inner_message_kind(); + if message.action == Action::LastTradeIndex { + print_commands_results(message, None, ctx).await? + } else { + return Err(anyhow::anyhow!( + "Received response with mismatched action. Expected: {:?}, Got: {:?}", + Action::LastTradeIndex, + message.action + )); + } + } else { + return Err(anyhow::anyhow!("No response received from Mostro")); + } + + println!("Last trade index message sent successfully."); + + Ok(()) +} diff --git a/src/cli/new_order.rs b/src/cli/new_order.rs index 2fda1e2..9e31546 100644 --- a/src/cli/new_order.rs +++ b/src/cli/new_order.rs @@ -1,8 +1,6 @@ use crate::cli::Context; -use crate::parser::dms::print_commands_results; use crate::parser::orders::print_order_preview; -use crate::parser::parse_dm_events; -use crate::util::{send_dm, uppercase_first, wait_for_dm}; +use crate::util::{print_dm_events, send_dm, uppercase_first, wait_for_dm}; use anyhow::Result; use mostro_core::prelude::*; use std::collections::HashMap; @@ -146,23 +144,7 @@ pub async fn execute_new_order( let recv_event = wait_for_dm(ctx, None).await?; // Parse the incoming DM - let messages = parse_dm_events(recv_event, &ctx.trade_keys, None).await; - if let Some((message, _, _)) = messages.first() { - let message = message.get_inner_message_kind(); - if message.request_id == Some(request_id) { - print_commands_results(message, None, ctx).await?; - } else { - return Err(anyhow::anyhow!( - "Received response with mismatched request_id. Expected: {}, Got: {:?}", - request_id, - message.request_id - )); - } - } else { - return Err(anyhow::anyhow!( - "No valid response received from Mostro after sending new order" - )); - } + print_dm_events(recv_event, request_id, ctx).await?; Ok(()) } diff --git a/src/cli/send_msg.rs b/src/cli/send_msg.rs index 27852ad..2e4576b 100644 --- a/src/cli/send_msg.rs +++ b/src/cli/send_msg.rs @@ -1,8 +1,6 @@ use crate::cli::{Commands, Context}; use crate::db::{Order, User}; -use crate::parser::dms::print_commands_results; -use crate::parser::parse_dm_events; -use crate::util::{send_dm, wait_for_dm}; +use crate::util::{print_dm_events, send_dm, wait_for_dm}; use anyhow::Result; use mostro_core::prelude::*; @@ -29,13 +27,13 @@ pub async fn execute_send_msg( } }; - match order_id { - Some(id) => println!( - "Sending {} command for order {} to mostro pubId {}", - requested_action, id, ctx.mostro_pubkey - ), - None => return Err(anyhow::anyhow!("Missing order id!")), - }; + // Printout command information + println!( + "Sending {} command for order {} to mostro pubId {}", + requested_action, + order_id.unwrap(), + ctx.mostro_pubkey + ); // Determine payload let payload = match requested_action { @@ -87,14 +85,9 @@ pub async fn execute_send_msg( // Wait for incoming DM let recv_event = wait_for_dm(ctx, Some(&trade_keys)).await?; - let messages = parse_dm_events(recv_event, &trade_keys, None).await; - if let Some(message) = messages.first() { - let message = message.0.get_inner_message_kind(); - println!("Message: {:?}", message); - if message.request_id == Some(request_id) { - let _ = print_commands_results(message, None, ctx).await; - } - } + + // Parse the incoming DM + print_dm_events(recv_event, request_id, ctx).await?; } } Ok(()) diff --git a/src/cli/take_order.rs b/src/cli/take_order.rs index f77da09..9bb75a0 100644 --- a/src/cli/take_order.rs +++ b/src/cli/take_order.rs @@ -6,9 +6,7 @@ use uuid::Uuid; use crate::cli::Context; use crate::lightning::is_valid_invoice; -use crate::parser::dms::print_commands_results; -use crate::parser::parse_dm_events; -use crate::util::{send_dm, wait_for_dm}; +use crate::util::{print_dm_events, send_dm, wait_for_dm}; /// Create payload based on action type and parameters fn create_take_order_payload( @@ -112,22 +110,7 @@ pub async fn execute_take_order( let recv_event = wait_for_dm(ctx, None).await?; // Parse the incoming DM - let messages = parse_dm_events(recv_event, &ctx.trade_keys, None).await; - if let Some((message, _, _)) = messages.first() { - let message = message.get_inner_message_kind(); - if message.request_id == Some(request_id) { - print_commands_results(message, None, ctx).await?; - } else { - return Err(anyhow::anyhow!( - "Received response with mismatched request_id. Expected: {}, Got: {:?}", - request_id, - message.request_id - )); - } - } else { - return Err(anyhow::anyhow!( - "No valid response received from Mostro after taking order" - )); - } + print_dm_events(recv_event, request_id, ctx).await?; + Ok(()) } diff --git a/src/parser/dms.rs b/src/parser/dms.rs index 75cca98..579a6b9 100644 --- a/src/parser/dms.rs +++ b/src/parser/dms.rs @@ -182,6 +182,23 @@ pub async fn print_commands_results( Err(anyhow::anyhow!("No order id found in message")) } } + Action::LastTradeIndex => { + if let Some(last_trade_index) = message.trade_index { + println!("Last trade index message received: {}", last_trade_index); + match User::get(&ctx.pool).await { + Ok(mut user) => { + user.set_last_trade_index(last_trade_index); + if let Err(e) = user.save(&ctx.pool).await { + println!("Failed to update user: {}", e); + } + } + Err(_) => return Err(anyhow::anyhow!("Failed to get user")), + } + Ok(()) + } else { + Err(anyhow::anyhow!("No trade index found in message")) + } + } _ => Err(anyhow::anyhow!("Unknown action: {:?}", message.action)), } } diff --git a/src/util.rs b/src/util.rs index 73fe0ca..de8c084 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,6 +1,7 @@ use crate::cli::send_msg::execute_send_msg; use crate::cli::{Commands, Context}; use crate::db::{Order, User}; +use crate::parser::dms::print_commands_results; use crate::parser::{parse_dispute_events, parse_dm_events, parse_orders_events}; use anyhow::{Error, Result}; use base64::engine::general_purpose; @@ -128,7 +129,8 @@ pub async fn wait_for_dm(ctx: &Context, order_trade_keys: Option<&Keys>) -> anyh // Get notifications from client let mut notifications = ctx.client.notifications(); // Create subscription - let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::WaitForEventsAfterEOSE(1)); + let opts = + SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::WaitForEventsAfterEOSE(1)); // Subscribe to gift wrap events - ONLY NEW ONES WITH LIMIT 0 let subscription = Filter::new() .pubkey(trade_keys.public_key()) @@ -548,8 +550,12 @@ pub fn get_mcli_path() -> String { mcli_path } -pub async fn run_simple_order_msg(command: Commands, order_id: &Uuid, ctx: &Context) -> Result<()> { - execute_send_msg(command, Some(*order_id), ctx, None).await +pub async fn run_simple_order_msg( + command: Commands, + order_id: Option, + ctx: &Context, +) -> Result<()> { + execute_send_msg(command, order_id, ctx, None).await } // helper (place near other CLI utils) @@ -567,5 +573,25 @@ pub async fn admin_send_dm(ctx: &Context, msg: String) -> anyhow::Result<()> { Ok(()) } +pub async fn print_dm_events(recv_event: Events, request_id: u64, ctx: &Context) -> Result<()> { + // Parse the incoming DM + let messages = parse_dm_events(recv_event, &ctx.trade_keys, None).await; + if let Some((message, _, _)) = messages.first() { + let message = message.get_inner_message_kind(); + if message.request_id == Some(request_id) { + print_commands_results(message, None, ctx).await?; + } else { + return Err(anyhow::anyhow!( + "Received response with mismatched request_id. Expected: {}, Got: {:?}", + request_id, + message.request_id + )); + } + } else { + return Err(anyhow::anyhow!("No response received from Mostro")); + } + Ok(()) +} + #[cfg(test)] mod tests {} From 39f4b2602b90daf26cfe262914c21368a1d58e44 Mon Sep 17 00:00:00 2001 From: arkanoider Date: Thu, 9 Oct 2025 20:38:56 +0200 Subject: [PATCH 18/23] feat: improved reception logic and small fix for last trade index --- src/cli/add_invoice.rs | 7 +++---- src/cli/last_trade_index.rs | 12 +++++++----- src/cli/new_order.rs | 7 +++---- src/cli/send_msg.rs | 7 +++---- src/cli/take_order.rs | 7 +++---- src/util.rs | 13 ++++++++++++- 6 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/cli/add_invoice.rs b/src/cli/add_invoice.rs index d7181d4..748d9cc 100644 --- a/src/cli/add_invoice.rs +++ b/src/cli/add_invoice.rs @@ -56,7 +56,7 @@ pub async fn execute_add_invoice(order_id: &Uuid, invoice: &str, ctx: &Context) .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?; // Send the DM - send_dm( + let sent_message = send_dm( &ctx.client, Some(&ctx.identity_keys), &order_trade_keys, @@ -64,11 +64,10 @@ pub async fn execute_add_invoice(order_id: &Uuid, invoice: &str, ctx: &Context) message_json, None, false, - ) - .await?; + ); // Wait for the DM to be sent from mostro - let recv_event = wait_for_dm(ctx, Some(&order_trade_keys)).await?; + let recv_event = wait_for_dm(ctx, Some(&order_trade_keys), sent_message).await?; // Parse the incoming DM print_dm_events(recv_event, request_id, ctx).await?; diff --git a/src/cli/last_trade_index.rs b/src/cli/last_trade_index.rs index f4895c4..0aa3ee8 100644 --- a/src/cli/last_trade_index.rs +++ b/src/cli/last_trade_index.rs @@ -20,7 +20,7 @@ pub async fn execute_last_trade_index( .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?; // Send the last trade index message to Mostro server - send_dm( + let sent_message = send_dm( &ctx.client, Some(identity_keys), identity_keys, @@ -28,14 +28,16 @@ pub async fn execute_last_trade_index( message_json, None, false, - ) - .await?; + ); + + // Log the sent message + println!("Sent request to Mostro to get last trade index of user {}", identity_keys.public_key().to_string()); // Wait for incoming DM - let recv_event = wait_for_dm(ctx, Some(identity_keys)).await?; + let recv_event = wait_for_dm(ctx, Some(identity_keys), sent_message).await?; // Parse the incoming DM - let messages = parse_dm_events(recv_event, &ctx.trade_keys, None).await; + let messages = parse_dm_events(recv_event, &ctx.identity_keys, None).await; if let Some((message, _, _)) = messages.first() { let message = message.get_inner_message_kind(); if message.action == Action::LastTradeIndex { diff --git a/src/cli/new_order.rs b/src/cli/new_order.rs index 9e31546..7dcbf86 100644 --- a/src/cli/new_order.rs +++ b/src/cli/new_order.rs @@ -129,7 +129,7 @@ pub async fn execute_new_order( .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?; // Send the DM - send_dm( + let sent_message = send_dm( &ctx.client, Some(&ctx.identity_keys), &ctx.trade_keys, @@ -137,11 +137,10 @@ pub async fn execute_new_order( message_json, None, false, - ) - .await?; + ); // Wait for the DM to be sent from mostro - let recv_event = wait_for_dm(ctx, None).await?; + let recv_event = wait_for_dm(ctx, None, sent_message).await?; // Parse the incoming DM print_dm_events(recv_event, request_id, ctx).await?; diff --git a/src/cli/send_msg.rs b/src/cli/send_msg.rs index 2e4576b..dca321b 100644 --- a/src/cli/send_msg.rs +++ b/src/cli/send_msg.rs @@ -72,7 +72,7 @@ pub async fn execute_send_msg( .map_err(|e| anyhow::anyhow!("Failed to serialize message: {e}"))?; // Send DM - send_dm( + let sent_message = send_dm( &ctx.client, Some(&ctx.identity_keys), &trade_keys, @@ -80,11 +80,10 @@ pub async fn execute_send_msg( message_json, None, false, - ) - .await?; + ); // Wait for incoming DM - let recv_event = wait_for_dm(ctx, Some(&trade_keys)).await?; + let recv_event = wait_for_dm(ctx, Some(&trade_keys), sent_message).await?; // Parse the incoming DM print_dm_events(recv_event, request_id, ctx).await?; diff --git a/src/cli/take_order.rs b/src/cli/take_order.rs index 9bb75a0..98d8a81 100644 --- a/src/cli/take_order.rs +++ b/src/cli/take_order.rs @@ -95,7 +95,7 @@ pub async fn execute_take_order( // Send the DM // This is so we can wait for the gift wrap event in the main thread - send_dm( + let sent_message = send_dm( &ctx.client, Some(&ctx.identity_keys), &ctx.trade_keys, @@ -103,11 +103,10 @@ pub async fn execute_take_order( message_json, None, false, - ) - .await?; + ); // Wait for the DM to be sent from mostro - let recv_event = wait_for_dm(ctx, None).await?; + let recv_event = wait_for_dm(ctx, None, sent_message).await?; // Parse the incoming DM print_dm_events(recv_event, request_id, ctx).await?; diff --git a/src/util.rs b/src/util.rs index de8c084..c6d4780 100644 --- a/src/util.rs +++ b/src/util.rs @@ -12,6 +12,7 @@ use mostro_core::prelude::*; use nip44::v2::{encrypt_to_bytes, ConversationKey}; use nostr_sdk::prelude::*; use sqlx::SqlitePool; +use std::future::Future; use std::time::Duration; use std::{fs, path::Path}; use uuid::Uuid; @@ -123,7 +124,14 @@ pub async fn save_order( } /// Wait for incoming gift wraps or events coming in -pub async fn wait_for_dm(ctx: &Context, order_trade_keys: Option<&Keys>) -> anyhow::Result { +pub async fn wait_for_dm( + ctx: &Context, + order_trade_keys: Option<&Keys>, + sent_message: F, +) -> anyhow::Result +where + F: Future> + Send, +{ // Get correct trade keys to wait for let trade_keys = order_trade_keys.unwrap_or(&ctx.trade_keys); // Get notifications from client @@ -139,6 +147,9 @@ pub async fn wait_for_dm(ctx: &Context, order_trade_keys: Option<&Keys>) -> anyh // Subscribe to subscription with exit policy of just waiting for 1 event ctx.client.subscribe(subscription, Some(opts)).await?; + // Await the sent message + sent_message.await?; + // Wait for event let event = tokio::time::timeout(FETCH_EVENTS_TIMEOUT, async move { loop { From 51f85888df74d7aa58abfd523e1ab9685d0276b9 Mon Sep 17 00:00:00 2001 From: arkanoider Date: Thu, 9 Oct 2025 22:09:03 +0200 Subject: [PATCH 19/23] chore: updateded README.md --- README.md | 58 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index b7aa387..0ca61cf 100644 --- a/README.md +++ b/README.md @@ -4,18 +4,18 @@ Very simple command line interface that show all new replaceable events from [Mostro](https://github.com/MostroP2P/mostro) -## Requirements: +## Requirements 0. You need Rust version 1.64 or higher to compile. 1. You will need a lightning network node -## Install dependencies: +## Install dependencies To compile on Ubuntu/Pop!\_OS, please install [cargo](https://www.rust-lang.org/tools/install), then run the following commands: ``` -$ sudo apt update -$ sudo apt install -y cmake build-essential pkg-config +sudo apt update +sudo apt install -y cmake build-essential pkg-config ``` ## Install @@ -23,33 +23,41 @@ $ sudo apt install -y cmake build-essential pkg-config To install you need to fill the env vars (`.env`) on the with your own private key and add a Mostro pubkey. ``` -$ git clone https://github.com/MostroP2P/mostro-cli.git -$ cd mostro-cli -$ cp .env-sample .env -$ cargo run +git clone https://github.com/MostroP2P/mostro-cli.git +cd mostro-cli +cp .env-sample .env +cargo run ``` # Usage ``` Commands: - listorders Requests open orders from Mostro pubkey - neworder Create a new buy/sell order on Mostro - takesell Take a sell order from a Mostro pubkey - takebuy Take a buy order from a Mostro pubkey - addinvoice Buyer add a new invoice to receive the payment - getdm Get the latest direct messages from Mostro - fiatsent Send fiat sent message to confirm payment to other user - release Settle the hold invoice and pay to buyer - cancel Cancel a pending order - rate Rate counterpart after a successful trade - dispute Start a dispute - admcancel Cancel an order (only admin) - admsettle Settle a seller's hold invoice (only admin) - admlistdisputes Requests open disputes from Mostro pubkey - admaddsolver Add a new dispute's solver (only admin) - admtakedispute Admin or solver take a Pending dispute (only admin) - help Print this message or the help of the given subcommand(s) + listorders Requests open orders from Mostro pubkey + neworder Create a new buy/sell order on Mostro + takesell Take a sell order from a Mostro pubkey + takebuy Take a buy order from a Mostro pubkey + addinvoice Buyer add a new invoice to receive the payment + getdm Get the latest direct messages + getdmuser Get direct messages sent to any trade keys + getadmindm Get the latest direct messages for admin + senddm Send direct message to a user + dmtouser Send gift wrapped direct message to a user + fiatsent Send fiat sent message to confirm payment to other user + release Settle the hold invoice and pay to buyer + cancel Cancel a pending order + rate Rate counterpart after a successful trade + restore Restore session to recover all pending orders and disputes + dispute Start a dispute + admcancel Cancel an order (only admin) + admsettle Settle a seller's hold invoice (only admin) + admlistdisputes Requests open disputes from Mostro pubkey + admaddsolver Add a new dispute's solver (only admin) + admtakedispute Admin or solver take a Pending dispute (only admin) + admsenddm Send gift wrapped direct message to a user (only admin) + conversationkey Get the conversation key for direct messaging with a user + getlasttradeindex Get last trade index of user + help Print this message or the help of the given subcommand(s) Options: -v, --verbose From 6301df0acbcceae9200a92bd9e57870a35bd3913 Mon Sep 17 00:00:00 2001 From: arkanoider <113362043+arkanoider@users.noreply.github.com> Date: Thu, 9 Oct 2025 22:35:36 +0200 Subject: [PATCH 20/23] chore: fix by rabbit Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/cli/last_trade_index.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cli/last_trade_index.rs b/src/cli/last_trade_index.rs index 0aa3ee8..591f2ea 100644 --- a/src/cli/last_trade_index.rs +++ b/src/cli/last_trade_index.rs @@ -31,7 +31,10 @@ pub async fn execute_last_trade_index( ); // Log the sent message - println!("Sent request to Mostro to get last trade index of user {}", identity_keys.public_key().to_string()); + println!( + "Sent request to Mostro to get last trade index of user {}", + identity_keys.public_key() + ); // Wait for incoming DM let recv_event = wait_for_dm(ctx, Some(identity_keys), sent_message).await?; From 6bc8f739930a1fad2e986019f788ee33bc01530b Mon Sep 17 00:00:00 2001 From: arkanoider Date: Thu, 9 Oct 2025 23:19:34 +0200 Subject: [PATCH 21/23] feat: fixed some wrong key in decryption --- README.md | 2 -- src/cli/add_invoice.rs | 2 +- src/cli/last_trade_index.rs | 4 +--- src/cli/new_order.rs | 2 +- src/cli/send_msg.rs | 2 +- src/cli/take_order.rs | 2 +- src/parser/dms.rs | 4 ++++ src/util.rs | 11 +++++++++-- 8 files changed, 18 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 0ca61cf..d97963d 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,8 @@ Commands: takebuy Take a buy order from a Mostro pubkey addinvoice Buyer add a new invoice to receive the payment getdm Get the latest direct messages - getdmuser Get direct messages sent to any trade keys getadmindm Get the latest direct messages for admin senddm Send direct message to a user - dmtouser Send gift wrapped direct message to a user fiatsent Send fiat sent message to confirm payment to other user release Settle the hold invoice and pay to buyer cancel Cancel a pending order diff --git a/src/cli/add_invoice.rs b/src/cli/add_invoice.rs index 748d9cc..266bb5e 100644 --- a/src/cli/add_invoice.rs +++ b/src/cli/add_invoice.rs @@ -70,7 +70,7 @@ pub async fn execute_add_invoice(order_id: &Uuid, invoice: &str, ctx: &Context) let recv_event = wait_for_dm(ctx, Some(&order_trade_keys), sent_message).await?; // Parse the incoming DM - print_dm_events(recv_event, request_id, ctx).await?; + print_dm_events(recv_event, request_id, ctx, Some(&order_trade_keys)).await?; Ok(()) } diff --git a/src/cli/last_trade_index.rs b/src/cli/last_trade_index.rs index 591f2ea..5657acf 100644 --- a/src/cli/last_trade_index.rs +++ b/src/cli/last_trade_index.rs @@ -40,7 +40,7 @@ pub async fn execute_last_trade_index( let recv_event = wait_for_dm(ctx, Some(identity_keys), sent_message).await?; // Parse the incoming DM - let messages = parse_dm_events(recv_event, &ctx.identity_keys, None).await; + let messages = parse_dm_events(recv_event, &identity_keys, None).await; if let Some((message, _, _)) = messages.first() { let message = message.get_inner_message_kind(); if message.action == Action::LastTradeIndex { @@ -56,7 +56,5 @@ pub async fn execute_last_trade_index( return Err(anyhow::anyhow!("No response received from Mostro")); } - println!("Last trade index message sent successfully."); - Ok(()) } diff --git a/src/cli/new_order.rs b/src/cli/new_order.rs index 7dcbf86..7cc1d6a 100644 --- a/src/cli/new_order.rs +++ b/src/cli/new_order.rs @@ -143,7 +143,7 @@ pub async fn execute_new_order( let recv_event = wait_for_dm(ctx, None, sent_message).await?; // Parse the incoming DM - print_dm_events(recv_event, request_id, ctx).await?; + print_dm_events(recv_event, request_id, ctx, None).await?; Ok(()) } diff --git a/src/cli/send_msg.rs b/src/cli/send_msg.rs index dca321b..1ae2dce 100644 --- a/src/cli/send_msg.rs +++ b/src/cli/send_msg.rs @@ -86,7 +86,7 @@ pub async fn execute_send_msg( let recv_event = wait_for_dm(ctx, Some(&trade_keys), sent_message).await?; // Parse the incoming DM - print_dm_events(recv_event, request_id, ctx).await?; + print_dm_events(recv_event, request_id, ctx, Some(&trade_keys)).await?; } } Ok(()) diff --git a/src/cli/take_order.rs b/src/cli/take_order.rs index 98d8a81..17e0d72 100644 --- a/src/cli/take_order.rs +++ b/src/cli/take_order.rs @@ -109,7 +109,7 @@ pub async fn execute_take_order( let recv_event = wait_for_dm(ctx, None, sent_message).await?; // Parse the incoming DM - print_dm_events(recv_event, request_id, ctx).await?; + print_dm_events(recv_event, request_id, ctx, None).await?; Ok(()) } diff --git a/src/parser/dms.rs b/src/parser/dms.rs index 579a6b9..26f8af0 100644 --- a/src/parser/dms.rs +++ b/src/parser/dms.rs @@ -199,6 +199,10 @@ pub async fn print_commands_results( Err(anyhow::anyhow!("No trade index found in message")) } } + Action::HoldInvoicePaymentSettled => { + println!("Hold invoice payment settled"); + Ok(()) + } _ => Err(anyhow::anyhow!("Unknown action: {:?}", message.action)), } } diff --git a/src/util.rs b/src/util.rs index c6d4780..17821b2 100644 --- a/src/util.rs +++ b/src/util.rs @@ -584,9 +584,16 @@ pub async fn admin_send_dm(ctx: &Context, msg: String) -> anyhow::Result<()> { Ok(()) } -pub async fn print_dm_events(recv_event: Events, request_id: u64, ctx: &Context) -> Result<()> { +pub async fn print_dm_events( + recv_event: Events, + request_id: u64, + ctx: &Context, + order_trade_keys: Option<&Keys>, +) -> Result<()> { + // Get the trade keys + let trade_keys = order_trade_keys.unwrap_or(&ctx.trade_keys); // Parse the incoming DM - let messages = parse_dm_events(recv_event, &ctx.trade_keys, None).await; + let messages = parse_dm_events(recv_event, &trade_keys, None).await; if let Some((message, _, _)) = messages.first() { let message = message.get_inner_message_kind(); if message.request_id == Some(request_id) { From a3be58263a162a84b025fc587e5a66559240d481 Mon Sep 17 00:00:00 2001 From: arkanoider Date: Thu, 9 Oct 2025 23:21:14 +0200 Subject: [PATCH 22/23] chore: fix for clippy gh actions --- src/cli/last_trade_index.rs | 2 +- src/util.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/last_trade_index.rs b/src/cli/last_trade_index.rs index 5657acf..fe2aaf4 100644 --- a/src/cli/last_trade_index.rs +++ b/src/cli/last_trade_index.rs @@ -40,7 +40,7 @@ pub async fn execute_last_trade_index( let recv_event = wait_for_dm(ctx, Some(identity_keys), sent_message).await?; // Parse the incoming DM - let messages = parse_dm_events(recv_event, &identity_keys, None).await; + let messages = parse_dm_events(recv_event, identity_keys, None).await; if let Some((message, _, _)) = messages.first() { let message = message.get_inner_message_kind(); if message.action == Action::LastTradeIndex { diff --git a/src/util.rs b/src/util.rs index 17821b2..e57fd80 100644 --- a/src/util.rs +++ b/src/util.rs @@ -593,7 +593,7 @@ pub async fn print_dm_events( // Get the trade keys let trade_keys = order_trade_keys.unwrap_or(&ctx.trade_keys); // Parse the incoming DM - let messages = parse_dm_events(recv_event, &trade_keys, None).await; + let messages = parse_dm_events(recv_event, trade_keys, None).await; if let Some((message, _, _)) = messages.first() { let message = message.get_inner_message_kind(); if message.request_id == Some(request_id) { From 4edef4d06cedda8612ddf693114ce98e5d810be0 Mon Sep 17 00:00:00 2001 From: arkanoider Date: Thu, 9 Oct 2025 23:25:46 +0200 Subject: [PATCH 23/23] chore: fix in README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d97963d..d0b9bd4 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Very simple command line interface that show all new replaceable events from [Mo To compile on Ubuntu/Pop!\_OS, please install [cargo](https://www.rust-lang.org/tools/install), then run the following commands: -``` +```bash sudo apt update sudo apt install -y cmake build-essential pkg-config ``` @@ -22,16 +22,16 @@ sudo apt install -y cmake build-essential pkg-config To install you need to fill the env vars (`.env`) on the with your own private key and add a Mostro pubkey. -``` +```bash git clone https://github.com/MostroP2P/mostro-cli.git cd mostro-cli cp .env-sample .env cargo run ``` -# Usage +## Usage -``` +```bash Commands: listorders Requests open orders from Mostro pubkey neworder Create a new buy/sell order on Mostro @@ -66,9 +66,9 @@ Options: -V, --version Print version ``` -# Examples +## Examples -``` +```bash $ mostro-cli -m npub1ykvsmrmw2hk7jgxgy64zr8tfkx4nnjhq9eyfxdlg3caha3ph0skq6jr3z0 -r 'wss://nos.lol,wss://relay.damus.io,wss://nostr-pub.wellorder.net,wss://nostr.mutinywallet.com,wss://relay.nostr.band,wss://nostr.cizmar.net,wss://140.f7z.io,wss://nostrrelay.com,wss://relay.nostrr.de' listorders # You can set the env vars to avoid the -m, -n and -r flags