mostro_client/cli/
new_order.rs

1use crate::cli::Context;
2use crate::parser::common::{
3    create_emoji_field_row, create_field_value_header, create_standard_table,
4};
5use crate::parser::orders::print_order_preview;
6use crate::util::{print_dm_events, send_dm, uppercase_first, wait_for_dm};
7use anyhow::Result;
8use mostro_core::prelude::*;
9use std::collections::HashMap;
10use std::io::{stdin, stdout, BufRead, Write};
11use std::process;
12use std::str::FromStr;
13use uuid::Uuid;
14
15pub type FiatNames = HashMap<String, String>;
16
17#[allow(clippy::too_many_arguments)]
18pub async fn execute_new_order(
19    kind: &str,
20    fiat_code: &str,
21    fiat_amount: &(i64, Option<i64>),
22    amount: &i64,
23    payment_method: &str,
24    premium: &i64,
25    invoice: &Option<String>,
26    ctx: &Context,
27    expiration_days: &i64,
28) -> Result<()> {
29    // Uppercase currency
30    let fiat_code = fiat_code.to_uppercase();
31    // Check if fiat currency selected is available on Yadio and eventually force user to set amount
32    // this is in the case of crypto <--> crypto offer for example
33    if *amount == 0 {
34        // Get Fiat list
35        let api_req_string = "https://api.yadio.io/currencies".to_string();
36        let fiat_list_check = reqwest::get(api_req_string)
37            .await?
38            .json::<FiatNames>()
39            .await?
40            .contains_key(&fiat_code);
41        if !fiat_list_check {
42            println!("{} is not present in the fiat market, please specify an amount with -a flag to fix the rate", fiat_code);
43            process::exit(0);
44        }
45    }
46    let kind = uppercase_first(kind);
47    // New check against strings
48    let kind_checked = mostro_core::order::Kind::from_str(&kind)
49        .map_err(|_| anyhow::anyhow!("Invalid order kind"))?;
50    let expires_at = match *expiration_days {
51        0 => None,
52        _ => {
53            let now = chrono::Utc::now();
54            let expires_at = now + chrono::Duration::days(*expiration_days);
55            Some(expires_at.timestamp())
56        }
57    };
58
59    // Get the type of neworder
60    // if both tuple field are valid than it's a range order
61    // otherwise use just fiat amount value as before
62    let amt = if fiat_amount.1.is_some() {
63        (0, Some(fiat_amount.0), fiat_amount.1)
64    } else {
65        (fiat_amount.0, None, None)
66    };
67
68    let small_order = SmallOrder::new(
69        None,
70        Some(kind_checked),
71        Some(Status::Pending),
72        *amount,
73        fiat_code.clone(),
74        amt.1,
75        amt.2,
76        amt.0,
77        payment_method.to_owned(),
78        *premium,
79        None,
80        None,
81        invoice.as_ref().to_owned().cloned(),
82        Some(0),
83        expires_at,
84    );
85
86    // Create new order for mostro
87    let order_content = Payload::Order(small_order.clone());
88
89    // Print order preview
90    let ord_preview = print_order_preview(order_content.clone())
91        .map_err(|e| anyhow::anyhow!("Failed to generate order preview: {}", e))?;
92    println!("{ord_preview}");
93    let mut user_input = String::new();
94    let _input = stdin();
95    stdout().flush()?;
96
97    let mut answer = stdin().lock();
98    answer.read_line(&mut user_input)?;
99
100    match user_input.to_lowercase().as_str().trim_end() {
101        "y" | "" => {}
102        "n" => {
103            println!("Ok you have cancelled the order, create another one please");
104            process::exit(0);
105        }
106        &_ => {
107            println!("Can't get what you're sayin!");
108            process::exit(0);
109        }
110    };
111    let request_id = Uuid::new_v4().as_u128() as u64;
112    // Create NewOrder message
113    let message = Message::new_order(
114        None,
115        Some(request_id),
116        Some(ctx.trade_index),
117        Action::NewOrder,
118        Some(order_content),
119    );
120
121    // Send dm to receiver pubkey
122    println!("šŸ†• Create New Order");
123    println!("═══════════════════════════════════════");
124
125    let mut table = create_standard_table();
126    table.set_header(create_field_value_header());
127
128    table.add_row(create_emoji_field_row("šŸ“ˆ ", "Order Type", &kind));
129    table.add_row(create_emoji_field_row("šŸ’± ", "Fiat Code", &fiat_code));
130    table.add_row(create_emoji_field_row(
131        "šŸ’° ",
132        "Amount (sats)",
133        &amount.to_string(),
134    ));
135
136    if let Some(max) = fiat_amount.1 {
137        table.add_row(create_emoji_field_row(
138            "šŸ“Š ",
139            "Fiat Range",
140            &format!("{}-{}", fiat_amount.0, max),
141        ));
142    } else {
143        table.add_row(create_emoji_field_row(
144            "šŸ’µ ",
145            "Fiat Amount",
146            &fiat_amount.0.to_string(),
147        ));
148    }
149
150    table.add_row(create_emoji_field_row(
151        "šŸ’³ ",
152        "Payment Method",
153        payment_method,
154    ));
155    table.add_row(create_emoji_field_row(
156        "šŸ“ˆ ",
157        "Premium (%)",
158        &premium.to_string(),
159    ));
160    table.add_row(create_emoji_field_row(
161        "šŸ”¢ ",
162        "Trade Index",
163        &ctx.trade_index.to_string(),
164    ));
165    table.add_row(create_emoji_field_row(
166        "šŸ”‘ ",
167        "Trade Key",
168        &ctx.trade_keys.public_key.to_hex(),
169    ));
170    table.add_row(create_emoji_field_row(
171        "šŸŽÆ ",
172        "Target",
173        &ctx.mostro_pubkey.to_string(),
174    ));
175    println!("{}", table);
176    println!("\nšŸ’” Sending new order to Mostro...\n");
177
178    // Serialize the message
179    let message_json = message
180        .as_json()
181        .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?;
182
183    // Send the DM
184    let sent_message = send_dm(
185        &ctx.client,
186        Some(&ctx.identity_keys),
187        &ctx.trade_keys,
188        &ctx.mostro_pubkey,
189        message_json,
190        None,
191        false,
192    );
193
194    // Wait for the DM to be sent from mostro
195    let recv_event = wait_for_dm(ctx, None, sent_message).await?;
196
197    // Parse the incoming DM
198    print_dm_events(recv_event, request_id, ctx, None).await?;
199
200    Ok(())
201}