mostro_client/
util.rs

1use crate::cli::send_msg::execute_send_msg;
2use crate::cli::{Commands, Context};
3use crate::db::{Order, User};
4use crate::parser::{parse_dispute_events, parse_dm_events, parse_orders_events};
5use anyhow::{Error, Result};
6use base64::engine::general_purpose;
7use base64::Engine;
8use dotenvy::var;
9use log::info;
10use mostro_core::prelude::*;
11use nip44::v2::{encrypt_to_bytes, ConversationKey};
12use nostr_sdk::prelude::*;
13use sqlx::SqlitePool;
14use std::time::Duration;
15use std::{fs, path::Path};
16use uuid::Uuid;
17
18const FAKE_SINCE: i64 = 2880;
19const FETCH_EVENTS_TIMEOUT: Duration = Duration::from_secs(15);
20
21#[derive(Clone, Debug)]
22pub enum Event {
23    SmallOrder(SmallOrder),
24    Dispute(Dispute), // Assuming you have a Dispute struct
25    MessageTuple(Box<(Message, u64)>),
26}
27
28#[derive(Clone, Debug)]
29pub enum ListKind {
30    Orders,
31    Disputes,
32    DirectMessagesUser,
33    DirectMessagesAdmin,
34    PrivateDirectMessagesUser,
35}
36
37async fn send_gift_wrap_dm_internal(
38    client: &Client,
39    sender_keys: &Keys,
40    receiver_pubkey: &PublicKey,
41    message: &str,
42    is_admin: bool,
43) -> Result<()> {
44    let pow: u8 = var("POW")
45        .unwrap_or_else(|_| "0".to_string())
46        .parse()
47        .unwrap_or(0);
48
49    // Create Message struct for consistency with Mostro protocol
50    let dm_message = Message::new_dm(
51        None,
52        None,
53        Action::SendDm,
54        Some(Payload::TextMessage(message.to_string())),
55    );
56
57    // Serialize as JSON with the expected format (Message, Option<Signature>)
58    let content = serde_json::to_string(&(dm_message, None::<String>))?;
59
60    // Create the rumor with JSON content
61    let rumor = EventBuilder::text_note(content)
62        .pow(pow)
63        .build(sender_keys.public_key());
64
65    // Create gift wrap using sender_keys as the signing key
66    let event = EventBuilder::gift_wrap(sender_keys, receiver_pubkey, rumor, Tags::new()).await?;
67
68    let sender_type = if is_admin { "admin" } else { "user" };
69    info!(
70        "Sending {} gift wrap event to {}",
71        sender_type, receiver_pubkey
72    );
73    client.send_event(&event).await?;
74
75    Ok(())
76}
77
78pub async fn send_admin_gift_wrap_dm(
79    client: &Client,
80    admin_keys: &Keys,
81    receiver_pubkey: &PublicKey,
82    message: &str,
83) -> Result<()> {
84    send_gift_wrap_dm_internal(client, admin_keys, receiver_pubkey, message, true).await
85}
86
87pub async fn send_gift_wrap_dm(
88    client: &Client,
89    trade_keys: &Keys,
90    receiver_pubkey: &PublicKey,
91    message: &str,
92) -> Result<()> {
93    send_gift_wrap_dm_internal(client, trade_keys, receiver_pubkey, message, false).await
94}
95
96pub async fn save_order(
97    order: SmallOrder,
98    trade_keys: &Keys,
99    request_id: u64,
100    trade_index: Option<i64>,
101    pool: &SqlitePool,
102) -> Result<()> {
103    if let Ok(order) = Order::new(pool, order, trade_keys, Some(request_id as i64)).await {
104        if let Some(order_id) = order.id {
105            println!("Order {} created", order_id);
106        } else {
107            println!("Warning: The newly created order has no ID.");
108        }
109        // Get trade index - we must have it
110        let trade_index = if let Some(trade_index) = trade_index {
111            trade_index
112        } else {
113            return Err(anyhow::anyhow!(
114                "No trade index found for new order, this should never happen"
115            ));
116        };
117
118        // Update last trade index to be used in next trade
119        match User::get(pool).await {
120            Ok(mut user) => {
121                user.set_last_trade_index(trade_index);
122                if let Err(e) = user.save(pool).await {
123                    println!("Failed to update user: {}", e);
124                }
125            }
126            Err(e) => println!("Failed to get user: {}", e),
127        }
128    }
129    Ok(())
130}
131
132/// Wait for incoming gift wraps or events coming in
133pub async fn wait_for_dm(
134    client: &Client,
135    trade_keys: &Keys,
136    request_id: u64,
137    trade_index: Option<i64>,
138    mut order: Option<Order>,
139    pool: &SqlitePool,
140) -> anyhow::Result<()> {
141    let mut notifications = client.notifications();
142
143    match tokio::time::timeout(FETCH_EVENTS_TIMEOUT, async move {
144        while let Ok(notification) = notifications.recv().await {
145            if let RelayPoolNotification::Event { event, .. } = notification {
146                if event.kind == nostr_sdk::Kind::GiftWrap {
147                let gift = match nip59::extract_rumor(trade_keys, &event).await {
148                    Ok(gift) => gift,
149                    Err(e) => {
150                        println!("Failed to extract rumor: {}", e);
151                        continue;
152                    }
153                };
154                let (message, _): (Message, Option<String>) = match serde_json::from_str(&gift.rumor.content) {
155                    Ok(msg) => msg,
156                    Err(e) => {
157                        println!("Failed to deserialize message: {}", e);
158                        continue;
159                    }
160                };
161                let message = message.get_inner_message_kind();
162                if message.request_id == Some(request_id) {
163                    match message.action {
164                        Action::NewOrder => {
165                            if let Some(Payload::Order(order)) = message.payload.as_ref() {
166                                if let Err(e) = save_order(order.clone(), trade_keys, request_id, trade_index, pool).await {
167                                    println!("Failed to save order: {}", e);
168                                    return Err(());
169                                }
170                                return Ok(());
171                            }
172                        }
173                        // this is the case where the buyer adds an invoice to a takesell order
174                        Action::WaitingSellerToPay => {
175                            println!("Now we should wait for the seller to pay the invoice");
176                            if let Some(mut order) = order.take() {
177                                match order
178                                .set_status(Status::WaitingPayment.to_string())
179                                .save(pool)
180                                .await
181                                {
182                                    Ok(_) => println!("Order status updated"),
183                                    Err(e) => println!("Failed to update order status: {}", e),
184                                }
185                                return Ok(());
186                            }
187                        }
188                        // this is the case where the buyer adds an invoice to a takesell order
189                        Action::AddInvoice => {
190                            if let Some(Payload::Order(order)) = &message.payload {
191                                println!(
192                                    "Please add a lightning invoice with amount of {}",
193                                    order.amount
194                                );
195                                // Save the order
196                                if let Err(e) = save_order(order.clone(), trade_keys, request_id, trade_index, pool).await {
197                                    println!("Failed to save order: {}", e);
198                                    return Err(());
199                                }
200                                return Ok(());
201                            }
202                        }
203                        // this is the case where the buyer pays the invoice coming from a takebuy
204                        Action::PayInvoice => {
205                            if let Some(Payload::PaymentRequest(order, invoice, _)) = &message.payload {
206                                println!(
207                                    "Mostro sent you this hold invoice for order id: {}",
208                                    order
209                                        .as_ref()
210                                        .and_then(|o| o.id)
211                                        .map_or("unknown".to_string(), |id| id.to_string())
212                                );
213                                println!();
214                                println!("Pay this invoice to continue -->  {}", invoice);
215                                println!();
216                                if let Some(order) = order {
217                                    let store_order = order.clone();
218                                    // Save the order
219                                    if let Err(e) = save_order(store_order, trade_keys, request_id, trade_index, pool).await {
220                                        println!("Failed to save order: {}", e);
221                                        return Err(());
222                                    }
223                                }
224                                return Ok(());
225                            }
226                        }
227                        Action::CantDo => {
228                            match message.payload {
229                                Some(Payload::CantDo(Some(CantDoReason::OutOfRangeFiatAmount | CantDoReason::OutOfRangeSatsAmount))) => {
230                                    println!("Error: Amount is outside the allowed range. Please check the order's min/max limits.");
231                                    return Err(());
232                                }
233                                Some(Payload::CantDo(Some(CantDoReason::PendingOrderExists))) => {
234                                        println!("Error: A pending order already exists. Please wait for it to be filled or canceled.");
235                                        return Err(());
236                                    }
237                                Some(Payload::CantDo(Some(CantDoReason::InvalidTradeIndex))) => {
238                                    println!("Error: Invalid trade index. Please synchronize the trade index with mostro");
239                                    return Err(());
240                                }
241                                _ => {
242                                    println!("Unknown reason: {:?}", message.payload);
243                                    return Err(());
244                                }
245                            }
246                        }
247                        // this is the case where the user cancels the order
248                        Action::Canceled => {
249                            if let Some(order_id) = &message.id {
250                            // Acquire database connection
251                            // Verify order exists before deletion
252                            if Order::get_by_id(pool, &order_id.to_string()).await.is_ok() {
253                                if let Err(e) = Order::delete_by_id(pool, &order_id.to_string()).await {
254                                    println!("Failed to delete order: {}", e);
255                                    return Err(());
256                                }
257                                // Release database connection
258                                println!("Order {} canceled!", order_id);
259                                return Ok(());
260                            } else {
261                                println!("Order not found: {}", order_id);
262                                return Err(());
263                                }
264                            }
265                        }
266                        _ => {}
267                    }
268                    }
269                }
270        }
271        }
272        Ok(())
273    })
274    .await {
275        Ok(result) => match result {
276            Ok(()) => Ok(()),
277            Err(()) => Err(anyhow::anyhow!("Error in timeout closure")),
278        },
279        Err(_) => Err(anyhow::anyhow!("Timeout waiting for DM or gift wrap event"))
280    }
281}
282
283#[derive(Debug, Clone, Copy)]
284enum MessageType {
285    PrivateDirectMessage,
286    PrivateGiftWrap,
287    SignedGiftWrap,
288}
289
290fn determine_message_type(to_user: bool, private: bool) -> MessageType {
291    match (to_user, private) {
292        (true, _) => MessageType::PrivateDirectMessage,
293        (false, true) => MessageType::PrivateGiftWrap,
294        (false, false) => MessageType::SignedGiftWrap,
295    }
296}
297
298fn create_expiration_tags(expiration: Option<Timestamp>) -> Tags {
299    let mut tags: Vec<Tag> = Vec::with_capacity(1 + usize::from(expiration.is_some()));
300
301    if let Some(timestamp) = expiration {
302        tags.push(Tag::expiration(timestamp));
303    }
304
305    Tags::from_list(tags)
306}
307
308async fn create_private_dm_event(
309    trade_keys: &Keys,
310    receiver_pubkey: &PublicKey,
311    payload: String,
312    pow: u8,
313) -> Result<nostr_sdk::Event> {
314    // Derive conversation key
315    let ck = ConversationKey::derive(trade_keys.secret_key(), receiver_pubkey)?;
316    // Encrypt payload
317    let encrypted_content = encrypt_to_bytes(&ck, payload.as_bytes())?;
318    // Encode with base64
319    let b64decoded_content = general_purpose::STANDARD.encode(encrypted_content);
320    // Compose builder
321    Ok(
322        EventBuilder::new(nostr_sdk::Kind::PrivateDirectMessage, b64decoded_content)
323            .pow(pow)
324            .tag(Tag::public_key(*receiver_pubkey))
325            .sign_with_keys(trade_keys)?,
326    )
327}
328
329async fn create_gift_wrap_event(
330    trade_keys: &Keys,
331    identity_keys: Option<&Keys>,
332    receiver_pubkey: &PublicKey,
333    payload: String,
334    pow: u8,
335    expiration: Option<Timestamp>,
336    signed: bool,
337) -> Result<nostr_sdk::Event> {
338    let message = Message::from_json(&payload)
339        .map_err(|e| anyhow::anyhow!("Failed to deserialize message: {e}"))?;
340
341    let content = if signed {
342        let _identity_keys = identity_keys
343            .ok_or_else(|| Error::msg("identity_keys required for signed messages"))?;
344        // We sign the message
345        let sig = Message::sign(payload, trade_keys);
346        serde_json::to_string(&(message, sig))
347            .map_err(|e| anyhow::anyhow!("Failed to serialize message: {e}"))?
348    } else {
349        // We compose the content, when private we don't sign the payload
350        let content: (Message, Option<Signature>) = (message, None);
351        serde_json::to_string(&content)
352            .map_err(|e| anyhow::anyhow!("Failed to serialize message: {e}"))?
353    };
354
355    // We create the rumor
356    let rumor = EventBuilder::text_note(content)
357        .pow(pow)
358        .build(trade_keys.public_key());
359
360    let tags = create_expiration_tags(expiration);
361
362    let signer_keys = if signed {
363        identity_keys.ok_or_else(|| Error::msg("identity_keys required for signed messages"))?
364    } else {
365        trade_keys
366    };
367
368    Ok(EventBuilder::gift_wrap(signer_keys, receiver_pubkey, rumor, tags).await?)
369}
370
371pub async fn send_dm(
372    client: &Client,
373    identity_keys: Option<&Keys>,
374    trade_keys: &Keys,
375    receiver_pubkey: &PublicKey,
376    payload: String,
377    expiration: Option<Timestamp>,
378    to_user: bool,
379) -> Result<()> {
380    let pow: u8 = var("POW")
381        .unwrap_or('0'.to_string())
382        .parse()
383        .map_err(|e| anyhow::anyhow!("Failed to parse POW: {}", e))?;
384    let private = var("SECRET")
385        .unwrap_or("false".to_string())
386        .parse::<bool>()
387        .map_err(|e| anyhow::anyhow!("Failed to parse SECRET: {}", e))?;
388
389    let message_type = determine_message_type(to_user, private);
390
391    let event = match message_type {
392        MessageType::PrivateDirectMessage => {
393            create_private_dm_event(trade_keys, receiver_pubkey, payload, pow).await?
394        }
395        MessageType::PrivateGiftWrap => {
396            create_gift_wrap_event(
397                trade_keys,
398                identity_keys,
399                receiver_pubkey,
400                payload,
401                pow,
402                expiration,
403                false,
404            )
405            .await?
406        }
407        MessageType::SignedGiftWrap => {
408            create_gift_wrap_event(
409                trade_keys,
410                identity_keys,
411                receiver_pubkey,
412                payload,
413                pow,
414                expiration,
415                true,
416            )
417            .await?
418        }
419    };
420
421    client.send_event(&event).await?;
422    Ok(())
423}
424
425pub async fn connect_nostr() -> Result<Client> {
426    let my_keys = Keys::generate();
427
428    let relays = var("RELAYS").expect("RELAYS is not set");
429    let relays = relays.split(',').collect::<Vec<&str>>();
430    // Create new client
431    let client = Client::new(my_keys);
432    // Add relays
433    for r in relays.into_iter() {
434        client.add_relay(r).await?;
435    }
436
437    // Connect to relays and keep connection alive
438    client.connect().await;
439
440    Ok(client)
441}
442
443pub async fn get_direct_messages_from_trade_keys(
444    client: &Client,
445    trade_keys_hex: Vec<String>,
446    since: i64,
447    _mostro_pubkey: &PublicKey,
448) -> Result<Vec<(Message, u64, PublicKey)>> {
449    if trade_keys_hex.is_empty() {
450        return Ok(Vec::new());
451    }
452
453    let since_time = chrono::Utc::now()
454        .checked_sub_signed(chrono::Duration::minutes(since))
455        .ok_or(anyhow::anyhow!("Failed to get since time"))?
456        .timestamp();
457
458    // Get the triple of message, timestamp and public key
459    let mut all_messages: Vec<(Message, u64, PublicKey)> = Vec::new();
460
461    // Fetch direct messages from trade keys and in case of since, we filter by since
462    // as bonus we also fetch the events from the admin pubkey in case is specified
463    for trade_key_hex in trade_keys_hex {
464        if let Ok(public_key) = PublicKey::from_hex(&trade_key_hex) {
465            // Create filter for fetching direct messages
466            let filter =
467                create_filter(ListKind::DirectMessagesUser, public_key, Some(&since_time))?;
468            let events = client.fetch_events(filter, FETCH_EVENTS_TIMEOUT).await?;
469            // Parse events without keys since we only have the public key
470            // We'll need to handle this differently - let's just collect the events for now
471            for event in events {
472                if let Ok(message) = Message::from_json(&event.content) {
473                    if event.created_at.as_u64() < since as u64 {
474                        continue;
475                    }
476                    all_messages.push((message, event.created_at.as_u64(), event.pubkey));
477                }
478            }
479        }
480    }
481    Ok(all_messages)
482}
483
484/// Create a fake timestamp to thwart time-analysis attacks
485fn create_fake_timestamp() -> Result<Timestamp> {
486    let fake_since_time = chrono::Utc::now()
487        .checked_sub_signed(chrono::Duration::minutes(FAKE_SINCE))
488        .ok_or(anyhow::anyhow!("Failed to get fake since time"))?
489        .timestamp() as u64;
490    Ok(Timestamp::from(fake_since_time))
491}
492
493// Create a filter for fetching events in the last 7 days
494fn create_seven_days_filter(letter: Alphabet, value: String, pubkey: PublicKey) -> Result<Filter> {
495    let since_time = chrono::Utc::now()
496        .checked_sub_signed(chrono::Duration::days(7))
497        .ok_or(anyhow::anyhow!("Failed to get since days ago"))?
498        .timestamp() as u64;
499
500    let timestamp = Timestamp::from(since_time);
501
502    Ok(Filter::new()
503        .author(pubkey)
504        .limit(50)
505        .since(timestamp)
506        .custom_tag(SingleLetterTag::lowercase(letter), value)
507        .kind(nostr_sdk::Kind::Custom(NOSTR_REPLACEABLE_EVENT_KIND)))
508}
509
510// Create a filter for fetching events
511pub fn create_filter(
512    list_kind: ListKind,
513    pubkey: PublicKey,
514    since: Option<&i64>,
515) -> Result<Filter> {
516    match list_kind {
517        ListKind::Orders => create_seven_days_filter(Alphabet::Z, "order".to_string(), pubkey),
518        ListKind::Disputes => create_seven_days_filter(Alphabet::Z, "dispute".to_string(), pubkey),
519        ListKind::DirectMessagesAdmin | ListKind::DirectMessagesUser => {
520            // We use a fake timestamp to thwart time-analysis attacks
521            let fake_timestamp = create_fake_timestamp()?;
522
523            Ok(Filter::new()
524                .kind(nostr_sdk::Kind::GiftWrap)
525                .pubkey(pubkey)
526                .since(fake_timestamp))
527        }
528        ListKind::PrivateDirectMessagesUser => {
529            // Get since from cli or use 30 minutes default
530            let since = if let Some(mins) = since {
531                chrono::Utc::now()
532                    .checked_sub_signed(chrono::Duration::minutes(*mins))
533                    .unwrap()
534                    .timestamp()
535            } else {
536                chrono::Utc::now()
537                    .checked_sub_signed(chrono::Duration::minutes(30))
538                    .unwrap()
539                    .timestamp()
540            } as u64;
541            // Create filter for fetching privatedirect messages
542            Ok(Filter::new()
543                .kind(nostr_sdk::Kind::PrivateDirectMessage)
544                .pubkey(pubkey)
545                .since(Timestamp::from(since)))
546        }
547    }
548}
549
550#[allow(clippy::too_many_arguments)]
551pub async fn fetch_events_list(
552    list_kind: ListKind,
553    status: Option<Status>,
554    currency: Option<String>,
555    kind: Option<mostro_core::order::Kind>,
556    ctx: &Context,
557    _since: Option<&i64>,
558) -> Result<Vec<Event>> {
559    match list_kind {
560        ListKind::Orders => {
561            let filters = create_filter(list_kind, ctx.mostro_pubkey, None)?;
562            let fetched_events = ctx
563                .client
564                .fetch_events(filters, FETCH_EVENTS_TIMEOUT)
565                .await?;
566            let orders = parse_orders_events(fetched_events, currency, status, kind);
567            Ok(orders.into_iter().map(Event::SmallOrder).collect())
568        }
569        ListKind::DirectMessagesAdmin => {
570            let filters = create_filter(list_kind, ctx.mostro_pubkey, None)?;
571            let fetched_events = ctx
572                .client
573                .fetch_events(filters, FETCH_EVENTS_TIMEOUT)
574                .await?;
575            let direct_messages_mostro = parse_dm_events(fetched_events, &ctx.context_keys).await;
576            Ok(direct_messages_mostro
577                .into_iter()
578                .map(|(message, timestamp, _)| Event::MessageTuple(Box::new((message, timestamp))))
579                .collect())
580        }
581        ListKind::PrivateDirectMessagesUser => {
582            let mut direct_messages: Vec<(Message, u64)> = Vec::new();
583            for index in 1..=ctx.trade_index {
584                let trade_key = User::get_trade_keys(&ctx.pool, index).await?;
585                let filter = create_filter(
586                    ListKind::PrivateDirectMessagesUser,
587                    trade_key.public_key(),
588                    None,
589                )?;
590                let fetched_user_messages = ctx
591                    .client
592                    .fetch_events(filter, FETCH_EVENTS_TIMEOUT)
593                    .await?;
594                let direct_messages_for_trade_key =
595                    parse_dm_events(fetched_user_messages, &trade_key).await;
596                direct_messages.extend(
597                    direct_messages_for_trade_key
598                        .into_iter()
599                        .map(|(message, timestamp, _)| (message, timestamp)),
600                );
601            }
602            Ok(direct_messages
603                .into_iter()
604                .map(|t| Event::MessageTuple(Box::new(t)))
605                .collect())
606        }
607        ListKind::DirectMessagesUser => {
608            let mut direct_messages: Vec<(Message, u64)> = Vec::new();
609            for index in 1..=ctx.trade_index {
610                let trade_key = User::get_trade_keys(&ctx.pool, index).await?;
611                let filter =
612                    create_filter(ListKind::DirectMessagesUser, trade_key.public_key(), None)?;
613                let fetched_user_messages = ctx
614                    .client
615                    .fetch_events(filter, FETCH_EVENTS_TIMEOUT)
616                    .await?;
617                let direct_messages_for_trade_key =
618                    parse_dm_events(fetched_user_messages, &trade_key).await;
619                direct_messages.extend(
620                    direct_messages_for_trade_key
621                        .into_iter()
622                        .map(|(message, timestamp, _)| (message, timestamp)),
623                );
624            }
625            Ok(direct_messages
626                .into_iter()
627                .map(|t| Event::MessageTuple(Box::new(t)))
628                .collect())
629        }
630        ListKind::Disputes => {
631            let filters = create_filter(list_kind, ctx.mostro_pubkey, None)?;
632            let fetched_events = ctx
633                .client
634                .fetch_events(filters, FETCH_EVENTS_TIMEOUT)
635                .await?;
636            let disputes = parse_dispute_events(fetched_events);
637            Ok(disputes.into_iter().map(Event::Dispute).collect())
638        }
639    }
640}
641
642/// Uppercase first letter of a string.
643pub fn uppercase_first(s: &str) -> String {
644    let mut c = s.chars();
645    match c.next() {
646        None => String::new(),
647        Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
648    }
649}
650
651pub fn get_mcli_path() -> String {
652    let home_dir = dirs::home_dir().expect("Couldn't get home directory");
653    let mcli_path = format!("{}/.mcli", home_dir.display());
654    if !Path::new(&mcli_path).exists() {
655        fs::create_dir(&mcli_path).expect("Couldn't create mostro-cli directory in HOME");
656        println!("Directory {} created.", mcli_path);
657    }
658    mcli_path
659}
660
661pub async fn run_simple_order_msg(command: Commands, order_id: &Uuid, ctx: &Context) -> Result<()> {
662    execute_send_msg(command, Some(*order_id), ctx, None).await
663}
664
665// helper (place near other CLI utils)
666pub async fn admin_send_dm(ctx: &Context, msg: String) -> anyhow::Result<()> {
667    send_dm(
668        &ctx.client,
669        Some(&ctx.context_keys),
670        &ctx.trade_keys,
671        &ctx.mostro_pubkey,
672        msg,
673        None,
674        false,
675    )
676    .await?;
677    Ok(())
678}
679
680#[cfg(test)]
681mod tests {}