mostro_client/cli/
take_order.rs

1use anyhow::Result;
2use lnurl::lightning_address::LightningAddress;
3use mostro_core::prelude::*;
4use nostr_sdk::prelude::*;
5use std::str::FromStr;
6use uuid::Uuid;
7
8use crate::cli::Context;
9use crate::lightning::is_valid_invoice;
10use crate::util::{send_dm, wait_for_dm};
11
12/// Create payload based on action type and parameters
13fn create_take_order_payload(
14    action: Action,
15    invoice: &Option<String>,
16    amount: Option<u32>,
17) -> Result<Option<Payload>> {
18    match action {
19        Action::TakeBuy => Ok(amount.map(|amt: u32| Payload::Amount(amt as i64))),
20        Action::TakeSell => Ok(Some(match invoice {
21            Some(inv) => {
22                let initial_payload = match LightningAddress::from_str(inv) {
23                    Ok(_) => Payload::PaymentRequest(None, inv.to_string(), None),
24                    Err(_) => match is_valid_invoice(inv) {
25                        Ok(i) => Payload::PaymentRequest(None, i.to_string(), None),
26                        Err(e) => {
27                            println!("{}", e);
28                            Payload::PaymentRequest(None, inv.to_string(), None)
29                        }
30                    },
31                };
32
33                match amount {
34                    Some(amt) => match initial_payload {
35                        Payload::PaymentRequest(a, b, _) => {
36                            Payload::PaymentRequest(a, b, Some(amt as i64))
37                        }
38                        payload => payload,
39                    },
40                    None => initial_payload,
41                }
42            }
43            None => amount
44                .map(|amt| Payload::Amount(amt.into()))
45                .unwrap_or(Payload::Amount(0)),
46        })),
47        _ => Err(anyhow::anyhow!("Invalid action for take order")),
48    }
49}
50
51/// Unified function to handle both take buy and take sell orders
52#[allow(clippy::too_many_arguments)]
53pub async fn execute_take_order(
54    order_id: &Uuid,
55    action: Action,
56    invoice: &Option<String>,
57    amount: Option<u32>,
58    ctx: &Context,
59) -> Result<()> {
60    let action_name = match action {
61        Action::TakeBuy => "take buy",
62        Action::TakeSell => "take sell",
63        _ => return Err(anyhow::anyhow!("Invalid action for take order")),
64    };
65
66    println!(
67        "Request of {} order {} from mostro pubId {}",
68        action_name, order_id, ctx.mostro_pubkey
69    );
70
71    // Create payload based on action type
72    let payload = create_take_order_payload(action.clone(), invoice, amount)?;
73
74    let request_id = Uuid::new_v4().as_u128() as u64;
75
76    // Create message
77    let take_order_message = Message::new_order(
78        Some(*order_id),
79        Some(request_id),
80        Some(ctx.trade_index),
81        action.clone(),
82        payload,
83    );
84
85    // Send dm to receiver pubkey
86    println!(
87        "SENDING DM with trade keys: {:?}",
88        ctx.trade_keys.public_key().to_hex()
89    );
90
91    let message_json = take_order_message
92        .as_json()
93        .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?;
94
95    // Clone the keys and client for the async call
96    let identity_keys_clone = ctx.identity_keys.clone();
97    let trade_keys_clone = ctx.trade_keys.clone();
98    let client_clone = ctx.client.clone();
99    let mostro_pubkey_clone = ctx.mostro_pubkey;
100
101    // Subscribe to gift wrap events - ONLY NEW ONES WITH LIMIT 0
102    let subscription = Filter::new()
103        .pubkey(ctx.trade_keys.public_key())
104        .kind(nostr_sdk::Kind::GiftWrap)
105        .limit(0);
106
107    let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::WaitForEvents(1));
108    ctx.client.subscribe(subscription, Some(opts)).await?;
109
110    // Spawn a new task to send the DM
111    // This is so we can wait for the gift wrap event in the main thread
112    tokio::spawn(async move {
113        let _ = send_dm(
114            &client_clone,
115            Some(&identity_keys_clone),
116            &trade_keys_clone,
117            &mostro_pubkey_clone,
118            message_json,
119            None,
120            false,
121        )
122        .await;
123    });
124
125    // For take_sell, add an additional subscription with timestamp filtering
126    if action == Action::TakeSell {
127        let subscription = Filter::new()
128            .pubkey(ctx.trade_keys.public_key())
129            .kind(nostr_sdk::Kind::GiftWrap)
130            .since(Timestamp::from(chrono::Utc::now().timestamp() as u64))
131            .limit(0);
132
133        ctx.client.subscribe(subscription, None).await?;
134    }
135
136    // Wait for the DM to be sent from mostro
137    wait_for_dm(
138        &ctx.client,
139        &ctx.trade_keys,
140        request_id,
141        Some(ctx.trade_index),
142        None,
143        &ctx.pool,
144    )
145    .await?;
146
147    Ok(())
148}