1use foldhash::HashMap;
4
5use alloy_primitives::{Address, U256};
6use serde::{Deserialize, Serialize};
7
8use crate::app_data::CowHook;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub enum BridgeProviderType {
15 HookBridgeProvider,
17 ReceiverAccountBridgeProvider,
19}
20
21impl BridgeProviderType {
22 #[must_use]
26 pub const fn is_hook_bridge_provider(self) -> bool {
27 matches!(self, Self::HookBridgeProvider)
28 }
29
30 #[must_use]
35 pub const fn is_receiver_account_bridge_provider(self) -> bool {
36 matches!(self, Self::ReceiverAccountBridgeProvider)
37 }
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct BridgeProviderInfo {
43 pub name: String,
45 pub logo_url: String,
47 pub dapp_id: String,
49 pub website: String,
51 pub provider_type: BridgeProviderType,
53}
54
55impl BridgeProviderInfo {
56 #[must_use]
66 pub const fn is_hook_bridge_provider(&self) -> bool {
67 self.provider_type.is_hook_bridge_provider()
68 }
69
70 #[must_use]
80 pub const fn is_receiver_account_bridge_provider(&self) -> bool {
81 self.provider_type.is_receiver_account_bridge_provider()
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
89pub enum BridgeStatus {
90 InProgress,
92 Executed,
94 Expired,
96 Refund,
98 Unknown,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct BridgeStatusResult {
105 pub status: BridgeStatus,
107 pub fill_time_in_seconds: Option<u64>,
109 pub deposit_tx_hash: Option<String>,
111 pub fill_tx_hash: Option<String>,
113}
114
115impl BridgeStatusResult {
116 #[must_use]
129 pub const fn new(status: BridgeStatus) -> Self {
130 Self { status, fill_time_in_seconds: None, deposit_tx_hash: None, fill_tx_hash: None }
131 }
132}
133
134#[derive(Debug, Clone)]
138pub struct QuoteBridgeRequest {
139 pub sell_chain_id: u64,
141 pub buy_chain_id: u64,
143 pub sell_token: Address,
145 pub sell_token_decimals: u8,
147 pub buy_token: Address,
149 pub buy_token_decimals: u8,
151 pub sell_amount: U256,
153 pub account: Address,
155 pub owner: Option<Address>,
157 pub receiver: Option<String>,
159 pub bridge_recipient: Option<String>,
161 pub slippage_bps: u32,
163 pub bridge_slippage_bps: Option<u32>,
165 pub kind: crate::OrderKind,
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct BridgeAmounts {
172 pub sell_amount: U256,
174 pub buy_amount: U256,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct BridgeCosts {
181 pub bridging_fee: BridgingFee,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct BridgingFee {
188 pub fee_bps: u32,
190 pub amount_in_sell_currency: U256,
192 pub amount_in_buy_currency: U256,
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct BridgeQuoteAmountsAndCosts {
199 pub costs: BridgeCosts,
201 pub before_fee: BridgeAmounts,
203 pub after_fee: BridgeAmounts,
205 pub after_slippage: BridgeAmounts,
207 pub slippage_bps: u32,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize)]
213pub struct BridgeLimits {
214 pub min_deposit: U256,
216 pub max_deposit: U256,
218}
219
220#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct BridgeFees {
223 pub bridge_fee: U256,
225 pub destination_gas_fee: U256,
227}
228
229#[derive(Debug, Clone)]
231pub struct QuoteBridgeResponse {
232 pub provider: String,
234 pub sell_amount: U256,
236 pub buy_amount: U256,
238 pub fee_amount: U256,
240 pub estimated_secs: u64,
242 pub bridge_hook: Option<CowHook>,
244}
245
246impl QuoteBridgeResponse {
247 #[must_use]
256 pub const fn has_bridge_hook(&self) -> bool {
257 self.bridge_hook.is_some()
258 }
259
260 #[must_use]
266 pub fn provider_ref(&self) -> &str {
267 &self.provider
268 }
269
270 #[must_use]
274 pub const fn net_buy_amount(&self) -> U256 {
275 self.buy_amount.saturating_sub(self.fee_amount)
276 }
277}
278
279#[derive(Debug, Clone)]
281pub struct BridgeQuoteResult {
282 pub id: Option<String>,
284 pub signature: Option<String>,
286 pub attestation_signature: Option<String>,
288 pub quote_body: Option<String>,
290 pub is_sell: bool,
292 pub amounts_and_costs: BridgeQuoteAmountsAndCosts,
294 pub expected_fill_time_seconds: Option<u64>,
296 pub quote_timestamp: u64,
298 pub fees: BridgeFees,
300 pub limits: BridgeLimits,
302}
303
304#[derive(Debug, Clone)]
306pub struct BridgeQuoteResults {
307 pub provider_info: BridgeProviderInfo,
309 pub quote: BridgeQuoteResult,
311 pub bridge_call_details: Option<BridgeCallDetails>,
313 pub bridge_receiver_override: Option<String>,
315}
316
317#[derive(Debug, Clone)]
319pub struct BridgeCallDetails {
320 pub unsigned_bridge_call: crate::config::EvmCall,
322 pub pre_authorized_bridging_hook: BridgeHook,
324}
325
326#[derive(Debug, Clone)]
328pub struct BridgeHook {
329 pub post_hook: CowHook,
331 pub recipient: String,
333}
334
335#[derive(Debug, Clone, Serialize, Deserialize)]
337pub struct BridgingDepositParams {
338 pub input_token_address: Address,
340 pub output_token_address: Address,
342 pub input_amount: U256,
344 pub output_amount: Option<U256>,
346 pub owner: Address,
348 pub quote_timestamp: Option<u64>,
350 pub fill_deadline: Option<u64>,
352 pub recipient: Address,
354 pub source_chain_id: u64,
356 pub destination_chain_id: u64,
358 pub bridging_id: String,
360}
361
362#[derive(Debug, Clone)]
364pub struct CrossChainOrder {
365 pub chain_id: u64,
367 pub status_result: BridgeStatusResult,
369 pub bridging_params: BridgingDepositParams,
371 pub trade_tx_hash: String,
373 pub explorer_url: Option<String>,
375}
376
377#[derive(Debug, Clone)]
379pub struct MultiQuoteResult {
380 pub provider_dapp_id: String,
382 pub quote: Option<BridgeQuoteAmountsAndCosts>,
384 pub error: Option<String>,
386}
387
388#[derive(Debug, thiserror::Error)]
392pub enum BridgeError {
393 #[error("no providers available")]
395 NoProviders,
396 #[error("no quote available for this route")]
398 NoQuote,
399 #[error("sell and buy chains must be different for cross-chain bridging")]
401 SameChain,
402 #[error("bridging only supports SELL orders")]
404 OnlySellOrderSupported,
405 #[error("no intermediate tokens available")]
407 NoIntermediateTokens,
408 #[error("bridge API error: {0}")]
410 ApiError(String),
411 #[error("invalid API JSON response: {0}")]
413 InvalidApiResponse(String),
414 #[error("transaction build error: {0}")]
416 TxBuildError(String),
417 #[error("quote error: {0}")]
419 QuoteError(String),
420 #[error("no routes available")]
422 NoRoutes,
423 #[error("invalid bridge: {0}")]
425 InvalidBridge(String),
426 #[error("quote does not match deposit address")]
428 QuoteDoesNotMatchDepositAddress,
429 #[error("sell amount too small")]
431 SellAmountTooSmall,
432 #[error("provider not found: {dapp_id}")]
434 ProviderNotFound {
435 dapp_id: String,
437 },
438 #[error("provider request timed out")]
440 Timeout,
441 #[error(transparent)]
443 Cow(#[from] crate::CowError),
444}
445
446#[must_use]
462pub const fn bridge_error_priority(error: &BridgeError) -> u32 {
463 match error {
464 BridgeError::SellAmountTooSmall => 10,
465 BridgeError::OnlySellOrderSupported => 9,
466 BridgeError::NoProviders |
467 BridgeError::NoQuote |
468 BridgeError::SameChain |
469 BridgeError::NoIntermediateTokens |
470 BridgeError::ApiError(_) |
471 BridgeError::InvalidApiResponse(_) |
472 BridgeError::TxBuildError(_) |
473 BridgeError::QuoteError(_) |
474 BridgeError::NoRoutes |
475 BridgeError::InvalidBridge(_) |
476 BridgeError::QuoteDoesNotMatchDepositAddress |
477 BridgeError::ProviderNotFound { .. } |
478 BridgeError::Timeout |
479 BridgeError::Cow(_) => 1,
480 }
481}
482
483#[derive(Debug, Clone, Serialize, Deserialize)]
489pub struct AcrossPctFee {
490 pub pct: String,
492 pub total: String,
494}
495
496#[derive(Debug, Clone, Serialize, Deserialize)]
498pub struct AcrossSuggestedFeesLimits {
499 pub min_deposit: String,
501 pub max_deposit: String,
503 pub max_deposit_instant: String,
505 pub max_deposit_short_delay: String,
507 pub recommended_deposit_instant: String,
509}
510
511#[derive(Debug, Clone, Serialize, Deserialize)]
513pub struct AcrossSuggestedFeesResponse {
514 pub total_relay_fee: AcrossPctFee,
516 pub relayer_capital_fee: AcrossPctFee,
518 pub relayer_gas_fee: AcrossPctFee,
520 pub lp_fee: AcrossPctFee,
522 pub timestamp: String,
524 pub is_amount_too_low: bool,
526 pub quote_block: String,
528 pub spoke_pool_address: String,
530 pub exclusive_relayer: String,
532 pub exclusivity_deadline: String,
534 pub estimated_fill_time_sec: String,
536 pub fill_deadline: String,
538 pub limits: AcrossSuggestedFeesLimits,
540}
541
542#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
544#[serde(rename_all = "camelCase")]
545pub enum AcrossDepositStatus {
546 Filled,
548 SlowFillRequested,
550 Pending,
552 Expired,
554 Refunded,
556}
557
558#[derive(Debug, Clone, Serialize, Deserialize)]
560pub struct AcrossDepositStatusResponse {
561 pub status: AcrossDepositStatus,
563 pub origin_chain_id: String,
565 pub deposit_id: String,
567 pub deposit_tx_hash: Option<String>,
569 pub fill_tx: Option<String>,
571 pub destination_chain_id: Option<String>,
573 pub deposit_refund_tx_hash: Option<String>,
575}
576
577#[derive(Debug, Clone)]
579pub struct AcrossDepositEvent {
580 pub input_token: Address,
582 pub output_token: Address,
584 pub input_amount: U256,
586 pub output_amount: U256,
588 pub destination_chain_id: u64,
590 pub deposit_id: U256,
592 pub quote_timestamp: u32,
594 pub fill_deadline: u32,
596 pub exclusivity_deadline: u32,
598 pub depositor: Address,
600 pub recipient: Address,
602 pub exclusive_relayer: Address,
604}
605
606#[derive(Debug, Clone)]
608pub struct AcrossChainConfig {
609 pub chain_id: u64,
611 pub tokens: HashMap<String, Address>,
613}
614
615#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
619pub enum BungeeBridge {
620 Across,
622 CircleCctp,
624 GnosisNative,
626}
627
628impl BungeeBridge {
629 #[must_use]
647 pub const fn as_str(&self) -> &'static str {
648 match self {
649 Self::Across => "across",
650 Self::CircleCctp => "cctp",
651 Self::GnosisNative => "gnosis-native-bridge",
652 }
653 }
654
655 #[must_use]
669 pub fn from_display_name(name: &str) -> Option<Self> {
670 match name {
671 "Across" => Some(Self::Across),
672 "Circle CCTP" => Some(Self::CircleCctp),
673 "Gnosis Native" => Some(Self::GnosisNative),
674 _ => None,
675 }
676 }
677
678 #[must_use]
687 pub const fn display_name(&self) -> &'static str {
688 match self {
689 Self::Across => "Across",
690 Self::CircleCctp => "Circle CCTP",
691 Self::GnosisNative => "Gnosis Native",
692 }
693 }
694}
695
696#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
698#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
699pub enum BungeeEventStatus {
700 Completed,
702 Pending,
704}
705
706#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
708#[serde(rename_all = "lowercase")]
709pub enum BungeeBridgeName {
710 Across,
712 Cctp,
714}
715
716#[derive(Debug, Clone, Serialize, Deserialize)]
718pub struct BungeeEvent {
719 pub identifier: String,
721 pub src_transaction_hash: Option<String>,
723 pub bridge_name: BungeeBridgeName,
725 pub from_chain_id: u64,
727 pub is_cowswap_trade: bool,
729 pub order_id: String,
731 pub src_tx_status: BungeeEventStatus,
733 pub dest_tx_status: BungeeEventStatus,
735 pub dest_transaction_hash: Option<String>,
737}
738
739#[derive(Debug, Clone, Copy)]
741pub struct BungeeTxDataBytesIndex {
742 pub bytes_start_index: usize,
744 pub bytes_length: usize,
746 pub bytes_string_start_index: usize,
748 pub bytes_string_length: usize,
750}
751
752#[derive(Debug, Clone)]
754pub struct DecodedBungeeTxData {
755 pub route_id: String,
757 pub encoded_function_data: String,
759 pub function_selector: String,
761}
762
763#[derive(Debug, Clone)]
765pub struct DecodedBungeeAmounts {
766 pub input_amount_bytes: String,
768 pub input_amount: U256,
770}
771
772#[cfg(test)]
773mod tests {
774 use super::*;
775
776 #[test]
779 fn hook_bridge_provider_is_hook() {
780 assert!(BridgeProviderType::HookBridgeProvider.is_hook_bridge_provider());
781 assert!(!BridgeProviderType::HookBridgeProvider.is_receiver_account_bridge_provider());
782 }
783
784 #[test]
785 fn receiver_account_bridge_provider_is_receiver() {
786 assert!(
787 BridgeProviderType::ReceiverAccountBridgeProvider.is_receiver_account_bridge_provider()
788 );
789 assert!(!BridgeProviderType::ReceiverAccountBridgeProvider.is_hook_bridge_provider());
790 }
791
792 #[test]
795 fn bridge_provider_info_delegates_hook() {
796 let info = BridgeProviderInfo {
797 name: "test".into(),
798 logo_url: String::new(),
799 dapp_id: String::new(),
800 website: String::new(),
801 provider_type: BridgeProviderType::HookBridgeProvider,
802 };
803 assert!(info.is_hook_bridge_provider());
804 assert!(!info.is_receiver_account_bridge_provider());
805 }
806
807 #[test]
808 fn bridge_provider_info_delegates_receiver() {
809 let info = BridgeProviderInfo {
810 name: "test".into(),
811 logo_url: String::new(),
812 dapp_id: String::new(),
813 website: String::new(),
814 provider_type: BridgeProviderType::ReceiverAccountBridgeProvider,
815 };
816 assert!(info.is_receiver_account_bridge_provider());
817 assert!(!info.is_hook_bridge_provider());
818 }
819
820 #[test]
823 fn bridge_status_variants_are_distinct() {
824 let statuses = [
825 BridgeStatus::InProgress,
826 BridgeStatus::Executed,
827 BridgeStatus::Expired,
828 BridgeStatus::Refund,
829 BridgeStatus::Unknown,
830 ];
831 for (i, a) in statuses.iter().enumerate() {
832 for (j, b) in statuses.iter().enumerate() {
833 if i == j {
834 assert_eq!(a, b);
835 } else {
836 assert_ne!(a, b);
837 }
838 }
839 }
840 }
841
842 #[test]
845 fn bridge_status_result_new_sets_status_only() {
846 let r = BridgeStatusResult::new(BridgeStatus::Executed);
847 assert_eq!(r.status, BridgeStatus::Executed);
848 assert!(r.fill_time_in_seconds.is_none());
849 assert!(r.deposit_tx_hash.is_none());
850 assert!(r.fill_tx_hash.is_none());
851 }
852
853 #[test]
854 fn bridge_status_result_new_all_statuses() {
855 for status in [
856 BridgeStatus::InProgress,
857 BridgeStatus::Executed,
858 BridgeStatus::Expired,
859 BridgeStatus::Refund,
860 BridgeStatus::Unknown,
861 ] {
862 let r = BridgeStatusResult::new(status);
863 assert_eq!(r.status, status);
864 }
865 }
866
867 fn make_quote(hook: Option<CowHook>, fee: U256) -> QuoteBridgeResponse {
870 QuoteBridgeResponse {
871 provider: "across".into(),
872 sell_amount: U256::from(1000u64),
873 buy_amount: U256::from(950u64),
874 fee_amount: fee,
875 estimated_secs: 60,
876 bridge_hook: hook,
877 }
878 }
879
880 #[test]
881 fn has_bridge_hook_true_when_some() {
882 let hook = CowHook {
883 target: "0xdead".into(),
884 call_data: "0x".into(),
885 gas_limit: "100000".into(),
886 dapp_id: None,
887 };
888 let q = make_quote(Some(hook), U256::ZERO);
889 assert!(q.has_bridge_hook());
890 }
891
892 #[test]
893 fn has_bridge_hook_false_when_none() {
894 let q = make_quote(None, U256::ZERO);
895 assert!(!q.has_bridge_hook());
896 }
897
898 #[test]
899 fn provider_ref_returns_provider_name() {
900 let q = make_quote(None, U256::ZERO);
901 assert_eq!(q.provider_ref(), "across");
902 }
903
904 #[test]
905 fn net_buy_amount_subtracts_fee() {
906 let q = make_quote(None, U256::from(50u64));
907 assert_eq!(q.net_buy_amount(), U256::from(900u64));
908 }
909
910 #[test]
911 fn net_buy_amount_saturates_at_zero() {
912 let q = make_quote(None, U256::from(2000u64));
913 assert_eq!(q.net_buy_amount(), U256::ZERO);
914 }
915
916 #[test]
917 fn net_buy_amount_zero_fee() {
918 let q = make_quote(None, U256::ZERO);
919 assert_eq!(q.net_buy_amount(), U256::from(950u64));
920 }
921
922 #[test]
925 fn bungee_bridge_as_str() {
926 assert_eq!(BungeeBridge::Across.as_str(), "across");
927 assert_eq!(BungeeBridge::CircleCctp.as_str(), "cctp");
928 assert_eq!(BungeeBridge::GnosisNative.as_str(), "gnosis-native-bridge");
929 }
930
931 #[test]
932 fn bungee_bridge_display_name() {
933 assert_eq!(BungeeBridge::Across.display_name(), "Across");
934 assert_eq!(BungeeBridge::CircleCctp.display_name(), "Circle CCTP");
935 assert_eq!(BungeeBridge::GnosisNative.display_name(), "Gnosis Native");
936 }
937
938 #[test]
939 fn bungee_bridge_from_display_name_valid() {
940 assert_eq!(BungeeBridge::from_display_name("Across"), Some(BungeeBridge::Across));
941 assert_eq!(BungeeBridge::from_display_name("Circle CCTP"), Some(BungeeBridge::CircleCctp));
942 assert_eq!(
943 BungeeBridge::from_display_name("Gnosis Native"),
944 Some(BungeeBridge::GnosisNative)
945 );
946 }
947
948 #[test]
949 fn bungee_bridge_from_display_name_invalid() {
950 assert_eq!(BungeeBridge::from_display_name("across"), None);
951 assert_eq!(BungeeBridge::from_display_name(""), None);
952 assert_eq!(BungeeBridge::from_display_name("Unknown"), None);
953 }
954
955 #[test]
956 fn bungee_bridge_roundtrip_display_name() {
957 for bridge in [BungeeBridge::Across, BungeeBridge::CircleCctp, BungeeBridge::GnosisNative] {
958 let name = bridge.display_name();
959 assert_eq!(BungeeBridge::from_display_name(name), Some(bridge));
960 }
961 }
962
963 #[test]
966 fn sell_amount_too_small_has_highest_priority() {
967 assert_eq!(bridge_error_priority(&BridgeError::SellAmountTooSmall), 10);
968 }
969
970 #[test]
971 fn only_sell_order_supported_has_second_priority() {
972 assert_eq!(bridge_error_priority(&BridgeError::OnlySellOrderSupported), 9);
973 }
974
975 #[test]
976 fn other_errors_have_base_priority() {
977 let base_errors: Vec<BridgeError> = vec![
978 BridgeError::NoProviders,
979 BridgeError::NoQuote,
980 BridgeError::SameChain,
981 BridgeError::NoIntermediateTokens,
982 BridgeError::ApiError("test".into()),
983 BridgeError::InvalidApiResponse("test".into()),
984 BridgeError::TxBuildError("test".into()),
985 BridgeError::QuoteError("test".into()),
986 BridgeError::NoRoutes,
987 BridgeError::InvalidBridge("test".into()),
988 BridgeError::QuoteDoesNotMatchDepositAddress,
989 BridgeError::ProviderNotFound { dapp_id: "test".into() },
990 BridgeError::Timeout,
991 ];
992 for e in &base_errors {
993 assert_eq!(bridge_error_priority(e), 1, "expected priority 1 for {e}");
994 }
995 }
996
997 #[test]
1000 fn bridge_error_display_messages() {
1001 assert_eq!(BridgeError::NoProviders.to_string(), "no providers available");
1002 assert_eq!(BridgeError::NoQuote.to_string(), "no quote available for this route");
1003 assert_eq!(
1004 BridgeError::SameChain.to_string(),
1005 "sell and buy chains must be different for cross-chain bridging"
1006 );
1007 assert_eq!(
1008 BridgeError::OnlySellOrderSupported.to_string(),
1009 "bridging only supports SELL orders"
1010 );
1011 assert_eq!(
1012 BridgeError::NoIntermediateTokens.to_string(),
1013 "no intermediate tokens available"
1014 );
1015 assert_eq!(BridgeError::ApiError("oops".into()).to_string(), "bridge API error: oops");
1016 assert_eq!(
1017 BridgeError::InvalidApiResponse("bad".into()).to_string(),
1018 "invalid API JSON response: bad"
1019 );
1020 assert_eq!(
1021 BridgeError::TxBuildError("fail".into()).to_string(),
1022 "transaction build error: fail"
1023 );
1024 assert_eq!(BridgeError::QuoteError("nope".into()).to_string(), "quote error: nope");
1025 assert_eq!(BridgeError::NoRoutes.to_string(), "no routes available");
1026 assert_eq!(BridgeError::InvalidBridge("x".into()).to_string(), "invalid bridge: x");
1027 assert_eq!(
1028 BridgeError::QuoteDoesNotMatchDepositAddress.to_string(),
1029 "quote does not match deposit address"
1030 );
1031 assert_eq!(BridgeError::SellAmountTooSmall.to_string(), "sell amount too small");
1032 assert_eq!(
1033 BridgeError::ProviderNotFound { dapp_id: "foo".into() }.to_string(),
1034 "provider not found: foo"
1035 );
1036 assert_eq!(BridgeError::Timeout.to_string(), "provider request timed out");
1037 }
1038
1039 #[test]
1042 fn bridge_provider_type_serde_roundtrip() {
1043 for v in [
1044 BridgeProviderType::HookBridgeProvider,
1045 BridgeProviderType::ReceiverAccountBridgeProvider,
1046 ] {
1047 let json = serde_json::to_string(&v).unwrap();
1048 let back: BridgeProviderType = serde_json::from_str(&json).unwrap();
1049 assert_eq!(v, back);
1050 }
1051 }
1052
1053 #[test]
1054 fn bridge_status_serde_roundtrip() {
1055 for v in [
1056 BridgeStatus::InProgress,
1057 BridgeStatus::Executed,
1058 BridgeStatus::Expired,
1059 BridgeStatus::Refund,
1060 BridgeStatus::Unknown,
1061 ] {
1062 let json = serde_json::to_string(&v).unwrap();
1063 let back: BridgeStatus = serde_json::from_str(&json).unwrap();
1064 assert_eq!(v, back);
1065 }
1066 }
1067
1068 #[test]
1069 fn bungee_bridge_serde_roundtrip() {
1070 for v in [BungeeBridge::Across, BungeeBridge::CircleCctp, BungeeBridge::GnosisNative] {
1071 let json = serde_json::to_string(&v).unwrap();
1072 let back: BungeeBridge = serde_json::from_str(&json).unwrap();
1073 assert_eq!(v, back);
1074 }
1075 }
1076
1077 #[test]
1078 fn across_deposit_status_serde_roundtrip() {
1079 for v in [
1080 AcrossDepositStatus::Filled,
1081 AcrossDepositStatus::SlowFillRequested,
1082 AcrossDepositStatus::Pending,
1083 AcrossDepositStatus::Expired,
1084 AcrossDepositStatus::Refunded,
1085 ] {
1086 let json = serde_json::to_string(&v).unwrap();
1087 let back: AcrossDepositStatus = serde_json::from_str(&json).unwrap();
1088 assert_eq!(v, back);
1089 }
1090 }
1091
1092 #[test]
1093 fn across_deposit_status_camel_case_serialization() {
1094 assert_eq!(serde_json::to_string(&AcrossDepositStatus::Filled).unwrap(), "\"filled\"");
1095 assert_eq!(
1096 serde_json::to_string(&AcrossDepositStatus::SlowFillRequested).unwrap(),
1097 "\"slowFillRequested\""
1098 );
1099 assert_eq!(serde_json::to_string(&AcrossDepositStatus::Pending).unwrap(), "\"pending\"");
1100 }
1101
1102 #[test]
1103 fn bungee_event_status_screaming_snake_case() {
1104 assert_eq!(serde_json::to_string(&BungeeEventStatus::Completed).unwrap(), "\"COMPLETED\"");
1105 assert_eq!(serde_json::to_string(&BungeeEventStatus::Pending).unwrap(), "\"PENDING\"");
1106 }
1107
1108 #[test]
1109 fn bungee_bridge_name_lowercase_serialization() {
1110 assert_eq!(serde_json::to_string(&BungeeBridgeName::Across).unwrap(), "\"across\"");
1111 assert_eq!(serde_json::to_string(&BungeeBridgeName::Cctp).unwrap(), "\"cctp\"");
1112 }
1113}