mostro_core/
lib.rs

1pub mod dispute;
2pub mod error;
3pub mod message;
4pub mod order;
5pub mod rating;
6pub mod user;
7
8/// All events broadcasted by Mostro daemon are Parameterized Replaceable Events
9/// and the event kind must be between 30000 and 39999
10pub const NOSTR_REPLACEABLE_EVENT_KIND: u16 = 38383;
11pub const PROTOCOL_VER: u8 = 1;
12
13#[cfg(test)]
14mod test {
15    use crate::error::CantDoReason;
16    use crate::message::{Action, Message, MessageKind, Payload, Peer};
17    use crate::order::{Kind, SmallOrder, Status};
18    use nostr_sdk::Keys;
19    use uuid::uuid;
20
21    #[test]
22    fn test_status_string() {
23        assert_eq!(Status::Active.to_string(), "active");
24        assert_eq!(Status::CompletedByAdmin.to_string(), "completed-by-admin");
25        assert_eq!(Status::FiatSent.to_string(), "fiat-sent");
26        assert_ne!(Status::Pending.to_string(), "Pending");
27    }
28
29    #[test]
30    fn test_kind_string() {
31        assert_ne!(Kind::Sell.to_string(), "active");
32        assert_eq!(Kind::Sell.to_string(), "sell");
33        assert_eq!(Kind::Buy.to_string(), "buy");
34        assert_ne!(Kind::Buy.to_string(), "active");
35    }
36
37    #[test]
38    fn test_order_message() {
39        let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
40        let payload = Payload::Order(SmallOrder::new(
41            Some(uuid),
42            Some(Kind::Sell),
43            Some(Status::Pending),
44            100,
45            "eur".to_string(),
46            None,
47            None,
48            100,
49            "SEPA".to_string(),
50            1,
51            None,
52            None,
53            None,
54            Some(1627371434),
55            None,
56            None,
57            None,
58        ));
59
60        let test_message = Message::Order(MessageKind::new(
61            Some(uuid),
62            Some(1),
63            Some(2),
64            Action::NewOrder,
65            Some(payload),
66        ));
67        let test_message_json = test_message.as_json().unwrap();
68        let sample_message = r#"{"order":{"version":1,"id":"308e1272-d5f4-47e6-bd97-3504baea9c23","request_id":1,"trade_index":2,"action":"new-order","payload":{"order":{"id":"308e1272-d5f4-47e6-bd97-3504baea9c23","kind":"sell","status":"pending","amount":100,"fiat_code":"eur","fiat_amount":100,"payment_method":"SEPA","premium":1,"created_at":1627371434}}}}"#;
69        let message = Message::from_json(sample_message).unwrap();
70        assert!(message.verify());
71        let message_json = message.as_json().unwrap();
72        assert_eq!(message_json, test_message_json);
73    }
74
75    #[test]
76    fn test_payment_request_payload_message() {
77        let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
78        let test_message = Message::Order(MessageKind::new(
79            Some(uuid),
80            Some(1),
81            Some(3),
82            Action::PayInvoice,
83            Some(Payload::PaymentRequest(
84                Some(SmallOrder::new(
85                    Some(uuid),
86                    Some(Kind::Sell),
87                    Some(Status::WaitingPayment),
88                    100,
89                    "eur".to_string(),
90                    None,
91                    None,
92                    100,
93                    "SEPA".to_string(),
94                    1,
95                    None,
96                    None,
97                    None,
98                    Some(1627371434),
99                    None,
100                    None,
101                    None,
102                )),
103                "lnbcrt78510n1pj59wmepp50677g8tffdqa2p8882y0x6newny5vtz0hjuyngdwv226nanv4uzsdqqcqzzsxqyz5vqsp5skn973360gp4yhlpmefwvul5hs58lkkl3u3ujvt57elmp4zugp4q9qyyssqw4nzlr72w28k4waycf27qvgzc9sp79sqlw83j56txltz4va44j7jda23ydcujj9y5k6k0rn5ms84w8wmcmcyk5g3mhpqepf7envhdccp72nz6e".to_string(),
104                None,
105            )),
106        ));
107        let sample_message = r#"{"order":{"version":1,"id":"308e1272-d5f4-47e6-bd97-3504baea9c23","request_id":1,"trade_index":3,"action":"pay-invoice","payload":{"payment_request":[{"id":"308e1272-d5f4-47e6-bd97-3504baea9c23","kind":"sell","status":"waiting-payment","amount":100,"fiat_code":"eur","fiat_amount":100,"payment_method":"SEPA","premium":1,"created_at":1627371434},"lnbcrt78510n1pj59wmepp50677g8tffdqa2p8882y0x6newny5vtz0hjuyngdwv226nanv4uzsdqqcqzzsxqyz5vqsp5skn973360gp4yhlpmefwvul5hs58lkkl3u3ujvt57elmp4zugp4q9qyyssqw4nzlr72w28k4waycf27qvgzc9sp79sqlw83j56txltz4va44j7jda23ydcujj9y5k6k0rn5ms84w8wmcmcyk5g3mhpqepf7envhdccp72nz6e",null]}}}"#;
108        let message = Message::from_json(sample_message).unwrap();
109        assert!(message.verify());
110        let message_json = message.as_json().unwrap();
111        let test_message_json = test_message.as_json().unwrap();
112        assert_eq!(message_json, test_message_json);
113    }
114
115    #[test]
116    fn test_message_payload_signature() {
117        let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
118        let peer = Peer::new(
119            "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
120        );
121        let payload = Payload::Peer(peer);
122        let test_message = Message::Order(MessageKind::new(
123            Some(uuid),
124            Some(1),
125            Some(2),
126            Action::FiatSentOk,
127            Some(payload),
128        ));
129        assert!(test_message.verify());
130        let test_message_json = test_message.as_json().unwrap();
131        // Message should be signed with the trade keys
132        let trade_keys =
133            Keys::parse("110e43647eae221ab1da33ddc17fd6ff423f2b2f49d809b9ffa40794a2ab996c")
134                .unwrap();
135        let sig = Message::sign(test_message_json.clone(), &trade_keys);
136
137        assert!(Message::verify_signature(
138            test_message_json,
139            trade_keys.public_key(),
140            sig
141        ));
142    }
143
144    #[test]
145    fn test_cant_do_message_serialization() {
146        // Test all CantDoReason variants
147        let reasons = vec![
148            CantDoReason::InvalidSignature,
149            CantDoReason::InvalidTradeIndex,
150            CantDoReason::InvalidAmount,
151            CantDoReason::InvalidInvoice,
152            CantDoReason::InvalidPaymentRequest,
153            CantDoReason::InvalidPeer,
154            CantDoReason::InvalidRating,
155            CantDoReason::InvalidTextMessage,
156            CantDoReason::InvalidOrderStatus,
157            CantDoReason::InvalidPubkey,
158            CantDoReason::InvalidParameters,
159            CantDoReason::OrderAlreadyCanceled,
160            CantDoReason::CantCreateUser,
161            CantDoReason::IsNotYourOrder,
162            CantDoReason::NotAllowedByStatus,
163            CantDoReason::OutOfRangeFiatAmount,
164            CantDoReason::OutOfRangeSatsAmount,
165            CantDoReason::IsNotYourDispute,
166            CantDoReason::NotFound,
167        ];
168
169        for reason in reasons {
170            let cant_do = Message::CantDo(MessageKind::new(
171                None,
172                None,
173                None,
174                Action::CantDo,
175                Some(Payload::CantDo(Some(reason.clone()))),
176            ));
177            let message = Message::from_json(&cant_do.as_json().unwrap()).unwrap();
178            assert!(message.verify());
179            assert_eq!(message.as_json().unwrap(), cant_do.as_json().unwrap());
180        }
181
182        // Test None case
183        let cant_do = Message::CantDo(MessageKind::new(
184            None,
185            None,
186            None,
187            Action::CantDo,
188            Some(Payload::CantDo(None)),
189        ));
190        let message = Message::from_json(&cant_do.as_json().unwrap()).unwrap();
191        assert!(message.verify());
192        assert_eq!(message.as_json().unwrap(), cant_do.as_json().unwrap());
193    }
194}