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 PayBondInvoice,
84 FiatSent,
86 FiatSentOk,
88 Release,
90 Released,
92 Cancel,
94 Canceled,
96 CooperativeCancelInitiatedByYou,
98 CooperativeCancelInitiatedByPeer,
100 DisputeInitiatedByYou,
102 DisputeInitiatedByPeer,
104 CooperativeCancelAccepted,
106 BuyerInvoiceAccepted,
108 PurchaseCompleted,
110 HoldInvoicePaymentAccepted,
112 HoldInvoicePaymentSettled,
114 HoldInvoicePaymentCanceled,
116 WaitingSellerToPay,
118 WaitingBuyerInvoice,
120 AddInvoice,
123 AddBondInvoice,
130 BuyerTookOrder,
132 Rate,
134 RateUser,
136 RateReceived,
138 CantDo,
140 Dispute,
142 AdminCancel,
144 AdminCanceled,
146 AdminSettle,
148 AdminSettled,
150 AdminAddSolver,
152 AdminTakeDispute,
154 AdminTookDispute,
156 PaymentFailed,
159 InvoiceUpdated,
161 SendDm,
163 TradePubkey,
165 RestoreSession,
167 LastTradeIndex,
170 Orders,
173}
174
175impl fmt::Display for Action {
176 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
177 write!(f, "{self:?}")
178 }
179}
180
181#[derive(Debug, Clone, Deserialize, Serialize)]
188#[serde(rename_all = "kebab-case")]
189pub enum Message {
190 Order(MessageKind),
192 Dispute(MessageKind),
194 CantDo(MessageKind),
196 Rate(MessageKind),
198 Dm(MessageKind),
200 Restore(MessageKind),
202}
203
204impl Message {
205 pub fn new_order(
208 id: Option<Uuid>,
209 request_id: Option<u64>,
210 trade_index: Option<i64>,
211 action: Action,
212 payload: Option<Payload>,
213 ) -> Self {
214 let kind = MessageKind::new(id, request_id, trade_index, action, payload);
215 Self::Order(kind)
216 }
217
218 pub fn new_dispute(
221 id: Option<Uuid>,
222 request_id: Option<u64>,
223 trade_index: Option<i64>,
224 action: Action,
225 payload: Option<Payload>,
226 ) -> Self {
227 let kind = MessageKind::new(id, request_id, trade_index, action, payload);
228
229 Self::Dispute(kind)
230 }
231
232 pub fn new_restore(payload: Option<Payload>) -> Self {
237 let kind = MessageKind::new(None, None, None, Action::RestoreSession, payload);
238 Self::Restore(kind)
239 }
240
241 pub fn cant_do(id: Option<Uuid>, request_id: Option<u64>, payload: Option<Payload>) -> Self {
244 let kind = MessageKind::new(id, request_id, None, Action::CantDo, payload);
245
246 Self::CantDo(kind)
247 }
248
249 pub fn new_dm(
251 id: Option<Uuid>,
252 request_id: Option<u64>,
253 action: Action,
254 payload: Option<Payload>,
255 ) -> Self {
256 let kind = MessageKind::new(id, request_id, None, action, payload);
257
258 Self::Dm(kind)
259 }
260
261 pub fn from_json(json: &str) -> Result<Self, ServiceError> {
263 serde_json::from_str(json).map_err(|_| ServiceError::MessageSerializationError)
264 }
265
266 pub fn as_json(&self) -> Result<String, ServiceError> {
268 serde_json::to_string(&self).map_err(|_| ServiceError::MessageSerializationError)
269 }
270
271 pub fn get_inner_message_kind(&self) -> &MessageKind {
273 match self {
274 Message::Dispute(k)
275 | Message::Order(k)
276 | Message::CantDo(k)
277 | Message::Rate(k)
278 | Message::Dm(k)
279 | Message::Restore(k) => k,
280 }
281 }
282
283 pub fn inner_action(&self) -> Option<Action> {
288 match self {
289 Message::Dispute(a)
290 | Message::Order(a)
291 | Message::CantDo(a)
292 | Message::Rate(a)
293 | Message::Dm(a)
294 | Message::Restore(a) => Some(a.get_action()),
295 }
296 }
297
298 pub fn verify(&self) -> bool {
301 match self {
302 Message::Order(m)
303 | Message::Dispute(m)
304 | Message::CantDo(m)
305 | Message::Rate(m)
306 | Message::Dm(m)
307 | Message::Restore(m) => m.verify(),
308 }
309 }
310
311 pub fn sign(message: String, keys: &Keys) -> Signature {
320 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 keys.sign_schnorr(&message)
325 }
326
327 pub fn verify_signature(message: String, pubkey: PublicKey, sig: Signature) -> bool {
333 let hash: Sha256Hash = Sha256Hash::hash(message.as_bytes());
335 let hash = hash.to_byte_array();
336 let message: BitcoinMessage = BitcoinMessage::from_digest(hash);
337
338 let secp = Secp256k1::verification_only();
340 if let Ok(xonlykey) = pubkey.xonly() {
342 xonlykey.verify(&secp, &message, &sig).is_ok()
343 } else {
344 false
345 }
346 }
347}
348
349#[derive(Debug, Clone, Deserialize, Serialize)]
356pub struct MessageKind {
357 pub version: u8,
360 pub request_id: Option<u64>,
363 pub trade_index: Option<i64>,
366 #[serde(skip_serializing_if = "Option::is_none")]
369 pub id: Option<Uuid>,
370 pub action: Action,
372 pub payload: Option<Payload>,
375}
376
377type Amount = i64;
379
380#[derive(Debug, Deserialize, Serialize, Clone)]
385pub struct PaymentFailedInfo {
386 pub payment_attempts: u32,
388 pub payment_retries_interval: u32,
390}
391
392#[cfg_attr(feature = "sqlx", derive(FromRow, SqlxCrud))]
397#[derive(Debug, Deserialize, Serialize, Clone)]
398pub struct RestoredOrderHelper {
399 pub id: Uuid,
401 pub status: String,
403 pub master_buyer_pubkey: Option<String>,
405 pub master_seller_pubkey: Option<String>,
407 pub trade_index_buyer: Option<i64>,
409 pub trade_index_seller: Option<i64>,
411}
412
413#[cfg_attr(feature = "sqlx", derive(FromRow, SqlxCrud))]
418#[derive(Debug, Deserialize, Serialize, Clone)]
419pub struct RestoredDisputeHelper {
420 pub dispute_id: Uuid,
422 pub order_id: Uuid,
424 pub dispute_status: String,
426 pub master_buyer_pubkey: Option<String>,
428 pub master_seller_pubkey: Option<String>,
430 pub trade_index_buyer: Option<i64>,
432 pub trade_index_seller: Option<i64>,
434 pub buyer_dispute: bool,
438 pub seller_dispute: bool,
442 pub solver_pubkey: Option<String>,
445}
446
447#[cfg_attr(feature = "sqlx", derive(FromRow, SqlxCrud))]
449#[derive(Debug, Deserialize, Serialize, Clone)]
450pub struct RestoredOrdersInfo {
451 pub order_id: Uuid,
453 pub trade_index: i64,
455 pub status: String,
457}
458
459#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
461#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
462#[serde(rename_all = "lowercase")]
463#[cfg_attr(feature = "sqlx", sqlx(type_name = "TEXT", rename_all = "lowercase"))]
464pub enum DisputeInitiator {
465 Buyer,
467 Seller,
469}
470
471#[cfg_attr(feature = "sqlx", derive(FromRow, SqlxCrud))]
473#[derive(Debug, Deserialize, Serialize, Clone)]
474pub struct RestoredDisputesInfo {
475 pub dispute_id: Uuid,
477 pub order_id: Uuid,
479 pub trade_index: i64,
481 pub status: String,
483 pub initiator: Option<DisputeInitiator>,
486 pub solver_pubkey: Option<String>,
489}
490
491#[derive(Debug, Deserialize, Serialize, Clone, Default)]
496pub struct RestoreSessionInfo {
497 #[serde(rename = "orders")]
499 pub restore_orders: Vec<RestoredOrdersInfo>,
500 #[serde(rename = "disputes")]
502 pub restore_disputes: Vec<RestoredDisputesInfo>,
503}
504
505#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Default)]
513pub struct BondResolution {
514 pub slash_seller: bool,
516 pub slash_buyer: bool,
518}
519
520#[derive(Debug, Deserialize, Serialize, Clone)]
535pub struct BondPayoutRequest {
536 pub order: SmallOrder,
540 pub slashed_at: i64,
545}
546
547#[derive(Debug, Deserialize, Serialize, Clone)]
553#[serde(rename_all = "snake_case")]
554pub enum Payload {
555 Order(SmallOrder),
557 PaymentRequest(Option<SmallOrder>, String, Option<Amount>),
565 TextMessage(String),
567 Peer(Peer),
569 RatingUser(u8),
571 Amount(Amount),
573 Dispute(Uuid, Option<SolverDisputeInfo>),
576 CantDo(Option<CantDoReason>),
578 NextTrade(String, u32),
581 PaymentFailed(PaymentFailedInfo),
583 RestoreData(RestoreSessionInfo),
585 Ids(Vec<Uuid>),
587 Orders(Vec<SmallOrder>),
589 BondResolution(BondResolution),
592 BondPayoutRequest(BondPayoutRequest),
598}
599
600#[allow(dead_code)]
601impl MessageKind {
602 pub fn new(
605 id: Option<Uuid>,
606 request_id: Option<u64>,
607 trade_index: Option<i64>,
608 action: Action,
609 payload: Option<Payload>,
610 ) -> Self {
611 Self {
612 version: PROTOCOL_VER,
613 request_id,
614 trade_index,
615 id,
616 action,
617 payload,
618 }
619 }
620 pub fn from_json(json: &str) -> Result<Self, ServiceError> {
622 serde_json::from_str(json).map_err(|_| ServiceError::MessageSerializationError)
623 }
624 pub fn as_json(&self) -> Result<String, ServiceError> {
626 serde_json::to_string(&self).map_err(|_| ServiceError::MessageSerializationError)
627 }
628
629 pub fn get_action(&self) -> Action {
631 self.action.clone()
632 }
633
634 pub fn get_next_trade_key(&self) -> Result<Option<(String, u32)>, ServiceError> {
641 match &self.payload {
642 Some(Payload::NextTrade(key, index)) => Ok(Some((key.to_string(), *index))),
643 None => Ok(None),
644 _ => Err(ServiceError::InvalidPayload),
645 }
646 }
647
648 pub fn get_rating(&self) -> Result<u8, ServiceError> {
656 if let Some(Payload::RatingUser(v)) = self.payload.to_owned() {
657 if !(MIN_RATING..=MAX_RATING).contains(&v) {
658 return Err(ServiceError::InvalidRatingValue);
659 }
660 Ok(v)
661 } else {
662 Err(ServiceError::InvalidRating)
663 }
664 }
665
666 pub fn verify(&self) -> bool {
673 match &self.action {
674 Action::NewOrder => matches!(&self.payload, Some(Payload::Order(_))),
675 Action::PayInvoice | Action::PayBondInvoice | Action::AddInvoice => {
676 if self.id.is_none() {
677 return false;
678 }
679 matches!(&self.payload, Some(Payload::PaymentRequest(_, _, _)))
680 }
681 Action::AddBondInvoice => {
682 if self.id.is_none() {
683 return false;
684 }
685 matches!(
691 &self.payload,
692 Some(Payload::BondPayoutRequest(_)) | Some(Payload::PaymentRequest(_, _, _))
693 )
694 }
695 Action::AdminSettle | Action::AdminCancel => {
696 if self.id.is_none() {
697 return false;
698 }
699 matches!(&self.payload, None | Some(Payload::BondResolution(_)))
700 }
701 Action::TakeSell
702 | Action::TakeBuy
703 | Action::FiatSent
704 | Action::FiatSentOk
705 | Action::Release
706 | Action::Released
707 | Action::Dispute
708 | Action::AdminCanceled
709 | Action::AdminSettled
710 | Action::Rate
711 | Action::RateReceived
712 | Action::AdminTakeDispute
713 | Action::AdminTookDispute
714 | Action::DisputeInitiatedByYou
715 | Action::DisputeInitiatedByPeer
716 | Action::WaitingBuyerInvoice
717 | Action::PurchaseCompleted
718 | Action::HoldInvoicePaymentAccepted
719 | Action::HoldInvoicePaymentSettled
720 | Action::HoldInvoicePaymentCanceled
721 | Action::WaitingSellerToPay
722 | Action::BuyerTookOrder
723 | Action::BuyerInvoiceAccepted
724 | Action::CooperativeCancelInitiatedByYou
725 | Action::CooperativeCancelInitiatedByPeer
726 | Action::CooperativeCancelAccepted
727 | Action::Cancel
728 | Action::InvoiceUpdated
729 | Action::AdminAddSolver
730 | Action::SendDm
731 | Action::TradePubkey
732 | Action::Canceled => {
733 if self.id.is_none() {
734 return false;
735 }
736 !matches!(
737 &self.payload,
738 Some(Payload::BondResolution(_)) | Some(Payload::BondPayoutRequest(_))
739 )
740 }
741 Action::LastTradeIndex | Action::RestoreSession => self.payload.is_none(),
742 Action::PaymentFailed => {
743 if self.id.is_none() {
744 return false;
745 }
746 matches!(&self.payload, Some(Payload::PaymentFailed(_)))
747 }
748 Action::RateUser => {
749 matches!(&self.payload, Some(Payload::RatingUser(_)))
750 }
751 Action::CantDo => {
752 matches!(&self.payload, Some(Payload::CantDo(_)))
753 }
754 Action::Orders => {
755 matches!(
756 &self.payload,
757 Some(Payload::Ids(_)) | Some(Payload::Orders(_))
758 )
759 }
760 }
761 }
762
763 pub fn get_order(&self) -> Option<&SmallOrder> {
768 if self.action != Action::NewOrder {
769 return None;
770 }
771 match &self.payload {
772 Some(Payload::Order(o)) => Some(o),
773 _ => None,
774 }
775 }
776
777 pub fn get_payment_request(&self) -> Option<String> {
784 if self.action != Action::TakeSell
785 && self.action != Action::AddInvoice
786 && self.action != Action::AddBondInvoice
787 && self.action != Action::NewOrder
788 {
789 return None;
790 }
791 match &self.payload {
792 Some(Payload::PaymentRequest(_, pr, _)) => Some(pr.to_owned()),
793 Some(Payload::Order(ord)) => ord.buyer_invoice.to_owned(),
794 _ => None,
795 }
796 }
797
798 pub fn get_amount(&self) -> Option<Amount> {
802 if self.action != Action::TakeSell && self.action != Action::TakeBuy {
803 return None;
804 }
805 match &self.payload {
806 Some(Payload::PaymentRequest(_, _, amount)) => *amount,
807 Some(Payload::Amount(amount)) => Some(*amount),
808 _ => None,
809 }
810 }
811
812 pub fn get_payload(&self) -> Option<&Payload> {
814 self.payload.as_ref()
815 }
816
817 pub fn has_trade_index(&self) -> (bool, i64) {
820 if let Some(index) = self.trade_index {
821 return (true, index);
822 }
823 (false, 0)
824 }
825
826 pub fn trade_index(&self) -> i64 {
828 if let Some(index) = self.trade_index {
829 return index;
830 }
831 0
832 }
833}
834
835#[cfg(test)]
836mod test {
837 use crate::message::{Action, BondPayoutRequest, Message, MessageKind, Payload, Peer};
838 use crate::order::SmallOrder;
839 use crate::user::UserInfo;
840 use nostr_sdk::Keys;
841 use uuid::uuid;
842
843 #[test]
844 fn test_peer_with_reputation() {
845 let reputation = UserInfo {
847 rating: 4.5,
848 reviews: 10,
849 operating_days: 30,
850 };
851 let peer = Peer::new(
852 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
853 Some(reputation.clone()),
854 );
855
856 assert_eq!(
858 peer.pubkey,
859 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8"
860 );
861 assert!(peer.reputation.is_some());
862 let peer_reputation = peer.reputation.clone().unwrap();
863 assert_eq!(peer_reputation.rating, 4.5);
864 assert_eq!(peer_reputation.reviews, 10);
865 assert_eq!(peer_reputation.operating_days, 30);
866
867 let json = peer.as_json().unwrap();
869 let deserialized_peer = Peer::from_json(&json).unwrap();
870 assert_eq!(deserialized_peer.pubkey, peer.pubkey);
871 assert!(deserialized_peer.reputation.is_some());
872 let deserialized_reputation = deserialized_peer.reputation.unwrap();
873 assert_eq!(deserialized_reputation.rating, 4.5);
874 assert_eq!(deserialized_reputation.reviews, 10);
875 assert_eq!(deserialized_reputation.operating_days, 30);
876 }
877
878 #[test]
879 fn test_peer_without_reputation() {
880 let peer = Peer::new(
882 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
883 None,
884 );
885
886 assert_eq!(
888 peer.pubkey,
889 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8"
890 );
891 assert!(peer.reputation.is_none());
892
893 let json = peer.as_json().unwrap();
895 let deserialized_peer = Peer::from_json(&json).unwrap();
896 assert_eq!(deserialized_peer.pubkey, peer.pubkey);
897 assert!(deserialized_peer.reputation.is_none());
898 }
899
900 #[test]
901 fn test_peer_in_message() {
902 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
903
904 let reputation = UserInfo {
906 rating: 4.5,
907 reviews: 10,
908 operating_days: 30,
909 };
910 let peer_with_reputation = Peer::new(
911 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
912 Some(reputation),
913 );
914 let payload_with_reputation = Payload::Peer(peer_with_reputation);
915 let message_with_reputation = Message::Order(MessageKind::new(
916 Some(uuid),
917 Some(1),
918 Some(2),
919 Action::FiatSentOk,
920 Some(payload_with_reputation),
921 ));
922
923 assert!(message_with_reputation.verify());
925 let message_json = message_with_reputation.as_json().unwrap();
926 let deserialized_message = Message::from_json(&message_json).unwrap();
927 assert!(deserialized_message.verify());
928
929 let peer_without_reputation = Peer::new(
931 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
932 None,
933 );
934 let payload_without_reputation = Payload::Peer(peer_without_reputation);
935 let message_without_reputation = Message::Order(MessageKind::new(
936 Some(uuid),
937 Some(1),
938 Some(2),
939 Action::FiatSentOk,
940 Some(payload_without_reputation),
941 ));
942
943 assert!(message_without_reputation.verify());
945 let message_json = message_without_reputation.as_json().unwrap();
946 let deserialized_message = Message::from_json(&message_json).unwrap();
947 assert!(deserialized_message.verify());
948 }
949
950 #[test]
951 fn test_bond_payout_request_payload_verifies_on_add_bond_invoice() {
952 let order_id = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
953 let order = SmallOrder {
954 id: Some(order_id),
955 kind: None,
956 status: None,
957 amount: 500,
958 fiat_code: "USD".to_string(),
959 min_amount: None,
960 max_amount: None,
961 fiat_amount: 0,
962 payment_method: "lightning".to_string(),
963 premium: 0,
964 buyer_trade_pubkey: None,
965 seller_trade_pubkey: None,
966 buyer_invoice: None,
967 created_at: None,
968 expires_at: None,
969 };
970 let payload = Payload::BondPayoutRequest(BondPayoutRequest {
971 order,
972 slashed_at: 1_734_000_000,
973 });
974 let kind = MessageKind::new(
975 Some(order_id),
976 None,
977 None,
978 Action::AddBondInvoice,
979 Some(payload),
980 );
981 assert!(
982 kind.verify(),
983 "BondPayoutRequest must verify on AddBondInvoice"
984 );
985
986 let m = Message::Order(kind);
989 let json = m.as_json().unwrap();
990 assert!(json.contains("bond_payout_request"));
991 let back = Message::from_json(&json).unwrap();
992 assert!(back.verify());
993 }
994
995 #[test]
996 fn test_bond_payout_request_payload_rejected_on_wrong_action() {
997 let order_id = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
1000 let order = SmallOrder {
1001 id: Some(order_id),
1002 kind: None,
1003 status: None,
1004 amount: 500,
1005 fiat_code: "USD".to_string(),
1006 min_amount: None,
1007 max_amount: None,
1008 fiat_amount: 0,
1009 payment_method: "lightning".to_string(),
1010 premium: 0,
1011 buyer_trade_pubkey: None,
1012 seller_trade_pubkey: None,
1013 buyer_invoice: None,
1014 created_at: None,
1015 expires_at: None,
1016 };
1017
1018 let _exhaustive: fn(Action) = |a| match a {
1023 Action::AddBondInvoice => {}
1024 Action::NewOrder
1025 | Action::TakeSell
1026 | Action::TakeBuy
1027 | Action::PayInvoice
1028 | Action::PayBondInvoice
1029 | Action::FiatSent
1030 | Action::FiatSentOk
1031 | Action::Release
1032 | Action::Released
1033 | Action::Cancel
1034 | Action::Canceled
1035 | Action::CooperativeCancelInitiatedByYou
1036 | Action::CooperativeCancelInitiatedByPeer
1037 | Action::DisputeInitiatedByYou
1038 | Action::DisputeInitiatedByPeer
1039 | Action::CooperativeCancelAccepted
1040 | Action::BuyerInvoiceAccepted
1041 | Action::PurchaseCompleted
1042 | Action::HoldInvoicePaymentAccepted
1043 | Action::HoldInvoicePaymentSettled
1044 | Action::HoldInvoicePaymentCanceled
1045 | Action::WaitingSellerToPay
1046 | Action::WaitingBuyerInvoice
1047 | Action::AddInvoice
1048 | Action::BuyerTookOrder
1049 | Action::Rate
1050 | Action::RateUser
1051 | Action::RateReceived
1052 | Action::CantDo
1053 | Action::Dispute
1054 | Action::AdminCancel
1055 | Action::AdminCanceled
1056 | Action::AdminSettle
1057 | Action::AdminSettled
1058 | Action::AdminAddSolver
1059 | Action::AdminTakeDispute
1060 | Action::AdminTookDispute
1061 | Action::PaymentFailed
1062 | Action::InvoiceUpdated
1063 | Action::SendDm
1064 | Action::TradePubkey
1065 | Action::RestoreSession
1066 | Action::LastTradeIndex
1067 | Action::Orders => {}
1068 };
1069
1070 let other_actions: &[Action] = &[
1071 Action::NewOrder,
1072 Action::TakeSell,
1073 Action::TakeBuy,
1074 Action::PayInvoice,
1075 Action::PayBondInvoice,
1076 Action::FiatSent,
1077 Action::FiatSentOk,
1078 Action::Release,
1079 Action::Released,
1080 Action::Cancel,
1081 Action::Canceled,
1082 Action::CooperativeCancelInitiatedByYou,
1083 Action::CooperativeCancelInitiatedByPeer,
1084 Action::DisputeInitiatedByYou,
1085 Action::DisputeInitiatedByPeer,
1086 Action::CooperativeCancelAccepted,
1087 Action::BuyerInvoiceAccepted,
1088 Action::PurchaseCompleted,
1089 Action::HoldInvoicePaymentAccepted,
1090 Action::HoldInvoicePaymentSettled,
1091 Action::HoldInvoicePaymentCanceled,
1092 Action::WaitingSellerToPay,
1093 Action::WaitingBuyerInvoice,
1094 Action::AddInvoice,
1095 Action::BuyerTookOrder,
1096 Action::Rate,
1097 Action::RateUser,
1098 Action::RateReceived,
1099 Action::CantDo,
1100 Action::Dispute,
1101 Action::AdminCancel,
1102 Action::AdminCanceled,
1103 Action::AdminSettle,
1104 Action::AdminSettled,
1105 Action::AdminAddSolver,
1106 Action::AdminTakeDispute,
1107 Action::AdminTookDispute,
1108 Action::PaymentFailed,
1109 Action::InvoiceUpdated,
1110 Action::SendDm,
1111 Action::TradePubkey,
1112 Action::RestoreSession,
1113 Action::LastTradeIndex,
1114 Action::Orders,
1115 ];
1116
1117 for action in other_actions {
1118 let payload = Payload::BondPayoutRequest(BondPayoutRequest {
1119 order: order.clone(),
1120 slashed_at: 0,
1121 });
1122 let kind = MessageKind::new(Some(order_id), None, None, action.clone(), Some(payload));
1123 assert!(
1124 !kind.verify(),
1125 "BondPayoutRequest must be rejected on {action:?}"
1126 );
1127 }
1128 }
1129
1130 #[test]
1131 fn test_payment_failed_payload() {
1132 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
1133
1134 let payment_failed_info = crate::message::PaymentFailedInfo {
1136 payment_attempts: 3,
1137 payment_retries_interval: 60,
1138 };
1139
1140 let payload = Payload::PaymentFailed(payment_failed_info);
1141 let message = Message::Order(MessageKind::new(
1142 Some(uuid),
1143 Some(1),
1144 Some(2),
1145 Action::PaymentFailed,
1146 Some(payload),
1147 ));
1148
1149 assert!(message.verify());
1151
1152 let message_json = message.as_json().unwrap();
1154
1155 let deserialized_message = Message::from_json(&message_json).unwrap();
1157 assert!(deserialized_message.verify());
1158
1159 if let Message::Order(kind) = deserialized_message {
1161 if let Some(Payload::PaymentFailed(info)) = kind.payload {
1162 assert_eq!(info.payment_attempts, 3);
1163 assert_eq!(info.payment_retries_interval, 60);
1164 } else {
1165 panic!("Expected PaymentFailed payload");
1166 }
1167 } else {
1168 panic!("Expected Order message");
1169 }
1170 }
1171
1172 #[test]
1173 fn test_message_payload_signature() {
1174 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
1175 let peer = Peer::new(
1176 "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
1177 None, );
1179 let payload = Payload::Peer(peer);
1180 let test_message = Message::Order(MessageKind::new(
1181 Some(uuid),
1182 Some(1),
1183 Some(2),
1184 Action::FiatSentOk,
1185 Some(payload),
1186 ));
1187 assert!(test_message.verify());
1188 let test_message_json = test_message.as_json().unwrap();
1189 let trade_keys =
1191 Keys::parse("110e43647eae221ab1da33ddc17fd6ff423f2b2f49d809b9ffa40794a2ab996c")
1192 .unwrap();
1193 let sig = Message::sign(test_message_json.clone(), &trade_keys);
1194
1195 assert!(Message::verify_signature(
1196 test_message_json,
1197 trade_keys.public_key(),
1198 sig
1199 ));
1200 }
1201
1202 #[test]
1203 fn test_restore_session_message() {
1204 let restore_request_message = Message::Restore(MessageKind::new(
1206 None,
1207 None,
1208 None,
1209 Action::RestoreSession,
1210 None,
1211 ));
1212
1213 assert!(restore_request_message.verify());
1215 assert_eq!(
1216 restore_request_message.inner_action(),
1217 Some(Action::RestoreSession)
1218 );
1219
1220 let message_json = restore_request_message.as_json().unwrap();
1222 let deserialized_message = Message::from_json(&message_json).unwrap();
1223 assert!(deserialized_message.verify());
1224 assert_eq!(
1225 deserialized_message.inner_action(),
1226 Some(Action::RestoreSession)
1227 );
1228
1229 let restored_orders = vec![
1231 crate::message::RestoredOrdersInfo {
1232 order_id: uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23"),
1233 trade_index: 1,
1234 status: "active".to_string(),
1235 },
1236 crate::message::RestoredOrdersInfo {
1237 order_id: uuid!("408e1272-d5f4-47e6-bd97-3504baea9c24"),
1238 trade_index: 2,
1239 status: "success".to_string(),
1240 },
1241 ];
1242
1243 let restored_disputes = vec![
1244 crate::message::RestoredDisputesInfo {
1245 dispute_id: uuid!("508e1272-d5f4-47e6-bd97-3504baea9c25"),
1246 order_id: uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23"),
1247 trade_index: 1,
1248 status: "initiated".to_string(),
1249 initiator: Some(crate::message::DisputeInitiator::Buyer),
1250 solver_pubkey: None,
1251 },
1252 crate::message::RestoredDisputesInfo {
1253 dispute_id: uuid!("608e1272-d5f4-47e6-bd97-3504baea9c26"),
1254 order_id: uuid!("408e1272-d5f4-47e6-bd97-3504baea9c24"),
1255 trade_index: 2,
1256 status: "in-progress".to_string(),
1257 initiator: None,
1258 solver_pubkey: Some(
1259 "aabbccdd11223344aabbccdd11223344aabbccdd11223344aabbccdd11223344".to_string(),
1260 ),
1261 },
1262 crate::message::RestoredDisputesInfo {
1263 dispute_id: uuid!("708e1272-d5f4-47e6-bd97-3504baea9c27"),
1264 order_id: uuid!("508e1272-d5f4-47e6-bd97-3504baea9c25"),
1265 trade_index: 3,
1266 status: "initiated".to_string(),
1267 initiator: Some(crate::message::DisputeInitiator::Seller),
1268 solver_pubkey: None,
1269 },
1270 ];
1271
1272 let restore_session_info = crate::message::RestoreSessionInfo {
1273 restore_orders: restored_orders.clone(),
1274 restore_disputes: restored_disputes.clone(),
1275 };
1276
1277 let restore_data_payload = Payload::RestoreData(restore_session_info);
1278 let restore_data_message = Message::Restore(MessageKind::new(
1279 None,
1280 None,
1281 None,
1282 Action::RestoreSession,
1283 Some(restore_data_payload),
1284 ));
1285
1286 assert!(!restore_data_message.verify());
1288
1289 let message_json = restore_data_message.as_json().unwrap();
1291 let deserialized_restore_message = Message::from_json(&message_json).unwrap();
1292
1293 if let Message::Restore(kind) = deserialized_restore_message {
1294 if let Some(Payload::RestoreData(session_info)) = kind.payload {
1295 assert_eq!(session_info.restore_disputes.len(), 3);
1296 assert_eq!(
1297 session_info.restore_disputes[0].initiator,
1298 Some(crate::message::DisputeInitiator::Buyer)
1299 );
1300 assert!(session_info.restore_disputes[0].solver_pubkey.is_none());
1301 assert_eq!(session_info.restore_disputes[1].initiator, None);
1302 assert_eq!(
1303 session_info.restore_disputes[1].solver_pubkey,
1304 Some(
1305 "aabbccdd11223344aabbccdd11223344aabbccdd11223344aabbccdd11223344"
1306 .to_string()
1307 )
1308 );
1309 assert_eq!(
1310 session_info.restore_disputes[2].initiator,
1311 Some(crate::message::DisputeInitiator::Seller)
1312 );
1313 assert!(session_info.restore_disputes[2].solver_pubkey.is_none());
1314 } else {
1315 panic!("Expected RestoreData payload");
1316 }
1317 } else {
1318 panic!("Expected Restore message");
1319 }
1320 }
1321
1322 #[test]
1323 fn test_restore_session_message_validation() {
1324 let restore_request_message = Message::Restore(MessageKind::new(
1326 None,
1327 None,
1328 None,
1329 Action::RestoreSession,
1330 None, ));
1332
1333 assert!(restore_request_message.verify());
1335
1336 let wrong_payload = Payload::TextMessage("wrong payload".to_string());
1338 let wrong_message = Message::Restore(MessageKind::new(
1339 None,
1340 None,
1341 None,
1342 Action::RestoreSession,
1343 Some(wrong_payload),
1344 ));
1345
1346 assert!(!wrong_message.verify());
1348
1349 let with_id = Message::Restore(MessageKind::new(
1351 Some(uuid!("00000000-0000-0000-0000-000000000001")),
1352 None,
1353 None,
1354 Action::RestoreSession,
1355 None,
1356 ));
1357 assert!(with_id.verify());
1358
1359 let with_request_id = Message::Restore(MessageKind::new(
1360 None,
1361 Some(42),
1362 None,
1363 Action::RestoreSession,
1364 None,
1365 ));
1366 assert!(with_request_id.verify());
1367
1368 let with_trade_index = Message::Restore(MessageKind::new(
1369 None,
1370 None,
1371 Some(7),
1372 Action::RestoreSession,
1373 None,
1374 ));
1375 assert!(with_trade_index.verify());
1376 }
1377
1378 #[test]
1379 fn test_restore_session_message_constructor() {
1380 let restore_request_message = Message::new_restore(None);
1382
1383 assert!(matches!(restore_request_message, Message::Restore(_)));
1384 assert!(restore_request_message.verify());
1385 assert_eq!(
1386 restore_request_message.inner_action(),
1387 Some(Action::RestoreSession)
1388 );
1389
1390 let restore_session_info = crate::message::RestoreSessionInfo {
1392 restore_orders: vec![],
1393 restore_disputes: vec![],
1394 };
1395 let restore_data_message =
1396 Message::new_restore(Some(Payload::RestoreData(restore_session_info)));
1397
1398 assert!(matches!(restore_data_message, Message::Restore(_)));
1399 assert!(!restore_data_message.verify());
1400 }
1401
1402 #[test]
1403 fn test_last_trade_index_valid_message() {
1404 let kind = MessageKind::new(None, None, Some(7), Action::LastTradeIndex, None);
1405 let msg = Message::Restore(kind);
1406
1407 assert!(msg.verify());
1408
1409 let json = msg.as_json().unwrap();
1411 let decoded = Message::from_json(&json).unwrap();
1412 assert!(decoded.verify());
1413
1414 let inner = decoded.get_inner_message_kind();
1416 assert_eq!(inner.trade_index(), 7);
1417 assert_eq!(inner.has_trade_index(), (true, 7));
1418 }
1419
1420 #[test]
1421 fn test_last_trade_index_without_id_is_valid() {
1422 let kind = MessageKind::new(None, None, Some(5), Action::LastTradeIndex, None);
1424 let msg = Message::Restore(kind);
1425 assert!(msg.verify());
1426 }
1427
1428 #[test]
1429 fn test_last_trade_index_with_payload_fails_validation() {
1430 let kind = MessageKind::new(
1432 None,
1433 None,
1434 Some(3),
1435 Action::LastTradeIndex,
1436 Some(Payload::TextMessage("ignored".to_string())),
1437 );
1438 let msg = Message::Restore(kind);
1439 assert!(!msg.verify());
1440 }
1441
1442 #[test]
1443 fn test_bond_resolution_admin_actions_accept_payload_or_none() {
1444 use crate::message::BondResolution;
1445
1446 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
1447
1448 for action in [Action::AdminSettle, Action::AdminCancel] {
1449 let with_resolution = Message::Order(MessageKind::new(
1450 Some(uuid),
1451 Some(1),
1452 Some(2),
1453 action.clone(),
1454 Some(Payload::BondResolution(BondResolution {
1455 slash_seller: true,
1456 slash_buyer: false,
1457 })),
1458 ));
1459 assert!(
1460 with_resolution.verify(),
1461 "{action:?} + BondResolution should verify"
1462 );
1463
1464 let without_payload = Message::Order(MessageKind::new(
1465 Some(uuid),
1466 Some(1),
1467 Some(2),
1468 action.clone(),
1469 None,
1470 ));
1471 assert!(without_payload.verify(), "{action:?} + None should verify");
1472
1473 let wrong = Message::Order(MessageKind::new(
1475 Some(uuid),
1476 Some(1),
1477 Some(2),
1478 action.clone(),
1479 Some(Payload::TextMessage("nope".to_string())),
1480 ));
1481 assert!(!wrong.verify(), "{action:?} + TextMessage must be rejected");
1482
1483 let no_id = Message::Order(MessageKind::new(
1485 None,
1486 Some(1),
1487 Some(2),
1488 action,
1489 Some(Payload::BondResolution(BondResolution {
1490 slash_seller: false,
1491 slash_buyer: false,
1492 })),
1493 ));
1494 assert!(!no_id.verify(), "admin action without id must be rejected");
1495 }
1496 }
1497
1498 #[test]
1499 fn test_bond_resolution_rejected_on_non_admin_actions() {
1500 use crate::message::BondResolution;
1501
1502 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
1503 let payload = Payload::BondResolution(BondResolution {
1504 slash_seller: true,
1505 slash_buyer: true,
1506 });
1507
1508 for action in [
1512 Action::NewOrder,
1513 Action::TakeSell,
1514 Action::TakeBuy,
1515 Action::PayInvoice,
1516 Action::PayBondInvoice,
1517 Action::FiatSent,
1518 Action::FiatSentOk,
1519 Action::Release,
1520 Action::Released,
1521 Action::Cancel,
1522 Action::Canceled,
1523 Action::CooperativeCancelInitiatedByYou,
1524 Action::CooperativeCancelInitiatedByPeer,
1525 Action::DisputeInitiatedByYou,
1526 Action::DisputeInitiatedByPeer,
1527 Action::CooperativeCancelAccepted,
1528 Action::BuyerInvoiceAccepted,
1529 Action::PurchaseCompleted,
1530 Action::HoldInvoicePaymentAccepted,
1531 Action::HoldInvoicePaymentSettled,
1532 Action::HoldInvoicePaymentCanceled,
1533 Action::WaitingSellerToPay,
1534 Action::WaitingBuyerInvoice,
1535 Action::AddInvoice,
1536 Action::AddBondInvoice,
1537 Action::BuyerTookOrder,
1538 Action::Rate,
1539 Action::RateUser,
1540 Action::RateReceived,
1541 Action::CantDo,
1542 Action::Dispute,
1543 Action::AdminCanceled,
1544 Action::AdminSettled,
1545 Action::AdminAddSolver,
1546 Action::AdminTakeDispute,
1547 Action::AdminTookDispute,
1548 Action::PaymentFailed,
1549 Action::InvoiceUpdated,
1550 Action::SendDm,
1551 Action::TradePubkey,
1552 Action::RestoreSession,
1553 Action::LastTradeIndex,
1554 Action::Orders,
1555 ] {
1556 let msg = Message::Order(MessageKind::new(
1557 Some(uuid),
1558 Some(1),
1559 Some(2),
1560 action.clone(),
1561 Some(payload.clone()),
1562 ));
1563 assert!(
1564 !msg.verify(),
1565 "{action:?} must reject BondResolution payload"
1566 );
1567 }
1568 }
1569
1570 #[test]
1571 fn test_bond_resolution_wire_format() {
1572 use crate::message::BondResolution;
1573
1574 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
1575 let msg = Message::Order(MessageKind::new(
1576 Some(uuid),
1577 None,
1578 None,
1579 Action::AdminCancel,
1580 Some(Payload::BondResolution(BondResolution {
1581 slash_seller: true,
1582 slash_buyer: false,
1583 })),
1584 ));
1585
1586 let json = msg.as_json().unwrap();
1587 assert!(
1589 json.contains("\"bond_resolution\""),
1590 "expected snake_case discriminator, got: {json}"
1591 );
1592 assert!(json.contains("\"slash_seller\":true"));
1593 assert!(json.contains("\"slash_buyer\":false"));
1594
1595 let decoded = Message::from_json(&json).unwrap();
1597 assert!(decoded.verify());
1598 if let Message::Order(kind) = decoded {
1599 match kind.payload {
1600 Some(Payload::BondResolution(b)) => {
1601 assert!(b.slash_seller);
1602 assert!(!b.slash_buyer);
1603 }
1604 other => panic!("expected BondResolution payload, got {other:?}"),
1605 }
1606 } else {
1607 panic!("expected Order message");
1608 }
1609 }
1610
1611 #[test]
1612 fn test_bond_resolution_legacy_null_payload() {
1613 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
1617 let json = format!(
1618 r#"{{"order":{{"version":1,"id":"{uuid}","action":"admin-cancel","payload":null}}}}"#
1619 );
1620 let msg = Message::from_json(&json).unwrap();
1621 assert!(msg.verify());
1622 }
1623
1624 #[test]
1625 fn test_pay_bond_invoice_wire_format_and_verify() {
1626 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
1627 let bolt11 = "lnbcrt78510n1pj59wmepp50677g8tffdqa2p8882y0x6newny5vtz0hjuyngdwv226nanv4uzsdqqcqzzsxqyz5vqsp5skn973360gp4yhlpmefwvul5hs58lkkl3u3ujvt57elmp4zugp4q9qyyssqw4nzlr72w28k4waycf27qvgzc9sp79sqlw83j56txltz4va44j7jda23ydcujj9y5k6k0rn5ms84w8wmcmcyk5g3mhpqepf7envhdccp72nz6e".to_string();
1628
1629 let msg = Message::Order(MessageKind::new(
1630 Some(uuid),
1631 Some(1),
1632 Some(2),
1633 Action::PayBondInvoice,
1634 Some(Payload::PaymentRequest(None, bolt11.clone(), None)),
1635 ));
1636 assert!(msg.verify());
1637
1638 let json = msg.as_json().unwrap();
1640 assert!(
1641 json.contains("\"action\":\"pay-bond-invoice\""),
1642 "expected kebab-case discriminator, got: {json}"
1643 );
1644
1645 let decoded = Message::from_json(&json).unwrap();
1647 assert!(decoded.verify());
1648 assert!(matches!(
1649 decoded.inner_action(),
1650 Some(Action::PayBondInvoice)
1651 ));
1652
1653 let no_id = Message::Order(MessageKind::new(
1655 None,
1656 Some(1),
1657 Some(2),
1658 Action::PayBondInvoice,
1659 Some(Payload::PaymentRequest(None, bolt11.clone(), None)),
1660 ));
1661 assert!(!no_id.verify());
1662
1663 let wrong_payload = Message::Order(MessageKind::new(
1665 Some(uuid),
1666 Some(1),
1667 Some(2),
1668 Action::PayBondInvoice,
1669 Some(Payload::TextMessage("nope".to_string())),
1670 ));
1671 assert!(!wrong_payload.verify());
1672
1673 let no_payload = Message::Order(MessageKind::new(
1675 Some(uuid),
1676 Some(1),
1677 Some(2),
1678 Action::PayBondInvoice,
1679 None,
1680 ));
1681 assert!(!no_payload.verify());
1682 }
1683
1684 #[test]
1685 fn test_add_bond_invoice_wire_format_and_verify() {
1686 let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
1687 let bolt11 = "lnbcrt78510n1pj59wmepp50677g8tffdqa2p8882y0x6newny5vtz0hjuyngdwv226nanv4uzsdqqcqzzsxqyz5vqsp5skn973360gp4yhlpmefwvul5hs58lkkl3u3ujvt57elmp4zugp4q9qyyssqw4nzlr72w28k4waycf27qvgzc9sp79sqlw83j56txltz4va44j7jda23ydcujj9y5k6k0rn5ms84w8wmcmcyk5g3mhpqepf7envhdccp72nz6e".to_string();
1688
1689 let msg = Message::Order(MessageKind::new(
1690 Some(uuid),
1691 Some(1),
1692 Some(2),
1693 Action::AddBondInvoice,
1694 Some(Payload::PaymentRequest(None, bolt11.clone(), None)),
1695 ));
1696 assert!(msg.verify());
1697
1698 let json = msg.as_json().unwrap();
1700 assert!(
1701 json.contains("\"action\":\"add-bond-invoice\""),
1702 "expected kebab-case discriminator, got: {json}"
1703 );
1704
1705 let decoded = Message::from_json(&json).unwrap();
1707 assert!(decoded.verify());
1708 assert!(matches!(
1709 decoded.inner_action(),
1710 Some(Action::AddBondInvoice)
1711 ));
1712
1713 let no_id = Message::Order(MessageKind::new(
1715 None,
1716 Some(1),
1717 Some(2),
1718 Action::AddBondInvoice,
1719 Some(Payload::PaymentRequest(None, bolt11.clone(), None)),
1720 ));
1721 assert!(!no_id.verify());
1722
1723 let wrong_payload = Message::Order(MessageKind::new(
1725 Some(uuid),
1726 Some(1),
1727 Some(2),
1728 Action::AddBondInvoice,
1729 Some(Payload::TextMessage("nope".to_string())),
1730 ));
1731 assert!(!wrong_payload.verify());
1732
1733 let no_payload = Message::Order(MessageKind::new(
1735 Some(uuid),
1736 Some(1),
1737 Some(2),
1738 Action::AddBondInvoice,
1739 None,
1740 ));
1741 assert!(!no_payload.verify());
1742
1743 if let Message::Order(kind) = &msg {
1745 assert_eq!(kind.get_payment_request(), Some(bolt11));
1746 } else {
1747 panic!("expected Message::Order");
1748 }
1749 }
1750
1751 #[test]
1752 fn test_restored_dispute_helper_serialization_roundtrip() {
1753 use crate::message::RestoredDisputeHelper;
1754
1755 let helper = RestoredDisputeHelper {
1756 dispute_id: uuid!("508e1272-d5f4-47e6-bd97-3504baea9c25"),
1757 order_id: uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23"),
1758 dispute_status: "initiated".to_string(),
1759 master_buyer_pubkey: Some("npub1buyerkey".to_string()),
1760 master_seller_pubkey: Some("npub1sellerkey".to_string()),
1761 trade_index_buyer: Some(1),
1762 trade_index_seller: Some(2),
1763 buyer_dispute: true,
1764 seller_dispute: false,
1765 solver_pubkey: None,
1766 };
1767
1768 let json = serde_json::to_string(&helper).unwrap();
1769 let deserialized: RestoredDisputeHelper = serde_json::from_str(&json).unwrap();
1770
1771 assert_eq!(deserialized.dispute_id, helper.dispute_id);
1772 assert_eq!(deserialized.order_id, helper.order_id);
1773 assert_eq!(deserialized.dispute_status, helper.dispute_status);
1774 assert_eq!(deserialized.master_buyer_pubkey, helper.master_buyer_pubkey);
1775 assert_eq!(
1776 deserialized.master_seller_pubkey,
1777 helper.master_seller_pubkey
1778 );
1779 assert_eq!(deserialized.trade_index_buyer, helper.trade_index_buyer);
1780 assert_eq!(deserialized.trade_index_seller, helper.trade_index_seller);
1781 assert_eq!(deserialized.buyer_dispute, helper.buyer_dispute);
1782 assert_eq!(deserialized.seller_dispute, helper.seller_dispute);
1783 assert_eq!(deserialized.solver_pubkey, helper.solver_pubkey);
1784
1785 let helper_seller_dispute = RestoredDisputeHelper {
1786 dispute_id: uuid!("608e1272-d5f4-47e6-bd97-3504baea9c26"),
1787 order_id: uuid!("408e1272-d5f4-47e6-bd97-3504baea9c24"),
1788 dispute_status: "in-progress".to_string(),
1789 master_buyer_pubkey: None,
1790 master_seller_pubkey: None,
1791 trade_index_buyer: None,
1792 trade_index_seller: None,
1793 buyer_dispute: false,
1794 seller_dispute: true,
1795 solver_pubkey: Some(
1796 "aabbccdd11223344aabbccdd11223344aabbccdd11223344aabbccdd11223344".to_string(),
1797 ),
1798 };
1799
1800 let json_seller = serde_json::to_string(&helper_seller_dispute).unwrap();
1801 let deserialized_seller: RestoredDisputeHelper =
1802 serde_json::from_str(&json_seller).unwrap();
1803
1804 assert_eq!(
1805 deserialized_seller.dispute_id,
1806 helper_seller_dispute.dispute_id
1807 );
1808 assert_eq!(deserialized_seller.order_id, helper_seller_dispute.order_id);
1809 assert_eq!(
1810 deserialized_seller.dispute_status,
1811 helper_seller_dispute.dispute_status
1812 );
1813 assert_eq!(deserialized_seller.master_buyer_pubkey, None);
1814 assert_eq!(deserialized_seller.master_seller_pubkey, None);
1815 assert_eq!(deserialized_seller.trade_index_buyer, None);
1816 assert_eq!(deserialized_seller.trade_index_seller, None);
1817 assert!(!deserialized_seller.buyer_dispute);
1818 assert!(deserialized_seller.seller_dispute);
1819 assert_eq!(
1820 deserialized_seller.solver_pubkey,
1821 helper_seller_dispute.solver_pubkey
1822 );
1823 }
1824}