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