1use super::{
15 Relayer, RelayerEvmPolicy, RelayerNetworkPolicy, RelayerNetworkType, RelayerRepoModel,
16 RelayerSolanaPolicy, RelayerSolanaSwapConfig, RelayerStellarPolicy, RpcConfig,
17 SolanaAllowedTokensPolicy, SolanaFeePaymentStrategy,
18};
19use crate::constants::{
20 DEFAULT_EVM_GAS_LIMIT_ESTIMATION, DEFAULT_EVM_MIN_BALANCE, DEFAULT_SOLANA_MAX_TX_DATA_SIZE,
21 DEFAULT_SOLANA_MIN_BALANCE, DEFAULT_STELLAR_MIN_BALANCE,
22};
23use serde::{Deserialize, Serialize};
24use utoipa::ToSchema;
25
26#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
28pub struct DeletePendingTransactionsResponse {
29 pub queued_for_cancellation_transaction_ids: Vec<String>,
30 pub failed_to_queue_transaction_ids: Vec<String>,
31 pub total_processed: u32,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ToSchema)]
37#[serde(untagged)]
38pub enum RelayerNetworkPolicyResponse {
39 Evm(EvmPolicyResponse),
42 Stellar(StellarPolicyResponse),
44 Solana(SolanaPolicyResponse),
46}
47
48impl From<RelayerNetworkPolicy> for RelayerNetworkPolicyResponse {
49 fn from(policy: RelayerNetworkPolicy) -> Self {
50 match policy {
51 RelayerNetworkPolicy::Evm(evm_policy) => {
52 RelayerNetworkPolicyResponse::Evm(evm_policy.into())
53 }
54 RelayerNetworkPolicy::Solana(solana_policy) => {
55 RelayerNetworkPolicyResponse::Solana(solana_policy.into())
56 }
57 RelayerNetworkPolicy::Stellar(stellar_policy) => {
58 RelayerNetworkPolicyResponse::Stellar(stellar_policy.into())
59 }
60 }
61 }
62}
63
64#[derive(Debug, Serialize, Clone, PartialEq, ToSchema)]
66pub struct RelayerResponse {
67 pub id: String,
68 pub name: String,
69 pub network: String,
70 pub network_type: RelayerNetworkType,
71 pub paused: bool,
72 #[serde(skip_serializing_if = "Option::is_none")]
75 #[schema(nullable = false)]
76 pub policies: Option<RelayerNetworkPolicyResponse>,
77 pub signer_id: String,
78 #[serde(skip_serializing_if = "Option::is_none")]
79 #[schema(nullable = false)]
80 pub notification_id: Option<String>,
81 #[serde(skip_serializing_if = "Option::is_none")]
82 #[schema(nullable = false)]
83 pub custom_rpc_urls: Option<Vec<RpcConfig>>,
84 #[schema(nullable = false)]
86 pub address: Option<String>,
87 #[schema(nullable = false)]
88 pub system_disabled: Option<bool>,
89}
90
91#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
93#[serde(tag = "network_type")]
94pub enum RelayerStatus {
95 #[serde(rename = "evm")]
96 Evm {
97 balance: String,
98 pending_transactions_count: u64,
99 last_confirmed_transaction_timestamp: Option<String>,
100 system_disabled: bool,
101 paused: bool,
102 nonce: String,
103 },
104 #[serde(rename = "stellar")]
105 Stellar {
106 balance: String,
107 pending_transactions_count: u64,
108 last_confirmed_transaction_timestamp: Option<String>,
109 system_disabled: bool,
110 paused: bool,
111 sequence_number: String,
112 },
113 #[serde(rename = "solana")]
114 Solana {
115 balance: String,
116 pending_transactions_count: u64,
117 last_confirmed_transaction_timestamp: Option<String>,
118 system_disabled: bool,
119 paused: bool,
120 },
121}
122
123fn convert_policy_to_response(
125 policy: RelayerNetworkPolicy,
126 network_type: RelayerNetworkType,
127) -> RelayerNetworkPolicyResponse {
128 match (policy, network_type) {
129 (RelayerNetworkPolicy::Evm(evm_policy), RelayerNetworkType::Evm) => {
130 RelayerNetworkPolicyResponse::Evm(EvmPolicyResponse::from(evm_policy))
131 }
132 (RelayerNetworkPolicy::Solana(solana_policy), RelayerNetworkType::Solana) => {
133 RelayerNetworkPolicyResponse::Solana(SolanaPolicyResponse::from(solana_policy))
134 }
135 (RelayerNetworkPolicy::Stellar(stellar_policy), RelayerNetworkType::Stellar) => {
136 RelayerNetworkPolicyResponse::Stellar(StellarPolicyResponse::from(stellar_policy))
137 }
138 (RelayerNetworkPolicy::Evm(evm_policy), _) => {
140 RelayerNetworkPolicyResponse::Evm(EvmPolicyResponse::from(evm_policy))
141 }
142 (RelayerNetworkPolicy::Solana(solana_policy), _) => {
143 RelayerNetworkPolicyResponse::Solana(SolanaPolicyResponse::from(solana_policy))
144 }
145 (RelayerNetworkPolicy::Stellar(stellar_policy), _) => {
146 RelayerNetworkPolicyResponse::Stellar(StellarPolicyResponse::from(stellar_policy))
147 }
148 }
149}
150
151impl From<Relayer> for RelayerResponse {
152 fn from(relayer: Relayer) -> Self {
153 Self {
154 id: relayer.id.clone(),
155 name: relayer.name.clone(),
156 network: relayer.network.clone(),
157 network_type: relayer.network_type,
158 paused: relayer.paused,
159 policies: relayer
160 .policies
161 .map(|policy| convert_policy_to_response(policy, relayer.network_type)),
162 signer_id: relayer.signer_id,
163 notification_id: relayer.notification_id,
164 custom_rpc_urls: relayer.custom_rpc_urls,
165 address: None,
166 system_disabled: None,
167 }
168 }
169}
170
171impl From<RelayerRepoModel> for RelayerResponse {
172 fn from(model: RelayerRepoModel) -> Self {
173 let policies = if is_empty_policy(&model.policies) {
175 None } else {
177 Some(convert_policy_to_response(
178 model.policies.clone(),
179 model.network_type,
180 ))
181 };
182
183 Self {
184 id: model.id,
185 name: model.name,
186 network: model.network,
187 network_type: model.network_type,
188 paused: model.paused,
189 policies,
190 signer_id: model.signer_id,
191 notification_id: model.notification_id,
192 custom_rpc_urls: model.custom_rpc_urls,
193 address: Some(model.address),
194 system_disabled: Some(model.system_disabled),
195 }
196 }
197}
198
199impl<'de> serde::Deserialize<'de> for RelayerResponse {
201 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
202 where
203 D: serde::Deserializer<'de>,
204 {
205 use serde::de::Error;
206 use serde_json::Value;
207
208 let value: Value = Value::deserialize(deserializer)?;
210
211 let network_type: RelayerNetworkType = value
213 .get("network_type")
214 .and_then(|v| serde_json::from_value(v.clone()).ok())
215 .ok_or_else(|| D::Error::missing_field("network_type"))?;
216
217 let policies = if let Some(policies_value) = value.get("policies") {
219 if policies_value.is_null() {
220 None
221 } else {
222 let policy_response = match network_type {
224 RelayerNetworkType::Evm => {
225 let evm_policy: EvmPolicyResponse =
226 serde_json::from_value(policies_value.clone())
227 .map_err(D::Error::custom)?;
228 RelayerNetworkPolicyResponse::Evm(evm_policy)
229 }
230 RelayerNetworkType::Solana => {
231 let solana_policy: SolanaPolicyResponse =
232 serde_json::from_value(policies_value.clone())
233 .map_err(D::Error::custom)?;
234 RelayerNetworkPolicyResponse::Solana(solana_policy)
235 }
236 RelayerNetworkType::Stellar => {
237 let stellar_policy: StellarPolicyResponse =
238 serde_json::from_value(policies_value.clone())
239 .map_err(D::Error::custom)?;
240 RelayerNetworkPolicyResponse::Stellar(stellar_policy)
241 }
242 };
243 Some(policy_response)
244 }
245 } else {
246 None
247 };
248
249 Ok(RelayerResponse {
251 id: value
252 .get("id")
253 .and_then(|v| serde_json::from_value(v.clone()).ok())
254 .ok_or_else(|| D::Error::missing_field("id"))?,
255 name: value
256 .get("name")
257 .and_then(|v| serde_json::from_value(v.clone()).ok())
258 .ok_or_else(|| D::Error::missing_field("name"))?,
259 network: value
260 .get("network")
261 .and_then(|v| serde_json::from_value(v.clone()).ok())
262 .ok_or_else(|| D::Error::missing_field("network"))?,
263 network_type,
264 paused: value
265 .get("paused")
266 .and_then(|v| serde_json::from_value(v.clone()).ok())
267 .ok_or_else(|| D::Error::missing_field("paused"))?,
268 policies,
269 signer_id: value
270 .get("signer_id")
271 .and_then(|v| serde_json::from_value(v.clone()).ok())
272 .ok_or_else(|| D::Error::missing_field("signer_id"))?,
273 notification_id: value
274 .get("notification_id")
275 .and_then(|v| serde_json::from_value(v.clone()).ok())
276 .unwrap_or(None),
277 custom_rpc_urls: value
278 .get("custom_rpc_urls")
279 .and_then(|v| serde_json::from_value(v.clone()).ok())
280 .unwrap_or(None),
281 address: value
282 .get("address")
283 .and_then(|v| serde_json::from_value(v.clone()).ok())
284 .unwrap_or(None),
285 system_disabled: value
286 .get("system_disabled")
287 .and_then(|v| serde_json::from_value(v.clone()).ok())
288 .unwrap_or(None),
289 })
290 }
291}
292
293fn is_empty_policy(policy: &RelayerNetworkPolicy) -> bool {
295 match policy {
296 RelayerNetworkPolicy::Evm(evm_policy) => {
297 evm_policy.min_balance.is_none()
298 && evm_policy.gas_limit_estimation.is_none()
299 && evm_policy.gas_price_cap.is_none()
300 && evm_policy.whitelist_receivers.is_none()
301 && evm_policy.eip1559_pricing.is_none()
302 && evm_policy.private_transactions.is_none()
303 }
304 RelayerNetworkPolicy::Solana(solana_policy) => {
305 solana_policy.allowed_programs.is_none()
306 && solana_policy.max_signatures.is_none()
307 && solana_policy.max_tx_data_size.is_none()
308 && solana_policy.min_balance.is_none()
309 && solana_policy.allowed_tokens.is_none()
310 && solana_policy.fee_payment_strategy.is_none()
311 && solana_policy.fee_margin_percentage.is_none()
312 && solana_policy.allowed_accounts.is_none()
313 && solana_policy.disallowed_accounts.is_none()
314 && solana_policy.max_allowed_fee_lamports.is_none()
315 && solana_policy.swap_config.is_none()
316 }
317 RelayerNetworkPolicy::Stellar(stellar_policy) => {
318 stellar_policy.min_balance.is_none()
319 && stellar_policy.max_fee.is_none()
320 && stellar_policy.timeout_seconds.is_none()
321 }
322 }
323}
324
325#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
327pub struct NetworkPolicyResponse {
328 #[serde(flatten)]
329 pub policy: RelayerNetworkPolicy,
330}
331
332fn default_evm_min_balance() -> u128 {
334 DEFAULT_EVM_MIN_BALANCE
335}
336
337fn default_evm_gas_limit_estimation() -> bool {
338 DEFAULT_EVM_GAS_LIMIT_ESTIMATION
339}
340
341fn default_solana_min_balance() -> u64 {
343 DEFAULT_SOLANA_MIN_BALANCE
344}
345
346fn default_stellar_min_balance() -> u64 {
348 DEFAULT_STELLAR_MIN_BALANCE
349}
350
351fn default_solana_max_tx_data_size() -> u16 {
353 DEFAULT_SOLANA_MAX_TX_DATA_SIZE
354}
355#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
357#[serde(deny_unknown_fields)]
358pub struct EvmPolicyResponse {
359 #[serde(
360 default = "default_evm_min_balance",
361 serialize_with = "crate::utils::serialize_u128_as_number",
362 deserialize_with = "crate::utils::deserialize_u128_as_number"
363 )]
364 #[schema(nullable = false)]
365 pub min_balance: u128,
366 #[serde(default = "default_evm_gas_limit_estimation")]
367 #[schema(nullable = false)]
368 pub gas_limit_estimation: bool,
369 #[serde(
370 skip_serializing_if = "Option::is_none",
371 serialize_with = "crate::utils::serialize_optional_u128_as_number",
372 deserialize_with = "crate::utils::deserialize_optional_u128_as_number",
373 default
374 )]
375 #[schema(nullable = false)]
376 pub gas_price_cap: Option<u128>,
377 #[serde(skip_serializing_if = "Option::is_none")]
378 #[schema(nullable = false)]
379 pub whitelist_receivers: Option<Vec<String>>,
380 #[serde(skip_serializing_if = "Option::is_none")]
381 #[schema(nullable = false)]
382 pub eip1559_pricing: Option<bool>,
383 #[serde(skip_serializing_if = "Option::is_none")]
384 #[schema(nullable = false)]
385 pub private_transactions: Option<bool>,
386}
387
388#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
390#[serde(deny_unknown_fields)]
391pub struct SolanaPolicyResponse {
392 #[serde(skip_serializing_if = "Option::is_none")]
393 #[schema(nullable = false)]
394 pub allowed_programs: Option<Vec<String>>,
395 #[serde(skip_serializing_if = "Option::is_none")]
396 #[schema(nullable = false)]
397 pub max_signatures: Option<u8>,
398 #[schema(nullable = false)]
399 #[serde(default = "default_solana_max_tx_data_size")]
400 pub max_tx_data_size: u16,
401 #[serde(default = "default_solana_min_balance")]
402 #[schema(nullable = false)]
403 pub min_balance: u64,
404 #[serde(skip_serializing_if = "Option::is_none")]
405 #[schema(nullable = false)]
406 pub allowed_tokens: Option<Vec<SolanaAllowedTokensPolicy>>,
407 #[serde(skip_serializing_if = "Option::is_none")]
408 #[schema(nullable = false)]
409 pub fee_payment_strategy: Option<SolanaFeePaymentStrategy>,
410 #[serde(skip_serializing_if = "Option::is_none")]
411 #[schema(nullable = false)]
412 pub fee_margin_percentage: Option<f32>,
413 #[serde(skip_serializing_if = "Option::is_none")]
414 #[schema(nullable = false)]
415 pub allowed_accounts: Option<Vec<String>>,
416 #[serde(skip_serializing_if = "Option::is_none")]
417 #[schema(nullable = false)]
418 pub disallowed_accounts: Option<Vec<String>>,
419 #[serde(skip_serializing_if = "Option::is_none")]
420 #[schema(nullable = false)]
421 pub max_allowed_fee_lamports: Option<u64>,
422 #[serde(skip_serializing_if = "Option::is_none")]
423 #[schema(nullable = false)]
424 pub swap_config: Option<RelayerSolanaSwapConfig>,
425}
426
427#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
429#[serde(deny_unknown_fields)]
430pub struct StellarPolicyResponse {
431 #[serde(skip_serializing_if = "Option::is_none")]
432 #[schema(nullable = false)]
433 pub max_fee: Option<u32>,
434 #[serde(skip_serializing_if = "Option::is_none")]
435 #[schema(nullable = false)]
436 pub timeout_seconds: Option<u64>,
437 #[serde(default = "default_stellar_min_balance")]
438 #[schema(nullable = false)]
439 pub min_balance: u64,
440}
441
442impl From<RelayerEvmPolicy> for EvmPolicyResponse {
443 fn from(policy: RelayerEvmPolicy) -> Self {
444 Self {
445 min_balance: policy.min_balance.unwrap_or(DEFAULT_EVM_MIN_BALANCE),
446 gas_limit_estimation: policy
447 .gas_limit_estimation
448 .unwrap_or(DEFAULT_EVM_GAS_LIMIT_ESTIMATION),
449 gas_price_cap: policy.gas_price_cap,
450 whitelist_receivers: policy.whitelist_receivers,
451 eip1559_pricing: policy.eip1559_pricing,
452 private_transactions: policy.private_transactions,
453 }
454 }
455}
456
457impl From<RelayerSolanaPolicy> for SolanaPolicyResponse {
458 fn from(policy: RelayerSolanaPolicy) -> Self {
459 Self {
460 allowed_programs: policy.allowed_programs,
461 max_signatures: policy.max_signatures,
462 max_tx_data_size: policy
463 .max_tx_data_size
464 .unwrap_or(DEFAULT_SOLANA_MAX_TX_DATA_SIZE),
465 min_balance: policy.min_balance.unwrap_or(DEFAULT_SOLANA_MIN_BALANCE),
466 allowed_tokens: policy.allowed_tokens,
467 fee_payment_strategy: policy.fee_payment_strategy,
468 fee_margin_percentage: policy.fee_margin_percentage,
469 allowed_accounts: policy.allowed_accounts,
470 disallowed_accounts: policy.disallowed_accounts,
471 max_allowed_fee_lamports: policy.max_allowed_fee_lamports,
472 swap_config: policy.swap_config,
473 }
474 }
475}
476
477impl From<RelayerStellarPolicy> for StellarPolicyResponse {
478 fn from(policy: RelayerStellarPolicy) -> Self {
479 Self {
480 min_balance: policy.min_balance.unwrap_or(DEFAULT_STELLAR_MIN_BALANCE),
481 max_fee: policy.max_fee,
482 timeout_seconds: policy.timeout_seconds,
483 }
484 }
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490 use crate::models::relayer::{
491 RelayerEvmPolicy, RelayerSolanaPolicy, RelayerSolanaSwapConfig, RelayerStellarPolicy,
492 SolanaAllowedTokensPolicy, SolanaFeePaymentStrategy, SolanaSwapStrategy,
493 };
494
495 #[test]
496 fn test_from_domain_relayer() {
497 let relayer = Relayer::new(
498 "test-relayer".to_string(),
499 "Test Relayer".to_string(),
500 "mainnet".to_string(),
501 false,
502 RelayerNetworkType::Evm,
503 Some(RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
504 gas_price_cap: Some(100_000_000_000),
505 whitelist_receivers: None,
506 eip1559_pricing: Some(true),
507 private_transactions: None,
508 min_balance: None,
509 gas_limit_estimation: None,
510 })),
511 "test-signer".to_string(),
512 None,
513 None,
514 );
515
516 let response: RelayerResponse = relayer.clone().into();
517
518 assert_eq!(response.id, relayer.id);
519 assert_eq!(response.name, relayer.name);
520 assert_eq!(response.network, relayer.network);
521 assert_eq!(response.network_type, relayer.network_type);
522 assert_eq!(response.paused, relayer.paused);
523 assert_eq!(
524 response.policies,
525 Some(RelayerNetworkPolicyResponse::Evm(
526 RelayerEvmPolicy {
527 gas_price_cap: Some(100_000_000_000),
528 whitelist_receivers: None,
529 eip1559_pricing: Some(true),
530 private_transactions: None,
531 min_balance: Some(DEFAULT_EVM_MIN_BALANCE),
532 gas_limit_estimation: Some(DEFAULT_EVM_GAS_LIMIT_ESTIMATION),
533 }
534 .into()
535 ))
536 );
537 assert_eq!(response.signer_id, relayer.signer_id);
538 assert_eq!(response.notification_id, relayer.notification_id);
539 assert_eq!(response.custom_rpc_urls, relayer.custom_rpc_urls);
540 assert_eq!(response.address, None);
541 assert_eq!(response.system_disabled, None);
542 }
543
544 #[test]
545 fn test_from_domain_relayer_solana() {
546 let relayer = Relayer::new(
547 "test-solana-relayer".to_string(),
548 "Test Solana Relayer".to_string(),
549 "mainnet".to_string(),
550 false,
551 RelayerNetworkType::Solana,
552 Some(RelayerNetworkPolicy::Solana(RelayerSolanaPolicy {
553 allowed_programs: Some(vec!["11111111111111111111111111111111".to_string()]),
554 max_signatures: Some(5),
555 min_balance: Some(1000000),
556 fee_payment_strategy: Some(SolanaFeePaymentStrategy::Relayer),
557 allowed_tokens: Some(vec![SolanaAllowedTokensPolicy::new(
558 "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
559 Some(100000),
560 None,
561 )]),
562 max_tx_data_size: None,
563 fee_margin_percentage: None,
564 allowed_accounts: None,
565 disallowed_accounts: None,
566 max_allowed_fee_lamports: None,
567 swap_config: None,
568 })),
569 "test-signer".to_string(),
570 None,
571 None,
572 );
573
574 let response: RelayerResponse = relayer.clone().into();
575
576 assert_eq!(response.id, relayer.id);
577 assert_eq!(response.network_type, RelayerNetworkType::Solana);
578 assert!(response.policies.is_some());
579
580 if let Some(RelayerNetworkPolicyResponse::Solana(solana_response)) = response.policies {
581 assert_eq!(solana_response.min_balance, 1000000);
582 assert_eq!(solana_response.max_signatures, Some(5));
583 } else {
584 panic!("Expected Solana policy response");
585 }
586 }
587
588 #[test]
589 fn test_from_domain_relayer_stellar() {
590 let relayer = Relayer::new(
591 "test-stellar-relayer".to_string(),
592 "Test Stellar Relayer".to_string(),
593 "mainnet".to_string(),
594 false,
595 RelayerNetworkType::Stellar,
596 Some(RelayerNetworkPolicy::Stellar(RelayerStellarPolicy {
597 min_balance: Some(20000000),
598 max_fee: Some(100000),
599 timeout_seconds: Some(30),
600 })),
601 "test-signer".to_string(),
602 None,
603 None,
604 );
605
606 let response: RelayerResponse = relayer.clone().into();
607
608 assert_eq!(response.id, relayer.id);
609 assert_eq!(response.network_type, RelayerNetworkType::Stellar);
610 assert!(response.policies.is_some());
611
612 if let Some(RelayerNetworkPolicyResponse::Stellar(stellar_response)) = response.policies {
613 assert_eq!(stellar_response.min_balance, 20000000);
614 } else {
615 panic!("Expected Stellar policy response");
616 }
617 }
618
619 #[test]
620 fn test_response_serialization() {
621 let response = RelayerResponse {
622 id: "test-relayer".to_string(),
623 name: "Test Relayer".to_string(),
624 network: "mainnet".to_string(),
625 network_type: RelayerNetworkType::Evm,
626 paused: false,
627 policies: Some(RelayerNetworkPolicyResponse::Evm(EvmPolicyResponse {
628 gas_price_cap: Some(50000000000),
629 whitelist_receivers: None,
630 eip1559_pricing: Some(true),
631 private_transactions: None,
632 min_balance: DEFAULT_EVM_MIN_BALANCE,
633 gas_limit_estimation: DEFAULT_EVM_GAS_LIMIT_ESTIMATION,
634 })),
635 signer_id: "test-signer".to_string(),
636 notification_id: None,
637 custom_rpc_urls: None,
638 address: Some("0x123...".to_string()),
639 system_disabled: Some(false),
640 };
641
642 let serialized = serde_json::to_string(&response).unwrap();
644 assert!(!serialized.is_empty());
645
646 let deserialized: RelayerResponse = serde_json::from_str(&serialized).unwrap();
648 assert_eq!(response.id, deserialized.id);
649 assert_eq!(response.name, deserialized.name);
650 }
651
652 #[test]
653 fn test_solana_response_serialization() {
654 let response = RelayerResponse {
655 id: "test-solana-relayer".to_string(),
656 name: "Test Solana Relayer".to_string(),
657 network: "mainnet".to_string(),
658 network_type: RelayerNetworkType::Solana,
659 paused: false,
660 policies: Some(RelayerNetworkPolicyResponse::Solana(SolanaPolicyResponse {
661 allowed_programs: Some(vec!["11111111111111111111111111111111".to_string()]),
662 max_signatures: Some(5),
663 max_tx_data_size: DEFAULT_SOLANA_MAX_TX_DATA_SIZE,
664 min_balance: 1000000,
665 allowed_tokens: Some(vec![SolanaAllowedTokensPolicy::new(
666 "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
667 Some(100000),
668 None,
669 )]),
670 fee_payment_strategy: Some(SolanaFeePaymentStrategy::Relayer),
671 fee_margin_percentage: Some(5.0),
672 allowed_accounts: None,
673 disallowed_accounts: None,
674 max_allowed_fee_lamports: Some(500000),
675 swap_config: Some(RelayerSolanaSwapConfig {
676 strategy: Some(SolanaSwapStrategy::JupiterSwap),
677 cron_schedule: Some("0 0 * * *".to_string()),
678 min_balance_threshold: Some(500000),
679 jupiter_swap_options: None,
680 }),
681 })),
682 signer_id: "test-signer".to_string(),
683 notification_id: None,
684 custom_rpc_urls: None,
685 address: Some("SolanaAddress123...".to_string()),
686 system_disabled: Some(false),
687 };
688
689 let serialized = serde_json::to_string(&response).unwrap();
691 assert!(!serialized.is_empty());
692
693 let deserialized: RelayerResponse = serde_json::from_str(&serialized).unwrap();
695 assert_eq!(response.id, deserialized.id);
696 assert_eq!(response.network_type, RelayerNetworkType::Solana);
697 }
698
699 #[test]
700 fn test_stellar_response_serialization() {
701 let response = RelayerResponse {
702 id: "test-stellar-relayer".to_string(),
703 name: "Test Stellar Relayer".to_string(),
704 network: "mainnet".to_string(),
705 network_type: RelayerNetworkType::Stellar,
706 paused: false,
707 policies: Some(RelayerNetworkPolicyResponse::Stellar(
708 StellarPolicyResponse {
709 max_fee: Some(5000),
710 timeout_seconds: None,
711 min_balance: 20000000,
712 },
713 )),
714 signer_id: "test-signer".to_string(),
715 notification_id: None,
716 custom_rpc_urls: None,
717 address: Some("GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX".to_string()),
718 system_disabled: Some(false),
719 };
720
721 let serialized = serde_json::to_string(&response).unwrap();
723 assert!(!serialized.is_empty());
724
725 let deserialized: RelayerResponse = serde_json::from_str(&serialized).unwrap();
727 assert_eq!(response.id, deserialized.id);
728 assert_eq!(response.network_type, RelayerNetworkType::Stellar);
729
730 if let Some(RelayerNetworkPolicyResponse::Stellar(stellar_policy)) = deserialized.policies {
732 assert_eq!(stellar_policy.min_balance, 20000000);
733 assert_eq!(stellar_policy.max_fee, Some(5000));
734 assert_eq!(stellar_policy.timeout_seconds, None);
735 } else {
736 panic!("Expected Stellar policy in deserialized response");
737 }
738 }
739
740 #[test]
741 fn test_response_without_redundant_network_type() {
742 let response = RelayerResponse {
743 id: "test-relayer".to_string(),
744 name: "Test Relayer".to_string(),
745 network: "mainnet".to_string(),
746 network_type: RelayerNetworkType::Evm,
747 paused: false,
748 policies: Some(RelayerNetworkPolicyResponse::Evm(EvmPolicyResponse {
749 gas_price_cap: Some(100_000_000_000),
750 whitelist_receivers: None,
751 eip1559_pricing: Some(true),
752 private_transactions: None,
753 min_balance: DEFAULT_EVM_MIN_BALANCE,
754 gas_limit_estimation: DEFAULT_EVM_GAS_LIMIT_ESTIMATION,
755 })),
756 signer_id: "test-signer".to_string(),
757 notification_id: None,
758 custom_rpc_urls: None,
759 address: Some("0x123...".to_string()),
760 system_disabled: Some(false),
761 };
762
763 let serialized = serde_json::to_string_pretty(&response).unwrap();
764
765 assert!(serialized.contains(r#""network_type": "evm""#));
766
767 let network_type_count = serialized.matches(r#""network_type""#).count();
769 assert_eq!(
770 network_type_count, 1,
771 "Should only have one network_type field at top level, not in policies"
772 );
773
774 assert!(serialized.contains(r#""gas_price_cap": 100000000000"#));
775 assert!(serialized.contains(r#""eip1559_pricing": true"#));
776 }
777
778 #[test]
779 fn test_solana_response_without_redundant_network_type() {
780 let response = RelayerResponse {
781 id: "test-solana-relayer".to_string(),
782 name: "Test Solana Relayer".to_string(),
783 network: "mainnet".to_string(),
784 network_type: RelayerNetworkType::Solana,
785 paused: false,
786 policies: Some(RelayerNetworkPolicyResponse::Solana(SolanaPolicyResponse {
787 allowed_programs: Some(vec!["11111111111111111111111111111111".to_string()]),
788 max_signatures: Some(5),
789 max_tx_data_size: DEFAULT_SOLANA_MAX_TX_DATA_SIZE,
790 min_balance: 1000000,
791 allowed_tokens: None,
792 fee_payment_strategy: Some(SolanaFeePaymentStrategy::Relayer),
793 fee_margin_percentage: None,
794 allowed_accounts: None,
795 disallowed_accounts: None,
796 max_allowed_fee_lamports: None,
797 swap_config: None,
798 })),
799 signer_id: "test-signer".to_string(),
800 notification_id: None,
801 custom_rpc_urls: None,
802 address: Some("SolanaAddress123...".to_string()),
803 system_disabled: Some(false),
804 };
805
806 let serialized = serde_json::to_string_pretty(&response).unwrap();
807
808 assert!(serialized.contains(r#""network_type": "solana""#));
809
810 let network_type_count = serialized.matches(r#""network_type""#).count();
812 assert_eq!(
813 network_type_count, 1,
814 "Should only have one network_type field at top level, not in policies"
815 );
816
817 assert!(serialized.contains(r#""max_signatures": 5"#));
818 assert!(serialized.contains(r#""fee_payment_strategy": "relayer""#));
819 }
820
821 #[test]
822 fn test_stellar_response_without_redundant_network_type() {
823 let response = RelayerResponse {
824 id: "test-stellar-relayer".to_string(),
825 name: "Test Stellar Relayer".to_string(),
826 network: "mainnet".to_string(),
827 network_type: RelayerNetworkType::Stellar,
828 paused: false,
829 policies: Some(RelayerNetworkPolicyResponse::Stellar(
830 StellarPolicyResponse {
831 min_balance: 20000000,
832 max_fee: Some(100000),
833 timeout_seconds: Some(30),
834 },
835 )),
836 signer_id: "test-signer".to_string(),
837 notification_id: None,
838 custom_rpc_urls: None,
839 address: Some("GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX".to_string()),
840 system_disabled: Some(false),
841 };
842
843 let serialized = serde_json::to_string_pretty(&response).unwrap();
844
845 assert!(serialized.contains(r#""network_type": "stellar""#));
846
847 let network_type_count = serialized.matches(r#""network_type""#).count();
849 assert_eq!(
850 network_type_count, 1,
851 "Should only have one network_type field at top level, not in policies"
852 );
853
854 assert!(serialized.contains(r#""min_balance": 20000000"#));
855 assert!(serialized.contains(r#""max_fee": 100000"#));
856 assert!(serialized.contains(r#""timeout_seconds": 30"#));
857 }
858
859 #[test]
860 fn test_empty_policies_not_returned_in_response() {
861 let repo_model = RelayerRepoModel {
863 id: "test-relayer".to_string(),
864 name: "Test Relayer".to_string(),
865 network: "mainnet".to_string(),
866 network_type: RelayerNetworkType::Evm,
867 paused: false,
868 policies: RelayerNetworkPolicy::Evm(RelayerEvmPolicy::default()), signer_id: "test-signer".to_string(),
870 notification_id: None,
871 custom_rpc_urls: None,
872 address: "0x123...".to_string(),
873 system_disabled: false,
874 };
875
876 let response = RelayerResponse::from(repo_model);
878
879 assert_eq!(response.policies, None);
881
882 let serialized = serde_json::to_string(&response).unwrap();
884 assert!(
885 !serialized.contains("policies"),
886 "Empty policies should not appear in JSON response"
887 );
888 }
889
890 #[test]
891 fn test_empty_solana_policies_not_returned_in_response() {
892 let repo_model = RelayerRepoModel {
894 id: "test-solana-relayer".to_string(),
895 name: "Test Solana Relayer".to_string(),
896 network: "mainnet".to_string(),
897 network_type: RelayerNetworkType::Solana,
898 paused: false,
899 policies: RelayerNetworkPolicy::Solana(RelayerSolanaPolicy::default()), signer_id: "test-signer".to_string(),
901 notification_id: None,
902 custom_rpc_urls: None,
903 address: "SolanaAddress123...".to_string(),
904 system_disabled: false,
905 };
906
907 let response = RelayerResponse::from(repo_model);
909
910 assert_eq!(response.policies, None);
912
913 let serialized = serde_json::to_string(&response).unwrap();
915 assert!(
916 !serialized.contains("policies"),
917 "Empty Solana policies should not appear in JSON response"
918 );
919 }
920
921 #[test]
922 fn test_empty_stellar_policies_not_returned_in_response() {
923 let repo_model = RelayerRepoModel {
925 id: "test-stellar-relayer".to_string(),
926 name: "Test Stellar Relayer".to_string(),
927 network: "mainnet".to_string(),
928 network_type: RelayerNetworkType::Stellar,
929 paused: false,
930 policies: RelayerNetworkPolicy::Stellar(RelayerStellarPolicy::default()), signer_id: "test-signer".to_string(),
932 notification_id: None,
933 custom_rpc_urls: None,
934 address: "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX".to_string(),
935 system_disabled: false,
936 };
937
938 let response = RelayerResponse::from(repo_model);
940
941 assert_eq!(response.policies, None);
943
944 let serialized = serde_json::to_string(&response).unwrap();
946 assert!(
947 !serialized.contains("policies"),
948 "Empty Stellar policies should not appear in JSON response"
949 );
950 }
951
952 #[test]
953 fn test_user_provided_policies_returned_in_response() {
954 let repo_model = RelayerRepoModel {
956 id: "test-relayer".to_string(),
957 name: "Test Relayer".to_string(),
958 network: "mainnet".to_string(),
959 network_type: RelayerNetworkType::Evm,
960 paused: false,
961 policies: RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
962 gas_price_cap: Some(100_000_000_000),
963 eip1559_pricing: Some(true),
964 min_balance: None, gas_limit_estimation: None,
966 whitelist_receivers: None,
967 private_transactions: None,
968 }),
969 signer_id: "test-signer".to_string(),
970 notification_id: None,
971 custom_rpc_urls: None,
972 address: "0x123...".to_string(),
973 system_disabled: false,
974 };
975
976 let response = RelayerResponse::from(repo_model);
978
979 assert!(response.policies.is_some());
981
982 let serialized = serde_json::to_string(&response).unwrap();
984 assert!(
985 serialized.contains("policies"),
986 "User-provided policies should appear in JSON response"
987 );
988 assert!(
989 serialized.contains("gas_price_cap"),
990 "User-provided policy values should appear in JSON response"
991 );
992 }
993
994 #[test]
995 fn test_user_provided_solana_policies_returned_in_response() {
996 let repo_model = RelayerRepoModel {
998 id: "test-solana-relayer".to_string(),
999 name: "Test Solana Relayer".to_string(),
1000 network: "mainnet".to_string(),
1001 network_type: RelayerNetworkType::Solana,
1002 paused: false,
1003 policies: RelayerNetworkPolicy::Solana(RelayerSolanaPolicy {
1004 max_signatures: Some(5),
1005 fee_payment_strategy: Some(SolanaFeePaymentStrategy::Relayer),
1006 min_balance: Some(1000000),
1007 allowed_programs: None, max_tx_data_size: None,
1009 allowed_tokens: None,
1010 fee_margin_percentage: None,
1011 allowed_accounts: None,
1012 disallowed_accounts: None,
1013 max_allowed_fee_lamports: None,
1014 swap_config: None,
1015 }),
1016 signer_id: "test-signer".to_string(),
1017 notification_id: None,
1018 custom_rpc_urls: None,
1019 address: "SolanaAddress123...".to_string(),
1020 system_disabled: false,
1021 };
1022
1023 let response = RelayerResponse::from(repo_model);
1025
1026 assert!(response.policies.is_some());
1028
1029 let serialized = serde_json::to_string(&response).unwrap();
1031 assert!(
1032 serialized.contains("policies"),
1033 "User-provided Solana policies should appear in JSON response"
1034 );
1035 assert!(
1036 serialized.contains("max_signatures"),
1037 "User-provided Solana policy values should appear in JSON response"
1038 );
1039 assert!(
1040 serialized.contains("fee_payment_strategy"),
1041 "User-provided Solana policy values should appear in JSON response"
1042 );
1043 }
1044
1045 #[test]
1046 fn test_user_provided_stellar_policies_returned_in_response() {
1047 let repo_model = RelayerRepoModel {
1049 id: "test-stellar-relayer".to_string(),
1050 name: "Test Stellar Relayer".to_string(),
1051 network: "mainnet".to_string(),
1052 network_type: RelayerNetworkType::Stellar,
1053 paused: false,
1054 policies: RelayerNetworkPolicy::Stellar(RelayerStellarPolicy {
1055 max_fee: Some(100000),
1056 timeout_seconds: Some(30),
1057 min_balance: None, }),
1059 signer_id: "test-signer".to_string(),
1060 notification_id: None,
1061 custom_rpc_urls: None,
1062 address: "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX".to_string(),
1063 system_disabled: false,
1064 };
1065
1066 let response = RelayerResponse::from(repo_model);
1068
1069 assert!(response.policies.is_some());
1071
1072 let serialized = serde_json::to_string(&response).unwrap();
1074 assert!(
1075 serialized.contains("policies"),
1076 "User-provided Stellar policies should appear in JSON response"
1077 );
1078 assert!(
1079 serialized.contains("max_fee"),
1080 "User-provided Stellar policy values should appear in JSON response"
1081 );
1082 assert!(
1083 serialized.contains("timeout_seconds"),
1084 "User-provided Stellar policy values should appear in JSON response"
1085 );
1086 }
1087
1088 #[test]
1089 fn test_relayer_status_serialization() {
1090 let evm_status = RelayerStatus::Evm {
1092 balance: "1000000000000000000".to_string(),
1093 pending_transactions_count: 5,
1094 last_confirmed_transaction_timestamp: Some("2024-01-01T00:00:00Z".to_string()),
1095 system_disabled: false,
1096 paused: false,
1097 nonce: "42".to_string(),
1098 };
1099
1100 let serialized = serde_json::to_string(&evm_status).unwrap();
1101 assert!(serialized.contains(r#""network_type":"evm""#));
1102 assert!(serialized.contains(r#""nonce":"42""#));
1103 assert!(serialized.contains(r#""balance":"1000000000000000000""#));
1104
1105 let solana_status = RelayerStatus::Solana {
1107 balance: "5000000000".to_string(),
1108 pending_transactions_count: 3,
1109 last_confirmed_transaction_timestamp: None,
1110 system_disabled: false,
1111 paused: true,
1112 };
1113
1114 let serialized = serde_json::to_string(&solana_status).unwrap();
1115 assert!(serialized.contains(r#""network_type":"solana""#));
1116 assert!(serialized.contains(r#""balance":"5000000000""#));
1117 assert!(serialized.contains(r#""paused":true"#));
1118
1119 let stellar_status = RelayerStatus::Stellar {
1121 balance: "1000000000".to_string(),
1122 pending_transactions_count: 2,
1123 last_confirmed_transaction_timestamp: Some("2024-01-01T12:00:00Z".to_string()),
1124 system_disabled: true,
1125 paused: false,
1126 sequence_number: "123456789".to_string(),
1127 };
1128
1129 let serialized = serde_json::to_string(&stellar_status).unwrap();
1130 assert!(serialized.contains(r#""network_type":"stellar""#));
1131 assert!(serialized.contains(r#""sequence_number":"123456789""#));
1132 assert!(serialized.contains(r#""system_disabled":true"#));
1133 }
1134
1135 #[test]
1136 fn test_relayer_status_deserialization() {
1137 let evm_json = r#"{
1139 "network_type": "evm",
1140 "balance": "1000000000000000000",
1141 "pending_transactions_count": 5,
1142 "last_confirmed_transaction_timestamp": "2024-01-01T00:00:00Z",
1143 "system_disabled": false,
1144 "paused": false,
1145 "nonce": "42"
1146 }"#;
1147
1148 let status: RelayerStatus = serde_json::from_str(evm_json).unwrap();
1149 if let RelayerStatus::Evm { nonce, balance, .. } = status {
1150 assert_eq!(nonce, "42");
1151 assert_eq!(balance, "1000000000000000000");
1152 } else {
1153 panic!("Expected EVM status");
1154 }
1155
1156 let solana_json = r#"{
1158 "network_type": "solana",
1159 "balance": "5000000000",
1160 "pending_transactions_count": 3,
1161 "last_confirmed_transaction_timestamp": null,
1162 "system_disabled": false,
1163 "paused": true
1164 }"#;
1165
1166 let status: RelayerStatus = serde_json::from_str(solana_json).unwrap();
1167 if let RelayerStatus::Solana {
1168 balance, paused, ..
1169 } = status
1170 {
1171 assert_eq!(balance, "5000000000");
1172 assert!(paused);
1173 } else {
1174 panic!("Expected Solana status");
1175 }
1176
1177 let stellar_json = r#"{
1179 "network_type": "stellar",
1180 "balance": "1000000000",
1181 "pending_transactions_count": 2,
1182 "last_confirmed_transaction_timestamp": "2024-01-01T12:00:00Z",
1183 "system_disabled": true,
1184 "paused": false,
1185 "sequence_number": "123456789"
1186 }"#;
1187
1188 let status: RelayerStatus = serde_json::from_str(stellar_json).unwrap();
1189 if let RelayerStatus::Stellar {
1190 sequence_number,
1191 system_disabled,
1192 ..
1193 } = status
1194 {
1195 assert_eq!(sequence_number, "123456789");
1196 assert!(system_disabled);
1197 } else {
1198 panic!("Expected Stellar status");
1199 }
1200 }
1201}