mostro_core/
order.rs

1use nostr_sdk::{PublicKey, Timestamp};
2use serde::{Deserialize, Serialize};
3#[cfg(feature = "sqlx")]
4use sqlx::FromRow;
5#[cfg(feature = "sqlx")]
6use sqlx_crud::SqlxCrud;
7use std::{fmt::Display, str::FromStr};
8use uuid::Uuid;
9use wasm_bindgen::prelude::*;
10
11use crate::error::{CantDoReason, ServiceError};
12
13/// Orders can be only Buy or Sell
14#[wasm_bindgen]
15#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
16#[serde(rename_all = "kebab-case")]
17pub enum Kind {
18    Buy,
19    Sell,
20}
21
22impl FromStr for Kind {
23    type Err = ();
24
25    fn from_str(kind: &str) -> std::result::Result<Self, Self::Err> {
26        match kind.to_lowercase().as_str() {
27            "buy" => std::result::Result::Ok(Self::Buy),
28            "sell" => std::result::Result::Ok(Self::Sell),
29            _ => Err(()),
30        }
31    }
32}
33
34impl Display for Kind {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        match self {
37            Kind::Sell => write!(f, "sell"),
38            Kind::Buy => write!(f, "buy"),
39        }
40    }
41}
42
43/// Each status that an order can have
44#[wasm_bindgen]
45#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
46#[serde(rename_all = "kebab-case")]
47pub enum Status {
48    Active,
49    Canceled,
50    CanceledByAdmin,
51    SettledByAdmin,
52    CompletedByAdmin,
53    Dispute,
54    Expired,
55    FiatSent,
56    SettledHoldInvoice,
57    Pending,
58    Success,
59    WaitingBuyerInvoice,
60    WaitingPayment,
61    CooperativelyCanceled,
62    InProgress,
63}
64
65impl Display for Status {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        match self {
68            Status::Active => write!(f, "active"),
69            Status::Canceled => write!(f, "canceled"),
70            Status::CanceledByAdmin => write!(f, "canceled-by-admin"),
71            Status::SettledByAdmin => write!(f, "settled-by-admin"),
72            Status::CompletedByAdmin => write!(f, "completed-by-admin"),
73            Status::Dispute => write!(f, "dispute"),
74            Status::Expired => write!(f, "expired"),
75            Status::FiatSent => write!(f, "fiat-sent"),
76            Status::SettledHoldInvoice => write!(f, "settled-hold-invoice"),
77            Status::Pending => write!(f, "pending"),
78            Status::Success => write!(f, "success"),
79            Status::WaitingBuyerInvoice => write!(f, "waiting-buyer-invoice"),
80            Status::WaitingPayment => write!(f, "waiting-payment"),
81            Status::CooperativelyCanceled => write!(f, "cooperatively-canceled"),
82            Status::InProgress => write!(f, "in-progress"),
83        }
84    }
85}
86
87impl FromStr for Status {
88    type Err = ();
89    /// Convert a string to a status
90    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
91        match s.to_lowercase().as_str() {
92            "active" => std::result::Result::Ok(Self::Active),
93            "canceled" => std::result::Result::Ok(Self::Canceled),
94            "canceled-by-admin" => std::result::Result::Ok(Self::CanceledByAdmin),
95            "settled-by-admin" => std::result::Result::Ok(Self::SettledByAdmin),
96            "completed-by-admin" => std::result::Result::Ok(Self::CompletedByAdmin),
97            "dispute" => std::result::Result::Ok(Self::Dispute),
98            "expired" => std::result::Result::Ok(Self::Expired),
99            "fiat-sent" => std::result::Result::Ok(Self::FiatSent),
100            "settled-hold-invoice" => std::result::Result::Ok(Self::SettledHoldInvoice),
101            "pending" => std::result::Result::Ok(Self::Pending),
102            "success" => std::result::Result::Ok(Self::Success),
103            "waiting-buyer-invoice" => std::result::Result::Ok(Self::WaitingBuyerInvoice),
104            "waiting-payment" => std::result::Result::Ok(Self::WaitingPayment),
105            "cooperatively-canceled" => std::result::Result::Ok(Self::CooperativelyCanceled),
106            "in-progress" => std::result::Result::Ok(Self::InProgress),
107            _ => Err(()),
108        }
109    }
110}
111
112/// Database representation of an order
113#[cfg_attr(feature = "sqlx", derive(FromRow, SqlxCrud), external_id)]
114#[derive(Debug, Default, Deserialize, Serialize, Clone)]
115pub struct Order {
116    pub id: Uuid,
117    pub kind: String,
118    pub event_id: String,
119    pub hash: Option<String>,
120    pub preimage: Option<String>,
121    pub creator_pubkey: String,
122    pub cancel_initiator_pubkey: Option<String>,
123    pub buyer_pubkey: Option<String>,
124    pub master_buyer_pubkey: Option<String>,
125    pub seller_pubkey: Option<String>,
126    pub master_seller_pubkey: Option<String>,
127    pub status: String,
128    pub price_from_api: bool,
129    pub premium: i64,
130    pub payment_method: String,
131    pub amount: i64,
132    pub min_amount: Option<i64>,
133    pub max_amount: Option<i64>,
134    pub buyer_dispute: bool,
135    pub seller_dispute: bool,
136    pub buyer_cooperativecancel: bool,
137    pub seller_cooperativecancel: bool,
138    pub fee: i64,
139    pub routing_fee: i64,
140    pub fiat_code: String,
141    pub fiat_amount: i64,
142    pub buyer_invoice: Option<String>,
143    pub range_parent_id: Option<Uuid>,
144    pub invoice_held_at: i64,
145    pub taken_at: i64,
146    pub created_at: i64,
147    pub buyer_sent_rate: bool,
148    pub seller_sent_rate: bool,
149    pub failed_payment: bool,
150    pub payment_attempts: i64,
151    pub expires_at: i64,
152    pub trade_index_seller: Option<i64>,
153    pub trade_index_buyer: Option<i64>,
154    pub next_trade_pubkey: Option<String>,
155    pub next_trade_index: Option<i64>,
156}
157
158impl From<SmallOrder> for Order {
159    fn from(small_order: SmallOrder) -> Self {
160        Self {
161            id: Uuid::new_v4(),
162            // order will be overwritten with the real one before publishing
163            kind: small_order
164                .kind
165                .map_or_else(|| Kind::Buy.to_string(), |k| k.to_string()),
166            status: small_order
167                .status
168                .map_or_else(|| Status::Active.to_string(), |s| s.to_string()),
169            amount: small_order.amount,
170            fiat_code: small_order.fiat_code,
171            min_amount: small_order.min_amount,
172            max_amount: small_order.max_amount,
173            fiat_amount: small_order.fiat_amount,
174            payment_method: small_order.payment_method,
175            premium: small_order.premium,
176            event_id: String::new(),
177            creator_pubkey: String::new(),
178            price_from_api: false,
179            fee: 0,
180            routing_fee: 0,
181            invoice_held_at: 0,
182            taken_at: 0,
183            created_at: small_order.created_at.unwrap_or(0),
184            expires_at: small_order.expires_at.unwrap_or(0),
185            payment_attempts: 0,
186            ..Default::default()
187        }
188    }
189}
190
191impl Order {
192    /// Convert an order to a small order
193    pub fn as_new_order(&self) -> SmallOrder {
194        SmallOrder::new(
195            Some(self.id),
196            Some(Kind::from_str(&self.kind).unwrap()),
197            Some(Status::from_str(&self.status).unwrap()),
198            self.amount,
199            self.fiat_code.clone(),
200            self.min_amount,
201            self.max_amount,
202            self.fiat_amount,
203            self.payment_method.clone(),
204            self.premium,
205            None,
206            None,
207            self.buyer_invoice.clone(),
208            Some(self.created_at),
209            Some(self.expires_at),
210            None,
211            None,
212        )
213    }
214
215    /// Get the kind of the order
216    pub fn get_order_kind(&self) -> Result<Kind, ServiceError> {
217        if let Ok(kind) = Kind::from_str(&self.kind) {
218            Ok(kind)
219        } else {
220            Err(ServiceError::InvalidOrderKind)
221        }
222    }
223
224    /// Get the status of the order in case
225    pub fn get_order_status(&self) -> Result<Status, ServiceError> {
226        if let Ok(status) = Status::from_str(&self.status) {
227            Ok(status)
228        } else {
229            Err(ServiceError::InvalidOrderStatus)
230        }
231    }
232
233    /// Compare the status of the order
234    pub fn check_status(&self, status: Status) -> Result<(), CantDoReason> {
235        match Status::from_str(&self.status) {
236            Ok(s) => match s == status {
237                true => Ok(()),
238                false => Err(CantDoReason::InvalidOrderStatus),
239            },
240            Err(_) => Err(CantDoReason::InvalidOrderStatus),
241        }
242    }
243
244    /// Check if the order is a buy order
245    pub fn is_buy_order(&self) -> Result<(), CantDoReason> {
246        if self.kind != Kind::Buy.to_string() {
247            return Err(CantDoReason::InvalidOrderKind);
248        }
249        Ok(())
250    }
251    /// Check if the order is a sell order
252    pub fn is_sell_order(&self) -> Result<(), CantDoReason> {
253        if self.kind != Kind::Sell.to_string() {
254            return Err(CantDoReason::InvalidOrderKind);
255        }
256        Ok(())
257    }
258
259    /// Check if the sender is the creator of the order
260    pub fn sent_from_maker(&self, sender: PublicKey) -> Result<(), CantDoReason> {
261        let sender = sender.to_string();
262        if self.creator_pubkey != sender {
263            return Err(CantDoReason::InvalidPubkey);
264        }
265        Ok(())
266    }
267
268    /// Check if the sender is the creator of the order
269    pub fn not_sent_from_maker(&self, sender: PublicKey) -> Result<(), CantDoReason> {
270        let sender = sender.to_string();
271        if self.creator_pubkey == sender {
272            return Err(CantDoReason::InvalidPubkey);
273        }
274        Ok(())
275    }
276
277    /// Get the creator pubkey
278    pub fn get_creator_pubkey(&self) -> Result<PublicKey, ServiceError> {
279        match PublicKey::from_str(self.creator_pubkey.as_ref()) {
280            Ok(pk) => Ok(pk),
281            Err(_) => Err(ServiceError::InvalidPubkey),
282        }
283    }
284
285    /// Get the buyer pubkey
286    pub fn get_buyer_pubkey(&self) -> Result<PublicKey, ServiceError> {
287        if let Some(pk) = self.buyer_pubkey.as_ref() {
288            PublicKey::from_str(pk).map_err(|_| ServiceError::InvalidPubkey)
289        } else {
290            Err(ServiceError::InvalidPubkey)
291        }
292    }
293    /// Get the seller pubkey
294    pub fn get_seller_pubkey(&self) -> Result<PublicKey, ServiceError> {
295        if let Some(pk) = self.seller_pubkey.as_ref() {
296            PublicKey::from_str(pk).map_err(|_| ServiceError::InvalidPubkey)
297        } else {
298            Err(ServiceError::InvalidPubkey)
299        }
300    }
301    /// Get the master buyer pubkey
302    pub fn get_master_buyer_pubkey(&self) -> Result<PublicKey, ServiceError> {
303        if let Some(pk) = self.master_buyer_pubkey.as_ref() {
304            PublicKey::from_str(pk).map_err(|_| ServiceError::InvalidPubkey)
305        } else {
306            Err(ServiceError::InvalidPubkey)
307        }
308    }
309    /// Get the master seller pubkey
310    pub fn get_master_seller_pubkey(&self) -> Result<PublicKey, ServiceError> {
311        if let Some(pk) = self.master_seller_pubkey.as_ref() {
312            PublicKey::from_str(pk).map_err(|_| ServiceError::InvalidPubkey)
313        } else {
314            Err(ServiceError::InvalidPubkey)
315        }
316    }
317
318    /// Check if the order is a range order
319    pub fn is_range_order(&self) -> bool {
320        self.min_amount.is_some() && self.max_amount.is_some()
321    }
322
323    pub fn count_failed_payment(&mut self, retries_number: i64) {
324        if !self.failed_payment {
325            self.failed_payment = true;
326            self.payment_attempts = 0;
327        } else if self.payment_attempts < retries_number {
328            self.payment_attempts += 1;
329        }
330    }
331
332    /// Check if the order has no amount
333    pub fn has_no_amount(&self) -> bool {
334        self.amount == 0
335    }
336
337    /// Set the timestamp to now
338    pub fn set_timestamp_now(&mut self) {
339        self.taken_at = Timestamp::now().as_u64() as i64
340    }
341
342    /// check if a user is creating a full privacy order so he doesn't to have reputation
343    pub fn is_full_privacy_order(&self) -> (bool, bool) {
344        let (mut full_privacy_buyer, mut full_privacy_seller) = (false, false);
345
346        // Find full privacy users in this trade
347        if self.buyer_pubkey.is_some()
348            && self.master_buyer_pubkey.is_some()
349            && self.master_buyer_pubkey == self.buyer_pubkey
350        {
351            full_privacy_buyer = true;
352        }
353
354        if self.seller_pubkey.is_some()
355            && self.master_seller_pubkey.is_some()
356            && self.master_seller_pubkey == self.seller_pubkey
357        {
358            full_privacy_seller = true;
359        }
360
361        (full_privacy_buyer, full_privacy_seller)
362    }
363    /// Setup the dispute status
364    ///
365    /// If the pubkey is the buyer, set the buyer dispute to true
366    /// If the pubkey is the seller, set the seller dispute to true
367    pub fn setup_dispute(&mut self, is_buyer_dispute: bool) -> Result<(), CantDoReason> {
368        // Get the opposite dispute status
369        let is_seller_dispute = !is_buyer_dispute;
370
371        // Update dispute flags based on who initiated
372        let mut update_seller_dispute = false;
373        let mut update_buyer_dispute = false;
374
375        if is_seller_dispute && !self.seller_dispute {
376            update_seller_dispute = true;
377            self.seller_dispute = update_seller_dispute;
378        } else if is_buyer_dispute && !self.buyer_dispute {
379            update_buyer_dispute = true;
380            self.buyer_dispute = update_buyer_dispute;
381        };
382        // Set the status to dispute
383        self.status = Status::Dispute.to_string();
384
385        // Update the database with dispute information
386        // Save the dispute to DB
387        if !update_buyer_dispute && !update_seller_dispute {
388            return Err(CantDoReason::DisputeCreationError);
389        }
390
391        Ok(())
392    }
393}
394
395/// We use this struct to create a new order
396#[derive(Debug, Default, Deserialize, Serialize, Clone)]
397pub struct SmallOrder {
398    #[serde(skip_serializing_if = "Option::is_none")]
399    pub id: Option<Uuid>,
400    pub kind: Option<Kind>,
401    pub status: Option<Status>,
402    pub amount: i64,
403    pub fiat_code: String,
404    pub min_amount: Option<i64>,
405    pub max_amount: Option<i64>,
406    pub fiat_amount: i64,
407    pub payment_method: String,
408    pub premium: i64,
409    #[serde(skip_serializing_if = "Option::is_none")]
410    pub buyer_trade_pubkey: Option<String>,
411    #[serde(skip_serializing_if = "Option::is_none")]
412    pub seller_trade_pubkey: Option<String>,
413    #[serde(skip_serializing_if = "Option::is_none")]
414    pub buyer_invoice: Option<String>,
415    pub created_at: Option<i64>,
416    pub expires_at: Option<i64>,
417    pub buyer_token: Option<u16>,
418    pub seller_token: Option<u16>,
419}
420
421#[allow(dead_code)]
422impl SmallOrder {
423    #[allow(clippy::too_many_arguments)]
424    pub fn new(
425        id: Option<Uuid>,
426        kind: Option<Kind>,
427        status: Option<Status>,
428        amount: i64,
429        fiat_code: String,
430        min_amount: Option<i64>,
431        max_amount: Option<i64>,
432        fiat_amount: i64,
433        payment_method: String,
434        premium: i64,
435        buyer_trade_pubkey: Option<String>,
436        seller_trade_pubkey: Option<String>,
437        buyer_invoice: Option<String>,
438        created_at: Option<i64>,
439        expires_at: Option<i64>,
440        buyer_token: Option<u16>,
441        seller_token: Option<u16>,
442    ) -> Self {
443        Self {
444            id,
445            kind,
446            status,
447            amount,
448            fiat_code,
449            min_amount,
450            max_amount,
451            fiat_amount,
452            payment_method,
453            premium,
454            buyer_trade_pubkey,
455            seller_trade_pubkey,
456            buyer_invoice,
457            created_at,
458            expires_at,
459            buyer_token,
460            seller_token,
461        }
462    }
463    /// New order from json string
464    pub fn from_json(json: &str) -> Result<Self, ServiceError> {
465        serde_json::from_str(json).map_err(|_| ServiceError::MessageSerializationError)
466    }
467
468    /// Get order as json string
469    pub fn as_json(&self) -> Result<String, ServiceError> {
470        serde_json::to_string(&self).map_err(|_| ServiceError::MessageSerializationError)
471    }
472
473    /// Get the amount of sats or the string "Market price"
474    pub fn sats_amount(&self) -> String {
475        if self.amount == 0 {
476            "Market price".to_string()
477        } else {
478            self.amount.to_string()
479        }
480    }
481    /// Check if the order has a zero amount and a premium or fiat amount
482    pub fn check_zero_amount_with_premium(&self) -> Result<(), CantDoReason> {
483        let premium = (self.premium != 0).then_some(self.premium);
484        let sats_amount = (self.amount != 0).then_some(self.amount);
485
486        if premium.is_some() && sats_amount.is_some() {
487            return Err(CantDoReason::InvalidParameters);
488        }
489        Ok(())
490    }
491    /// Check if the order is a range order and if the amount is zero
492    pub fn check_range_order_limits(&self, amounts: &mut Vec<i64>) -> Result<(), CantDoReason> {
493        // Check if the min and max amount are valid and update the vector
494        if let (Some(min), Some(max)) = (self.min_amount, self.max_amount) {
495            if min < 0 || max < 0 {
496                return Err(CantDoReason::InvalidAmount);
497            }
498            if min >= max {
499                return Err(CantDoReason::InvalidAmount);
500            }
501            if self.amount != 0 {
502                return Err(CantDoReason::InvalidAmount);
503            }
504            amounts.clear();
505            amounts.push(min);
506            amounts.push(max);
507        }
508        Ok(())
509    }
510
511    // Get the fiat amount, if the order is a range order, return the range as min-max string
512    pub fn fiat_amount(&self) -> String {
513        if self.max_amount.is_some() {
514            format!("{}-{}", self.min_amount.unwrap(), self.max_amount.unwrap())
515        } else {
516            self.fiat_amount.to_string()
517        }
518    }
519}
520
521impl From<Order> for SmallOrder {
522    fn from(order: Order) -> Self {
523        let id = Some(order.id);
524        let kind = Kind::from_str(&order.kind).unwrap();
525        let status = Status::from_str(&order.status).unwrap();
526        let amount = order.amount;
527        let fiat_code = order.fiat_code.clone();
528        let min_amount = order.min_amount;
529        let max_amount = order.max_amount;
530        let fiat_amount = order.fiat_amount;
531        let payment_method = order.payment_method.clone();
532        let premium = order.premium;
533        let buyer_trade_pubkey = order.buyer_pubkey.clone();
534        let seller_trade_pubkey = order.seller_pubkey.clone();
535        let buyer_invoice = order.buyer_invoice.clone();
536
537        Self {
538            id,
539            kind: Some(kind),
540            status: Some(status),
541            amount,
542            fiat_code,
543            min_amount,
544            max_amount,
545            fiat_amount,
546            payment_method,
547            premium,
548            buyer_trade_pubkey,
549            seller_trade_pubkey,
550            buyer_invoice,
551            created_at: None,
552            expires_at: None,
553            buyer_token: None,
554            seller_token: None,
555        }
556    }
557}