1use crate::prelude::*;
14use bitcoin::hashes::sha256::Hash as Sha256Hash;
15use bitcoin::hashes::Hash;
16use bitcoin::key::Secp256k1;
17use bitcoin::secp256k1::Message as BitcoinMessage;
18use nostr_sdk::prelude::*;
19#[cfg(feature = "sqlx")]
20use sqlx::FromRow;
21#[cfg(feature = "sqlx")]
22use sqlx_crud::SqlxCrud;
23
24use std::fmt;
25use uuid::Uuid;
26
27#[derive(Debug, Deserialize, Serialize, Clone)]
34pub struct Peer {
35 pub pubkey: String,
37 pub reputation: Option<UserInfo>,
40}
41
42impl Peer {
43 pub fn new(pubkey: String, reputation: Option<UserInfo>) -> Self {
45 Self { pubkey, reputation }
46 }
47
48 pub fn from_json(json: &str) -> Result<Self, ServiceError> {
50 serde_json::from_str(json).map_err(|_| ServiceError::MessageSerializationError)
51 }
52
53 pub fn as_json(&self) -> Result<String, ServiceError> {
55 serde_json::to_string(&self).map_err(|_| ServiceError::MessageSerializationError)
56 }
57}
58
59#[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Clone)]
65#[serde(rename_all = "kebab-case")]
66pub enum Action {
67 NewOrder,
69 TakeSell,
72 TakeBuy,
74 PayInvoice,
77 FiatSent,
79 FiatSentOk,
81 Release,
83 Released,
85 Cancel,
87 Canceled,
89 CooperativeCancelInitiatedByYou,
91 CooperativeCancelInitiatedByPeer,
93 DisputeInitiatedByYou,
95 DisputeInitiatedByPeer,
97 CooperativeCancelAccepted,
99 BuyerInvoiceAccepted,
101 PurchaseCompleted,
103 HoldInvoicePaymentAccepted,
105 HoldInvoicePaymentSettled,
107 HoldInvoicePaymentCanceled,
109 WaitingSellerToPay,
111 WaitingBuyerInvoice,
113 AddInvoice,
116 BuyerTookOrder,
118 Rate,
120 RateUser,
122 RateReceived,
124 CantDo,
126 Dispute,
128 AdminCancel,
130 AdminCanceled,
132 AdminSettle,
134 AdminSettled,
136 AdminAddSolver,
138 AdminTakeDispute,
140 AdminTookDispute,
142 PaymentFailed,
145 InvoiceUpdated,
147 SendDm,
149 TradePubkey,
151 RestoreSession,
153 LastTradeIndex,
156 Orders,
159}
160
161impl fmt::Display for Action {
162 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
163 write!(f, "{self:?}")
164 }
165}
166
167#[derive(Debug, Clone, Deserialize, Serialize)]
174#[serde(rename_all = "kebab-case")]
175pub enum Message {
176 Order(MessageKind),
178 Dispute(MessageKind),
180 CantDo(MessageKind),
182 Rate(MessageKind),
184 Dm(MessageKind),
186 Restore(MessageKind),
188}
189
190impl Message {
191 pub fn new_order(
194 id: Option<Uuid>,
195 request_id: Option<u64>,
196 trade_index: Option<i64>,
197 action: Action,
198 payload: Option<Payload>,
199 ) -> Self {
200 let kind = MessageKind::new(id, request_id, trade_index, action, payload);
201 Self::Order(kind)
202 }
203
204 pub fn new_dispute(
207 id: Option<Uuid>,
208 request_id: Option<u64>,
209 trade_index: Option<i64>,
210 action: Action,
211 payload: Option<Payload>,
212 ) -> Self {
213 let kind = MessageKind::new(id, request_id, trade_index, action, payload);
214
215 Self::Dispute(kind)
216 }
217
218 pub fn new_restore(payload: Option<Payload>) -> Self {
223 let kind = MessageKind::new(None, None, None, Action::RestoreSession, payload);
224 Self::Restore(kind)
225 }
226
227 pub fn cant_do(id: Option<Uuid>, request_id: Option<u64>, payload: Option<Payload>) -> Self {
230 let kind = MessageKind::new(id, request_id, None, Action::CantDo, payload);
231
232 Self::CantDo(kind)
233 }
234
235 pub fn new_dm(
237 id: Option<Uuid>,
238 request_id: Option<u64>,
239 action: Action,
240 payload: Option<Payload>,
241 ) -> Self {
242 let kind = MessageKind::new(id, request_id, None, action, payload);
243
244 Self::Dm(kind)
245 }
246
247 pub fn from_json(json: &str) -> Result<Self, ServiceError> {
249 serde_json::from_str(json).map_err(|_| ServiceError::MessageSerializationError)
250 }
251
252 pub fn as_json(&self) -> Result<String, ServiceError> {
254 serde_json::to_string(&self).map_err(|_| ServiceError::MessageSerializationError)
255 }
256
257 pub fn get_inner_message_kind(&self) -> &MessageKind {
259 match self {
260 Message::Dispute(k)
261 | Message::Order(k)
262 | Message::CantDo(k)
263 | Message::Rate(k)
264 | Message::Dm(k)
265 | Message::Restore(k) => k,
266 }
267 }
268
269 pub fn inner_action(&self) -> Option<Action> {
274 match self {
275 Message::Dispute(a)
276 | Message::Order(a)
277 | Message::CantDo(a)
278 | Message::Rate(a)
279 | Message::Dm(a)
280 | Message::Restore(a) => Some(a.get_action()),
281 }
282 }
283
284 pub fn verify(&self) -> bool {
287 match self {
288 Message::Order(m)
289 | Message::Dispute(m)
290 | Message::CantDo(m)
291 | Message::Rate(m)
292 | Message::Dm(m)
293 | Message::Restore(m) => m.verify(),
294 }
295 }
296
297 pub fn sign(message: String, keys: &Keys) -> Signature {
306 let hash: Sha256Hash = Sha256Hash::hash(message.as_bytes());
307 let hash = hash.to_byte_array();
308 let message: BitcoinMessage = BitcoinMessage::from_digest(hash);
309
310 keys.sign_schnorr(&message)
311 }
312
313 pub fn verify_signature(message: String, pubkey: PublicKey, sig: Signature) -> bool {
319 let hash: Sha256Hash = Sha256Hash::hash(message.as_bytes());
321 let hash = hash.to_byte_array();
322 let message: BitcoinMessage = BitcoinMessage::from_digest(hash);
323
324 let secp = Secp256k1::verification_only();
326 if let Ok(xonlykey) = pubkey.xonly() {
328 xonlykey.verify(&secp, &message, &sig).is_ok()
329 } else {
330 false
331 }
332 }
333}
334
335#[derive(Debug, Clone, Deserialize, Serialize)]
342pub struct MessageKind {
343 pub version: u8,
346 pub request_id: Option<u64>,
349 pub trade_index: Option<i64>,
352 #[serde(skip_serializing_if = "Option::is_none")]
355 pub id: Option<Uuid>,
356 pub action: Action,
358 pub payload: Option<Payload>,
361}
362
363type Amount = i64;
365
366#[derive(Debug, Deserialize, Serialize, Clone)]
371pub struct PaymentFailedInfo {
372 pub payment_attempts: u32,
374 pub payment_retries_interval: u32,
376}
377
378#[cfg_attr(feature = "sqlx", derive(FromRow, SqlxCrud))]
383#[derive(Debug, Deserialize, Serialize, Clone)]
384pub struct RestoredOrderHelper {
385 pub id: Uuid,
387 pub status: String,
389 pub master_buyer_pubkey: Option<String>,
391 pub master_seller_pubkey: Option<String>,
393 pub trade_index_buyer: Option<i64>,
395 pub trade_index_seller: Option<i64>,
397}
398
399#[cfg_attr(feature = "sqlx", derive(FromRow, SqlxCrud))]
404#[derive(Debug, Deserialize, Serialize, Clone)]
405pub struct RestoredDisputeHelper {
406 pub dispute_id: Uuid,
408 pub order_id: Uuid,
410 pub dispute_status: String,
412 pub master_buyer_pubkey: Option<String>,
414 pub master_seller_pubkey: Option<String>,
416 pub trade_index_buyer: Option<i64>,
418 pub trade_index_seller: Option<i64>,
420 pub buyer_dispute: bool,
424 pub seller_dispute: bool,
428 pub solver_pubkey: Option<String>,
431}
432
433#[cfg_attr(feature = "sqlx", derive(FromRow, SqlxCrud))]
435#[derive(Debug, Deserialize, Serialize, Clone)]
436pub struct RestoredOrdersInfo {
437 pub order_id: Uuid,
439 pub trade_index: i64,
441 pub status: String,
443}
444
445#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
447#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
448#[serde(rename_all = "lowercase")]
449#[cfg_attr(feature = "sqlx", sqlx(type_name = "TEXT", rename_all = "lowercase"))]
450pub enum DisputeInitiator {
451 Buyer,
453 Seller,
455}
456
457#[cfg_attr(feature = "sqlx", derive(FromRow, SqlxCrud))]
459#[derive(Debug, Deserialize, Serialize, Clone)]
460pub struct RestoredDisputesInfo {
461 pub dispute_id: Uuid,
463 pub order_id: Uuid,
465 pub trade_index: i64,
467 pub status: String,
469 pub initiator: Option<DisputeInitiator>,
472 pub solver_pubkey: Option<String>,
475}
476
477#[derive(Debug, Deserialize, Serialize, Clone, Default)]
482pub struct RestoreSessionInfo {
483 #[serde(rename = "orders")]
485 pub restore_orders: Vec<RestoredOrdersInfo>,
486 #[serde(rename = "disputes")]
488 pub restore_disputes: Vec<RestoredDisputesInfo>,
489}
490
491#[derive(Debug, Deserialize, Serialize, Clone)]
497#[serde(rename_all = "snake_case")]
498pub enum Payload {
499 Order(SmallOrder),
501 PaymentRequest(Option<SmallOrder>, String, Option<Amount>),
507 TextMessage(String),
509 Peer(Peer),
511 RatingUser(u8),
513 Amount(Amount),
515 Dispute(Uuid, Option<SolverDisputeInfo>),
518 CantDo(Option<CantDoReason>),
520 NextTrade(String, u32),
523 PaymentFailed(PaymentFailedInfo),
525 RestoreData(RestoreSessionInfo),
527 Ids(Vec<Uuid>),
529 Orders(Vec<SmallOrder>),
531}
532
533#[allow(dead_code)]
534impl MessageKind {
535 pub fn new(
538 id: Option<Uuid>,
539 request_id: Option<u64>,
540 trade_index: Option<i64>,
541 action: Action,
542 payload: Option<Payload>,
543 ) -> Self {
544 Self {
545 version: PROTOCOL_VER,
546 request_id,
547 trade_index,
548 id,
549 action,
550 payload,
551 }
552 }
553 pub fn from_json(json: &str) -> Result<Self, ServiceError> {
555 serde_json::from_str(json).map_err(|_| ServiceError::MessageSerializationError)
556 }
557 pub fn as_json(&self) -> Result<String, ServiceError> {
559 serde_json::to_string(&self).map_err(|_| ServiceError::MessageSerializationError)
560 }
561
562 pub fn get_action(&self) -> Action {
564 self.action.clone()
565 }
566
567 pub fn get_next_trade_key(&self) -> Result<Option<(String, u32)>, ServiceError> {
574 match &self.payload {
575 Some(Payload::NextTrade(key, index)) => Ok(Some((key.to_string(), *index))),
576 None => Ok(None),
577 _ => Err(ServiceError::InvalidPayload),
578 }
579 }
580
581 pub fn get_rating(&self) -> Result<u8, ServiceError> {
589 if let Some(Payload::RatingUser(v)) = self.payload.to_owned() {
590 if !(MIN_RATING..=MAX_RATING).contains(&v) {
591 return Err(ServiceError::InvalidRatingValue);
592 }
593 Ok(v)
594 } else {
595 Err(ServiceError::InvalidRating)
596 }
597 }
598
599 pub fn verify(&self) -> bool {
606 match &self.action {
607 Action::NewOrder => matches!(&self.payload, Some(Payload::Order(_))),
608 Action::PayInvoice | Action::AddInvoice => {
609 if self.id.is_none() {
610 return false;
611 }
612 matches!(&self.payload, Some(Payload::PaymentRequest(_, _, _)))
613 }
614 Action::TakeSell
615 | Action::TakeBuy
616 | Action::FiatSent
617 | Action::FiatSentOk
618 | Action::Release
619 | Action::Released
620 | Action::Dispute
621 | Action::AdminCancel
622 | Action::AdminCanceled
623 | Action::AdminSettle
624 | Action::AdminSettled
625 | Action::Rate
626 | Action::RateReceived
627 | Action::AdminTakeDispute
628 | Action::AdminTookDispute
629 | Action::DisputeInitiatedByYou
630 | Action::DisputeInitiatedByPeer
631 | Action::WaitingBuyerInvoice
632 | Action::PurchaseCompleted
633 | Action::HoldInvoicePaymentAccepted
634 | Action::HoldInvoicePaymentSettled
635 | Action::HoldInvoicePaymentCanceled
636 | Action::WaitingSellerToPay
637 | Action::BuyerTookOrder
638 | Action::BuyerInvoiceAccepted
639 | Action::CooperativeCancelInitiatedByYou
640 | Action::CooperativeCancelInitiatedByPeer
641 | Action::CooperativeCancelAccepted
642 | Action::Cancel
643 | Action::InvoiceUpdated
644 | Action::AdminAddSolver
645 | Action::SendDm
646 | Action::TradePubkey
647 | Action::Canceled => {
648 if self.id.is_none() {
649 return false;
650 }
651 true
652 }
653 Action::LastTradeIndex | Action::RestoreSession => self.payload.is_none(),
654 Action::PaymentFailed => {
655 if self.id.is_none() {
656 return false;
657 }
658 matches!(&self.payload, Some(Payload::PaymentFailed(_)))
659 }
660 Action::RateUser => {
661 matches!(&self.payload, Some(Payload::RatingUser(_)))
662 }
663 Action::CantDo => {
664 matches!(&self.payload, Some(Payload::CantDo(_)))
665 }
666 Action::Orders => {
667 matches!(
668 &self.payload,
669 Some(Payload::Ids(_)) | Some(Payload::Orders(_))
670 )
671 }
672 }
673 }
674
675 pub fn get_order(&self) -> Option<&SmallOrder> {
680 if self.action != Action::NewOrder {
681 return None;
682 }
683 match &self.payload {
684 Some(Payload::Order(o)) => Some(o),
685 _ => None,
686 }
687 }
688
689 pub fn get_payment_request(&self) -> Option<String> {
695 if self.action != Action::TakeSell
696 && self.action != Action::AddInvoice
697 && self.action != Action::NewOrder
698 {
699 return None;
700 }
701 match &self.payload {
702 Some(Payload::PaymentRequest(_, pr, _)) => Some(pr.to_owned()),
703 Some(Payload::Order(ord)) => ord.buyer_invoice.to_owned(),
704 _ => None,
705 }
706 }
707
708 pub fn get_amount(&self) -> Option<Amount> {
712 if self.action != Action::TakeSell && self.action != Action::TakeBuy {
713 return None;
714 }
715 match &self.payload {
716 Some(Payload::PaymentRequest(_, _, amount)) => *amount,
717 Some(Payload::Amount(amount)) => Some(*amount),
718 _ => None,
719 }
720 }
721
722 pub fn get_payload(&self) -> Option<&Payload> {
724 self.payload.as_ref()
725 }
726
727 pub fn has_trade_index(&self) -> (bool, i64) {
730 if let Some(index) = self.trade_index {
731 return (true, index);
732 }
733 (false, 0)
734 }
735
736 pub fn trade_index(&self) -> i64 {
738 if let Some(index) = self.trade_index {
739 return index;
740 }
741 0
742 }
743}
744
745#[cfg(test)]
746mod test {
747 use crate::message::{Action, Message, MessageKind, Payload, Peer};
748 use crate::user::UserInfo;
749 use nostr_sdk::Keys;
750 use uuid::uuid;
751
752 #[test]
753 fn test_peer_with_reputation() {
754 let reputation = UserInfo {
756 rating: 4.5,
757 reviews: 10,
758 operating_days: 30,
759 };
760 let peer = Peer::new(
761 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
762 Some(reputation.clone()),
763 );
764
765 assert_eq!(
767 peer.pubkey,
768 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8"
769 );
770 assert!(peer.reputation.is_some());
771 let peer_reputation = peer.reputation.clone().unwrap();
772 assert_eq!(peer_reputation.rating, 4.5);
773 assert_eq!(peer_reputation.reviews, 10);
774 assert_eq!(peer_reputation.operating_days, 30);
775
776 let json = peer.as_json().unwrap();
778 let deserialized_peer = Peer::from_json(&json).unwrap();
779 assert_eq!(deserialized_peer.pubkey, peer.pubkey);
780 assert!(deserialized_peer.reputation.is_some());
781 let deserialized_reputation = deserialized_peer.reputation.unwrap();
782 assert_eq!(deserialized_reputation.rating, 4.5);
783 assert_eq!(deserialized_reputation.reviews, 10);
784 assert_eq!(deserialized_reputation.operating_days, 30);
785 }
786
787 #[test]
788 fn test_peer_without_reputation() {
789 let peer = Peer::new(
791 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
792 None,
793 );
794
795 assert_eq!(
797 peer.pubkey,
798 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8"
799 );
800 assert!(peer.reputation.is_none());
801
802 let json = peer.as_json().unwrap();
804 let deserialized_peer = Peer::from_json(&json).unwrap();
805 assert_eq!(deserialized_peer.pubkey, peer.pubkey);
806 assert!(deserialized_peer.reputation.is_none());
807 }
808
809 #[test]
810 fn test_peer_in_message() {
811 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
812
813 let reputation = UserInfo {
815 rating: 4.5,
816 reviews: 10,
817 operating_days: 30,
818 };
819 let peer_with_reputation = Peer::new(
820 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
821 Some(reputation),
822 );
823 let payload_with_reputation = Payload::Peer(peer_with_reputation);
824 let message_with_reputation = Message::Order(MessageKind::new(
825 Some(uuid),
826 Some(1),
827 Some(2),
828 Action::FiatSentOk,
829 Some(payload_with_reputation),
830 ));
831
832 assert!(message_with_reputation.verify());
834 let message_json = message_with_reputation.as_json().unwrap();
835 let deserialized_message = Message::from_json(&message_json).unwrap();
836 assert!(deserialized_message.verify());
837
838 let peer_without_reputation = Peer::new(
840 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
841 None,
842 );
843 let payload_without_reputation = Payload::Peer(peer_without_reputation);
844 let message_without_reputation = Message::Order(MessageKind::new(
845 Some(uuid),
846 Some(1),
847 Some(2),
848 Action::FiatSentOk,
849 Some(payload_without_reputation),
850 ));
851
852 assert!(message_without_reputation.verify());
854 let message_json = message_without_reputation.as_json().unwrap();
855 let deserialized_message = Message::from_json(&message_json).unwrap();
856 assert!(deserialized_message.verify());
857 }
858
859 #[test]
860 fn test_payment_failed_payload() {
861 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
862
863 let payment_failed_info = crate::message::PaymentFailedInfo {
865 payment_attempts: 3,
866 payment_retries_interval: 60,
867 };
868
869 let payload = Payload::PaymentFailed(payment_failed_info);
870 let message = Message::Order(MessageKind::new(
871 Some(uuid),
872 Some(1),
873 Some(2),
874 Action::PaymentFailed,
875 Some(payload),
876 ));
877
878 assert!(message.verify());
880
881 let message_json = message.as_json().unwrap();
883
884 let deserialized_message = Message::from_json(&message_json).unwrap();
886 assert!(deserialized_message.verify());
887
888 if let Message::Order(kind) = deserialized_message {
890 if let Some(Payload::PaymentFailed(info)) = kind.payload {
891 assert_eq!(info.payment_attempts, 3);
892 assert_eq!(info.payment_retries_interval, 60);
893 } else {
894 panic!("Expected PaymentFailed payload");
895 }
896 } else {
897 panic!("Expected Order message");
898 }
899 }
900
901 #[test]
902 fn test_message_payload_signature() {
903 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
904 let peer = Peer::new(
905 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
906 None, );
908 let payload = Payload::Peer(peer);
909 let test_message = Message::Order(MessageKind::new(
910 Some(uuid),
911 Some(1),
912 Some(2),
913 Action::FiatSentOk,
914 Some(payload),
915 ));
916 assert!(test_message.verify());
917 let test_message_json = test_message.as_json().unwrap();
918 let trade_keys =
920 Keys::parse("110e43647eae221ab1da33ddc17fd6ff423f2b2f49d809b9ffa40794a2ab996c")
921 .unwrap();
922 let sig = Message::sign(test_message_json.clone(), &trade_keys);
923
924 assert!(Message::verify_signature(
925 test_message_json,
926 trade_keys.public_key(),
927 sig
928 ));
929 }
930
931 #[test]
932 fn test_restore_session_message() {
933 let restore_request_message = Message::Restore(MessageKind::new(
935 None,
936 None,
937 None,
938 Action::RestoreSession,
939 None,
940 ));
941
942 assert!(restore_request_message.verify());
944 assert_eq!(
945 restore_request_message.inner_action(),
946 Some(Action::RestoreSession)
947 );
948
949 let message_json = restore_request_message.as_json().unwrap();
951 let deserialized_message = Message::from_json(&message_json).unwrap();
952 assert!(deserialized_message.verify());
953 assert_eq!(
954 deserialized_message.inner_action(),
955 Some(Action::RestoreSession)
956 );
957
958 let restored_orders = vec![
960 crate::message::RestoredOrdersInfo {
961 order_id: uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23"),
962 trade_index: 1,
963 status: "active".to_string(),
964 },
965 crate::message::RestoredOrdersInfo {
966 order_id: uuid!("408e1272-d5f4-47e6-bd97-3504baea9c24"),
967 trade_index: 2,
968 status: "success".to_string(),
969 },
970 ];
971
972 let restored_disputes = vec![
973 crate::message::RestoredDisputesInfo {
974 dispute_id: uuid!("508e1272-d5f4-47e6-bd97-3504baea9c25"),
975 order_id: uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23"),
976 trade_index: 1,
977 status: "initiated".to_string(),
978 initiator: Some(crate::message::DisputeInitiator::Buyer),
979 solver_pubkey: None,
980 },
981 crate::message::RestoredDisputesInfo {
982 dispute_id: uuid!("608e1272-d5f4-47e6-bd97-3504baea9c26"),
983 order_id: uuid!("408e1272-d5f4-47e6-bd97-3504baea9c24"),
984 trade_index: 2,
985 status: "in-progress".to_string(),
986 initiator: None,
987 solver_pubkey: Some(
988 "aabbccdd11223344aabbccdd11223344aabbccdd11223344aabbccdd11223344".to_string(),
989 ),
990 },
991 crate::message::RestoredDisputesInfo {
992 dispute_id: uuid!("708e1272-d5f4-47e6-bd97-3504baea9c27"),
993 order_id: uuid!("508e1272-d5f4-47e6-bd97-3504baea9c25"),
994 trade_index: 3,
995 status: "initiated".to_string(),
996 initiator: Some(crate::message::DisputeInitiator::Seller),
997 solver_pubkey: None,
998 },
999 ];
1000
1001 let restore_session_info = crate::message::RestoreSessionInfo {
1002 restore_orders: restored_orders.clone(),
1003 restore_disputes: restored_disputes.clone(),
1004 };
1005
1006 let restore_data_payload = Payload::RestoreData(restore_session_info);
1007 let restore_data_message = Message::Restore(MessageKind::new(
1008 None,
1009 None,
1010 None,
1011 Action::RestoreSession,
1012 Some(restore_data_payload),
1013 ));
1014
1015 assert!(!restore_data_message.verify());
1017
1018 let message_json = restore_data_message.as_json().unwrap();
1020 let deserialized_restore_message = Message::from_json(&message_json).unwrap();
1021
1022 if let Message::Restore(kind) = deserialized_restore_message {
1023 if let Some(Payload::RestoreData(session_info)) = kind.payload {
1024 assert_eq!(session_info.restore_disputes.len(), 3);
1025 assert_eq!(
1026 session_info.restore_disputes[0].initiator,
1027 Some(crate::message::DisputeInitiator::Buyer)
1028 );
1029 assert!(session_info.restore_disputes[0].solver_pubkey.is_none());
1030 assert_eq!(session_info.restore_disputes[1].initiator, None);
1031 assert_eq!(
1032 session_info.restore_disputes[1].solver_pubkey,
1033 Some(
1034 "aabbccdd11223344aabbccdd11223344aabbccdd11223344aabbccdd11223344"
1035 .to_string()
1036 )
1037 );
1038 assert_eq!(
1039 session_info.restore_disputes[2].initiator,
1040 Some(crate::message::DisputeInitiator::Seller)
1041 );
1042 assert!(session_info.restore_disputes[2].solver_pubkey.is_none());
1043 } else {
1044 panic!("Expected RestoreData payload");
1045 }
1046 } else {
1047 panic!("Expected Restore message");
1048 }
1049 }
1050
1051 #[test]
1052 fn test_restore_session_message_validation() {
1053 let restore_request_message = Message::Restore(MessageKind::new(
1055 None,
1056 None,
1057 None,
1058 Action::RestoreSession,
1059 None, ));
1061
1062 assert!(restore_request_message.verify());
1064
1065 let wrong_payload = Payload::TextMessage("wrong payload".to_string());
1067 let wrong_message = Message::Restore(MessageKind::new(
1068 None,
1069 None,
1070 None,
1071 Action::RestoreSession,
1072 Some(wrong_payload),
1073 ));
1074
1075 assert!(!wrong_message.verify());
1077
1078 let with_id = Message::Restore(MessageKind::new(
1080 Some(uuid!("00000000-0000-0000-0000-000000000001")),
1081 None,
1082 None,
1083 Action::RestoreSession,
1084 None,
1085 ));
1086 assert!(with_id.verify());
1087
1088 let with_request_id = Message::Restore(MessageKind::new(
1089 None,
1090 Some(42),
1091 None,
1092 Action::RestoreSession,
1093 None,
1094 ));
1095 assert!(with_request_id.verify());
1096
1097 let with_trade_index = Message::Restore(MessageKind::new(
1098 None,
1099 None,
1100 Some(7),
1101 Action::RestoreSession,
1102 None,
1103 ));
1104 assert!(with_trade_index.verify());
1105 }
1106
1107 #[test]
1108 fn test_restore_session_message_constructor() {
1109 let restore_request_message = Message::new_restore(None);
1111
1112 assert!(matches!(restore_request_message, Message::Restore(_)));
1113 assert!(restore_request_message.verify());
1114 assert_eq!(
1115 restore_request_message.inner_action(),
1116 Some(Action::RestoreSession)
1117 );
1118
1119 let restore_session_info = crate::message::RestoreSessionInfo {
1121 restore_orders: vec![],
1122 restore_disputes: vec![],
1123 };
1124 let restore_data_message =
1125 Message::new_restore(Some(Payload::RestoreData(restore_session_info)));
1126
1127 assert!(matches!(restore_data_message, Message::Restore(_)));
1128 assert!(!restore_data_message.verify());
1129 }
1130
1131 #[test]
1132 fn test_last_trade_index_valid_message() {
1133 let kind = MessageKind::new(None, None, Some(7), Action::LastTradeIndex, None);
1134 let msg = Message::Restore(kind);
1135
1136 assert!(msg.verify());
1137
1138 let json = msg.as_json().unwrap();
1140 let decoded = Message::from_json(&json).unwrap();
1141 assert!(decoded.verify());
1142
1143 let inner = decoded.get_inner_message_kind();
1145 assert_eq!(inner.trade_index(), 7);
1146 assert_eq!(inner.has_trade_index(), (true, 7));
1147 }
1148
1149 #[test]
1150 fn test_last_trade_index_without_id_is_valid() {
1151 let kind = MessageKind::new(None, None, Some(5), Action::LastTradeIndex, None);
1153 let msg = Message::Restore(kind);
1154 assert!(msg.verify());
1155 }
1156
1157 #[test]
1158 fn test_last_trade_index_with_payload_fails_validation() {
1159 let kind = MessageKind::new(
1161 None,
1162 None,
1163 Some(3),
1164 Action::LastTradeIndex,
1165 Some(Payload::TextMessage("ignored".to_string())),
1166 );
1167 let msg = Message::Restore(kind);
1168 assert!(!msg.verify());
1169 }
1170
1171 #[test]
1172 fn test_restored_dispute_helper_serialization_roundtrip() {
1173 use crate::message::RestoredDisputeHelper;
1174
1175 let helper = RestoredDisputeHelper {
1176 dispute_id: uuid!("508e1272-d5f4-47e6-bd97-3504baea9c25"),
1177 order_id: uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23"),
1178 dispute_status: "initiated".to_string(),
1179 master_buyer_pubkey: Some("npub1buyerkey".to_string()),
1180 master_seller_pubkey: Some("npub1sellerkey".to_string()),
1181 trade_index_buyer: Some(1),
1182 trade_index_seller: Some(2),
1183 buyer_dispute: true,
1184 seller_dispute: false,
1185 solver_pubkey: None,
1186 };
1187
1188 let json = serde_json::to_string(&helper).unwrap();
1189 let deserialized: RestoredDisputeHelper = serde_json::from_str(&json).unwrap();
1190
1191 assert_eq!(deserialized.dispute_id, helper.dispute_id);
1192 assert_eq!(deserialized.order_id, helper.order_id);
1193 assert_eq!(deserialized.dispute_status, helper.dispute_status);
1194 assert_eq!(deserialized.master_buyer_pubkey, helper.master_buyer_pubkey);
1195 assert_eq!(
1196 deserialized.master_seller_pubkey,
1197 helper.master_seller_pubkey
1198 );
1199 assert_eq!(deserialized.trade_index_buyer, helper.trade_index_buyer);
1200 assert_eq!(deserialized.trade_index_seller, helper.trade_index_seller);
1201 assert_eq!(deserialized.buyer_dispute, helper.buyer_dispute);
1202 assert_eq!(deserialized.seller_dispute, helper.seller_dispute);
1203 assert_eq!(deserialized.solver_pubkey, helper.solver_pubkey);
1204
1205 let helper_seller_dispute = RestoredDisputeHelper {
1206 dispute_id: uuid!("608e1272-d5f4-47e6-bd97-3504baea9c26"),
1207 order_id: uuid!("408e1272-d5f4-47e6-bd97-3504baea9c24"),
1208 dispute_status: "in-progress".to_string(),
1209 master_buyer_pubkey: None,
1210 master_seller_pubkey: None,
1211 trade_index_buyer: None,
1212 trade_index_seller: None,
1213 buyer_dispute: false,
1214 seller_dispute: true,
1215 solver_pubkey: Some(
1216 "aabbccdd11223344aabbccdd11223344aabbccdd11223344aabbccdd11223344".to_string(),
1217 ),
1218 };
1219
1220 let json_seller = serde_json::to_string(&helper_seller_dispute).unwrap();
1221 let deserialized_seller: RestoredDisputeHelper =
1222 serde_json::from_str(&json_seller).unwrap();
1223
1224 assert_eq!(
1225 deserialized_seller.dispute_id,
1226 helper_seller_dispute.dispute_id
1227 );
1228 assert_eq!(deserialized_seller.order_id, helper_seller_dispute.order_id);
1229 assert_eq!(
1230 deserialized_seller.dispute_status,
1231 helper_seller_dispute.dispute_status
1232 );
1233 assert_eq!(deserialized_seller.master_buyer_pubkey, None);
1234 assert_eq!(deserialized_seller.master_seller_pubkey, None);
1235 assert_eq!(deserialized_seller.trade_index_buyer, None);
1236 assert_eq!(deserialized_seller.trade_index_seller, None);
1237 assert!(!deserialized_seller.buyer_dispute);
1238 assert!(deserialized_seller.seller_dispute);
1239 assert_eq!(
1240 deserialized_seller.solver_pubkey,
1241 helper_seller_dispute.solver_pubkey
1242 );
1243 }
1244}