openzeppelin_relayer/domain/relayer/stellar/
stellar_relayer.rs

1use crate::constants::get_stellar_sponsored_transaction_validity_duration;
2use crate::domain::map_provider_error;
3use crate::domain::relayer::evm::create_error_response;
4use crate::services::stellar_dex::StellarDexService;
5/// This module defines the `StellarRelayer` struct and its associated functionality for
6/// interacting with Stellar networks. The `StellarRelayer` is responsible for managing
7/// transactions, synchronizing sequence numbers, and ensuring the relayer's state is
8/// consistent with the Stellar blockchain.
9///
10/// # Components
11///
12/// - `StellarRelayer`: The main struct that encapsulates the relayer's state and operations for Stellar.
13/// - `RelayerRepoModel`: Represents the relayer's data model.
14/// - `StellarProvider`: Provides blockchain interaction capabilities, such as fetching account details.
15/// - `TransactionCounterService`: Manages the sequence number for transactions to ensure correct ordering.
16/// - `JobProducer`: Produces jobs for processing transactions and sending notifications.
17///
18/// # Error Handling
19///
20/// The module uses the `RelayerError` enum to handle various errors that can occur during
21/// operations, such as provider errors, sequence synchronization failures, and transaction failures.
22///
23/// # Usage
24///
25/// To use the `StellarRelayer`, create an instance using the `new` method, providing the necessary
26/// components. Then, call the appropriate methods to process transactions and manage the relayer's state.
27use crate::{
28    constants::{STELLAR_SMALLEST_UNIT_NAME, STELLAR_STATUS_CHECK_INITIAL_DELAY_SECONDS},
29    domain::{
30        create_success_response, transaction::stellar::fetch_next_sequence_from_chain,
31        BalanceResponse, SignDataRequest, SignDataResponse, SignTransactionExternalResponse,
32        SignTransactionExternalResponseStellar, SignTransactionRequest, SignTypedDataRequest,
33    },
34    jobs::{JobProducerTrait, RelayerHealthCheck, TransactionRequest, TransactionStatusCheck},
35    models::{
36        produce_relayer_disabled_payload, DeletePendingTransactionsResponse, DisabledReason,
37        HealthCheckFailure, JsonRpcRequest, JsonRpcResponse, NetworkRepoModel, NetworkRpcRequest,
38        NetworkRpcResult, NetworkTransactionRequest, NetworkType, RelayerNetworkPolicy,
39        RelayerRepoModel, RelayerStatus, RelayerStellarPolicy, RepositoryError, RpcErrorCodes,
40        StellarAllowedTokensPolicy, StellarFeePaymentStrategy, StellarNetwork, StellarRpcRequest,
41        TransactionRepoModel, TransactionStatus,
42    },
43    repositories::{NetworkRepository, RelayerRepository, Repository, TransactionRepository},
44    services::{
45        provider::{StellarProvider, StellarProviderTrait},
46        signer::{StellarSignTrait, StellarSigner},
47        stellar_dex::StellarDexServiceTrait,
48        TransactionCounterService, TransactionCounterServiceTrait,
49    },
50    utils::calculate_scheduled_timestamp,
51};
52use async_trait::async_trait;
53use eyre::Result;
54use futures::future::try_join_all;
55use std::sync::Arc;
56use tracing::{debug, info, warn};
57
58use crate::domain::relayer::stellar::xdr_utils::parse_transaction_xdr;
59use crate::domain::relayer::{Relayer, RelayerError, StellarRelayerDexTrait};
60use crate::domain::transaction::stellar::token::get_token_metadata;
61use crate::domain::transaction::stellar::StellarTransactionValidator;
62
63/// Dependencies container for `StellarRelayer` construction.
64pub struct StellarRelayerDependencies<RR, NR, TR, J, TCS>
65where
66    RR: Repository<RelayerRepoModel, String> + RelayerRepository + Send + Sync + 'static,
67    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
68    TR: Repository<TransactionRepoModel, String> + TransactionRepository + Send + Sync + 'static,
69    J: JobProducerTrait + Send + Sync + 'static,
70    TCS: TransactionCounterServiceTrait + Send + Sync + 'static,
71{
72    pub relayer_repository: Arc<RR>,
73    pub network_repository: Arc<NR>,
74    pub transaction_repository: Arc<TR>,
75    pub transaction_counter_service: Arc<TCS>,
76    pub job_producer: Arc<J>,
77}
78
79impl<RR, NR, TR, J, TCS> StellarRelayerDependencies<RR, NR, TR, J, TCS>
80where
81    RR: Repository<RelayerRepoModel, String> + RelayerRepository + Send + Sync + 'static,
82    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
83    TR: Repository<TransactionRepoModel, String> + TransactionRepository + Send + Sync + 'static,
84    J: JobProducerTrait + Send + Sync,
85    TCS: TransactionCounterServiceTrait + Send + Sync + 'static,
86{
87    /// Creates a new dependencies container for `StellarRelayer`.
88    ///
89    /// # Arguments
90    ///
91    /// * `relayer_repository` - Repository for managing relayer model persistence
92    /// * `network_repository` - Repository for accessing network configuration data (RPC URLs, chain settings)
93    /// * `transaction_repository` - Repository for storing and retrieving transaction models
94    /// * `transaction_counter_service` - Service for managing sequence numbers to ensure proper transaction ordering
95    /// * `job_producer` - Service for creating background jobs for transaction processing and notifications
96    ///
97    /// # Returns
98    ///
99    /// Returns a new `StellarRelayerDependencies` instance containing all provided dependencies.
100    pub fn new(
101        relayer_repository: Arc<RR>,
102        network_repository: Arc<NR>,
103        transaction_repository: Arc<TR>,
104        transaction_counter_service: Arc<TCS>,
105        job_producer: Arc<J>,
106    ) -> Self {
107        Self {
108            relayer_repository,
109            network_repository,
110            transaction_repository,
111            transaction_counter_service,
112            job_producer,
113        }
114    }
115}
116
117#[allow(dead_code)]
118pub struct StellarRelayer<P, RR, NR, TR, J, TCS, S, D>
119where
120    P: StellarProviderTrait + Send + Sync + 'static,
121    RR: Repository<RelayerRepoModel, String> + RelayerRepository + Send + Sync + 'static,
122    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
123    TR: Repository<TransactionRepoModel, String> + TransactionRepository + Send + Sync + 'static,
124    J: JobProducerTrait + Send + Sync + 'static,
125    TCS: TransactionCounterServiceTrait + Send + Sync + 'static,
126    S: StellarSignTrait + Send + Sync + 'static,
127    D: StellarDexServiceTrait + Send + Sync + 'static,
128{
129    pub(crate) relayer: RelayerRepoModel,
130    signer: Arc<S>,
131    pub(crate) network: StellarNetwork,
132    pub(crate) provider: P,
133    pub(crate) relayer_repository: Arc<RR>,
134    network_repository: Arc<NR>,
135    transaction_repository: Arc<TR>,
136    transaction_counter_service: Arc<TCS>,
137    pub(crate) job_producer: Arc<J>,
138    pub(crate) dex_service: Arc<D>,
139}
140
141pub type DefaultStellarRelayer<J, TR, NR, RR, TCR> = StellarRelayer<
142    StellarProvider,
143    RR,
144    NR,
145    TR,
146    J,
147    TransactionCounterService<TCR>,
148    StellarSigner,
149    StellarDexService<StellarProvider, StellarSigner>,
150>;
151
152impl<P, RR, NR, TR, J, TCS, S, D> StellarRelayer<P, RR, NR, TR, J, TCS, S, D>
153where
154    P: StellarProviderTrait + Send + Sync,
155    RR: Repository<RelayerRepoModel, String> + RelayerRepository + Send + Sync + 'static,
156    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
157    TR: Repository<TransactionRepoModel, String> + TransactionRepository + Send + Sync + 'static,
158    J: JobProducerTrait + Send + Sync + 'static,
159    TCS: TransactionCounterServiceTrait + Send + Sync + 'static,
160    S: StellarSignTrait + Send + Sync + 'static,
161    D: StellarDexServiceTrait + Send + Sync + 'static,
162{
163    /// Creates a new `StellarRelayer` instance.
164    ///
165    /// This constructor initializes a new Stellar relayer with the provided configuration,
166    /// provider, and dependencies. It validates the network configuration and sets up
167    /// all necessary components for transaction processing.
168    ///
169    /// # Arguments
170    ///
171    /// * `relayer` - The relayer model containing configuration like ID, address, network name, and policies
172    /// * `signer` - The Stellar signer for signing transactions
173    /// * `provider` - The Stellar provider implementation for blockchain interactions (account queries, transaction submission)
174    /// * `dependencies` - Container with all required repositories and services (see [`StellarRelayerDependencies`])
175    /// * `dex_service` - The DEX service implementation for swap operations
176    ///
177    /// # Returns
178    ///
179    /// * `Ok(StellarRelayer)` - Successfully initialized relayer ready for operation
180    /// * `Err(RelayerError)` - If initialization fails due to configuration or validation errors
181    #[allow(clippy::too_many_arguments)]
182    pub async fn new(
183        relayer: RelayerRepoModel,
184        signer: Arc<S>,
185        provider: P,
186        dependencies: StellarRelayerDependencies<RR, NR, TR, J, TCS>,
187        dex_service: Arc<D>,
188    ) -> Result<Self, RelayerError> {
189        let network_repo = dependencies
190            .network_repository
191            .get_by_name(NetworkType::Stellar, &relayer.network)
192            .await
193            .ok()
194            .flatten()
195            .ok_or_else(|| {
196                RelayerError::NetworkConfiguration(format!("Network {} not found", relayer.network))
197            })?;
198
199        let network = StellarNetwork::try_from(network_repo.clone())?;
200
201        Ok(Self {
202            relayer,
203            signer,
204            network,
205            provider,
206            relayer_repository: dependencies.relayer_repository,
207            network_repository: dependencies.network_repository,
208            transaction_repository: dependencies.transaction_repository,
209            transaction_counter_service: dependencies.transaction_counter_service,
210            job_producer: dependencies.job_producer,
211            dex_service,
212        })
213    }
214
215    async fn sync_sequence(&self) -> Result<(), RelayerError> {
216        info!(
217            "Syncing sequence for relayer: {} ({})",
218            self.relayer.id, self.relayer.address
219        );
220
221        let next = fetch_next_sequence_from_chain(&self.provider, &self.relayer.address)
222            .await
223            .map_err(RelayerError::ProviderError)?;
224
225        info!(
226            "Setting next sequence {} for relayer {}",
227            next, self.relayer.id
228        );
229        self.transaction_counter_service
230            .set(next)
231            .await
232            .map_err(RelayerError::from)?;
233        Ok(())
234    }
235
236    /// Populates the allowed tokens metadata for the Stellar relayer policy.
237    ///
238    /// This method checks whether allowed tokens have been configured in the relayer's policy.
239    /// If allowed tokens are provided, it concurrently fetches token metadata for each token,
240    /// determines the token kind (Native, Classic, or Contract), and populates metadata including
241    /// decimals and canonical asset ID. The updated policy is then stored in the repository.
242    ///
243    /// If no allowed tokens are specified, it logs an informational message and returns the policy
244    /// unchanged.
245    async fn populate_allowed_tokens_metadata(&self) -> Result<RelayerStellarPolicy, RelayerError> {
246        let mut policy = self.relayer.policies.get_stellar_policy();
247        // Check if allowed_tokens is specified; if not, return the policy unchanged.
248        let allowed_tokens = match policy.allowed_tokens.as_ref() {
249            Some(tokens) if !tokens.is_empty() => tokens,
250            _ => {
251                info!("No allowed tokens specified; skipping token metadata population.");
252                return Ok(policy);
253            }
254        };
255
256        let token_metadata_futures = allowed_tokens.iter().map(|token| {
257            let asset_id = token.asset.clone();
258            let provider = &self.provider;
259            async move {
260                let metadata = get_token_metadata(provider, &asset_id)
261                    .await
262                    .map_err(RelayerError::from)?;
263
264                Ok::<StellarAllowedTokensPolicy, RelayerError>(StellarAllowedTokensPolicy {
265                    asset: asset_id,
266                    metadata: Some(metadata),
267                    max_allowed_fee: token.max_allowed_fee,
268                    swap_config: token.swap_config.clone(),
269                })
270            }
271        });
272
273        let updated_allowed_tokens = try_join_all(token_metadata_futures).await?;
274
275        policy.allowed_tokens = Some(updated_allowed_tokens.clone());
276
277        self.relayer_repository
278            .update_policy(
279                self.relayer.id.clone(),
280                RelayerNetworkPolicy::Stellar(policy.clone()),
281            )
282            .await?;
283
284        Ok(policy)
285    }
286
287    /// Migrates fee_payment_strategy policy for older relayers that don't have it set.
288    ///
289    /// This migration is needed for relayers that were created before `fee_payment_strategy`
290    /// became a required policy. For relayers persisted in Redis storage, this ensures
291    /// backward compatibility by setting the policy to `Relayer` (the old default behavior).
292    ///
293    /// In-memory relayers don't need this migration as they are recreated from config.json
294    /// on startup, which would have the policy set if using a newer version.
295    async fn migrate_fee_payment_strategy_if_needed(&self) -> Result<(), RelayerError> {
296        // Only migrate if using persistent storage (Redis)
297        // In-memory relayers are recreated from config.json on startup
298        if !self.relayer_repository.is_persistent_storage() {
299            debug!(
300                relayer_id = %self.relayer.id,
301                "Skipping migration: using in-memory storage"
302            );
303            return Ok(());
304        }
305
306        let policy = self.relayer.policies.get_stellar_policy();
307
308        // If fee_payment_strategy is already set, no migration needed
309        if policy.fee_payment_strategy.is_some() {
310            return Ok(());
311        }
312
313        // Migration needed: fee_payment_strategy is missing
314        info!(
315            relayer_id = %self.relayer.id,
316            "Migrating Stellar relayer: setting fee_payment_strategy to 'Relayer' (old default behavior)"
317        );
318
319        // Create updated policy with fee_payment_strategy set to Relayer
320        let mut updated_policy = policy;
321        updated_policy.fee_payment_strategy = Some(StellarFeePaymentStrategy::Relayer);
322
323        // Update the relayer in the repository
324        self.relayer_repository
325            .update_policy(
326                self.relayer.id.clone(),
327                RelayerNetworkPolicy::Stellar(updated_policy),
328            )
329            .await
330            .map_err(|e| {
331                RelayerError::PolicyConfigurationError(format!(
332                    "Failed to migrate fee_payment_strategy policy: {e}"
333                ))
334            })?;
335
336        debug!(
337            relayer_id = %self.relayer.id,
338            "Successfully migrated fee_payment_strategy policy"
339        );
340
341        Ok(())
342    }
343
344    /// Checks the relayer's XLM balance and triggers token swap if it falls below the
345    /// specified threshold. Only proceeds with swap if balance is below the configured
346    /// min_balance_threshold.
347    async fn check_balance_and_trigger_token_swap_if_needed(&self) -> Result<(), RelayerError> {
348        let policy = self.relayer.policies.get_stellar_policy();
349
350        // Check if swap config exists
351        let swap_config = match policy.get_swap_config() {
352            Some(config) => config,
353            None => {
354                debug!(
355                    relayer_id = %self.relayer.id,
356                    "No swap configuration specified; skipping balance check"
357                );
358                return Ok(());
359            }
360        };
361
362        // Early return if no threshold is configured (mirrors Solana logic)
363        let threshold = match swap_config.min_balance_threshold {
364            Some(threshold) => threshold,
365            None => {
366                debug!(
367                    relayer_id = %self.relayer.id,
368                    "No swap min balance threshold specified; skipping validation"
369                );
370                return Ok(());
371            }
372        };
373
374        // Get balance only when threshold is configured
375        let balance_response = self.get_balance().await?;
376        let current_balance = u64::try_from(balance_response.balance).map_err(|_| {
377            RelayerError::Internal("Account balance exceeds u64 maximum value".to_string())
378        })?;
379
380        // Only trigger swap if balance is below threshold
381        if current_balance < threshold {
382            debug!(
383                relayer_id = %self.relayer.id,
384                balance = current_balance,
385                threshold = threshold,
386                "XLM balance is below threshold, triggering token swap"
387            );
388
389            let _swap_results = self
390                .handle_token_swap_request(self.relayer.id.clone())
391                .await?;
392        } else {
393            debug!(
394                relayer_id = %self.relayer.id,
395                balance = current_balance,
396                threshold = threshold,
397                "XLM balance is above threshold, no swap needed"
398            );
399        }
400
401        Ok(())
402    }
403}
404
405#[async_trait]
406impl<P, RR, NR, TR, J, TCS, S, D> Relayer for StellarRelayer<P, RR, NR, TR, J, TCS, S, D>
407where
408    P: StellarProviderTrait + Send + Sync + 'static,
409    D: StellarDexServiceTrait + Send + Sync + 'static,
410    RR: Repository<RelayerRepoModel, String> + RelayerRepository + Send + Sync + 'static,
411    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
412    TR: Repository<TransactionRepoModel, String> + TransactionRepository + Send + Sync + 'static,
413    J: JobProducerTrait + Send + Sync + 'static,
414    TCS: TransactionCounterServiceTrait + Send + Sync + 'static,
415    S: StellarSignTrait + Send + Sync + 'static,
416{
417    async fn process_transaction_request(
418        &self,
419        network_transaction: NetworkTransactionRequest,
420    ) -> Result<TransactionRepoModel, RelayerError> {
421        let network_model = self
422            .network_repository
423            .get_by_name(NetworkType::Stellar, &self.relayer.network)
424            .await?
425            .ok_or_else(|| {
426                RelayerError::NetworkConfiguration(format!(
427                    "Network {} not found",
428                    self.relayer.network
429                ))
430            })?;
431        let transaction =
432            TransactionRepoModel::try_from((&network_transaction, &self.relayer, &network_model))?;
433
434        self.transaction_repository
435            .create(transaction.clone())
436            .await
437            .map_err(|e| RepositoryError::TransactionFailure(e.to_string()))?;
438
439        self.job_producer
440            .produce_transaction_request_job(
441                TransactionRequest::new(transaction.id.clone(), transaction.relayer_id.clone()),
442                None,
443            )
444            .await?;
445
446        self.job_producer
447            .produce_check_transaction_status_job(
448                TransactionStatusCheck::new(
449                    transaction.id.clone(),
450                    transaction.relayer_id.clone(),
451                    NetworkType::Stellar,
452                ),
453                Some(calculate_scheduled_timestamp(
454                    STELLAR_STATUS_CHECK_INITIAL_DELAY_SECONDS,
455                )),
456            )
457            .await?;
458
459        Ok(transaction)
460    }
461
462    async fn get_balance(&self) -> Result<BalanceResponse, RelayerError> {
463        let account_entry = self
464            .provider
465            .get_account(&self.relayer.address)
466            .await
467            .map_err(|e| {
468                RelayerError::ProviderError(format!("Failed to fetch account for balance: {e}"))
469            })?;
470
471        Ok(BalanceResponse {
472            balance: account_entry.balance as u128,
473            unit: STELLAR_SMALLEST_UNIT_NAME.to_string(),
474        })
475    }
476
477    async fn get_status(&self) -> Result<RelayerStatus, RelayerError> {
478        let relayer_model = &self.relayer;
479
480        let account_entry = self
481            .provider
482            .get_account(&relayer_model.address)
483            .await
484            .map_err(|e| {
485                RelayerError::ProviderError(format!("Failed to get account details: {e}"))
486            })?;
487
488        let sequence_number_str = account_entry.seq_num.0.to_string();
489
490        let balance_response = self.get_balance().await?;
491
492        let pending_statuses = [TransactionStatus::Pending, TransactionStatus::Submitted];
493        let pending_transactions = self
494            .transaction_repository
495            .find_by_status(&relayer_model.id, &pending_statuses[..])
496            .await
497            .map_err(RelayerError::from)?;
498        let pending_transactions_count = pending_transactions.len() as u64;
499
500        let confirmed_statuses = [TransactionStatus::Confirmed];
501        let confirmed_transactions = self
502            .transaction_repository
503            .find_by_status(&relayer_model.id, &confirmed_statuses[..])
504            .await
505            .map_err(RelayerError::from)?;
506
507        let last_confirmed_transaction_timestamp = confirmed_transactions
508            .iter()
509            .filter_map(|tx| tx.confirmed_at.as_ref())
510            .max()
511            .cloned();
512
513        Ok(RelayerStatus::Stellar {
514            balance: balance_response.balance.to_string(),
515            pending_transactions_count,
516            last_confirmed_transaction_timestamp,
517            system_disabled: relayer_model.system_disabled,
518            paused: relayer_model.paused,
519            sequence_number: sequence_number_str,
520        })
521    }
522
523    async fn delete_pending_transactions(
524        &self,
525    ) -> Result<DeletePendingTransactionsResponse, RelayerError> {
526        println!("Stellar delete_pending_transactions...");
527        Ok(DeletePendingTransactionsResponse {
528            queued_for_cancellation_transaction_ids: vec![],
529            failed_to_queue_transaction_ids: vec![],
530            total_processed: 0,
531        })
532    }
533
534    async fn sign_data(&self, _request: SignDataRequest) -> Result<SignDataResponse, RelayerError> {
535        Err(RelayerError::NotSupported(
536            "Signing data not supported for Stellar".to_string(),
537        ))
538    }
539
540    async fn sign_typed_data(
541        &self,
542        _request: SignTypedDataRequest,
543    ) -> Result<SignDataResponse, RelayerError> {
544        Err(RelayerError::NotSupported(
545            "Signing typed data not supported for Stellar".to_string(),
546        ))
547    }
548
549    async fn rpc(
550        &self,
551        request: JsonRpcRequest<NetworkRpcRequest>,
552    ) -> Result<JsonRpcResponse<NetworkRpcResult>, RelayerError> {
553        let JsonRpcRequest { id, params, .. } = request;
554        let stellar_request = match params {
555            NetworkRpcRequest::Stellar(stellar_req) => stellar_req,
556            _ => {
557                return Ok(create_error_response(
558                    id.clone(),
559                    RpcErrorCodes::INVALID_PARAMS,
560                    "Invalid params",
561                    "Expected Stellar network request",
562                ))
563            }
564        };
565
566        // Parse method and params from the Stellar request (single unified variant)
567        let (method, params_json) = match stellar_request {
568            StellarRpcRequest::RawRpcRequest { method, params } => (method, params),
569        };
570
571        match self
572            .provider
573            .raw_request_dyn(&method, params_json, id.clone())
574            .await
575        {
576            Ok(result_value) => Ok(create_success_response(id.clone(), result_value)),
577            Err(provider_error) => {
578                let (error_code, error_message) = map_provider_error(&provider_error);
579                Ok(create_error_response(
580                    id.clone(),
581                    error_code,
582                    error_message,
583                    &provider_error.to_string(),
584                ))
585            }
586        }
587    }
588
589    async fn validate_min_balance(&self) -> Result<(), RelayerError> {
590        Ok(())
591    }
592
593    async fn initialize_relayer(&self) -> Result<(), RelayerError> {
594        debug!("initializing Stellar relayer {}", self.relayer.id);
595
596        // Migration: Check if relayer needs fee_payment_strategy migration
597        // Older relayers persisted in Redis may not have this policy set.
598        // We automatically set it to "Relayer" (the old default behavior) for backward compatibility.
599        self.migrate_fee_payment_strategy_if_needed().await?;
600
601        // Populate model with allowed token metadata and update DB entry
602        // Error will be thrown if any of the tokens are not found
603        self.populate_allowed_tokens_metadata().await.map_err(|e| {
604            RelayerError::PolicyConfigurationError(format!(
605                "Error while processing allowed tokens policy: {e}"
606            ))
607        })?;
608
609        match self.check_health().await {
610            Ok(_) => {
611                // All checks passed
612                if self.relayer.system_disabled {
613                    // Silently re-enable if was disabled (startup, not recovery)
614                    self.relayer_repository
615                        .enable_relayer(self.relayer.id.clone())
616                        .await?;
617                }
618            }
619            Err(failures) => {
620                // Health checks failed
621                let reason = DisabledReason::from_health_failures(failures).unwrap_or_else(|| {
622                    DisabledReason::SequenceSyncFailed("Unknown error".to_string())
623                });
624
625                warn!(reason = %reason, "disabling relayer");
626                let updated_relayer = self
627                    .relayer_repository
628                    .disable_relayer(self.relayer.id.clone(), reason.clone())
629                    .await?;
630
631                // Send notification if configured
632                if let Some(notification_id) = &self.relayer.notification_id {
633                    self.job_producer
634                        .produce_send_notification_job(
635                            produce_relayer_disabled_payload(
636                                notification_id,
637                                &updated_relayer,
638                                &reason.safe_description(),
639                            ),
640                            None,
641                        )
642                        .await?;
643                }
644
645                // Schedule health check to try re-enabling the relayer after 10 seconds
646                self.job_producer
647                    .produce_relayer_health_check_job(
648                        RelayerHealthCheck::new(self.relayer.id.clone()),
649                        Some(calculate_scheduled_timestamp(10)),
650                    )
651                    .await?;
652            }
653        }
654        debug!(
655            "Stellar relayer initialized successfully: {}",
656            self.relayer.id
657        );
658        Ok(())
659    }
660
661    async fn check_health(&self) -> Result<(), Vec<HealthCheckFailure>> {
662        debug!(
663            "running health checks for Stellar relayer {}",
664            self.relayer.id
665        );
666
667        let mut failures = Vec::new();
668
669        // Check sequence synchronization
670        match self.sync_sequence().await {
671            Ok(_) => {
672                debug!(
673                    "sequence sync passed for Stellar relayer {}",
674                    self.relayer.id
675                );
676            }
677            Err(e) => {
678                let reason = HealthCheckFailure::SequenceSyncFailed(e.to_string());
679                warn!("sequence sync failed: {:?}", reason);
680                failures.push(reason);
681            }
682        }
683
684        // Check balance and trigger token swap if fee_payment_strategy is User
685        // Note: Swap failures are logged but don't cause health check failures
686        // to avoid disabling the relayer due to transient swap issues
687        let policy = self.relayer.policies.get_stellar_policy();
688        if matches!(
689            policy.fee_payment_strategy,
690            Some(StellarFeePaymentStrategy::User)
691        ) {
692            debug!(
693                "checking balance and attempting token swap for user fee payment strategy relayer {}",
694                self.relayer.id
695            );
696            if let Err(e) = self.check_balance_and_trigger_token_swap_if_needed().await {
697                warn!(
698                    relayer_id = %self.relayer.id,
699                    error = %e,
700                    "Balance check or token swap failed, but not treating as health check failure"
701                );
702            } else {
703                debug!(
704                    "balance check and token swap completed for Stellar relayer {}",
705                    self.relayer.id
706                );
707            }
708        }
709
710        if failures.is_empty() {
711            debug!(
712                "all health checks passed for Stellar relayer {}",
713                self.relayer.id
714            );
715            Ok(())
716        } else {
717            warn!(
718                "health checks failed for Stellar relayer {}: {:?}",
719                self.relayer.id, failures
720            );
721            Err(failures)
722        }
723    }
724
725    async fn sign_transaction(
726        &self,
727        request: &SignTransactionRequest,
728    ) -> Result<SignTransactionExternalResponse, RelayerError> {
729        let stellar_req = match request {
730            SignTransactionRequest::Stellar(req) => req,
731            _ => {
732                return Err(RelayerError::NotSupported(
733                    "Invalid request type for Stellar relayer".to_string(),
734                ))
735            }
736        };
737
738        let policy = self.relayer.policies.get_stellar_policy();
739        let user_pays_fee = matches!(
740            policy.fee_payment_strategy,
741            Some(StellarFeePaymentStrategy::User)
742        );
743
744        // For user-paid fees, validate transaction before signing
745        if user_pays_fee {
746            // Parse the transaction XDR
747            let envelope = parse_transaction_xdr(&stellar_req.unsigned_xdr, false)
748                .map_err(|e| RelayerError::ValidationError(format!("Failed to parse XDR: {e}")))?;
749
750            // Comprehensive validation for user fee payment transactions when signing
751            // This validates: transaction structure, fee payments, allowed tokens, payment amounts, and time bounds
752            StellarTransactionValidator::validate_user_fee_payment_transaction(
753                &envelope,
754                &self.relayer.address,
755                &policy,
756                &self.provider,
757                self.dex_service.as_ref(),
758                Some(get_stellar_sponsored_transaction_validity_duration()), // Enforce 1 minute max validity for signing flow
759            )
760            .await
761            .map_err(|e| {
762                RelayerError::ValidationError(format!("Failed to validate transaction: {e}"))
763            })?;
764        }
765
766        // Use the signer's sign_xdr_transaction method
767        let response = self
768            .signer
769            .sign_xdr_transaction(&stellar_req.unsigned_xdr, &self.network.passphrase)
770            .await
771            .map_err(RelayerError::SignerError)?;
772
773        // Convert DecoratedSignature to base64 string
774        let signature_bytes = &response.signature.signature.0;
775        let signature_string =
776            base64::Engine::encode(&base64::engine::general_purpose::STANDARD, signature_bytes);
777
778        Ok(SignTransactionExternalResponse::Stellar(
779            SignTransactionExternalResponseStellar {
780                signed_xdr: response.signed_xdr,
781                signature: signature_string,
782            },
783        ))
784    }
785}
786
787#[cfg(test)]
788mod tests {
789    use super::*;
790    use crate::{
791        config::{NetworkConfigCommon, StellarNetworkConfig},
792        constants::STELLAR_SMALLEST_UNIT_NAME,
793        domain::{SignTransactionRequestStellar, SignXdrTransactionResponseStellar},
794        jobs::MockJobProducerTrait,
795        models::{
796            NetworkConfigData, NetworkRepoModel, NetworkType, RelayerNetworkPolicy,
797            RelayerRepoModel, RelayerStellarPolicy, SignerError,
798        },
799        repositories::{
800            InMemoryNetworkRepository, MockRelayerRepository, MockTransactionRepository,
801        },
802        services::{
803            provider::{MockStellarProviderTrait, ProviderError},
804            signer::MockStellarSignTrait,
805            stellar_dex::MockStellarDexServiceTrait,
806            MockTransactionCounterServiceTrait,
807        },
808    };
809    use mockall::predicate::*;
810    use soroban_rs::xdr::{
811        AccountEntry, AccountEntryExt, AccountId, DecoratedSignature, PublicKey, SequenceNumber,
812        Signature, SignatureHint, String32, Thresholds, Uint256, VecM,
813    };
814    use std::future::ready;
815    use std::sync::Arc;
816
817    /// Helper function to create a mock DEX service for testing
818    fn create_mock_dex_service() -> Arc<MockStellarDexServiceTrait> {
819        let mut mock_dex = MockStellarDexServiceTrait::new();
820        mock_dex.expect_supported_asset_types().returning(|| {
821            use crate::services::stellar_dex::AssetType;
822            std::collections::HashSet::from([AssetType::Native, AssetType::Classic])
823        });
824        Arc::new(mock_dex)
825    }
826
827    /// Test context structure to manage test dependencies
828    struct TestCtx {
829        relayer_model: RelayerRepoModel,
830        network_repository: Arc<InMemoryNetworkRepository>,
831    }
832
833    impl Default for TestCtx {
834        fn default() -> Self {
835            let network_repository = Arc::new(InMemoryNetworkRepository::new());
836
837            let relayer_model = RelayerRepoModel {
838                id: "test-relayer-id".to_string(),
839                name: "Test Relayer".to_string(),
840                network: "testnet".to_string(),
841                paused: false,
842                network_type: NetworkType::Stellar,
843                signer_id: "signer-id".to_string(),
844                policies: RelayerNetworkPolicy::Stellar(RelayerStellarPolicy::default()),
845                address: "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF".to_string(),
846                notification_id: Some("notification-id".to_string()),
847                system_disabled: false,
848                custom_rpc_urls: None,
849                ..Default::default()
850            };
851
852            TestCtx {
853                relayer_model,
854                network_repository,
855            }
856        }
857    }
858
859    impl TestCtx {
860        async fn setup_network(&self) {
861            let test_network = NetworkRepoModel {
862                id: "stellar:testnet".to_string(),
863                name: "testnet".to_string(),
864                network_type: NetworkType::Stellar,
865                config: NetworkConfigData::Stellar(StellarNetworkConfig {
866                    common: NetworkConfigCommon {
867                        network: "testnet".to_string(),
868                        from: None,
869                        rpc_urls: Some(vec!["https://horizon-testnet.stellar.org".to_string()]),
870                        explorer_urls: None,
871                        average_blocktime_ms: Some(5000),
872                        is_testnet: Some(true),
873                        tags: None,
874                    },
875                    passphrase: Some("Test SDF Network ; September 2015".to_string()),
876                    horizon_url: Some("https://horizon-testnet.stellar.org".to_string()),
877                }),
878            };
879
880            self.network_repository.create(test_network).await.unwrap();
881        }
882    }
883
884    #[tokio::test]
885    async fn test_sync_sequence_success() {
886        let ctx = TestCtx::default();
887        ctx.setup_network().await;
888        let relayer_model = ctx.relayer_model.clone();
889        let mut provider = MockStellarProviderTrait::new();
890        provider
891            .expect_get_account()
892            .with(eq(relayer_model.address.clone()))
893            .returning(|_| {
894                Box::pin(async {
895                    Ok(AccountEntry {
896                        account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
897                        balance: 0,
898                        ext: AccountEntryExt::V0,
899                        flags: 0,
900                        home_domain: String32::default(),
901                        inflation_dest: None,
902                        seq_num: SequenceNumber(5),
903                        num_sub_entries: 0,
904                        signers: VecM::default(),
905                        thresholds: Thresholds([0, 0, 0, 0]),
906                    })
907                })
908            });
909        let mut counter = MockTransactionCounterServiceTrait::new();
910        counter
911            .expect_set()
912            .with(eq(6u64))
913            .returning(|_| Box::pin(async { Ok(()) }));
914        let relayer_repo = MockRelayerRepository::new();
915        let tx_repo = MockTransactionRepository::new();
916        let job_producer = MockJobProducerTrait::new();
917        let signer = Arc::new(MockStellarSignTrait::new());
918        let dex_service = create_mock_dex_service();
919
920        let relayer = StellarRelayer::new(
921            relayer_model.clone(),
922            signer,
923            provider,
924            StellarRelayerDependencies::new(
925                Arc::new(relayer_repo),
926                ctx.network_repository.clone(),
927                Arc::new(tx_repo),
928                Arc::new(counter),
929                Arc::new(job_producer),
930            ),
931            dex_service,
932        )
933        .await
934        .unwrap();
935
936        let result = relayer.sync_sequence().await;
937        assert!(result.is_ok());
938    }
939
940    #[tokio::test]
941    async fn test_sync_sequence_provider_error() {
942        let ctx = TestCtx::default();
943        ctx.setup_network().await;
944        let relayer_model = ctx.relayer_model.clone();
945        let mut provider = MockStellarProviderTrait::new();
946        provider
947            .expect_get_account()
948            .with(eq(relayer_model.address.clone()))
949            .returning(|_| Box::pin(async { Err(ProviderError::Other("fail".to_string())) }));
950        let counter = MockTransactionCounterServiceTrait::new();
951        let relayer_repo = MockRelayerRepository::new();
952        let tx_repo = MockTransactionRepository::new();
953        let job_producer = MockJobProducerTrait::new();
954        let signer = Arc::new(MockStellarSignTrait::new());
955        let dex_service = create_mock_dex_service();
956
957        let relayer = StellarRelayer::new(
958            relayer_model.clone(),
959            signer,
960            provider,
961            StellarRelayerDependencies::new(
962                Arc::new(relayer_repo),
963                ctx.network_repository.clone(),
964                Arc::new(tx_repo),
965                Arc::new(counter),
966                Arc::new(job_producer),
967            ),
968            dex_service,
969        )
970        .await
971        .unwrap();
972
973        let result = relayer.sync_sequence().await;
974        assert!(matches!(result, Err(RelayerError::ProviderError(_))));
975    }
976
977    #[tokio::test]
978    async fn test_get_status_success_stellar() {
979        let ctx = TestCtx::default();
980        ctx.setup_network().await;
981        let relayer_model = ctx.relayer_model.clone();
982        let mut provider_mock = MockStellarProviderTrait::new();
983        let mut tx_repo_mock = MockTransactionRepository::new();
984        let relayer_repo_mock = MockRelayerRepository::new();
985        let job_producer_mock = MockJobProducerTrait::new();
986        let counter_mock = MockTransactionCounterServiceTrait::new();
987
988        provider_mock.expect_get_account().times(2).returning(|_| {
989            Box::pin(ready(Ok(AccountEntry {
990                account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
991                balance: 10000000,
992                seq_num: SequenceNumber(12345),
993                ext: AccountEntryExt::V0,
994                flags: 0,
995                home_domain: String32::default(),
996                inflation_dest: None,
997                num_sub_entries: 0,
998                signers: VecM::default(),
999                thresholds: Thresholds([0, 0, 0, 0]),
1000            })))
1001        });
1002
1003        tx_repo_mock
1004            .expect_find_by_status()
1005            .withf(|relayer_id, statuses| {
1006                relayer_id == "test-relayer-id"
1007                    && statuses == [TransactionStatus::Pending, TransactionStatus::Submitted]
1008            })
1009            .returning(|_, _| Ok(vec![]) as Result<Vec<TransactionRepoModel>, RepositoryError>)
1010            .once();
1011
1012        let confirmed_tx = TransactionRepoModel {
1013            id: "tx1_stellar".to_string(),
1014            relayer_id: relayer_model.id.clone(),
1015            status: TransactionStatus::Confirmed,
1016            confirmed_at: Some("2023-02-01T12:00:00Z".to_string()),
1017            ..TransactionRepoModel::default()
1018        };
1019        tx_repo_mock
1020            .expect_find_by_status()
1021            .withf(|relayer_id, statuses| {
1022                relayer_id == "test-relayer-id" && statuses == [TransactionStatus::Confirmed]
1023            })
1024            .returning(move |_, _| {
1025                Ok(vec![confirmed_tx.clone()]) as Result<Vec<TransactionRepoModel>, RepositoryError>
1026            })
1027            .once();
1028        let signer = Arc::new(MockStellarSignTrait::new());
1029        let dex_service = create_mock_dex_service();
1030
1031        let stellar_relayer = StellarRelayer::new(
1032            relayer_model.clone(),
1033            signer,
1034            provider_mock,
1035            StellarRelayerDependencies::new(
1036                Arc::new(relayer_repo_mock),
1037                ctx.network_repository.clone(),
1038                Arc::new(tx_repo_mock),
1039                Arc::new(counter_mock),
1040                Arc::new(job_producer_mock),
1041            ),
1042            dex_service,
1043        )
1044        .await
1045        .unwrap();
1046
1047        let status = stellar_relayer.get_status().await.unwrap();
1048
1049        match status {
1050            RelayerStatus::Stellar {
1051                balance,
1052                pending_transactions_count,
1053                last_confirmed_transaction_timestamp,
1054                system_disabled,
1055                paused,
1056                sequence_number,
1057            } => {
1058                assert_eq!(balance, "10000000");
1059                assert_eq!(pending_transactions_count, 0);
1060                assert_eq!(
1061                    last_confirmed_transaction_timestamp,
1062                    Some("2023-02-01T12:00:00Z".to_string())
1063                );
1064                assert_eq!(system_disabled, relayer_model.system_disabled);
1065                assert_eq!(paused, relayer_model.paused);
1066                assert_eq!(sequence_number, "12345");
1067            }
1068            _ => panic!("Expected Stellar RelayerStatus"),
1069        }
1070    }
1071
1072    #[tokio::test]
1073    async fn test_get_status_stellar_provider_error() {
1074        let ctx = TestCtx::default();
1075        ctx.setup_network().await;
1076        let relayer_model = ctx.relayer_model.clone();
1077        let mut provider_mock = MockStellarProviderTrait::new();
1078        let tx_repo_mock = MockTransactionRepository::new();
1079        let relayer_repo_mock = MockRelayerRepository::new();
1080        let job_producer_mock = MockJobProducerTrait::new();
1081        let counter_mock = MockTransactionCounterServiceTrait::new();
1082
1083        provider_mock
1084            .expect_get_account()
1085            .with(eq(relayer_model.address.clone()))
1086            .returning(|_| {
1087                Box::pin(async { Err(ProviderError::Other("Stellar provider down".to_string())) })
1088            });
1089        let signer = Arc::new(MockStellarSignTrait::new());
1090        let dex_service = create_mock_dex_service();
1091
1092        let stellar_relayer = StellarRelayer::new(
1093            relayer_model.clone(),
1094            signer,
1095            provider_mock,
1096            StellarRelayerDependencies::new(
1097                Arc::new(relayer_repo_mock),
1098                ctx.network_repository.clone(),
1099                Arc::new(tx_repo_mock),
1100                Arc::new(counter_mock),
1101                Arc::new(job_producer_mock),
1102            ),
1103            dex_service,
1104        )
1105        .await
1106        .unwrap();
1107
1108        let result = stellar_relayer.get_status().await;
1109        assert!(result.is_err());
1110        match result.err().unwrap() {
1111            RelayerError::ProviderError(msg) => {
1112                assert!(msg.contains("Failed to get account details"))
1113            }
1114            _ => panic!("Expected ProviderError for get_account failure"),
1115        }
1116    }
1117
1118    #[tokio::test]
1119    async fn test_get_balance_success() {
1120        let ctx = TestCtx::default();
1121        ctx.setup_network().await;
1122        let relayer_model = ctx.relayer_model.clone();
1123        let mut provider = MockStellarProviderTrait::new();
1124        let expected_balance = 100_000_000i64; // 10 XLM in stroops
1125
1126        provider
1127            .expect_get_account()
1128            .with(eq(relayer_model.address.clone()))
1129            .returning(move |_| {
1130                Box::pin(async move {
1131                    Ok(AccountEntry {
1132                        account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
1133                        balance: expected_balance,
1134                        ext: AccountEntryExt::V0,
1135                        flags: 0,
1136                        home_domain: String32::default(),
1137                        inflation_dest: None,
1138                        seq_num: SequenceNumber(5),
1139                        num_sub_entries: 0,
1140                        signers: VecM::default(),
1141                        thresholds: Thresholds([0, 0, 0, 0]),
1142                    })
1143                })
1144            });
1145
1146        let relayer_repo = Arc::new(MockRelayerRepository::new());
1147        let tx_repo = Arc::new(MockTransactionRepository::new());
1148        let job_producer = Arc::new(MockJobProducerTrait::new());
1149        let counter = Arc::new(MockTransactionCounterServiceTrait::new());
1150        let signer = Arc::new(MockStellarSignTrait::new());
1151        let dex_service = create_mock_dex_service();
1152
1153        let relayer = StellarRelayer::new(
1154            relayer_model,
1155            signer,
1156            provider,
1157            StellarRelayerDependencies::new(
1158                relayer_repo,
1159                ctx.network_repository.clone(),
1160                tx_repo,
1161                counter,
1162                job_producer,
1163            ),
1164            dex_service,
1165        )
1166        .await
1167        .unwrap();
1168
1169        let result = relayer.get_balance().await;
1170        assert!(result.is_ok());
1171        let balance_response = result.unwrap();
1172        assert_eq!(balance_response.balance, expected_balance as u128);
1173        assert_eq!(balance_response.unit, STELLAR_SMALLEST_UNIT_NAME);
1174    }
1175
1176    #[tokio::test]
1177    async fn test_get_balance_provider_error() {
1178        let ctx = TestCtx::default();
1179        ctx.setup_network().await;
1180        let relayer_model = ctx.relayer_model.clone();
1181        let mut provider = MockStellarProviderTrait::new();
1182
1183        provider
1184            .expect_get_account()
1185            .with(eq(relayer_model.address.clone()))
1186            .returning(|_| {
1187                Box::pin(async { Err(ProviderError::Other("provider failed".to_string())) })
1188            });
1189
1190        let relayer_repo = Arc::new(MockRelayerRepository::new());
1191        let tx_repo = Arc::new(MockTransactionRepository::new());
1192        let job_producer = Arc::new(MockJobProducerTrait::new());
1193        let counter = Arc::new(MockTransactionCounterServiceTrait::new());
1194        let signer = Arc::new(MockStellarSignTrait::new());
1195        let dex_service = create_mock_dex_service();
1196
1197        let relayer = StellarRelayer::new(
1198            relayer_model,
1199            signer,
1200            provider,
1201            StellarRelayerDependencies::new(
1202                relayer_repo,
1203                ctx.network_repository.clone(),
1204                tx_repo,
1205                counter,
1206                job_producer,
1207            ),
1208            dex_service,
1209        )
1210        .await
1211        .unwrap();
1212
1213        let result = relayer.get_balance().await;
1214        assert!(result.is_err());
1215        match result.err().unwrap() {
1216            RelayerError::ProviderError(msg) => {
1217                assert!(msg.contains("Failed to fetch account for balance"));
1218            }
1219            _ => panic!("Unexpected error type"),
1220        }
1221    }
1222
1223    #[tokio::test]
1224    async fn test_sign_transaction_success() {
1225        let ctx = TestCtx::default();
1226        ctx.setup_network().await;
1227        let relayer_model = ctx.relayer_model.clone();
1228        let provider = MockStellarProviderTrait::new();
1229        let mut signer = MockStellarSignTrait::new();
1230
1231        let unsigned_xdr = "AAAAAgAAAAD///8AAAAAAAAAAQAAAAAAAAACAAAAAQAAAAAAAAAB";
1232        let expected_signed_xdr =
1233            "AAAAAgAAAAD///8AAAAAAAABAAAAAAAAAAIAAAABAAAAAAAAAAEAAAABAAAAA...";
1234        let expected_signature = DecoratedSignature {
1235            hint: SignatureHint([1, 2, 3, 4]),
1236            signature: Signature([5u8; 64].try_into().unwrap()),
1237        };
1238        let expected_signature_for_closure = expected_signature.clone();
1239
1240        signer
1241            .expect_sign_xdr_transaction()
1242            .with(eq(unsigned_xdr), eq("Test SDF Network ; September 2015"))
1243            .returning(move |_, _| {
1244                Ok(SignXdrTransactionResponseStellar {
1245                    signed_xdr: expected_signed_xdr.to_string(),
1246                    signature: expected_signature_for_closure.clone(),
1247                })
1248            });
1249
1250        let relayer_repo = Arc::new(MockRelayerRepository::new());
1251        let tx_repo = Arc::new(MockTransactionRepository::new());
1252        let job_producer = Arc::new(MockJobProducerTrait::new());
1253        let counter = Arc::new(MockTransactionCounterServiceTrait::new());
1254        let dex_service = create_mock_dex_service();
1255
1256        let relayer = StellarRelayer::new(
1257            relayer_model,
1258            Arc::new(signer),
1259            provider,
1260            StellarRelayerDependencies::new(
1261                relayer_repo,
1262                ctx.network_repository.clone(),
1263                tx_repo,
1264                counter,
1265                job_producer,
1266            ),
1267            dex_service,
1268        )
1269        .await
1270        .unwrap();
1271
1272        let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
1273            unsigned_xdr: unsigned_xdr.to_string(),
1274        });
1275        let result = relayer.sign_transaction(&request).await;
1276        assert!(result.is_ok());
1277
1278        match result.unwrap() {
1279            SignTransactionExternalResponse::Stellar(response) => {
1280                assert_eq!(response.signed_xdr, expected_signed_xdr);
1281                // Compare the base64 encoded signature
1282                let expected_signature_base64 = base64::Engine::encode(
1283                    &base64::engine::general_purpose::STANDARD,
1284                    &expected_signature.signature.0,
1285                );
1286                assert_eq!(response.signature, expected_signature_base64);
1287            }
1288            _ => panic!("Expected Stellar response"),
1289        }
1290    }
1291
1292    #[tokio::test]
1293    async fn test_sign_transaction_signer_error() {
1294        let ctx = TestCtx::default();
1295        ctx.setup_network().await;
1296        let relayer_model = ctx.relayer_model.clone();
1297        let provider = MockStellarProviderTrait::new();
1298        let mut signer = MockStellarSignTrait::new();
1299
1300        let unsigned_xdr = "INVALID_XDR";
1301
1302        signer
1303            .expect_sign_xdr_transaction()
1304            .with(eq(unsigned_xdr), eq("Test SDF Network ; September 2015"))
1305            .returning(|_, _| Err(SignerError::SigningError("Invalid XDR format".to_string())));
1306
1307        let relayer_repo = Arc::new(MockRelayerRepository::new());
1308        let tx_repo = Arc::new(MockTransactionRepository::new());
1309        let job_producer = Arc::new(MockJobProducerTrait::new());
1310        let counter = Arc::new(MockTransactionCounterServiceTrait::new());
1311        let dex_service = create_mock_dex_service();
1312
1313        let relayer = StellarRelayer::new(
1314            relayer_model,
1315            Arc::new(signer),
1316            provider,
1317            StellarRelayerDependencies::new(
1318                relayer_repo,
1319                ctx.network_repository.clone(),
1320                tx_repo,
1321                counter,
1322                job_producer,
1323            ),
1324            dex_service,
1325        )
1326        .await
1327        .unwrap();
1328
1329        let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
1330            unsigned_xdr: unsigned_xdr.to_string(),
1331        });
1332        let result = relayer.sign_transaction(&request).await;
1333        assert!(result.is_err());
1334
1335        match result.err().unwrap() {
1336            RelayerError::SignerError(err) => match err {
1337                SignerError::SigningError(msg) => {
1338                    assert_eq!(msg, "Invalid XDR format");
1339                }
1340                _ => panic!("Expected SigningError"),
1341            },
1342            _ => panic!("Expected RelayerError::SignerError"),
1343        }
1344    }
1345
1346    #[tokio::test]
1347    async fn test_sign_transaction_with_different_network_passphrase() {
1348        let ctx = TestCtx::default();
1349        // Create a custom network with a different passphrase
1350        let custom_network = NetworkRepoModel {
1351            id: "stellar:mainnet".to_string(),
1352            name: "mainnet".to_string(),
1353            network_type: NetworkType::Stellar,
1354            config: NetworkConfigData::Stellar(StellarNetworkConfig {
1355                common: NetworkConfigCommon {
1356                    network: "mainnet".to_string(),
1357                    from: None,
1358                    rpc_urls: Some(vec!["https://horizon.stellar.org".to_string()]),
1359                    explorer_urls: None,
1360                    average_blocktime_ms: Some(5000),
1361                    is_testnet: Some(false),
1362                    tags: None,
1363                },
1364                passphrase: Some("Public Global Stellar Network ; September 2015".to_string()),
1365                horizon_url: Some("https://horizon.stellar.org".to_string()),
1366            }),
1367        };
1368        ctx.network_repository.create(custom_network).await.unwrap();
1369
1370        let mut relayer_model = ctx.relayer_model.clone();
1371        relayer_model.network = "mainnet".to_string();
1372
1373        let provider = MockStellarProviderTrait::new();
1374        let mut signer = MockStellarSignTrait::new();
1375
1376        let unsigned_xdr = "AAAAAgAAAAD///8AAAAAAAAAAQAAAAAAAAACAAAAAQAAAAAAAAAB";
1377        let expected_signature = DecoratedSignature {
1378            hint: SignatureHint([10, 20, 30, 40]),
1379            signature: Signature([15u8; 64].try_into().unwrap()),
1380        };
1381        let expected_signature_for_closure = expected_signature.clone();
1382
1383        signer
1384            .expect_sign_xdr_transaction()
1385            .with(
1386                eq(unsigned_xdr),
1387                eq("Public Global Stellar Network ; September 2015"),
1388            )
1389            .returning(move |_, _| {
1390                Ok(SignXdrTransactionResponseStellar {
1391                    signed_xdr: "mainnet_signed_xdr".to_string(),
1392                    signature: expected_signature_for_closure.clone(),
1393                })
1394            });
1395
1396        let relayer_repo = Arc::new(MockRelayerRepository::new());
1397        let tx_repo = Arc::new(MockTransactionRepository::new());
1398        let job_producer = Arc::new(MockJobProducerTrait::new());
1399        let counter = Arc::new(MockTransactionCounterServiceTrait::new());
1400        let dex_service = create_mock_dex_service();
1401
1402        let relayer = StellarRelayer::new(
1403            relayer_model,
1404            Arc::new(signer),
1405            provider,
1406            StellarRelayerDependencies::new(
1407                relayer_repo,
1408                ctx.network_repository.clone(),
1409                tx_repo,
1410                counter,
1411                job_producer,
1412            ),
1413            dex_service,
1414        )
1415        .await
1416        .unwrap();
1417
1418        let request = SignTransactionRequest::Stellar(SignTransactionRequestStellar {
1419            unsigned_xdr: unsigned_xdr.to_string(),
1420        });
1421        let result = relayer.sign_transaction(&request).await;
1422        assert!(result.is_ok());
1423
1424        match result.unwrap() {
1425            SignTransactionExternalResponse::Stellar(response) => {
1426                assert_eq!(response.signed_xdr, "mainnet_signed_xdr");
1427                // Convert expected signature to base64 for comparison (just the signature bytes, not the whole struct)
1428                let expected_signature_string = base64::Engine::encode(
1429                    &base64::engine::general_purpose::STANDARD,
1430                    &expected_signature.signature.0,
1431                );
1432                assert_eq!(response.signature, expected_signature_string);
1433            }
1434            _ => panic!("Expected Stellar response"),
1435        }
1436    }
1437
1438    #[tokio::test]
1439    async fn test_initialize_relayer_disables_when_validation_fails() {
1440        let ctx = TestCtx::default();
1441        ctx.setup_network().await;
1442        let mut relayer_model = ctx.relayer_model.clone();
1443        relayer_model.system_disabled = false; // Start as enabled
1444        relayer_model.notification_id = Some("test-notification-id".to_string());
1445
1446        let mut provider = MockStellarProviderTrait::new();
1447        let mut relayer_repo = MockRelayerRepository::new();
1448        let mut job_producer = MockJobProducerTrait::new();
1449
1450        relayer_repo
1451            .expect_is_persistent_storage()
1452            .returning(|| false);
1453
1454        // Mock validation failure - sequence sync fails
1455        provider
1456            .expect_get_account()
1457            .returning(|_| Box::pin(ready(Err(ProviderError::Other("RPC error".to_string())))));
1458
1459        // Mock disable_relayer call
1460        let mut disabled_relayer = relayer_model.clone();
1461        disabled_relayer.system_disabled = true;
1462        relayer_repo
1463            .expect_disable_relayer()
1464            .withf(|id, reason| {
1465                id == "test-relayer-id"
1466                    && matches!(reason, crate::models::DisabledReason::SequenceSyncFailed(_))
1467            })
1468            .returning(move |_, _| Ok(disabled_relayer.clone()));
1469
1470        // Mock notification job production
1471        job_producer
1472            .expect_produce_send_notification_job()
1473            .returning(|_, _| Box::pin(async { Ok(()) }));
1474
1475        // Mock health check job scheduling
1476        job_producer
1477            .expect_produce_relayer_health_check_job()
1478            .returning(|_, _| Box::pin(async { Ok(()) }));
1479
1480        let tx_repo = MockTransactionRepository::new();
1481        let counter = MockTransactionCounterServiceTrait::new();
1482        let signer = Arc::new(MockStellarSignTrait::new());
1483        let dex_service = create_mock_dex_service();
1484
1485        let relayer = StellarRelayer::new(
1486            relayer_model.clone(),
1487            signer,
1488            provider,
1489            StellarRelayerDependencies::new(
1490                Arc::new(relayer_repo),
1491                ctx.network_repository.clone(),
1492                Arc::new(tx_repo),
1493                Arc::new(counter),
1494                Arc::new(job_producer),
1495            ),
1496            dex_service,
1497        )
1498        .await
1499        .unwrap();
1500
1501        let result = relayer.initialize_relayer().await;
1502        assert!(result.is_ok());
1503    }
1504
1505    #[tokio::test]
1506    async fn test_initialize_relayer_enables_when_validation_passes_and_was_disabled() {
1507        let ctx = TestCtx::default();
1508        ctx.setup_network().await;
1509        let mut relayer_model = ctx.relayer_model.clone();
1510        relayer_model.system_disabled = true; // Start as disabled
1511
1512        let mut provider = MockStellarProviderTrait::new();
1513        let mut relayer_repo = MockRelayerRepository::new();
1514
1515        relayer_repo
1516            .expect_is_persistent_storage()
1517            .returning(|| false);
1518
1519        // Mock successful validations - sequence sync succeeds
1520        provider.expect_get_account().returning(|_| {
1521            Box::pin(ready(Ok(AccountEntry {
1522                account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
1523                balance: 1000000000, // 100 XLM
1524                seq_num: SequenceNumber(1),
1525                num_sub_entries: 0,
1526                inflation_dest: None,
1527                flags: 0,
1528                home_domain: String32::default(),
1529                thresholds: Thresholds([0; 4]),
1530                signers: VecM::default(),
1531                ext: AccountEntryExt::V0,
1532            })))
1533        });
1534
1535        // Mock enable_relayer call
1536        let mut enabled_relayer = relayer_model.clone();
1537        enabled_relayer.system_disabled = false;
1538        relayer_repo
1539            .expect_enable_relayer()
1540            .with(eq("test-relayer-id".to_string()))
1541            .returning(move |_| Ok(enabled_relayer.clone()));
1542
1543        let tx_repo = MockTransactionRepository::new();
1544        let mut counter = MockTransactionCounterServiceTrait::new();
1545        counter
1546            .expect_set()
1547            .returning(|_| Box::pin(async { Ok(()) }));
1548        let signer = Arc::new(MockStellarSignTrait::new());
1549        let dex_service = create_mock_dex_service();
1550        let job_producer = MockJobProducerTrait::new();
1551
1552        let relayer = StellarRelayer::new(
1553            relayer_model.clone(),
1554            signer,
1555            provider,
1556            StellarRelayerDependencies::new(
1557                Arc::new(relayer_repo),
1558                ctx.network_repository.clone(),
1559                Arc::new(tx_repo),
1560                Arc::new(counter),
1561                Arc::new(job_producer),
1562            ),
1563            dex_service,
1564        )
1565        .await
1566        .unwrap();
1567
1568        let result = relayer.initialize_relayer().await;
1569        assert!(result.is_ok());
1570    }
1571
1572    #[tokio::test]
1573    async fn test_initialize_relayer_no_action_when_enabled_and_validation_passes() {
1574        let ctx = TestCtx::default();
1575        ctx.setup_network().await;
1576        let mut relayer_model = ctx.relayer_model.clone();
1577        relayer_model.system_disabled = false; // Start as enabled
1578
1579        let mut provider = MockStellarProviderTrait::new();
1580
1581        // Mock successful validations - sequence sync succeeds
1582        provider.expect_get_account().returning(|_| {
1583            Box::pin(ready(Ok(AccountEntry {
1584                account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
1585                balance: 1000000000, // 100 XLM
1586                seq_num: SequenceNumber(1),
1587                num_sub_entries: 0,
1588                inflation_dest: None,
1589                flags: 0,
1590                home_domain: String32::default(),
1591                thresholds: Thresholds([0; 4]),
1592                signers: VecM::default(),
1593                ext: AccountEntryExt::V0,
1594            })))
1595        });
1596
1597        // No repository calls should be made since relayer is already enabled
1598
1599        let tx_repo = MockTransactionRepository::new();
1600        let mut counter = MockTransactionCounterServiceTrait::new();
1601        counter
1602            .expect_set()
1603            .returning(|_| Box::pin(async { Ok(()) }));
1604        let signer = Arc::new(MockStellarSignTrait::new());
1605        let dex_service = create_mock_dex_service();
1606        let job_producer = MockJobProducerTrait::new();
1607        let mut relayer_repo = MockRelayerRepository::new();
1608
1609        relayer_repo
1610            .expect_is_persistent_storage()
1611            .returning(|| false);
1612
1613        let relayer = StellarRelayer::new(
1614            relayer_model.clone(),
1615            signer,
1616            provider,
1617            StellarRelayerDependencies::new(
1618                Arc::new(relayer_repo),
1619                ctx.network_repository.clone(),
1620                Arc::new(tx_repo),
1621                Arc::new(counter),
1622                Arc::new(job_producer),
1623            ),
1624            dex_service,
1625        )
1626        .await
1627        .unwrap();
1628
1629        let result = relayer.initialize_relayer().await;
1630        assert!(result.is_ok());
1631    }
1632
1633    #[tokio::test]
1634    async fn test_initialize_relayer_sends_notification_when_disabled() {
1635        let ctx = TestCtx::default();
1636        ctx.setup_network().await;
1637        let mut relayer_model = ctx.relayer_model.clone();
1638        relayer_model.system_disabled = false; // Start as enabled
1639        relayer_model.notification_id = Some("test-notification-id".to_string());
1640
1641        let mut provider = MockStellarProviderTrait::new();
1642        let mut relayer_repo = MockRelayerRepository::new();
1643        let mut job_producer = MockJobProducerTrait::new();
1644
1645        relayer_repo
1646            .expect_is_persistent_storage()
1647            .returning(|| false);
1648
1649        // Mock validation failure - sequence sync fails
1650        provider.expect_get_account().returning(|_| {
1651            Box::pin(ready(Err(ProviderError::Other(
1652                "Sequence sync failed".to_string(),
1653            ))))
1654        });
1655
1656        // Mock disable_relayer call
1657        let mut disabled_relayer = relayer_model.clone();
1658        disabled_relayer.system_disabled = true;
1659        relayer_repo
1660            .expect_disable_relayer()
1661            .withf(|id, reason| {
1662                id == "test-relayer-id"
1663                    && matches!(reason, crate::models::DisabledReason::SequenceSyncFailed(_))
1664            })
1665            .returning(move |_, _| Ok(disabled_relayer.clone()));
1666
1667        // Mock notification job production - verify it's called
1668        job_producer
1669            .expect_produce_send_notification_job()
1670            .returning(|_, _| Box::pin(async { Ok(()) }));
1671
1672        // Mock health check job scheduling
1673        job_producer
1674            .expect_produce_relayer_health_check_job()
1675            .returning(|_, _| Box::pin(async { Ok(()) }));
1676
1677        let tx_repo = MockTransactionRepository::new();
1678        let counter = MockTransactionCounterServiceTrait::new();
1679        let signer = Arc::new(MockStellarSignTrait::new());
1680        let dex_service = create_mock_dex_service();
1681
1682        let relayer = StellarRelayer::new(
1683            relayer_model.clone(),
1684            signer,
1685            provider,
1686            StellarRelayerDependencies::new(
1687                Arc::new(relayer_repo),
1688                ctx.network_repository.clone(),
1689                Arc::new(tx_repo),
1690                Arc::new(counter),
1691                Arc::new(job_producer),
1692            ),
1693            dex_service,
1694        )
1695        .await
1696        .unwrap();
1697
1698        let result = relayer.initialize_relayer().await;
1699        assert!(result.is_ok());
1700    }
1701
1702    #[tokio::test]
1703    async fn test_initialize_relayer_no_notification_when_no_notification_id() {
1704        let ctx = TestCtx::default();
1705        ctx.setup_network().await;
1706        let mut relayer_model = ctx.relayer_model.clone();
1707        relayer_model.system_disabled = false; // Start as enabled
1708        relayer_model.notification_id = None; // No notification ID
1709
1710        let mut provider = MockStellarProviderTrait::new();
1711        let mut relayer_repo = MockRelayerRepository::new();
1712        relayer_repo
1713            .expect_is_persistent_storage()
1714            .returning(|| false);
1715
1716        // Mock validation failure - sequence sync fails
1717        provider.expect_get_account().returning(|_| {
1718            Box::pin(ready(Err(ProviderError::Other(
1719                "Sequence sync failed".to_string(),
1720            ))))
1721        });
1722
1723        // Mock disable_relayer call
1724        let mut disabled_relayer = relayer_model.clone();
1725        disabled_relayer.system_disabled = true;
1726        relayer_repo
1727            .expect_disable_relayer()
1728            .withf(|id, reason| {
1729                id == "test-relayer-id"
1730                    && matches!(reason, crate::models::DisabledReason::SequenceSyncFailed(_))
1731            })
1732            .returning(move |_, _| Ok(disabled_relayer.clone()));
1733
1734        // No notification job should be produced since notification_id is None
1735        // But health check job should still be scheduled
1736        let mut job_producer = MockJobProducerTrait::new();
1737        job_producer
1738            .expect_produce_relayer_health_check_job()
1739            .returning(|_, _| Box::pin(async { Ok(()) }));
1740
1741        let tx_repo = MockTransactionRepository::new();
1742        let counter = MockTransactionCounterServiceTrait::new();
1743        let signer = Arc::new(MockStellarSignTrait::new());
1744        let dex_service = create_mock_dex_service();
1745
1746        let relayer = StellarRelayer::new(
1747            relayer_model.clone(),
1748            signer,
1749            provider,
1750            StellarRelayerDependencies::new(
1751                Arc::new(relayer_repo),
1752                ctx.network_repository.clone(),
1753                Arc::new(tx_repo),
1754                Arc::new(counter),
1755                Arc::new(job_producer),
1756            ),
1757            dex_service,
1758        )
1759        .await
1760        .unwrap();
1761
1762        let result = relayer.initialize_relayer().await;
1763        assert!(result.is_ok());
1764    }
1765
1766    mod process_transaction_request_tests {
1767        use super::*;
1768        use crate::constants::STELLAR_STATUS_CHECK_INITIAL_DELAY_SECONDS;
1769        use crate::models::{
1770            NetworkTransactionRequest, NetworkType, StellarTransactionRequest, TransactionStatus,
1771        };
1772        use chrono::Utc;
1773
1774        // Helper function to create a valid test transaction request
1775        fn create_test_transaction_request() -> NetworkTransactionRequest {
1776            NetworkTransactionRequest::Stellar(StellarTransactionRequest {
1777                source_account: None,
1778                network: "testnet".to_string(),
1779                operations: None,
1780                memo: None,
1781                valid_until: None,
1782                transaction_xdr: Some("AAAAAgAAAACige4lTdwSB/sto4SniEdJ2kOa2X65s5bqkd40J4DjSwAAAAEAAHAkAAAADwAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAKKB7iVN3BIH+y2jhKeIR0naQ5rZfrmzluqR3jQngONLAAAAAAAAAAAAD0JAAAAAAAAAAAA=".to_string()),
1783                fee_bump: None,
1784                max_fee: None,
1785            })
1786        }
1787
1788        #[tokio::test]
1789        async fn test_process_transaction_request_calls_job_producer_methods() {
1790            let ctx = TestCtx::default();
1791            ctx.setup_network().await;
1792            let relayer_model = ctx.relayer_model.clone();
1793
1794            let provider = MockStellarProviderTrait::new();
1795            let signer = Arc::new(MockStellarSignTrait::new());
1796            let dex_service = create_mock_dex_service();
1797
1798            // Create a test transaction request
1799            let tx_request = create_test_transaction_request();
1800
1801            // Mock transaction repository - we expect it to create a transaction
1802            let mut tx_repo = MockTransactionRepository::new();
1803            tx_repo.expect_create().returning(|t| Ok(t.clone()));
1804
1805            // Mock job producer to verify both methods are called
1806            let mut job_producer = MockJobProducerTrait::new();
1807
1808            // Verify produce_transaction_request_job is called
1809            job_producer
1810                .expect_produce_transaction_request_job()
1811                .withf(|req, delay| {
1812                    !req.transaction_id.is_empty() && !req.relayer_id.is_empty() && delay.is_none()
1813                })
1814                .times(1)
1815                .returning(|_, _| Box::pin(async { Ok(()) }));
1816
1817            // Verify produce_check_transaction_status_job is called with correct parameters
1818            job_producer
1819                .expect_produce_check_transaction_status_job()
1820                .withf(|check, delay| {
1821                    !check.transaction_id.is_empty()
1822                        && !check.relayer_id.is_empty()
1823                        && check.network_type == Some(NetworkType::Stellar)
1824                        && delay.is_some()
1825                })
1826                .times(1)
1827                .returning(|_, _| Box::pin(async { Ok(()) }));
1828
1829            let relayer_repo = Arc::new(MockRelayerRepository::new());
1830            let counter = MockTransactionCounterServiceTrait::new();
1831
1832            let relayer = StellarRelayer::new(
1833                relayer_model,
1834                signer,
1835                provider,
1836                StellarRelayerDependencies::new(
1837                    relayer_repo,
1838                    ctx.network_repository.clone(),
1839                    Arc::new(tx_repo),
1840                    Arc::new(counter),
1841                    Arc::new(job_producer),
1842                ),
1843                dex_service,
1844            )
1845            .await
1846            .unwrap();
1847
1848            let result = relayer.process_transaction_request(tx_request).await;
1849            if let Err(e) = &result {
1850                panic!("process_transaction_request failed: {}", e);
1851            }
1852            assert!(result.is_ok());
1853        }
1854
1855        #[tokio::test]
1856        async fn test_process_transaction_request_with_scheduled_delay() {
1857            let ctx = TestCtx::default();
1858            ctx.setup_network().await;
1859            let relayer_model = ctx.relayer_model.clone();
1860
1861            let provider = MockStellarProviderTrait::new();
1862            let signer = Arc::new(MockStellarSignTrait::new());
1863            let dex_service = create_mock_dex_service();
1864
1865            let tx_request = create_test_transaction_request();
1866
1867            let mut tx_repo = MockTransactionRepository::new();
1868            tx_repo.expect_create().returning(|t| Ok(t.clone()));
1869
1870            let mut job_producer = MockJobProducerTrait::new();
1871
1872            job_producer
1873                .expect_produce_transaction_request_job()
1874                .returning(|_, _| Box::pin(async { Ok(()) }));
1875
1876            // Verify that the status check is scheduled with the initial delay
1877            job_producer
1878                .expect_produce_check_transaction_status_job()
1879                .withf(|_, delay| {
1880                    // Should have a delay timestamp
1881                    if let Some(scheduled_at) = delay {
1882                        // The scheduled time should be approximately STELLAR_STATUS_CHECK_INITIAL_DELAY_SECONDS from now
1883                        let now = Utc::now().timestamp();
1884                        let diff = scheduled_at - now;
1885                        // Allow some tolerance (within 2 seconds)
1886                        diff >= (STELLAR_STATUS_CHECK_INITIAL_DELAY_SECONDS - 2)
1887                            && diff <= (STELLAR_STATUS_CHECK_INITIAL_DELAY_SECONDS + 2)
1888                    } else {
1889                        false
1890                    }
1891                })
1892                .times(1)
1893                .returning(|_, _| Box::pin(async { Ok(()) }));
1894
1895            let relayer_repo = Arc::new(MockRelayerRepository::new());
1896            let counter = MockTransactionCounterServiceTrait::new();
1897
1898            let relayer = StellarRelayer::new(
1899                relayer_model,
1900                signer,
1901                provider,
1902                StellarRelayerDependencies::new(
1903                    relayer_repo,
1904                    ctx.network_repository.clone(),
1905                    Arc::new(tx_repo),
1906                    Arc::new(counter),
1907                    Arc::new(job_producer),
1908                ),
1909                dex_service,
1910            )
1911            .await
1912            .unwrap();
1913
1914            let result = relayer.process_transaction_request(tx_request).await;
1915            assert!(result.is_ok());
1916        }
1917
1918        #[tokio::test]
1919        async fn test_process_transaction_request_repository_failure() {
1920            let ctx = TestCtx::default();
1921            ctx.setup_network().await;
1922            let relayer_model = ctx.relayer_model.clone();
1923
1924            let provider = MockStellarProviderTrait::new();
1925            let signer = Arc::new(MockStellarSignTrait::new());
1926            let dex_service = create_mock_dex_service();
1927
1928            let tx_request = create_test_transaction_request();
1929
1930            // Mock repository failure
1931            let mut tx_repo = MockTransactionRepository::new();
1932            tx_repo.expect_create().returning(|_| {
1933                Err(RepositoryError::TransactionFailure(
1934                    "Database connection failed".to_string(),
1935                ))
1936            });
1937
1938            // Job producer should NOT be called when repository fails
1939            let job_producer = MockJobProducerTrait::new();
1940
1941            let relayer_repo = Arc::new(MockRelayerRepository::new());
1942            let counter = MockTransactionCounterServiceTrait::new();
1943
1944            let relayer = StellarRelayer::new(
1945                relayer_model,
1946                signer,
1947                provider,
1948                StellarRelayerDependencies::new(
1949                    relayer_repo,
1950                    ctx.network_repository.clone(),
1951                    Arc::new(tx_repo),
1952                    Arc::new(counter),
1953                    Arc::new(job_producer),
1954                ),
1955                dex_service,
1956            )
1957            .await
1958            .unwrap();
1959
1960            let result = relayer.process_transaction_request(tx_request).await;
1961            assert!(result.is_err());
1962            // RepositoryError is converted to RelayerError::NetworkConfiguration
1963            let err_msg = result.err().unwrap().to_string();
1964            assert!(
1965                err_msg.contains("Database connection failed"),
1966                "Error was: {}",
1967                err_msg
1968            );
1969        }
1970
1971        #[tokio::test]
1972        async fn test_process_transaction_request_job_producer_request_failure() {
1973            let ctx = TestCtx::default();
1974            ctx.setup_network().await;
1975            let relayer_model = ctx.relayer_model.clone();
1976
1977            let provider = MockStellarProviderTrait::new();
1978            let signer = Arc::new(MockStellarSignTrait::new());
1979            let dex_service = create_mock_dex_service();
1980
1981            let tx_request = create_test_transaction_request();
1982
1983            let mut tx_repo = MockTransactionRepository::new();
1984            tx_repo.expect_create().returning(|t| Ok(t.clone()));
1985
1986            // Mock produce_transaction_request_job to fail
1987            let mut job_producer = MockJobProducerTrait::new();
1988            job_producer
1989                .expect_produce_transaction_request_job()
1990                .returning(|_, _| {
1991                    Box::pin(async {
1992                        Err(crate::jobs::JobProducerError::QueueError(
1993                            "Queue is full".to_string(),
1994                        ))
1995                    })
1996                });
1997
1998            // Status check job should NOT be called if request job fails
1999
2000            let relayer_repo = Arc::new(MockRelayerRepository::new());
2001            let counter = MockTransactionCounterServiceTrait::new();
2002
2003            let relayer = StellarRelayer::new(
2004                relayer_model,
2005                signer,
2006                provider,
2007                StellarRelayerDependencies::new(
2008                    relayer_repo,
2009                    ctx.network_repository.clone(),
2010                    Arc::new(tx_repo),
2011                    Arc::new(counter),
2012                    Arc::new(job_producer),
2013                ),
2014                dex_service,
2015            )
2016            .await
2017            .unwrap();
2018
2019            let result = relayer.process_transaction_request(tx_request).await;
2020            assert!(result.is_err());
2021        }
2022
2023        #[tokio::test]
2024        async fn test_process_transaction_request_job_producer_status_check_failure() {
2025            let ctx = TestCtx::default();
2026            ctx.setup_network().await;
2027            let relayer_model = ctx.relayer_model.clone();
2028
2029            let provider = MockStellarProviderTrait::new();
2030            let signer = Arc::new(MockStellarSignTrait::new());
2031            let dex_service = create_mock_dex_service();
2032
2033            let tx_request = create_test_transaction_request();
2034
2035            let mut tx_repo = MockTransactionRepository::new();
2036            tx_repo.expect_create().returning(|t| Ok(t.clone()));
2037
2038            let mut job_producer = MockJobProducerTrait::new();
2039
2040            // Request job succeeds
2041            job_producer
2042                .expect_produce_transaction_request_job()
2043                .returning(|_, _| Box::pin(async { Ok(()) }));
2044
2045            // Status check job fails
2046            job_producer
2047                .expect_produce_check_transaction_status_job()
2048                .returning(|_, _| {
2049                    Box::pin(async {
2050                        Err(crate::jobs::JobProducerError::QueueError(
2051                            "Failed to queue job".to_string(),
2052                        ))
2053                    })
2054                });
2055
2056            let relayer_repo = Arc::new(MockRelayerRepository::new());
2057            let counter = MockTransactionCounterServiceTrait::new();
2058
2059            let relayer = StellarRelayer::new(
2060                relayer_model,
2061                signer,
2062                provider,
2063                StellarRelayerDependencies::new(
2064                    relayer_repo,
2065                    ctx.network_repository.clone(),
2066                    Arc::new(tx_repo),
2067                    Arc::new(counter),
2068                    Arc::new(job_producer),
2069                ),
2070                dex_service,
2071            )
2072            .await
2073            .unwrap();
2074
2075            let result = relayer.process_transaction_request(tx_request).await;
2076            assert!(result.is_err());
2077        }
2078
2079        #[tokio::test]
2080        async fn test_process_transaction_request_preserves_transaction_data() {
2081            let ctx = TestCtx::default();
2082            ctx.setup_network().await;
2083            let relayer_model = ctx.relayer_model.clone();
2084
2085            let provider = MockStellarProviderTrait::new();
2086            let signer = Arc::new(MockStellarSignTrait::new());
2087            let dex_service = create_mock_dex_service();
2088
2089            let tx_request = create_test_transaction_request();
2090
2091            let mut tx_repo = MockTransactionRepository::new();
2092            tx_repo.expect_create().returning(|t| Ok(t.clone()));
2093
2094            let mut job_producer = MockJobProducerTrait::new();
2095            job_producer
2096                .expect_produce_transaction_request_job()
2097                .returning(|_, _| Box::pin(async { Ok(()) }));
2098            job_producer
2099                .expect_produce_check_transaction_status_job()
2100                .returning(|_, _| Box::pin(async { Ok(()) }));
2101
2102            let relayer_repo = Arc::new(MockRelayerRepository::new());
2103            let counter = MockTransactionCounterServiceTrait::new();
2104
2105            let relayer = StellarRelayer::new(
2106                relayer_model.clone(),
2107                signer,
2108                provider,
2109                StellarRelayerDependencies::new(
2110                    relayer_repo,
2111                    ctx.network_repository.clone(),
2112                    Arc::new(tx_repo),
2113                    Arc::new(counter),
2114                    Arc::new(job_producer),
2115                ),
2116                dex_service,
2117            )
2118            .await
2119            .unwrap();
2120
2121            let result = relayer.process_transaction_request(tx_request).await;
2122            assert!(result.is_ok());
2123
2124            let returned_tx = result.unwrap();
2125            assert_eq!(returned_tx.relayer_id, relayer_model.id);
2126            assert_eq!(returned_tx.network_type, NetworkType::Stellar);
2127            assert_eq!(returned_tx.status, TransactionStatus::Pending);
2128        }
2129    }
2130
2131    // Tests for populate_allowed_tokens_metadata
2132    mod populate_allowed_tokens_metadata_tests {
2133        use super::*;
2134        use crate::models::StellarTokenKind;
2135
2136        #[tokio::test]
2137        async fn test_populate_allowed_tokens_metadata_no_tokens() {
2138            let ctx = TestCtx::default();
2139            ctx.setup_network().await;
2140            let relayer_model = ctx.relayer_model.clone();
2141
2142            let provider = MockStellarProviderTrait::new();
2143            let signer = Arc::new(MockStellarSignTrait::new());
2144            let dex_service = create_mock_dex_service();
2145
2146            let mut relayer_repo = MockRelayerRepository::new();
2147            // Should not be called since no tokens
2148            relayer_repo.expect_update_policy().times(0);
2149
2150            let tx_repo = MockTransactionRepository::new();
2151            let job_producer = MockJobProducerTrait::new();
2152            let counter = MockTransactionCounterServiceTrait::new();
2153
2154            let relayer = StellarRelayer::new(
2155                relayer_model.clone(),
2156                signer,
2157                provider,
2158                StellarRelayerDependencies::new(
2159                    Arc::new(relayer_repo),
2160                    ctx.network_repository.clone(),
2161                    Arc::new(tx_repo),
2162                    Arc::new(counter),
2163                    Arc::new(job_producer),
2164                ),
2165                dex_service,
2166            )
2167            .await
2168            .unwrap();
2169
2170            let result = relayer.populate_allowed_tokens_metadata().await;
2171            assert!(result.is_ok());
2172        }
2173
2174        #[tokio::test]
2175        async fn test_populate_allowed_tokens_metadata_empty_tokens() {
2176            let ctx = TestCtx::default();
2177            ctx.setup_network().await;
2178            let mut relayer_model = ctx.relayer_model.clone();
2179
2180            // Set up empty allowed tokens
2181            let mut policy = RelayerStellarPolicy::default();
2182            policy.allowed_tokens = Some(vec![]);
2183            relayer_model.policies = RelayerNetworkPolicy::Stellar(policy);
2184
2185            let provider = MockStellarProviderTrait::new();
2186            let signer = Arc::new(MockStellarSignTrait::new());
2187            let dex_service = create_mock_dex_service();
2188
2189            let mut relayer_repo = MockRelayerRepository::new();
2190            // Should not be called since tokens list is empty
2191            relayer_repo.expect_update_policy().times(0);
2192
2193            let tx_repo = MockTransactionRepository::new();
2194            let job_producer = MockJobProducerTrait::new();
2195            let counter = MockTransactionCounterServiceTrait::new();
2196
2197            let relayer = StellarRelayer::new(
2198                relayer_model.clone(),
2199                signer,
2200                provider,
2201                StellarRelayerDependencies::new(
2202                    Arc::new(relayer_repo),
2203                    ctx.network_repository.clone(),
2204                    Arc::new(tx_repo),
2205                    Arc::new(counter),
2206                    Arc::new(job_producer),
2207                ),
2208                dex_service,
2209            )
2210            .await
2211            .unwrap();
2212
2213            let result = relayer.populate_allowed_tokens_metadata().await;
2214            assert!(result.is_ok());
2215        }
2216
2217        #[tokio::test]
2218        async fn test_populate_allowed_tokens_metadata_classic_asset_success() {
2219            let ctx = TestCtx::default();
2220            ctx.setup_network().await;
2221            let mut relayer_model = ctx.relayer_model.clone();
2222
2223            // Set up allowed tokens with a classic asset (USDC)
2224            let mut policy = RelayerStellarPolicy::default();
2225            policy.allowed_tokens = Some(vec![crate::models::StellarAllowedTokensPolicy {
2226                asset: "USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5".to_string(),
2227                metadata: None,
2228                max_allowed_fee: None,
2229                swap_config: None,
2230            }]);
2231            relayer_model.policies = RelayerNetworkPolicy::Stellar(policy);
2232
2233            let provider = MockStellarProviderTrait::new();
2234            let signer = Arc::new(MockStellarSignTrait::new());
2235            let dex_service = create_mock_dex_service();
2236
2237            let mut relayer_repo = MockRelayerRepository::new();
2238            relayer_repo
2239                .expect_update_policy()
2240                .times(1)
2241                .returning(|_, _| Ok(RelayerRepoModel::default()));
2242
2243            let tx_repo = MockTransactionRepository::new();
2244            let job_producer = MockJobProducerTrait::new();
2245            let counter = MockTransactionCounterServiceTrait::new();
2246
2247            let relayer = StellarRelayer::new(
2248                relayer_model.clone(),
2249                signer,
2250                provider,
2251                StellarRelayerDependencies::new(
2252                    Arc::new(relayer_repo),
2253                    ctx.network_repository.clone(),
2254                    Arc::new(tx_repo),
2255                    Arc::new(counter),
2256                    Arc::new(job_producer),
2257                ),
2258                dex_service,
2259            )
2260            .await
2261            .unwrap();
2262
2263            let result = relayer.populate_allowed_tokens_metadata().await;
2264            assert!(result.is_ok());
2265
2266            let updated_policy = result.unwrap();
2267            assert!(updated_policy.allowed_tokens.is_some());
2268
2269            let tokens = updated_policy.allowed_tokens.unwrap();
2270            assert_eq!(tokens.len(), 1);
2271
2272            // Verify metadata was populated
2273            let token = &tokens[0];
2274            assert!(token.metadata.is_some());
2275
2276            let metadata = token.metadata.as_ref().unwrap();
2277            assert_eq!(metadata.decimals, 7); // Default Stellar decimals
2278            assert_eq!(
2279                metadata.canonical_asset_id,
2280                "USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5"
2281            );
2282
2283            // Verify it's a classic asset
2284            match &metadata.kind {
2285                StellarTokenKind::Classic { code, issuer } => {
2286                    assert_eq!(code, "USDC");
2287                    assert_eq!(
2288                        issuer,
2289                        "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5"
2290                    );
2291                }
2292                _ => panic!("Expected Classic token kind"),
2293            }
2294        }
2295
2296        #[tokio::test]
2297        async fn test_populate_allowed_tokens_metadata_multiple_tokens() {
2298            let ctx = TestCtx::default();
2299            ctx.setup_network().await;
2300            let mut relayer_model = ctx.relayer_model.clone();
2301
2302            // Set up multiple allowed tokens
2303            let mut policy = RelayerStellarPolicy::default();
2304            policy.allowed_tokens = Some(vec![
2305                crate::models::StellarAllowedTokensPolicy {
2306                    asset: "USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5"
2307                        .to_string(),
2308                    metadata: None,
2309                    max_allowed_fee: None,
2310                    swap_config: None,
2311                },
2312                crate::models::StellarAllowedTokensPolicy {
2313                    asset: "AQUA:GAHPYWLK6YRN7CVYZOO4H3VDRZ7PVF5UJGLZCSPAEIKJE2XSWF5LAGER"
2314                        .to_string(),
2315                    metadata: None,
2316                    max_allowed_fee: Some(1000000),
2317                    swap_config: None,
2318                },
2319            ]);
2320            relayer_model.policies = RelayerNetworkPolicy::Stellar(policy);
2321
2322            let provider = MockStellarProviderTrait::new();
2323            let signer = Arc::new(MockStellarSignTrait::new());
2324            let dex_service = create_mock_dex_service();
2325
2326            let mut relayer_repo = MockRelayerRepository::new();
2327            relayer_repo
2328                .expect_update_policy()
2329                .times(1)
2330                .returning(|_, _| Ok(RelayerRepoModel::default()));
2331
2332            let tx_repo = MockTransactionRepository::new();
2333            let job_producer = MockJobProducerTrait::new();
2334            let counter = MockTransactionCounterServiceTrait::new();
2335
2336            let relayer = StellarRelayer::new(
2337                relayer_model.clone(),
2338                signer,
2339                provider,
2340                StellarRelayerDependencies::new(
2341                    Arc::new(relayer_repo),
2342                    ctx.network_repository.clone(),
2343                    Arc::new(tx_repo),
2344                    Arc::new(counter),
2345                    Arc::new(job_producer),
2346                ),
2347                dex_service,
2348            )
2349            .await
2350            .unwrap();
2351
2352            let result = relayer.populate_allowed_tokens_metadata().await;
2353            assert!(result.is_ok());
2354
2355            let updated_policy = result.unwrap();
2356            let tokens = updated_policy.allowed_tokens.unwrap();
2357            assert_eq!(tokens.len(), 2);
2358
2359            // Verify both tokens have metadata
2360            assert!(tokens[0].metadata.is_some());
2361            assert!(tokens[1].metadata.is_some());
2362
2363            // Verify first token (USDC)
2364            let usdc_metadata = tokens[0].metadata.as_ref().unwrap();
2365            match &usdc_metadata.kind {
2366                StellarTokenKind::Classic { code, .. } => {
2367                    assert_eq!(code, "USDC");
2368                }
2369                _ => panic!("Expected Classic token kind for USDC"),
2370            }
2371
2372            // Verify second token (AQUA)
2373            let aqua_metadata = tokens[1].metadata.as_ref().unwrap();
2374            match &aqua_metadata.kind {
2375                StellarTokenKind::Classic { code, .. } => {
2376                    assert_eq!(code, "AQUA");
2377                }
2378                _ => panic!("Expected Classic token kind for AQUA"),
2379            }
2380
2381            // Verify max_allowed_fee is preserved
2382            assert_eq!(tokens[1].max_allowed_fee, Some(1000000));
2383        }
2384
2385        #[tokio::test]
2386        async fn test_populate_allowed_tokens_metadata_invalid_asset() {
2387            let ctx = TestCtx::default();
2388            ctx.setup_network().await;
2389            let mut relayer_model = ctx.relayer_model.clone();
2390
2391            // Set up allowed tokens with invalid asset format
2392            let mut policy = RelayerStellarPolicy::default();
2393            policy.allowed_tokens = Some(vec![crate::models::StellarAllowedTokensPolicy {
2394                asset: "INVALID_FORMAT".to_string(), // Missing issuer
2395                metadata: None,
2396                max_allowed_fee: None,
2397                swap_config: None,
2398            }]);
2399            relayer_model.policies = RelayerNetworkPolicy::Stellar(policy);
2400
2401            let provider = MockStellarProviderTrait::new();
2402            let signer = Arc::new(MockStellarSignTrait::new());
2403            let dex_service = create_mock_dex_service();
2404
2405            let relayer_repo = MockRelayerRepository::new();
2406            let tx_repo = MockTransactionRepository::new();
2407            let job_producer = MockJobProducerTrait::new();
2408            let counter = MockTransactionCounterServiceTrait::new();
2409
2410            let relayer = StellarRelayer::new(
2411                relayer_model.clone(),
2412                signer,
2413                provider,
2414                StellarRelayerDependencies::new(
2415                    Arc::new(relayer_repo),
2416                    ctx.network_repository.clone(),
2417                    Arc::new(tx_repo),
2418                    Arc::new(counter),
2419                    Arc::new(job_producer),
2420                ),
2421                dex_service,
2422            )
2423            .await
2424            .unwrap();
2425
2426            let result = relayer.populate_allowed_tokens_metadata().await;
2427            assert!(result.is_err());
2428        }
2429    }
2430
2431    // Tests for migrate_fee_payment_strategy_if_needed
2432    mod migrate_fee_payment_strategy_tests {
2433        use super::*;
2434
2435        #[tokio::test]
2436        async fn test_migrate_fee_payment_strategy_in_memory_storage() {
2437            let ctx = TestCtx::default();
2438            ctx.setup_network().await;
2439            let relayer_model = ctx.relayer_model.clone();
2440
2441            let provider = MockStellarProviderTrait::new();
2442            let signer = Arc::new(MockStellarSignTrait::new());
2443            let dex_service = create_mock_dex_service();
2444
2445            let mut relayer_repo = MockRelayerRepository::new();
2446            // Mock in-memory storage
2447            relayer_repo
2448                .expect_is_persistent_storage()
2449                .returning(|| false);
2450            // Should not call update_policy for in-memory storage
2451            relayer_repo.expect_update_policy().times(0);
2452
2453            let tx_repo = MockTransactionRepository::new();
2454            let job_producer = MockJobProducerTrait::new();
2455            let counter = MockTransactionCounterServiceTrait::new();
2456
2457            let relayer = StellarRelayer::new(
2458                relayer_model.clone(),
2459                signer,
2460                provider,
2461                StellarRelayerDependencies::new(
2462                    Arc::new(relayer_repo),
2463                    ctx.network_repository.clone(),
2464                    Arc::new(tx_repo),
2465                    Arc::new(counter),
2466                    Arc::new(job_producer),
2467                ),
2468                dex_service,
2469            )
2470            .await
2471            .unwrap();
2472
2473            let result = relayer.migrate_fee_payment_strategy_if_needed().await;
2474            assert!(result.is_ok());
2475        }
2476
2477        #[tokio::test]
2478        async fn test_migrate_fee_payment_strategy_already_set() {
2479            let ctx = TestCtx::default();
2480            ctx.setup_network().await;
2481            let mut relayer_model = ctx.relayer_model.clone();
2482
2483            // Set fee_payment_strategy
2484            let mut policy = RelayerStellarPolicy::default();
2485            policy.fee_payment_strategy = Some(StellarFeePaymentStrategy::User);
2486            relayer_model.policies = RelayerNetworkPolicy::Stellar(policy);
2487
2488            let provider = MockStellarProviderTrait::new();
2489            let signer = Arc::new(MockStellarSignTrait::new());
2490            let dex_service = create_mock_dex_service();
2491
2492            let mut relayer_repo = MockRelayerRepository::new();
2493            relayer_repo
2494                .expect_is_persistent_storage()
2495                .returning(|| true);
2496            // Should not call update_policy since already set
2497            relayer_repo.expect_update_policy().times(0);
2498
2499            let tx_repo = MockTransactionRepository::new();
2500            let job_producer = MockJobProducerTrait::new();
2501            let counter = MockTransactionCounterServiceTrait::new();
2502
2503            let relayer = StellarRelayer::new(
2504                relayer_model.clone(),
2505                signer,
2506                provider,
2507                StellarRelayerDependencies::new(
2508                    Arc::new(relayer_repo),
2509                    ctx.network_repository.clone(),
2510                    Arc::new(tx_repo),
2511                    Arc::new(counter),
2512                    Arc::new(job_producer),
2513                ),
2514                dex_service,
2515            )
2516            .await
2517            .unwrap();
2518
2519            let result = relayer.migrate_fee_payment_strategy_if_needed().await;
2520            assert!(result.is_ok());
2521        }
2522
2523        #[tokio::test]
2524        async fn test_migrate_fee_payment_strategy_migration_needed() {
2525            let ctx = TestCtx::default();
2526            ctx.setup_network().await;
2527            let relayer_model = ctx.relayer_model.clone();
2528
2529            let provider = MockStellarProviderTrait::new();
2530            let signer = Arc::new(MockStellarSignTrait::new());
2531            let dex_service = create_mock_dex_service();
2532
2533            let mut relayer_repo = MockRelayerRepository::new();
2534            relayer_repo
2535                .expect_is_persistent_storage()
2536                .returning(|| true);
2537            relayer_repo
2538                .expect_update_policy()
2539                .times(1)
2540                .returning(|_, policy| {
2541                    // Verify the policy is set to Relayer
2542                    if let RelayerNetworkPolicy::Stellar(stellar_policy) = &policy {
2543                        assert_eq!(
2544                            stellar_policy.fee_payment_strategy,
2545                            Some(StellarFeePaymentStrategy::Relayer)
2546                        );
2547                    }
2548                    Ok(RelayerRepoModel::default())
2549                });
2550
2551            let tx_repo = MockTransactionRepository::new();
2552            let job_producer = MockJobProducerTrait::new();
2553            let counter = MockTransactionCounterServiceTrait::new();
2554
2555            let relayer = StellarRelayer::new(
2556                relayer_model.clone(),
2557                signer,
2558                provider,
2559                StellarRelayerDependencies::new(
2560                    Arc::new(relayer_repo),
2561                    ctx.network_repository.clone(),
2562                    Arc::new(tx_repo),
2563                    Arc::new(counter),
2564                    Arc::new(job_producer),
2565                ),
2566                dex_service,
2567            )
2568            .await
2569            .unwrap();
2570
2571            let result = relayer.migrate_fee_payment_strategy_if_needed().await;
2572            assert!(result.is_ok());
2573        }
2574
2575        #[tokio::test]
2576        async fn test_migrate_fee_payment_strategy_update_fails() {
2577            let ctx = TestCtx::default();
2578            ctx.setup_network().await;
2579            let relayer_model = ctx.relayer_model.clone();
2580
2581            let provider = MockStellarProviderTrait::new();
2582            let signer = Arc::new(MockStellarSignTrait::new());
2583            let dex_service = create_mock_dex_service();
2584
2585            let mut relayer_repo = MockRelayerRepository::new();
2586            relayer_repo
2587                .expect_is_persistent_storage()
2588                .returning(|| true);
2589            relayer_repo
2590                .expect_update_policy()
2591                .times(1)
2592                .returning(|_, _| {
2593                    Err(RepositoryError::TransactionFailure(
2594                        "Database error".to_string(),
2595                    ))
2596                });
2597
2598            let tx_repo = MockTransactionRepository::new();
2599            let job_producer = MockJobProducerTrait::new();
2600            let counter = MockTransactionCounterServiceTrait::new();
2601
2602            let relayer = StellarRelayer::new(
2603                relayer_model.clone(),
2604                signer,
2605                provider,
2606                StellarRelayerDependencies::new(
2607                    Arc::new(relayer_repo),
2608                    ctx.network_repository.clone(),
2609                    Arc::new(tx_repo),
2610                    Arc::new(counter),
2611                    Arc::new(job_producer),
2612                ),
2613                dex_service,
2614            )
2615            .await
2616            .unwrap();
2617
2618            let result = relayer.migrate_fee_payment_strategy_if_needed().await;
2619            assert!(result.is_err());
2620            assert!(matches!(
2621                result.unwrap_err(),
2622                RelayerError::PolicyConfigurationError(_)
2623            ));
2624        }
2625    }
2626
2627    // Tests for check_balance_and_trigger_token_swap_if_needed
2628    mod check_balance_and_trigger_token_swap_tests {
2629        use super::*;
2630        use crate::models::RelayerStellarSwapConfig;
2631
2632        #[tokio::test]
2633        async fn test_check_balance_no_swap_config() {
2634            let ctx = TestCtx::default();
2635            ctx.setup_network().await;
2636            let relayer_model = ctx.relayer_model.clone();
2637
2638            let provider = MockStellarProviderTrait::new();
2639            let signer = Arc::new(MockStellarSignTrait::new());
2640            let dex_service = create_mock_dex_service();
2641
2642            let relayer_repo = MockRelayerRepository::new();
2643            let tx_repo = MockTransactionRepository::new();
2644            let job_producer = MockJobProducerTrait::new();
2645            let counter = MockTransactionCounterServiceTrait::new();
2646
2647            let relayer = StellarRelayer::new(
2648                relayer_model.clone(),
2649                signer,
2650                provider,
2651                StellarRelayerDependencies::new(
2652                    Arc::new(relayer_repo),
2653                    ctx.network_repository.clone(),
2654                    Arc::new(tx_repo),
2655                    Arc::new(counter),
2656                    Arc::new(job_producer),
2657                ),
2658                dex_service,
2659            )
2660            .await
2661            .unwrap();
2662
2663            let result = relayer
2664                .check_balance_and_trigger_token_swap_if_needed()
2665                .await;
2666            assert!(result.is_ok());
2667        }
2668
2669        #[tokio::test]
2670        async fn test_check_balance_no_threshold() {
2671            let ctx = TestCtx::default();
2672            ctx.setup_network().await;
2673            let mut relayer_model = ctx.relayer_model.clone();
2674
2675            // Set up swap config without threshold
2676            let mut policy = RelayerStellarPolicy::default();
2677            policy.swap_config = Some(RelayerStellarSwapConfig {
2678                strategies: vec![],
2679                min_balance_threshold: None,
2680                cron_schedule: None,
2681            });
2682            relayer_model.policies = RelayerNetworkPolicy::Stellar(policy);
2683
2684            let provider = MockStellarProviderTrait::new();
2685            let signer = Arc::new(MockStellarSignTrait::new());
2686            let dex_service = create_mock_dex_service();
2687
2688            let relayer_repo = MockRelayerRepository::new();
2689            let tx_repo = MockTransactionRepository::new();
2690            let job_producer = MockJobProducerTrait::new();
2691            let counter = MockTransactionCounterServiceTrait::new();
2692
2693            let relayer = StellarRelayer::new(
2694                relayer_model.clone(),
2695                signer,
2696                provider,
2697                StellarRelayerDependencies::new(
2698                    Arc::new(relayer_repo),
2699                    ctx.network_repository.clone(),
2700                    Arc::new(tx_repo),
2701                    Arc::new(counter),
2702                    Arc::new(job_producer),
2703                ),
2704                dex_service,
2705            )
2706            .await
2707            .unwrap();
2708
2709            let result = relayer
2710                .check_balance_and_trigger_token_swap_if_needed()
2711                .await;
2712            assert!(result.is_ok());
2713        }
2714
2715        #[tokio::test]
2716        async fn test_check_balance_above_threshold() {
2717            let ctx = TestCtx::default();
2718            ctx.setup_network().await;
2719            let mut relayer_model = ctx.relayer_model.clone();
2720
2721            // Set up swap config with threshold
2722            let mut policy = RelayerStellarPolicy::default();
2723            policy.swap_config = Some(RelayerStellarSwapConfig {
2724                strategies: vec![],
2725                min_balance_threshold: Some(1000000), // 1 XLM
2726                cron_schedule: None,
2727            });
2728            relayer_model.policies = RelayerNetworkPolicy::Stellar(policy);
2729
2730            let mut provider = MockStellarProviderTrait::new();
2731            // Mock get_account to return balance above threshold
2732            provider.expect_get_account().returning(|_| {
2733                Box::pin(async {
2734                    Ok(AccountEntry {
2735                        account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
2736                        balance: 10000000, // 10 XLM (above threshold)
2737                        ext: AccountEntryExt::V0,
2738                        flags: 0,
2739                        home_domain: String32::default(),
2740                        inflation_dest: None,
2741                        seq_num: SequenceNumber(5),
2742                        num_sub_entries: 0,
2743                        signers: VecM::default(),
2744                        thresholds: Thresholds([0, 0, 0, 0]),
2745                    })
2746                })
2747            });
2748
2749            let signer = Arc::new(MockStellarSignTrait::new());
2750            let dex_service = create_mock_dex_service();
2751
2752            let relayer_repo = MockRelayerRepository::new();
2753            let tx_repo = MockTransactionRepository::new();
2754            let job_producer = MockJobProducerTrait::new();
2755            let counter = MockTransactionCounterServiceTrait::new();
2756
2757            let relayer = StellarRelayer::new(
2758                relayer_model.clone(),
2759                signer,
2760                provider,
2761                StellarRelayerDependencies::new(
2762                    Arc::new(relayer_repo),
2763                    ctx.network_repository.clone(),
2764                    Arc::new(tx_repo),
2765                    Arc::new(counter),
2766                    Arc::new(job_producer),
2767                ),
2768                dex_service,
2769            )
2770            .await
2771            .unwrap();
2772
2773            let result = relayer
2774                .check_balance_and_trigger_token_swap_if_needed()
2775                .await;
2776            assert!(result.is_ok());
2777        }
2778
2779        #[tokio::test]
2780        async fn test_check_balance_provider_error() {
2781            let ctx = TestCtx::default();
2782            ctx.setup_network().await;
2783            let mut relayer_model = ctx.relayer_model.clone();
2784
2785            // Set up swap config with threshold
2786            let mut policy = RelayerStellarPolicy::default();
2787            policy.swap_config = Some(RelayerStellarSwapConfig {
2788                strategies: vec![],
2789                min_balance_threshold: Some(1000000),
2790                cron_schedule: None,
2791            });
2792            relayer_model.policies = RelayerNetworkPolicy::Stellar(policy);
2793
2794            let mut provider = MockStellarProviderTrait::new();
2795            provider.expect_get_account().returning(|_| {
2796                Box::pin(async { Err(ProviderError::Other("Network error".to_string())) })
2797            });
2798
2799            let signer = Arc::new(MockStellarSignTrait::new());
2800            let dex_service = create_mock_dex_service();
2801
2802            let relayer_repo = MockRelayerRepository::new();
2803            let tx_repo = MockTransactionRepository::new();
2804            let job_producer = MockJobProducerTrait::new();
2805            let counter = MockTransactionCounterServiceTrait::new();
2806
2807            let relayer = StellarRelayer::new(
2808                relayer_model.clone(),
2809                signer,
2810                provider,
2811                StellarRelayerDependencies::new(
2812                    Arc::new(relayer_repo),
2813                    ctx.network_repository.clone(),
2814                    Arc::new(tx_repo),
2815                    Arc::new(counter),
2816                    Arc::new(job_producer),
2817                ),
2818                dex_service,
2819            )
2820            .await
2821            .unwrap();
2822
2823            let result = relayer
2824                .check_balance_and_trigger_token_swap_if_needed()
2825                .await;
2826            assert!(result.is_err());
2827        }
2828    }
2829
2830    // Tests for check_health
2831    mod check_health_tests {
2832        use super::*;
2833        use crate::models::RelayerStellarSwapConfig;
2834
2835        #[tokio::test]
2836        async fn test_check_health_success() {
2837            let ctx = TestCtx::default();
2838            ctx.setup_network().await;
2839            let relayer_model = ctx.relayer_model.clone();
2840
2841            let mut provider = MockStellarProviderTrait::new();
2842            provider.expect_get_account().returning(|_| {
2843                Box::pin(async {
2844                    Ok(AccountEntry {
2845                        account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
2846                        balance: 10000000,
2847                        ext: AccountEntryExt::V0,
2848                        flags: 0,
2849                        home_domain: String32::default(),
2850                        inflation_dest: None,
2851                        seq_num: SequenceNumber(5),
2852                        num_sub_entries: 0,
2853                        signers: VecM::default(),
2854                        thresholds: Thresholds([0, 0, 0, 0]),
2855                    })
2856                })
2857            });
2858
2859            let signer = Arc::new(MockStellarSignTrait::new());
2860            let dex_service = create_mock_dex_service();
2861
2862            let relayer_repo = MockRelayerRepository::new();
2863            let tx_repo = MockTransactionRepository::new();
2864            let job_producer = MockJobProducerTrait::new();
2865
2866            let mut counter = MockTransactionCounterServiceTrait::new();
2867            counter
2868                .expect_set()
2869                .returning(|_| Box::pin(async { Ok(()) }));
2870
2871            let relayer = StellarRelayer::new(
2872                relayer_model.clone(),
2873                signer,
2874                provider,
2875                StellarRelayerDependencies::new(
2876                    Arc::new(relayer_repo),
2877                    ctx.network_repository.clone(),
2878                    Arc::new(tx_repo),
2879                    Arc::new(counter),
2880                    Arc::new(job_producer),
2881                ),
2882                dex_service,
2883            )
2884            .await
2885            .unwrap();
2886
2887            let result = relayer.check_health().await;
2888            assert!(result.is_ok());
2889        }
2890
2891        #[tokio::test]
2892        async fn test_check_health_sequence_sync_fails() {
2893            let ctx = TestCtx::default();
2894            ctx.setup_network().await;
2895            let relayer_model = ctx.relayer_model.clone();
2896
2897            let mut provider = MockStellarProviderTrait::new();
2898            provider.expect_get_account().returning(|_| {
2899                Box::pin(async { Err(ProviderError::Other("Network error".to_string())) })
2900            });
2901
2902            let signer = Arc::new(MockStellarSignTrait::new());
2903            let dex_service = create_mock_dex_service();
2904
2905            let relayer_repo = MockRelayerRepository::new();
2906            let tx_repo = MockTransactionRepository::new();
2907            let job_producer = MockJobProducerTrait::new();
2908            let counter = MockTransactionCounterServiceTrait::new();
2909
2910            let relayer = StellarRelayer::new(
2911                relayer_model.clone(),
2912                signer,
2913                provider,
2914                StellarRelayerDependencies::new(
2915                    Arc::new(relayer_repo),
2916                    ctx.network_repository.clone(),
2917                    Arc::new(tx_repo),
2918                    Arc::new(counter),
2919                    Arc::new(job_producer),
2920                ),
2921                dex_service,
2922            )
2923            .await
2924            .unwrap();
2925
2926            let result = relayer.check_health().await;
2927            assert!(result.is_err());
2928            let failures = result.unwrap_err();
2929            assert_eq!(failures.len(), 1);
2930            assert!(matches!(
2931                failures[0],
2932                HealthCheckFailure::SequenceSyncFailed(_)
2933            ));
2934        }
2935
2936        #[tokio::test]
2937        async fn test_check_health_with_user_fee_strategy() {
2938            let ctx = TestCtx::default();
2939            ctx.setup_network().await;
2940            let mut relayer_model = ctx.relayer_model.clone();
2941
2942            // Set up user fee payment strategy
2943            let mut policy = RelayerStellarPolicy::default();
2944            policy.fee_payment_strategy = Some(StellarFeePaymentStrategy::User);
2945            policy.swap_config = Some(RelayerStellarSwapConfig {
2946                strategies: vec![],
2947                min_balance_threshold: Some(1000000),
2948                cron_schedule: None,
2949            });
2950            relayer_model.policies = RelayerNetworkPolicy::Stellar(policy);
2951
2952            let mut provider = MockStellarProviderTrait::new();
2953            provider.expect_get_account().returning(|_| {
2954                Box::pin(async {
2955                    Ok(AccountEntry {
2956                        account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
2957                        balance: 10000000, // Above threshold
2958                        ext: AccountEntryExt::V0,
2959                        flags: 0,
2960                        home_domain: String32::default(),
2961                        inflation_dest: None,
2962                        seq_num: SequenceNumber(5),
2963                        num_sub_entries: 0,
2964                        signers: VecM::default(),
2965                        thresholds: Thresholds([0, 0, 0, 0]),
2966                    })
2967                })
2968            });
2969
2970            let signer = Arc::new(MockStellarSignTrait::new());
2971            let dex_service = create_mock_dex_service();
2972
2973            let relayer_repo = MockRelayerRepository::new();
2974            let tx_repo = MockTransactionRepository::new();
2975            let job_producer = MockJobProducerTrait::new();
2976
2977            let mut counter = MockTransactionCounterServiceTrait::new();
2978            counter
2979                .expect_set()
2980                .returning(|_| Box::pin(async { Ok(()) }));
2981
2982            let relayer = StellarRelayer::new(
2983                relayer_model.clone(),
2984                signer,
2985                provider,
2986                StellarRelayerDependencies::new(
2987                    Arc::new(relayer_repo),
2988                    ctx.network_repository.clone(),
2989                    Arc::new(tx_repo),
2990                    Arc::new(counter),
2991                    Arc::new(job_producer),
2992                ),
2993                dex_service,
2994            )
2995            .await
2996            .unwrap();
2997
2998            let result = relayer.check_health().await;
2999            // Should pass even with user fee strategy
3000            assert!(result.is_ok());
3001        }
3002    }
3003
3004    // Tests for RPC method
3005    mod rpc_tests {
3006        use super::*;
3007        use crate::models::{JsonRpcId, StellarRpcRequest};
3008
3009        #[tokio::test]
3010        async fn test_rpc_invalid_network_request() {
3011            let ctx = TestCtx::default();
3012            ctx.setup_network().await;
3013            let relayer_model = ctx.relayer_model.clone();
3014
3015            let provider = MockStellarProviderTrait::new();
3016            let signer = Arc::new(MockStellarSignTrait::new());
3017            let dex_service = create_mock_dex_service();
3018
3019            let relayer_repo = MockRelayerRepository::new();
3020            let tx_repo = MockTransactionRepository::new();
3021            let job_producer = MockJobProducerTrait::new();
3022            let counter = MockTransactionCounterServiceTrait::new();
3023
3024            let relayer = StellarRelayer::new(
3025                relayer_model.clone(),
3026                signer,
3027                provider,
3028                StellarRelayerDependencies::new(
3029                    Arc::new(relayer_repo),
3030                    ctx.network_repository.clone(),
3031                    Arc::new(tx_repo),
3032                    Arc::new(counter),
3033                    Arc::new(job_producer),
3034                ),
3035                dex_service,
3036            )
3037            .await
3038            .unwrap();
3039
3040            // Create a request with wrong network type
3041            let request = JsonRpcRequest {
3042                jsonrpc: "2.0".to_string(),
3043                id: Some(JsonRpcId::Number(1)),
3044                params: NetworkRpcRequest::Evm(crate::models::EvmRpcRequest::RawRpcRequest {
3045                    method: "eth_blockNumber".to_string(),
3046                    params: serde_json::Value::Null,
3047                }),
3048            };
3049
3050            let result = relayer.rpc(request).await;
3051            assert!(result.is_ok());
3052            let response = result.unwrap();
3053            // Should return an error response for invalid network type
3054            assert!(response.error.is_some());
3055        }
3056
3057        #[tokio::test]
3058        async fn test_rpc_provider_error() {
3059            let ctx = TestCtx::default();
3060            ctx.setup_network().await;
3061            let relayer_model = ctx.relayer_model.clone();
3062
3063            let mut provider = MockStellarProviderTrait::new();
3064            provider.expect_raw_request_dyn().returning(|_, _, _| {
3065                Box::pin(async { Err(ProviderError::Other("RPC error".to_string())) })
3066            });
3067
3068            let signer = Arc::new(MockStellarSignTrait::new());
3069            let dex_service = create_mock_dex_service();
3070
3071            let relayer_repo = MockRelayerRepository::new();
3072            let tx_repo = MockTransactionRepository::new();
3073            let job_producer = MockJobProducerTrait::new();
3074            let counter = MockTransactionCounterServiceTrait::new();
3075
3076            let relayer = StellarRelayer::new(
3077                relayer_model.clone(),
3078                signer,
3079                provider,
3080                StellarRelayerDependencies::new(
3081                    Arc::new(relayer_repo),
3082                    ctx.network_repository.clone(),
3083                    Arc::new(tx_repo),
3084                    Arc::new(counter),
3085                    Arc::new(job_producer),
3086                ),
3087                dex_service,
3088            )
3089            .await
3090            .unwrap();
3091
3092            let request = JsonRpcRequest {
3093                jsonrpc: "2.0".to_string(),
3094                id: Some(JsonRpcId::Number(1)),
3095                params: NetworkRpcRequest::Stellar(StellarRpcRequest::RawRpcRequest {
3096                    method: "getHealth".to_string(),
3097                    params: serde_json::Value::Null,
3098                }),
3099            };
3100
3101            let result = relayer.rpc(request).await;
3102            assert!(result.is_ok());
3103            let response = result.unwrap();
3104            // Should return an error response for provider error
3105            assert!(response.error.is_some());
3106        }
3107
3108        #[tokio::test]
3109        async fn test_rpc_success() {
3110            let ctx = TestCtx::default();
3111            ctx.setup_network().await;
3112            let relayer_model = ctx.relayer_model.clone();
3113
3114            let mut provider = MockStellarProviderTrait::new();
3115            provider.expect_raw_request_dyn().returning(|_, _, _| {
3116                Box::pin(async { Ok(serde_json::json!({"status": "healthy"})) })
3117            });
3118
3119            let signer = Arc::new(MockStellarSignTrait::new());
3120            let dex_service = create_mock_dex_service();
3121
3122            let relayer_repo = MockRelayerRepository::new();
3123            let tx_repo = MockTransactionRepository::new();
3124            let job_producer = MockJobProducerTrait::new();
3125            let counter = MockTransactionCounterServiceTrait::new();
3126
3127            let relayer = StellarRelayer::new(
3128                relayer_model.clone(),
3129                signer,
3130                provider,
3131                StellarRelayerDependencies::new(
3132                    Arc::new(relayer_repo),
3133                    ctx.network_repository.clone(),
3134                    Arc::new(tx_repo),
3135                    Arc::new(counter),
3136                    Arc::new(job_producer),
3137                ),
3138                dex_service,
3139            )
3140            .await
3141            .unwrap();
3142
3143            let request = JsonRpcRequest {
3144                jsonrpc: "2.0".to_string(),
3145                id: Some(JsonRpcId::Number(1)),
3146                params: NetworkRpcRequest::Stellar(StellarRpcRequest::RawRpcRequest {
3147                    method: "getHealth".to_string(),
3148                    params: serde_json::Value::Null,
3149                }),
3150            };
3151
3152            let result = relayer.rpc(request).await;
3153            assert!(result.is_ok());
3154            let response = result.unwrap();
3155            assert!(response.error.is_none());
3156            assert!(response.result.is_some());
3157        }
3158    }
3159}