mostro_client/cli/
new_order.rs

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