1use super::{
15 DisabledReason, MaskedRpcConfig, Relayer, RelayerEvmPolicy, RelayerNetworkPolicy,
16 RelayerNetworkType, RelayerRepoModel, RelayerSolanaPolicy, RelayerSolanaSwapConfig,
17 RelayerStellarPolicy, RelayerStellarSwapConfig, SolanaAllowedTokensPolicy,
18 SolanaFeePaymentStrategy, StellarAllowedTokensPolicy, StellarFeePaymentStrategy,
19};
20use crate::constants::{
21 DEFAULT_EVM_GAS_LIMIT_ESTIMATION, DEFAULT_EVM_MIN_BALANCE, DEFAULT_SOLANA_MAX_TX_DATA_SIZE,
22 DEFAULT_SOLANA_MIN_BALANCE, DEFAULT_STELLAR_MIN_BALANCE,
23};
24use serde::{Deserialize, Serialize};
25use utoipa::ToSchema;
26
27#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
29pub struct DeletePendingTransactionsResponse {
30 pub queued_for_cancellation_transaction_ids: Vec<String>,
31 pub failed_to_queue_transaction_ids: Vec<String>,
32 pub total_processed: u32,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ToSchema)]
38#[serde(untagged)]
39pub enum RelayerNetworkPolicyResponse {
40 Evm(EvmPolicyResponse),
43 Stellar(StellarPolicyResponse),
45 Solana(SolanaPolicyResponse),
47}
48
49impl From<RelayerNetworkPolicy> for RelayerNetworkPolicyResponse {
50 fn from(policy: RelayerNetworkPolicy) -> Self {
51 match policy {
52 RelayerNetworkPolicy::Evm(evm_policy) => {
53 RelayerNetworkPolicyResponse::Evm(evm_policy.into())
54 }
55 RelayerNetworkPolicy::Solana(solana_policy) => {
56 RelayerNetworkPolicyResponse::Solana(solana_policy.into())
57 }
58 RelayerNetworkPolicy::Stellar(stellar_policy) => {
59 RelayerNetworkPolicyResponse::Stellar(stellar_policy.into())
60 }
61 }
62 }
63}
64
65#[derive(Debug, Serialize, Clone, PartialEq, ToSchema)]
67pub struct RelayerResponse {
68 pub id: String,
69 pub name: String,
70 pub network: String,
71 pub network_type: RelayerNetworkType,
72 pub paused: bool,
73 #[serde(skip_serializing_if = "Option::is_none")]
76 #[schema(nullable = false)]
77 pub policies: Option<RelayerNetworkPolicyResponse>,
78 pub signer_id: String,
79 #[serde(skip_serializing_if = "Option::is_none")]
80 #[schema(nullable = false)]
81 pub notification_id: Option<String>,
82 #[serde(skip_serializing_if = "Option::is_none")]
86 #[schema(nullable = false)]
87 pub custom_rpc_urls: Option<Vec<MaskedRpcConfig>>,
88 #[schema(nullable = false)]
90 pub address: Option<String>,
91 #[schema(nullable = false)]
92 pub system_disabled: Option<bool>,
93 #[serde(skip_serializing_if = "Option::is_none")]
94 #[schema(nullable = false)]
95 pub disabled_reason: Option<DisabledReason>,
96}
97
98#[cfg(test)]
99impl Default for RelayerResponse {
100 fn default() -> Self {
101 Self {
102 id: String::new(),
103 name: String::new(),
104 network: String::new(),
105 network_type: RelayerNetworkType::Evm, paused: false,
107 policies: None,
108 signer_id: String::new(),
109 notification_id: None,
110 custom_rpc_urls: None,
111 address: None,
112 system_disabled: None,
113 disabled_reason: None,
114 }
115 }
116}
117
118#[derive(Debug, Clone, Copy)]
120pub struct GetStatusOptions {
121 pub include_balance: bool,
122 pub include_pending_count: bool,
123 pub include_last_confirmed_tx: bool,
124}
125
126impl Default for GetStatusOptions {
127 fn default() -> Self {
128 Self {
129 include_balance: true,
130 include_pending_count: true,
131 include_last_confirmed_tx: true,
132 }
133 }
134}
135
136#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
138#[serde(tag = "network_type")]
139pub enum RelayerStatus {
140 #[serde(rename = "evm")]
141 Evm {
142 #[serde(default, skip_serializing_if = "Option::is_none")]
143 balance: Option<String>,
144 #[serde(default, skip_serializing_if = "Option::is_none")]
145 pending_transactions_count: Option<u64>,
146 #[serde(default, skip_serializing_if = "Option::is_none")]
147 last_confirmed_transaction_timestamp: Option<String>,
148 system_disabled: bool,
149 paused: bool,
150 nonce: String,
151 },
152 #[serde(rename = "stellar")]
153 Stellar {
154 #[serde(default, skip_serializing_if = "Option::is_none")]
155 balance: Option<String>,
156 #[serde(default, skip_serializing_if = "Option::is_none")]
157 pending_transactions_count: Option<u64>,
158 #[serde(default, skip_serializing_if = "Option::is_none")]
159 last_confirmed_transaction_timestamp: Option<String>,
160 system_disabled: bool,
161 paused: bool,
162 sequence_number: String,
163 },
164 #[serde(rename = "solana")]
165 Solana {
166 #[serde(default, skip_serializing_if = "Option::is_none")]
167 balance: Option<String>,
168 #[serde(default, skip_serializing_if = "Option::is_none")]
169 pending_transactions_count: Option<u64>,
170 #[serde(default, skip_serializing_if = "Option::is_none")]
171 last_confirmed_transaction_timestamp: Option<String>,
172 system_disabled: bool,
173 paused: bool,
174 },
175}
176
177fn convert_policy_to_response(
179 policy: RelayerNetworkPolicy,
180 network_type: RelayerNetworkType,
181) -> RelayerNetworkPolicyResponse {
182 match (policy, network_type) {
183 (RelayerNetworkPolicy::Evm(evm_policy), RelayerNetworkType::Evm) => {
184 RelayerNetworkPolicyResponse::Evm(EvmPolicyResponse::from(evm_policy))
185 }
186 (RelayerNetworkPolicy::Solana(solana_policy), RelayerNetworkType::Solana) => {
187 RelayerNetworkPolicyResponse::Solana(SolanaPolicyResponse::from(solana_policy))
188 }
189 (RelayerNetworkPolicy::Stellar(stellar_policy), RelayerNetworkType::Stellar) => {
190 RelayerNetworkPolicyResponse::Stellar(StellarPolicyResponse::from(stellar_policy))
191 }
192 (RelayerNetworkPolicy::Evm(evm_policy), _) => {
194 RelayerNetworkPolicyResponse::Evm(EvmPolicyResponse::from(evm_policy))
195 }
196 (RelayerNetworkPolicy::Solana(solana_policy), _) => {
197 RelayerNetworkPolicyResponse::Solana(SolanaPolicyResponse::from(solana_policy))
198 }
199 (RelayerNetworkPolicy::Stellar(stellar_policy), _) => {
200 RelayerNetworkPolicyResponse::Stellar(StellarPolicyResponse::from(stellar_policy))
201 }
202 }
203}
204
205impl From<Relayer> for RelayerResponse {
206 fn from(relayer: Relayer) -> Self {
207 Self {
208 id: relayer.id.clone(),
209 name: relayer.name.clone(),
210 network: relayer.network.clone(),
211 network_type: relayer.network_type,
212 paused: relayer.paused,
213 policies: relayer
214 .policies
215 .map(|policy| convert_policy_to_response(policy, relayer.network_type)),
216 signer_id: relayer.signer_id,
217 notification_id: relayer.notification_id,
218 custom_rpc_urls: relayer
219 .custom_rpc_urls
220 .map(|urls| urls.into_iter().map(MaskedRpcConfig::from).collect()),
221 address: None,
222 system_disabled: None,
223 disabled_reason: None,
224 }
225 }
226}
227
228impl From<RelayerRepoModel> for RelayerResponse {
229 fn from(model: RelayerRepoModel) -> Self {
230 let policies = if is_empty_policy(&model.policies) {
232 None } else {
234 Some(convert_policy_to_response(
235 model.policies.clone(),
236 model.network_type,
237 ))
238 };
239
240 Self {
241 id: model.id,
242 name: model.name,
243 network: model.network,
244 network_type: model.network_type,
245 paused: model.paused,
246 policies,
247 signer_id: model.signer_id,
248 notification_id: model.notification_id,
249 custom_rpc_urls: model
250 .custom_rpc_urls
251 .map(|urls| urls.into_iter().map(MaskedRpcConfig::from).collect()),
252 address: Some(model.address),
253 system_disabled: Some(model.system_disabled),
254 disabled_reason: model.disabled_reason,
255 }
256 }
257}
258
259impl<'de> serde::Deserialize<'de> for RelayerResponse {
261 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
262 where
263 D: serde::Deserializer<'de>,
264 {
265 use serde::de::Error;
266 use serde_json::Value;
267
268 let value: Value = Value::deserialize(deserializer)?;
270
271 let network_type: RelayerNetworkType = value
273 .get("network_type")
274 .and_then(|v| serde_json::from_value(v.clone()).ok())
275 .ok_or_else(|| D::Error::missing_field("network_type"))?;
276
277 let policies = if let Some(policies_value) = value.get("policies") {
279 if policies_value.is_null() {
280 None
281 } else {
282 let policy_response = match network_type {
284 RelayerNetworkType::Evm => {
285 let evm_policy: EvmPolicyResponse =
286 serde_json::from_value(policies_value.clone())
287 .map_err(D::Error::custom)?;
288 RelayerNetworkPolicyResponse::Evm(evm_policy)
289 }
290 RelayerNetworkType::Solana => {
291 let solana_policy: SolanaPolicyResponse =
292 serde_json::from_value(policies_value.clone())
293 .map_err(D::Error::custom)?;
294 RelayerNetworkPolicyResponse::Solana(solana_policy)
295 }
296 RelayerNetworkType::Stellar => {
297 let stellar_policy: StellarPolicyResponse =
298 serde_json::from_value(policies_value.clone())
299 .map_err(D::Error::custom)?;
300 RelayerNetworkPolicyResponse::Stellar(stellar_policy)
301 }
302 };
303 Some(policy_response)
304 }
305 } else {
306 None
307 };
308
309 Ok(RelayerResponse {
311 id: value
312 .get("id")
313 .and_then(|v| serde_json::from_value(v.clone()).ok())
314 .ok_or_else(|| D::Error::missing_field("id"))?,
315 name: value
316 .get("name")
317 .and_then(|v| serde_json::from_value(v.clone()).ok())
318 .ok_or_else(|| D::Error::missing_field("name"))?,
319 network: value
320 .get("network")
321 .and_then(|v| serde_json::from_value(v.clone()).ok())
322 .ok_or_else(|| D::Error::missing_field("network"))?,
323 network_type,
324 paused: value
325 .get("paused")
326 .and_then(|v| serde_json::from_value(v.clone()).ok())
327 .ok_or_else(|| D::Error::missing_field("paused"))?,
328 policies,
329 signer_id: value
330 .get("signer_id")
331 .and_then(|v| serde_json::from_value(v.clone()).ok())
332 .ok_or_else(|| D::Error::missing_field("signer_id"))?,
333 notification_id: value
334 .get("notification_id")
335 .and_then(|v| serde_json::from_value(v.clone()).ok())
336 .unwrap_or(None),
337 custom_rpc_urls: value
338 .get("custom_rpc_urls")
339 .and_then(|v| serde_json::from_value(v.clone()).ok())
340 .unwrap_or(None),
341 address: value
342 .get("address")
343 .and_then(|v| serde_json::from_value(v.clone()).ok())
344 .unwrap_or(None),
345 system_disabled: value
346 .get("system_disabled")
347 .and_then(|v| serde_json::from_value(v.clone()).ok())
348 .unwrap_or(None),
349 disabled_reason: value
350 .get("disabled_reason")
351 .and_then(|v| serde_json::from_value(v.clone()).ok())
352 .unwrap_or(None),
353 })
354 }
355}
356
357fn is_empty_policy(policy: &RelayerNetworkPolicy) -> bool {
359 match policy {
360 RelayerNetworkPolicy::Evm(evm_policy) => {
361 evm_policy.min_balance.is_none()
362 && evm_policy.gas_limit_estimation.is_none()
363 && evm_policy.gas_price_cap.is_none()
364 && evm_policy.whitelist_receivers.is_none()
365 && evm_policy.eip1559_pricing.is_none()
366 && evm_policy.private_transactions.is_none()
367 }
368 RelayerNetworkPolicy::Solana(solana_policy) => {
369 solana_policy.allowed_programs.is_none()
370 && solana_policy.max_signatures.is_none()
371 && solana_policy.max_tx_data_size.is_none()
372 && solana_policy.min_balance.is_none()
373 && solana_policy.allowed_tokens.is_none()
374 && solana_policy.fee_payment_strategy.is_none()
375 && solana_policy.fee_margin_percentage.is_none()
376 && solana_policy.allowed_accounts.is_none()
377 && solana_policy.disallowed_accounts.is_none()
378 && solana_policy.max_allowed_fee_lamports.is_none()
379 && solana_policy.swap_config.is_none()
380 }
381 RelayerNetworkPolicy::Stellar(stellar_policy) => {
382 stellar_policy.min_balance.is_none()
383 && stellar_policy.max_fee.is_none()
384 && stellar_policy.timeout_seconds.is_none()
385 && stellar_policy.concurrent_transactions.is_none()
386 && stellar_policy.allowed_tokens.is_none()
387 && stellar_policy.fee_payment_strategy.is_none()
388 && stellar_policy.slippage_percentage.is_none()
389 && stellar_policy.fee_margin_percentage.is_none()
390 && stellar_policy.swap_config.is_none()
391 }
392 }
393}
394
395#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
397pub struct NetworkPolicyResponse {
398 #[serde(flatten)]
399 pub policy: RelayerNetworkPolicy,
400}
401
402fn default_evm_min_balance() -> u128 {
404 DEFAULT_EVM_MIN_BALANCE
405}
406
407fn default_evm_gas_limit_estimation() -> bool {
408 DEFAULT_EVM_GAS_LIMIT_ESTIMATION
409}
410
411fn default_solana_min_balance() -> u64 {
413 DEFAULT_SOLANA_MIN_BALANCE
414}
415
416fn default_stellar_min_balance() -> u64 {
418 DEFAULT_STELLAR_MIN_BALANCE
419}
420
421fn default_solana_max_tx_data_size() -> u16 {
423 DEFAULT_SOLANA_MAX_TX_DATA_SIZE
424}
425#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
427#[serde(deny_unknown_fields)]
428pub struct EvmPolicyResponse {
429 #[serde(
430 default = "default_evm_min_balance",
431 serialize_with = "crate::utils::serialize_u128_as_number",
432 deserialize_with = "crate::utils::deserialize_u128_as_number"
433 )]
434 #[schema(nullable = false)]
435 pub min_balance: u128,
436 #[serde(default = "default_evm_gas_limit_estimation")]
437 #[schema(nullable = false)]
438 pub gas_limit_estimation: bool,
439 #[serde(
440 skip_serializing_if = "Option::is_none",
441 serialize_with = "crate::utils::serialize_optional_u128_as_number",
442 deserialize_with = "crate::utils::deserialize_optional_u128_as_number",
443 default
444 )]
445 #[schema(nullable = false)]
446 pub gas_price_cap: Option<u128>,
447 #[serde(skip_serializing_if = "Option::is_none")]
448 #[schema(nullable = false)]
449 pub whitelist_receivers: Option<Vec<String>>,
450 #[serde(skip_serializing_if = "Option::is_none")]
451 #[schema(nullable = false)]
452 pub eip1559_pricing: Option<bool>,
453 #[serde(skip_serializing_if = "Option::is_none")]
454 #[schema(nullable = false)]
455 pub private_transactions: Option<bool>,
456}
457
458#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
460#[serde(deny_unknown_fields)]
461pub struct SolanaPolicyResponse {
462 #[serde(skip_serializing_if = "Option::is_none")]
463 #[schema(nullable = false)]
464 pub allowed_programs: Option<Vec<String>>,
465 #[serde(skip_serializing_if = "Option::is_none")]
466 #[schema(nullable = false)]
467 pub max_signatures: Option<u8>,
468 #[schema(nullable = false)]
469 #[serde(default = "default_solana_max_tx_data_size")]
470 pub max_tx_data_size: u16,
471 #[serde(default = "default_solana_min_balance")]
472 #[schema(nullable = false)]
473 pub min_balance: u64,
474 #[serde(skip_serializing_if = "Option::is_none")]
475 #[schema(nullable = false)]
476 pub allowed_tokens: Option<Vec<SolanaAllowedTokensPolicy>>,
477 #[serde(skip_serializing_if = "Option::is_none")]
478 #[schema(nullable = false)]
479 pub fee_payment_strategy: Option<SolanaFeePaymentStrategy>,
480 #[serde(skip_serializing_if = "Option::is_none")]
481 #[schema(nullable = false)]
482 pub fee_margin_percentage: Option<f32>,
483 #[serde(skip_serializing_if = "Option::is_none")]
484 #[schema(nullable = false)]
485 pub allowed_accounts: Option<Vec<String>>,
486 #[serde(skip_serializing_if = "Option::is_none")]
487 #[schema(nullable = false)]
488 pub disallowed_accounts: Option<Vec<String>>,
489 #[serde(skip_serializing_if = "Option::is_none")]
490 #[schema(nullable = false)]
491 pub max_allowed_fee_lamports: Option<u64>,
492 #[serde(skip_serializing_if = "Option::is_none")]
493 #[schema(nullable = false)]
494 pub swap_config: Option<RelayerSolanaSwapConfig>,
495}
496
497#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
499#[serde(deny_unknown_fields)]
500pub struct StellarPolicyResponse {
501 #[serde(skip_serializing_if = "Option::is_none")]
502 #[schema(nullable = false)]
503 pub max_fee: Option<u32>,
504 #[serde(skip_serializing_if = "Option::is_none")]
505 #[schema(nullable = false)]
506 pub timeout_seconds: Option<u64>,
507 #[serde(default = "default_stellar_min_balance")]
508 #[schema(nullable = false)]
509 pub min_balance: u64,
510 #[serde(skip_serializing_if = "Option::is_none")]
511 #[schema(nullable = false)]
512 pub concurrent_transactions: Option<bool>,
513 #[serde(skip_serializing_if = "Option::is_none")]
514 #[schema(nullable = false)]
515 pub allowed_tokens: Option<Vec<StellarAllowedTokensPolicy>>,
516 #[serde(skip_serializing_if = "Option::is_none")]
517 #[schema(nullable = false)]
518 pub fee_payment_strategy: Option<StellarFeePaymentStrategy>,
519 #[serde(skip_serializing_if = "Option::is_none")]
520 #[schema(nullable = false)]
521 pub slippage_percentage: Option<f32>,
522 #[serde(skip_serializing_if = "Option::is_none")]
523 #[schema(nullable = false)]
524 pub fee_margin_percentage: Option<f32>,
525 #[serde(skip_serializing_if = "Option::is_none")]
526 #[schema(nullable = false)]
527 pub swap_config: Option<RelayerStellarSwapConfig>,
528}
529
530impl From<RelayerEvmPolicy> for EvmPolicyResponse {
531 fn from(policy: RelayerEvmPolicy) -> Self {
532 Self {
533 min_balance: policy.min_balance.unwrap_or(DEFAULT_EVM_MIN_BALANCE),
534 gas_limit_estimation: policy
535 .gas_limit_estimation
536 .unwrap_or(DEFAULT_EVM_GAS_LIMIT_ESTIMATION),
537 gas_price_cap: policy.gas_price_cap,
538 whitelist_receivers: policy.whitelist_receivers,
539 eip1559_pricing: policy.eip1559_pricing,
540 private_transactions: policy.private_transactions,
541 }
542 }
543}
544
545impl From<RelayerSolanaPolicy> for SolanaPolicyResponse {
546 fn from(policy: RelayerSolanaPolicy) -> Self {
547 Self {
548 allowed_programs: policy.allowed_programs,
549 max_signatures: policy.max_signatures,
550 max_tx_data_size: policy
551 .max_tx_data_size
552 .unwrap_or(DEFAULT_SOLANA_MAX_TX_DATA_SIZE),
553 min_balance: policy.min_balance.unwrap_or(DEFAULT_SOLANA_MIN_BALANCE),
554 allowed_tokens: policy.allowed_tokens,
555 fee_payment_strategy: policy.fee_payment_strategy,
556 fee_margin_percentage: policy.fee_margin_percentage,
557 allowed_accounts: policy.allowed_accounts,
558 disallowed_accounts: policy.disallowed_accounts,
559 max_allowed_fee_lamports: policy.max_allowed_fee_lamports,
560 swap_config: policy.swap_config,
561 }
562 }
563}
564
565impl From<RelayerStellarPolicy> for StellarPolicyResponse {
566 fn from(policy: RelayerStellarPolicy) -> Self {
567 Self {
568 min_balance: policy.min_balance.unwrap_or(DEFAULT_STELLAR_MIN_BALANCE),
569 max_fee: policy.max_fee,
570 timeout_seconds: policy.timeout_seconds,
571 concurrent_transactions: policy.concurrent_transactions,
572 allowed_tokens: policy.allowed_tokens,
573 fee_payment_strategy: policy.fee_payment_strategy,
574 slippage_percentage: policy.slippage_percentage,
575 fee_margin_percentage: policy.fee_margin_percentage,
576 swap_config: policy.swap_config,
577 }
578 }
579}
580
581#[cfg(test)]
582mod tests {
583 use super::*;
584 use crate::models::{
585 relayer::{
586 RelayerEvmPolicy, RelayerSolanaPolicy, RelayerSolanaSwapConfig, RelayerStellarPolicy,
587 SolanaAllowedTokensPolicy, SolanaFeePaymentStrategy, SolanaSwapStrategy,
588 StellarAllowedTokensPolicy, StellarFeePaymentStrategy, StellarSwapStrategy,
589 },
590 StellarTokenKind, StellarTokenMetadata,
591 };
592
593 #[test]
594 fn test_from_domain_relayer() {
595 let relayer = Relayer::new(
596 "test-relayer".to_string(),
597 "Test Relayer".to_string(),
598 "mainnet".to_string(),
599 false,
600 RelayerNetworkType::Evm,
601 Some(RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
602 gas_price_cap: Some(100_000_000_000),
603 whitelist_receivers: None,
604 eip1559_pricing: Some(true),
605 private_transactions: None,
606 min_balance: None,
607 gas_limit_estimation: None,
608 })),
609 "test-signer".to_string(),
610 None,
611 None,
612 );
613
614 let response: RelayerResponse = relayer.clone().into();
615
616 assert_eq!(response.id, relayer.id);
617 assert_eq!(response.name, relayer.name);
618 assert_eq!(response.network, relayer.network);
619 assert_eq!(response.network_type, relayer.network_type);
620 assert_eq!(response.paused, relayer.paused);
621 assert_eq!(
622 response.policies,
623 Some(RelayerNetworkPolicyResponse::Evm(
624 RelayerEvmPolicy {
625 gas_price_cap: Some(100_000_000_000),
626 whitelist_receivers: None,
627 eip1559_pricing: Some(true),
628 private_transactions: None,
629 min_balance: Some(DEFAULT_EVM_MIN_BALANCE),
630 gas_limit_estimation: Some(DEFAULT_EVM_GAS_LIMIT_ESTIMATION),
631 }
632 .into()
633 ))
634 );
635 assert_eq!(response.signer_id, relayer.signer_id);
636 assert_eq!(response.notification_id, relayer.notification_id);
637 assert_eq!(response.custom_rpc_urls, None);
639 assert_eq!(response.address, None);
640 assert_eq!(response.system_disabled, None);
641 }
642
643 #[test]
644 fn test_from_domain_relayer_solana() {
645 let relayer = Relayer::new(
646 "test-solana-relayer".to_string(),
647 "Test Solana Relayer".to_string(),
648 "mainnet".to_string(),
649 false,
650 RelayerNetworkType::Solana,
651 Some(RelayerNetworkPolicy::Solana(RelayerSolanaPolicy {
652 allowed_programs: Some(vec!["11111111111111111111111111111111".to_string()]),
653 max_signatures: Some(5),
654 min_balance: Some(1000000),
655 fee_payment_strategy: Some(SolanaFeePaymentStrategy::Relayer),
656 allowed_tokens: Some(vec![SolanaAllowedTokensPolicy::new(
657 "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
658 Some(100000),
659 None,
660 )]),
661 max_tx_data_size: None,
662 fee_margin_percentage: None,
663 allowed_accounts: None,
664 disallowed_accounts: None,
665 max_allowed_fee_lamports: None,
666 swap_config: None,
667 })),
668 "test-signer".to_string(),
669 None,
670 None,
671 );
672
673 let response: RelayerResponse = relayer.clone().into();
674
675 assert_eq!(response.id, relayer.id);
676 assert_eq!(response.network_type, RelayerNetworkType::Solana);
677 assert!(response.policies.is_some());
678
679 if let Some(RelayerNetworkPolicyResponse::Solana(solana_response)) = response.policies {
680 assert_eq!(solana_response.min_balance, 1000000);
681 assert_eq!(solana_response.max_signatures, Some(5));
682 } else {
683 panic!("Expected Solana policy response");
684 }
685 }
686
687 #[test]
688 fn test_from_domain_relayer_stellar() {
689 let relayer = Relayer::new(
690 "test-stellar-relayer".to_string(),
691 "Test Stellar Relayer".to_string(),
692 "mainnet".to_string(),
693 false,
694 RelayerNetworkType::Stellar,
695 Some(RelayerNetworkPolicy::Stellar(RelayerStellarPolicy {
696 min_balance: Some(20000000),
697 max_fee: Some(100000),
698 timeout_seconds: Some(30),
699 concurrent_transactions: None,
700 allowed_tokens: None,
701 fee_payment_strategy: Some(StellarFeePaymentStrategy::Relayer),
702 slippage_percentage: None,
703 fee_margin_percentage: None,
704 swap_config: None,
705 })),
706 "test-signer".to_string(),
707 None,
708 None,
709 );
710
711 let response: RelayerResponse = relayer.clone().into();
712
713 assert_eq!(response.id, relayer.id);
714 assert_eq!(response.network_type, RelayerNetworkType::Stellar);
715 assert!(response.policies.is_some());
716
717 if let Some(RelayerNetworkPolicyResponse::Stellar(stellar_response)) = response.policies {
718 assert_eq!(stellar_response.min_balance, 20000000);
719 } else {
720 panic!("Expected Stellar policy response");
721 }
722 }
723
724 #[test]
725 fn test_response_serialization() {
726 let response = RelayerResponse {
727 id: "test-relayer".to_string(),
728 name: "Test Relayer".to_string(),
729 network: "mainnet".to_string(),
730 network_type: RelayerNetworkType::Evm,
731 paused: false,
732 policies: Some(RelayerNetworkPolicyResponse::Evm(EvmPolicyResponse {
733 gas_price_cap: Some(50000000000),
734 whitelist_receivers: None,
735 eip1559_pricing: Some(true),
736 private_transactions: None,
737 min_balance: DEFAULT_EVM_MIN_BALANCE,
738 gas_limit_estimation: DEFAULT_EVM_GAS_LIMIT_ESTIMATION,
739 })),
740 signer_id: "test-signer".to_string(),
741 notification_id: None,
742 custom_rpc_urls: None,
743 address: Some("0x123...".to_string()),
744 system_disabled: Some(false),
745 ..Default::default()
746 };
747
748 let serialized = serde_json::to_string(&response).unwrap();
750 assert!(!serialized.is_empty());
751
752 let deserialized: RelayerResponse = serde_json::from_str(&serialized).unwrap();
754 assert_eq!(response.id, deserialized.id);
755 assert_eq!(response.name, deserialized.name);
756 }
757
758 #[test]
759 fn test_solana_response_serialization() {
760 let response = RelayerResponse {
761 id: "test-solana-relayer".to_string(),
762 name: "Test Solana Relayer".to_string(),
763 network: "mainnet".to_string(),
764 network_type: RelayerNetworkType::Solana,
765 paused: false,
766 policies: Some(RelayerNetworkPolicyResponse::Solana(SolanaPolicyResponse {
767 allowed_programs: Some(vec!["11111111111111111111111111111111".to_string()]),
768 max_signatures: Some(5),
769 max_tx_data_size: DEFAULT_SOLANA_MAX_TX_DATA_SIZE,
770 min_balance: 1000000,
771 allowed_tokens: Some(vec![SolanaAllowedTokensPolicy::new(
772 "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
773 Some(100000),
774 None,
775 )]),
776 fee_payment_strategy: Some(SolanaFeePaymentStrategy::Relayer),
777 fee_margin_percentage: Some(5.0),
778 allowed_accounts: None,
779 disallowed_accounts: None,
780 max_allowed_fee_lamports: Some(500000),
781 swap_config: Some(RelayerSolanaSwapConfig {
782 strategy: Some(SolanaSwapStrategy::JupiterSwap),
783 cron_schedule: Some("0 0 * * *".to_string()),
784 min_balance_threshold: Some(500000),
785 jupiter_swap_options: None,
786 }),
787 })),
788 signer_id: "test-signer".to_string(),
789 notification_id: None,
790 custom_rpc_urls: None,
791 address: Some("SolanaAddress123...".to_string()),
792 system_disabled: Some(false),
793 ..Default::default()
794 };
795
796 let serialized = serde_json::to_string(&response).unwrap();
798 assert!(!serialized.is_empty());
799
800 let deserialized: RelayerResponse = serde_json::from_str(&serialized).unwrap();
802 assert_eq!(response.id, deserialized.id);
803 assert_eq!(response.network_type, RelayerNetworkType::Solana);
804 }
805
806 #[test]
807 fn test_stellar_response_serialization() {
808 let response = RelayerResponse {
809 id: "test-stellar-relayer".to_string(),
810 name: "Test Stellar Relayer".to_string(),
811 network: "mainnet".to_string(),
812 network_type: RelayerNetworkType::Stellar,
813 paused: false,
814 policies: Some(RelayerNetworkPolicyResponse::Stellar(
815 StellarPolicyResponse {
816 max_fee: Some(5000),
817 timeout_seconds: None,
818 min_balance: 20000000,
819 concurrent_transactions: None,
820 allowed_tokens: None,
821 fee_payment_strategy: None,
822 slippage_percentage: None,
823 fee_margin_percentage: None,
824 swap_config: None,
825 },
826 )),
827 signer_id: "test-signer".to_string(),
828 notification_id: None,
829 custom_rpc_urls: None,
830 address: Some("GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX".to_string()),
831 system_disabled: Some(false),
832 ..Default::default()
833 };
834
835 let serialized = serde_json::to_string(&response).unwrap();
837 assert!(!serialized.is_empty());
838
839 let deserialized: RelayerResponse = serde_json::from_str(&serialized).unwrap();
841 assert_eq!(response.id, deserialized.id);
842 assert_eq!(response.network_type, RelayerNetworkType::Stellar);
843
844 if let Some(RelayerNetworkPolicyResponse::Stellar(stellar_policy)) = deserialized.policies {
846 assert_eq!(stellar_policy.min_balance, 20000000);
847 assert_eq!(stellar_policy.max_fee, Some(5000));
848 assert_eq!(stellar_policy.timeout_seconds, None);
849 } else {
850 panic!("Expected Stellar policy in deserialized response");
851 }
852 }
853
854 #[test]
855 fn test_response_without_redundant_network_type() {
856 let response = RelayerResponse {
857 id: "test-relayer".to_string(),
858 name: "Test Relayer".to_string(),
859 network: "mainnet".to_string(),
860 network_type: RelayerNetworkType::Evm,
861 paused: false,
862 policies: Some(RelayerNetworkPolicyResponse::Evm(EvmPolicyResponse {
863 gas_price_cap: Some(100_000_000_000),
864 whitelist_receivers: None,
865 eip1559_pricing: Some(true),
866 private_transactions: None,
867 min_balance: DEFAULT_EVM_MIN_BALANCE,
868 gas_limit_estimation: DEFAULT_EVM_GAS_LIMIT_ESTIMATION,
869 })),
870 signer_id: "test-signer".to_string(),
871 notification_id: None,
872 custom_rpc_urls: None,
873 address: Some("0x123...".to_string()),
874 system_disabled: Some(false),
875 ..Default::default()
876 };
877
878 let serialized = serde_json::to_string_pretty(&response).unwrap();
879
880 assert!(serialized.contains(r#""network_type": "evm""#));
881
882 let network_type_count = serialized.matches(r#""network_type""#).count();
884 assert_eq!(
885 network_type_count, 1,
886 "Should only have one network_type field at top level, not in policies"
887 );
888
889 assert!(serialized.contains(r#""gas_price_cap": 100000000000"#));
890 assert!(serialized.contains(r#""eip1559_pricing": true"#));
891 }
892
893 #[test]
894 fn test_solana_response_without_redundant_network_type() {
895 let response = RelayerResponse {
896 id: "test-solana-relayer".to_string(),
897 name: "Test Solana Relayer".to_string(),
898 network: "mainnet".to_string(),
899 network_type: RelayerNetworkType::Solana,
900 paused: false,
901 policies: Some(RelayerNetworkPolicyResponse::Solana(SolanaPolicyResponse {
902 allowed_programs: Some(vec!["11111111111111111111111111111111".to_string()]),
903 max_signatures: Some(5),
904 max_tx_data_size: DEFAULT_SOLANA_MAX_TX_DATA_SIZE,
905 min_balance: 1000000,
906 allowed_tokens: None,
907 fee_payment_strategy: Some(SolanaFeePaymentStrategy::Relayer),
908 fee_margin_percentage: None,
909 allowed_accounts: None,
910 disallowed_accounts: None,
911 max_allowed_fee_lamports: None,
912 swap_config: None,
913 })),
914 signer_id: "test-signer".to_string(),
915 notification_id: None,
916 custom_rpc_urls: None,
917 address: Some("SolanaAddress123...".to_string()),
918 system_disabled: Some(false),
919 ..Default::default()
920 };
921
922 let serialized = serde_json::to_string_pretty(&response).unwrap();
923
924 assert!(serialized.contains(r#""network_type": "solana""#));
925
926 let network_type_count = serialized.matches(r#""network_type""#).count();
928 assert_eq!(
929 network_type_count, 1,
930 "Should only have one network_type field at top level, not in policies"
931 );
932
933 assert!(serialized.contains(r#""max_signatures": 5"#));
934 assert!(serialized.contains(r#""fee_payment_strategy": "relayer""#));
935 }
936
937 #[test]
938 fn test_stellar_response_without_redundant_network_type() {
939 let response = RelayerResponse {
940 id: "test-stellar-relayer".to_string(),
941 name: "Test Stellar Relayer".to_string(),
942 network: "mainnet".to_string(),
943 network_type: RelayerNetworkType::Stellar,
944 paused: false,
945 policies: Some(RelayerNetworkPolicyResponse::Stellar(
946 StellarPolicyResponse {
947 min_balance: 20000000,
948 max_fee: Some(100000),
949 timeout_seconds: Some(30),
950 concurrent_transactions: None,
951 allowed_tokens: None,
952 fee_payment_strategy: None,
953 slippage_percentage: None,
954 fee_margin_percentage: None,
955 swap_config: None,
956 },
957 )),
958 signer_id: "test-signer".to_string(),
959 notification_id: None,
960 custom_rpc_urls: None,
961 address: Some("GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX".to_string()),
962 system_disabled: Some(false),
963 ..Default::default()
964 };
965
966 let serialized = serde_json::to_string_pretty(&response).unwrap();
967
968 assert!(serialized.contains(r#""network_type": "stellar""#));
969
970 let network_type_count = serialized.matches(r#""network_type""#).count();
972 assert_eq!(
973 network_type_count, 1,
974 "Should only have one network_type field at top level, not in policies"
975 );
976
977 assert!(serialized.contains(r#""min_balance": 20000000"#));
978 assert!(serialized.contains(r#""max_fee": 100000"#));
979 assert!(serialized.contains(r#""timeout_seconds": 30"#));
980 }
981
982 #[test]
983 fn test_empty_policies_not_returned_in_response() {
984 let repo_model = RelayerRepoModel {
986 id: "test-relayer".to_string(),
987 name: "Test Relayer".to_string(),
988 network: "mainnet".to_string(),
989 network_type: RelayerNetworkType::Evm,
990 paused: false,
991 policies: RelayerNetworkPolicy::Evm(RelayerEvmPolicy::default()), signer_id: "test-signer".to_string(),
993 notification_id: None,
994 custom_rpc_urls: None,
995 address: "0x123...".to_string(),
996 system_disabled: false,
997 ..Default::default()
998 };
999
1000 let response = RelayerResponse::from(repo_model);
1002
1003 assert_eq!(response.policies, None);
1005
1006 let serialized = serde_json::to_string(&response).unwrap();
1008 assert!(
1009 !serialized.contains("policies"),
1010 "Empty policies should not appear in JSON response"
1011 );
1012 }
1013
1014 #[test]
1015 fn test_empty_solana_policies_not_returned_in_response() {
1016 let repo_model = RelayerRepoModel {
1018 id: "test-solana-relayer".to_string(),
1019 name: "Test Solana Relayer".to_string(),
1020 network: "mainnet".to_string(),
1021 network_type: RelayerNetworkType::Solana,
1022 paused: false,
1023 policies: RelayerNetworkPolicy::Solana(RelayerSolanaPolicy::default()), signer_id: "test-signer".to_string(),
1025 notification_id: None,
1026 custom_rpc_urls: None,
1027 address: "SolanaAddress123...".to_string(),
1028 system_disabled: false,
1029 ..Default::default()
1030 };
1031
1032 let response = RelayerResponse::from(repo_model);
1034
1035 assert_eq!(response.policies, None);
1037
1038 let serialized = serde_json::to_string(&response).unwrap();
1040 assert!(
1041 !serialized.contains("policies"),
1042 "Empty Solana policies should not appear in JSON response"
1043 );
1044 }
1045
1046 #[test]
1047 fn test_empty_stellar_policies_not_returned_in_response() {
1048 let repo_model = RelayerRepoModel {
1050 id: "test-stellar-relayer".to_string(),
1051 name: "Test Stellar Relayer".to_string(),
1052 network: "mainnet".to_string(),
1053 network_type: RelayerNetworkType::Stellar,
1054 paused: false,
1055 policies: RelayerNetworkPolicy::Stellar(RelayerStellarPolicy::default()), signer_id: "test-signer".to_string(),
1057 notification_id: None,
1058 custom_rpc_urls: None,
1059 address: "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX".to_string(),
1060 system_disabled: false,
1061 ..Default::default()
1062 };
1063
1064 let response = RelayerResponse::from(repo_model);
1066
1067 assert_eq!(response.policies, None);
1069
1070 let serialized = serde_json::to_string(&response).unwrap();
1072 assert!(
1073 !serialized.contains("policies"),
1074 "Empty Stellar policies should not appear in JSON response"
1075 );
1076 }
1077
1078 #[test]
1079 fn test_user_provided_policies_returned_in_response() {
1080 let repo_model = RelayerRepoModel {
1082 id: "test-relayer".to_string(),
1083 name: "Test Relayer".to_string(),
1084 network: "mainnet".to_string(),
1085 network_type: RelayerNetworkType::Evm,
1086 paused: false,
1087 policies: RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
1088 gas_price_cap: Some(100_000_000_000),
1089 eip1559_pricing: Some(true),
1090 min_balance: None, gas_limit_estimation: None,
1092 whitelist_receivers: None,
1093 private_transactions: None,
1094 }),
1095 signer_id: "test-signer".to_string(),
1096 notification_id: None,
1097 custom_rpc_urls: None,
1098 address: "0x123...".to_string(),
1099 system_disabled: false,
1100 ..Default::default()
1101 };
1102
1103 let response = RelayerResponse::from(repo_model);
1105
1106 assert!(response.policies.is_some());
1108
1109 let serialized = serde_json::to_string(&response).unwrap();
1111 assert!(
1112 serialized.contains("policies"),
1113 "User-provided policies should appear in JSON response"
1114 );
1115 assert!(
1116 serialized.contains("gas_price_cap"),
1117 "User-provided policy values should appear in JSON response"
1118 );
1119 }
1120
1121 #[test]
1122 fn test_user_provided_solana_policies_returned_in_response() {
1123 let repo_model = RelayerRepoModel {
1125 id: "test-solana-relayer".to_string(),
1126 name: "Test Solana Relayer".to_string(),
1127 network: "mainnet".to_string(),
1128 network_type: RelayerNetworkType::Solana,
1129 paused: false,
1130 policies: RelayerNetworkPolicy::Solana(RelayerSolanaPolicy {
1131 max_signatures: Some(5),
1132 fee_payment_strategy: Some(SolanaFeePaymentStrategy::Relayer),
1133 min_balance: Some(1000000),
1134 allowed_programs: None, max_tx_data_size: None,
1136 allowed_tokens: None,
1137 fee_margin_percentage: None,
1138 allowed_accounts: None,
1139 disallowed_accounts: None,
1140 max_allowed_fee_lamports: None,
1141 swap_config: None,
1142 }),
1143 signer_id: "test-signer".to_string(),
1144 notification_id: None,
1145 custom_rpc_urls: None,
1146 address: "SolanaAddress123...".to_string(),
1147 system_disabled: false,
1148 ..Default::default()
1149 };
1150
1151 let response = RelayerResponse::from(repo_model);
1153
1154 assert!(response.policies.is_some());
1156
1157 let serialized = serde_json::to_string(&response).unwrap();
1159 assert!(
1160 serialized.contains("policies"),
1161 "User-provided Solana policies should appear in JSON response"
1162 );
1163 assert!(
1164 serialized.contains("max_signatures"),
1165 "User-provided Solana policy values should appear in JSON response"
1166 );
1167 assert!(
1168 serialized.contains("fee_payment_strategy"),
1169 "User-provided Solana policy values should appear in JSON response"
1170 );
1171 }
1172
1173 #[test]
1174 fn test_user_provided_stellar_policies_returned_in_response() {
1175 let repo_model = RelayerRepoModel {
1177 id: "test-stellar-relayer".to_string(),
1178 name: "Test Stellar Relayer".to_string(),
1179 network: "mainnet".to_string(),
1180 network_type: RelayerNetworkType::Stellar,
1181 paused: false,
1182 policies: RelayerNetworkPolicy::Stellar(RelayerStellarPolicy {
1183 max_fee: Some(100000),
1184 timeout_seconds: Some(30),
1185 min_balance: Some(20000000),
1186 concurrent_transactions: Some(true),
1187 allowed_tokens: Some(vec![StellarAllowedTokensPolicy::new(
1188 "USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN".to_string(),
1189 Some(StellarTokenMetadata {
1190 kind: StellarTokenKind::Classic {
1191 code: "USDC".to_string(),
1192 issuer: "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"
1193 .to_string(),
1194 },
1195 decimals: 6,
1196 canonical_asset_id:
1197 "USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"
1198 .to_string(),
1199 }),
1200 None,
1201 None,
1202 )]),
1203 fee_payment_strategy: Some(StellarFeePaymentStrategy::Relayer),
1204 slippage_percentage: Some(0.5),
1205 fee_margin_percentage: Some(2.0),
1206 swap_config: Some(RelayerStellarSwapConfig {
1207 strategies: vec![StellarSwapStrategy::Soroswap],
1208 cron_schedule: Some("0 0 * * *".to_string()),
1209 min_balance_threshold: Some(10000000),
1210 }),
1211 }),
1212 signer_id: "test-signer".to_string(),
1213 notification_id: None,
1214 custom_rpc_urls: None,
1215 address: "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX".to_string(),
1216 system_disabled: false,
1217 ..Default::default()
1218 };
1219
1220 let response = RelayerResponse::from(repo_model);
1222
1223 assert!(response.policies.is_some());
1225
1226 let serialized = serde_json::to_string(&response).unwrap();
1228 assert!(
1229 serialized.contains("policies"),
1230 "User-provided Stellar policies should appear in JSON response"
1231 );
1232 assert!(
1233 serialized.contains("max_fee"),
1234 "User-provided Stellar policy values should appear in JSON response"
1235 );
1236 assert!(
1237 serialized.contains("timeout_seconds"),
1238 "User-provided Stellar policy values should appear in JSON response"
1239 );
1240 assert!(
1241 serialized.contains("allowed_tokens"),
1242 "User-provided Stellar policy values should appear in JSON response"
1243 );
1244 assert!(
1245 serialized.contains("fee_payment_strategy"),
1246 "User-provided Stellar policy values should appear in JSON response"
1247 );
1248 assert!(
1249 serialized.contains("slippage_percentage"),
1250 "User-provided Stellar policy values should appear in JSON response"
1251 );
1252 assert!(
1253 serialized.contains("fee_margin_percentage"),
1254 "User-provided Stellar policy values should appear in JSON response"
1255 );
1256 assert!(
1257 serialized.contains("swap_config"),
1258 "User-provided Stellar policy values should appear in JSON response"
1259 );
1260 }
1261
1262 #[test]
1263 fn test_stellar_fee_payment_strategy_explicitly_set_vs_omitted() {
1264 let policy_with_user = RelayerStellarPolicy {
1266 min_balance: Some(20000000),
1267 max_fee: Some(100000),
1268 timeout_seconds: Some(30),
1269 concurrent_transactions: None,
1270 allowed_tokens: None,
1271 fee_payment_strategy: Some(StellarFeePaymentStrategy::User),
1272 slippage_percentage: None,
1273 fee_margin_percentage: None,
1274 swap_config: None,
1275 };
1276
1277 let response_with_user = StellarPolicyResponse::from(policy_with_user);
1278 let serialized_with_user = serde_json::to_string(&response_with_user).unwrap();
1279 assert!(
1280 serialized_with_user.contains(r#""fee_payment_strategy":"user""#),
1281 "Explicitly set User fee_payment_strategy should appear in JSON response"
1282 );
1283
1284 let policy_with_relayer = RelayerStellarPolicy {
1286 min_balance: Some(20000000),
1287 max_fee: Some(100000),
1288 timeout_seconds: Some(30),
1289 concurrent_transactions: None,
1290 allowed_tokens: None,
1291 fee_payment_strategy: Some(StellarFeePaymentStrategy::Relayer),
1292 slippage_percentage: None,
1293 fee_margin_percentage: None,
1294 swap_config: None,
1295 };
1296
1297 let response_with_relayer = StellarPolicyResponse::from(policy_with_relayer);
1298 let serialized_with_relayer = serde_json::to_string(&response_with_relayer).unwrap();
1299 assert!(
1300 serialized_with_relayer.contains(r#""fee_payment_strategy":"relayer""#),
1301 "Explicitly set Relayer fee_payment_strategy should appear in JSON response"
1302 );
1303
1304 let policy_omitted = RelayerStellarPolicy {
1306 min_balance: Some(20000000),
1307 max_fee: Some(100000),
1308 timeout_seconds: Some(30),
1309 concurrent_transactions: None,
1310 allowed_tokens: None,
1311 fee_payment_strategy: None,
1312 slippage_percentage: None,
1313 fee_margin_percentage: None,
1314 swap_config: None,
1315 };
1316
1317 let response_omitted = StellarPolicyResponse::from(policy_omitted);
1318 let serialized_omitted = serde_json::to_string(&response_omitted).unwrap();
1319 assert!(
1320 !serialized_omitted.contains("fee_payment_strategy"),
1321 "Omitted fee_payment_strategy (None) should NOT appear in JSON response"
1322 );
1323
1324 let empty_policy = RelayerStellarPolicy::default();
1326 assert!(
1327 is_empty_policy(&RelayerNetworkPolicy::Stellar(empty_policy)),
1328 "Policy with all None values should be considered empty"
1329 );
1330
1331 let policy_with_user_only = RelayerStellarPolicy {
1332 fee_payment_strategy: Some(StellarFeePaymentStrategy::User),
1333 ..Default::default()
1334 };
1335 assert!(
1336 !is_empty_policy(&RelayerNetworkPolicy::Stellar(policy_with_user_only)),
1337 "Policy with explicitly set User fee_payment_strategy should NOT be considered empty"
1338 );
1339
1340 let policy_with_relayer_only = RelayerStellarPolicy {
1341 fee_payment_strategy: Some(StellarFeePaymentStrategy::Relayer),
1342 ..Default::default()
1343 };
1344 assert!(
1345 !is_empty_policy(&RelayerNetworkPolicy::Stellar(policy_with_relayer_only)),
1346 "Policy with explicitly set Relayer fee_payment_strategy should NOT be considered empty"
1347 );
1348 }
1349
1350 #[test]
1351 fn test_relayer_status_serialization() {
1352 let evm_status = RelayerStatus::Evm {
1354 balance: Some("1000000000000000000".to_string()),
1355 pending_transactions_count: Some(5),
1356 last_confirmed_transaction_timestamp: Some("2024-01-01T00:00:00Z".to_string()),
1357 system_disabled: false,
1358 paused: false,
1359 nonce: "42".to_string(),
1360 };
1361
1362 let serialized = serde_json::to_string(&evm_status).unwrap();
1363 assert!(serialized.contains(r#""network_type":"evm""#));
1364 assert!(serialized.contains(r#""nonce":"42""#));
1365 assert!(serialized.contains(r#""balance":"1000000000000000000""#));
1366
1367 let solana_status = RelayerStatus::Solana {
1369 balance: Some("5000000000".to_string()),
1370 pending_transactions_count: Some(3),
1371 last_confirmed_transaction_timestamp: None,
1372 system_disabled: false,
1373 paused: true,
1374 };
1375
1376 let serialized = serde_json::to_string(&solana_status).unwrap();
1377 assert!(serialized.contains(r#""network_type":"solana""#));
1378 assert!(serialized.contains(r#""balance":"5000000000""#));
1379 assert!(serialized.contains(r#""paused":true"#));
1380
1381 let stellar_status = RelayerStatus::Stellar {
1383 balance: Some("1000000000".to_string()),
1384 pending_transactions_count: Some(2),
1385 last_confirmed_transaction_timestamp: Some("2024-01-01T12:00:00Z".to_string()),
1386 system_disabled: true,
1387 paused: false,
1388 sequence_number: "123456789".to_string(),
1389 };
1390
1391 let serialized = serde_json::to_string(&stellar_status).unwrap();
1392 assert!(serialized.contains(r#""network_type":"stellar""#));
1393 assert!(serialized.contains(r#""sequence_number":"123456789""#));
1394 assert!(serialized.contains(r#""system_disabled":true"#));
1395
1396 let evm_minimal = RelayerStatus::Evm {
1398 balance: None,
1399 pending_transactions_count: None,
1400 last_confirmed_transaction_timestamp: None,
1401 system_disabled: false,
1402 paused: false,
1403 nonce: "0".to_string(),
1404 };
1405 let serialized = serde_json::to_string(&evm_minimal).unwrap();
1406 assert!(!serialized.contains("balance"));
1407 assert!(!serialized.contains("pending_transactions_count"));
1408 assert!(!serialized.contains("last_confirmed_transaction_timestamp"));
1409 }
1410
1411 #[test]
1412 fn test_relayer_status_deserialization() {
1413 let evm_json = r#"{
1415 "network_type": "evm",
1416 "balance": "1000000000000000000",
1417 "pending_transactions_count": 5,
1418 "last_confirmed_transaction_timestamp": "2024-01-01T00:00:00Z",
1419 "system_disabled": false,
1420 "paused": false,
1421 "nonce": "42"
1422 }"#;
1423
1424 let status: RelayerStatus = serde_json::from_str(evm_json).unwrap();
1425 if let RelayerStatus::Evm { nonce, balance, .. } = status {
1426 assert_eq!(nonce, "42");
1427 assert_eq!(balance, Some("1000000000000000000".to_string()));
1428 } else {
1429 panic!("Expected EVM status");
1430 }
1431
1432 let solana_json = r#"{
1434 "network_type": "solana",
1435 "balance": "5000000000",
1436 "pending_transactions_count": 3,
1437 "last_confirmed_transaction_timestamp": null,
1438 "system_disabled": false,
1439 "paused": true
1440 }"#;
1441
1442 let status: RelayerStatus = serde_json::from_str(solana_json).unwrap();
1443 if let RelayerStatus::Solana {
1444 balance, paused, ..
1445 } = status
1446 {
1447 assert_eq!(balance, Some("5000000000".to_string()));
1448 assert!(paused);
1449 } else {
1450 panic!("Expected Solana status");
1451 }
1452
1453 let stellar_json = r#"{
1455 "network_type": "stellar",
1456 "balance": "1000000000",
1457 "pending_transactions_count": 2,
1458 "last_confirmed_transaction_timestamp": "2024-01-01T12:00:00Z",
1459 "system_disabled": true,
1460 "paused": false,
1461 "sequence_number": "123456789"
1462 }"#;
1463
1464 let status: RelayerStatus = serde_json::from_str(stellar_json).unwrap();
1465 if let RelayerStatus::Stellar {
1466 sequence_number,
1467 system_disabled,
1468 ..
1469 } = status
1470 {
1471 assert_eq!(sequence_number, "123456789");
1472 assert!(system_disabled);
1473 } else {
1474 panic!("Expected Stellar status");
1475 }
1476 }
1477}