mostro_client/cli/
new_order.rs

1use anyhow::Result;
2use mostro_core::message::{Action, CantDoReason, Message, Payload};
3use mostro_core::order::SmallOrder;
4use mostro_core::order::{Kind, Status};
5use nostr_sdk::prelude::*;
6use std::collections::HashMap;
7use std::io::{stdin, stdout, BufRead, Write};
8use std::process;
9use std::str::FromStr;
10use uuid::Uuid;
11
12use crate::db::{connect, Order, User};
13use crate::pretty_table::print_order_preview;
14use crate::util::{send_message_sync, uppercase_first};
15
16pub type FiatNames = HashMap<String, String>;
17
18#[allow(clippy::too_many_arguments)]
19pub async fn execute_new_order(
20    kind: &str,
21    fiat_code: &str,
22    fiat_amount: &(i64, Option<i64>),
23    amount: &i64,
24    payment_method: &String,
25    premium: &i64,
26    invoice: &Option<String>,
27    identity_keys: &Keys,
28    trade_keys: &Keys,
29    trade_index: i64,
30    mostro_key: PublicKey,
31    client: &Client,
32    expiration_days: &i64,
33) -> Result<()> {
34    // Uppercase currency
35    let fiat_code = fiat_code.to_uppercase();
36    // Check if fiat currency selected is available on Yadio and eventually force user to set amount
37    // this is in the case of crypto <--> crypto offer for example
38    if *amount == 0 {
39        // Get Fiat list
40        let api_req_string = "https://api.yadio.io/currencies".to_string();
41        let fiat_list_check = reqwest::get(api_req_string)
42            .await?
43            .json::<FiatNames>()
44            .await?
45            .contains_key(&fiat_code);
46        if !fiat_list_check {
47            println!("{} is not present in the fiat market, please specify an amount with -a flag to fix the rate", fiat_code);
48            process::exit(0);
49        }
50    }
51    let kind = uppercase_first(kind);
52    // New check against strings
53    let kind_checked = Kind::from_str(&kind).unwrap();
54    let expires_at = match *expiration_days {
55        0 => None,
56        _ => {
57            let now = chrono::Utc::now();
58            let expires_at = now + chrono::Duration::days(*expiration_days);
59            Some(expires_at.timestamp())
60        }
61    };
62
63    // Get the type of neworder
64    // if both tuple field are valid than it's a range order
65    // otherwise use just fiat amount value as before
66    let amt = if fiat_amount.1.is_some() {
67        (0, Some(fiat_amount.0), fiat_amount.1)
68    } else {
69        (fiat_amount.0, None, None)
70    };
71    let small_order = SmallOrder::new(
72        None,
73        Some(kind_checked),
74        Some(Status::Pending),
75        *amount,
76        fiat_code.clone(),
77        amt.1,
78        amt.2,
79        amt.0,
80        payment_method.to_owned(),
81        *premium,
82        None,
83        None,
84        invoice.as_ref().to_owned().cloned(),
85        Some(0),
86        expires_at,
87        None,
88        None,
89    );
90
91    // Create new order for mostro
92    let order_content = Payload::Order(small_order.clone());
93
94    // Print order preview
95    let ord_preview = print_order_preview(order_content.clone()).unwrap();
96    println!("{ord_preview}");
97    let mut user_input = String::new();
98    let _input = stdin();
99    print!("Check your order! Is it correct? (Y/n) > ");
100    stdout().flush()?;
101
102    let mut answer = stdin().lock();
103    answer.read_line(&mut user_input)?;
104
105    match user_input.to_lowercase().as_str().trim_end() {
106        "y" | "" => {}
107        "n" => {
108            println!("Ok you have cancelled the order, create another one please");
109            process::exit(0);
110        }
111        &_ => {
112            println!("Can't get what you're sayin!");
113            process::exit(0);
114        }
115    };
116    let request_id = Uuid::new_v4().as_u128() as u64;
117    // Create NewOrder message
118    let message = Message::new_order(
119        None,
120        Some(request_id),
121        Some(trade_index),
122        Action::NewOrder,
123        Some(order_content),
124    );
125
126    let dm = send_message_sync(
127        client,
128        Some(identity_keys),
129        trade_keys,
130        mostro_key,
131        message,
132        true,
133        false,
134    )
135    .await?;
136    let order_id = dm
137        .iter()
138        .find_map(|el| {
139            let message = el.0.get_inner_message_kind();
140            if message.request_id == Some(request_id) {
141                match message.action {
142                    Action::NewOrder => {
143                        if let Some(Payload::Order(order)) = message.payload.as_ref() {
144                            return order.id;
145                        }
146                    }
147                    Action::CantDo => {
148                        if let Some(Payload::CantDo(Some(cant_do_reason))) = &message.payload {
149                            match cant_do_reason {
150                                CantDoReason::OutOfRangeFiatAmount | CantDoReason::OutOfRangeSatsAmount => {
151                                    println!("Error: Amount is outside the allowed range. Please check the order's min/max limits.");
152                                }
153                                _ => {
154                                    println!("Unknown reason: {:?}", message.payload);
155                                }
156                            }
157                        } else {
158                            println!("Unknown reason: {:?}", message.payload);
159                            return None;
160                        }
161                    }
162                    _ => {
163                        println!("Unknown action: {:?}", message.action);
164                        return None;
165                    }
166                }
167            }
168            None
169        })
170        .or_else(|| {
171            println!("Error: No matching order found in response");
172            None
173        });
174
175    if let Some(order_id) = order_id {
176        println!("Order id {} created", order_id);
177        // Create order in db
178        let pool = connect().await?;
179        let db_order = Order::new(&pool, small_order, trade_keys, Some(request_id as i64))
180            .await
181            .map_err(|e| anyhow::anyhow!("Failed to create DB order: {:?}", e))?;
182        // Update last trade index
183        match User::get(&pool).await {
184            Ok(mut user) => {
185                user.set_last_trade_index(trade_index);
186                if let Err(e) = user.save(&pool).await {
187                    println!("Failed to update user: {}", e);
188                }
189            }
190            Err(e) => println!("Failed to get user: {}", e),
191        }
192        let db_order_id = db_order
193            .id
194            .clone()
195            .ok_or(anyhow::anyhow!("Missing order id"))?;
196        Order::save_new_id(&pool, db_order_id, order_id.to_string()).await?;
197    }
198    Ok(())
199}