1use crate::{
15 constants::{STELLAR_HORIZON_MAINNET_URL, STELLAR_HORIZON_TESTNET_URL},
16 jobs::{JobProducer, StatusCheckContext},
17 models::{
18 EvmNetwork, NetworkTransactionRequest, NetworkType, RelayerRepoModel, SignerRepoModel,
19 SolanaNetwork, StellarNetwork, StellarSwapStrategy, TransactionError, TransactionRepoModel,
20 },
21 repositories::{
22 NetworkRepository, NetworkRepositoryStorage, RelayerRepositoryStorage,
23 TransactionCounterRepositoryStorage, TransactionRepositoryStorage,
24 },
25 services::{
26 gas::{
27 cache::GasPriceCache, evm_gas_price::EvmGasPriceService,
28 price_params_handler::PriceParamsHandler,
29 },
30 provider::get_network_provider,
31 signer::{EvmSignerFactory, SolanaSignerFactory, StellarSignerFactory},
32 stellar_dex::{DexServiceWrapper, OrderBookService, SoroswapService, StellarDexService},
33 },
34};
35use async_trait::async_trait;
36use eyre::Result;
37#[cfg(test)]
38use mockall::automock;
39use std::sync::Arc;
40use tracing::instrument;
41
42pub mod common;
43pub mod evm;
44pub mod solana;
45pub mod stellar;
46
47mod util;
48pub use util::*;
49
50pub use common::is_final_state;
52pub use common::*;
53pub use evm::{ensure_status, ensure_status_one_of, DefaultEvmTransaction, EvmRelayerTransaction};
54pub use solana::{DefaultSolanaTransaction, SolanaRelayerTransaction};
55pub use stellar::{DefaultStellarTransaction, StellarRelayerTransaction};
56
57#[cfg_attr(test, automock)]
59#[async_trait]
60#[allow(dead_code)]
61pub trait Transaction {
62 async fn prepare_transaction(
72 &self,
73 tx: TransactionRepoModel,
74 ) -> Result<TransactionRepoModel, TransactionError>;
75
76 async fn submit_transaction(
86 &self,
87 tx: TransactionRepoModel,
88 ) -> Result<TransactionRepoModel, TransactionError>;
89
90 async fn resubmit_transaction(
100 &self,
101 tx: TransactionRepoModel,
102 ) -> Result<TransactionRepoModel, TransactionError>;
103
104 async fn handle_transaction_status(
118 &self,
119 tx: TransactionRepoModel,
120 context: Option<StatusCheckContext>,
121 ) -> Result<TransactionRepoModel, TransactionError>;
122
123 async fn cancel_transaction(
133 &self,
134 tx: TransactionRepoModel,
135 ) -> Result<TransactionRepoModel, TransactionError>;
136
137 async fn replace_transaction(
148 &self,
149 old_tx: TransactionRepoModel,
150 new_tx_request: NetworkTransactionRequest,
151 ) -> Result<TransactionRepoModel, TransactionError>;
152
153 async fn sign_transaction(
163 &self,
164 tx: TransactionRepoModel,
165 ) -> Result<TransactionRepoModel, TransactionError>;
166
167 async fn validate_transaction(
178 &self,
179 tx: TransactionRepoModel,
180 ) -> Result<bool, TransactionError>;
181}
182
183pub enum NetworkTransaction {
185 Evm(Box<DefaultEvmTransaction>),
186 Solana(DefaultSolanaTransaction),
187 Stellar(DefaultStellarTransaction),
188}
189
190#[async_trait]
191impl Transaction for NetworkTransaction {
192 #[instrument(
202 level = "debug",
203 skip(self, tx),
204 fields(
205 request_id = ?crate::observability::request_id::get_request_id(),
206 tx_id = %tx.id,
207 relayer_id = %tx.relayer_id,
208 tx_status = ?tx.status,
209 network_type = ?tx.network_type,
210 )
211 )]
212 async fn prepare_transaction(
213 &self,
214 tx: TransactionRepoModel,
215 ) -> Result<TransactionRepoModel, TransactionError> {
216 match self {
217 NetworkTransaction::Evm(relayer) => relayer.prepare_transaction(tx).await,
218 NetworkTransaction::Solana(relayer) => relayer.prepare_transaction(tx).await,
219 NetworkTransaction::Stellar(relayer) => relayer.prepare_transaction(tx).await,
220 }
221 }
222
223 #[instrument(
233 level = "debug",
234 skip(self, tx),
235 fields(
236 request_id = ?crate::observability::request_id::get_request_id(),
237 tx_id = %tx.id,
238 relayer_id = %tx.relayer_id,
239 tx_status = ?tx.status,
240 network_type = ?tx.network_type,
241 )
242 )]
243 async fn submit_transaction(
244 &self,
245 tx: TransactionRepoModel,
246 ) -> Result<TransactionRepoModel, TransactionError> {
247 match self {
248 NetworkTransaction::Evm(relayer) => relayer.submit_transaction(tx).await,
249 NetworkTransaction::Solana(relayer) => relayer.submit_transaction(tx).await,
250 NetworkTransaction::Stellar(relayer) => relayer.submit_transaction(tx).await,
251 }
252 }
253 #[instrument(
263 level = "debug",
264 skip(self, tx),
265 fields(
266 request_id = ?crate::observability::request_id::get_request_id(),
267 tx_id = %tx.id,
268 relayer_id = %tx.relayer_id,
269 tx_status = ?tx.status,
270 network_type = ?tx.network_type,
271 )
272 )]
273 async fn resubmit_transaction(
274 &self,
275 tx: TransactionRepoModel,
276 ) -> Result<TransactionRepoModel, TransactionError> {
277 match self {
278 NetworkTransaction::Evm(relayer) => relayer.resubmit_transaction(tx).await,
279 NetworkTransaction::Solana(relayer) => relayer.resubmit_transaction(tx).await,
280 NetworkTransaction::Stellar(relayer) => relayer.resubmit_transaction(tx).await,
281 }
282 }
283
284 #[instrument(
297 level = "debug",
298 skip(self, tx, context),
299 fields(
300 request_id = ?crate::observability::request_id::get_request_id(),
301 tx_id = %tx.id,
302 relayer_id = %tx.relayer_id,
303 tx_status = ?tx.status,
304 network_type = ?tx.network_type,
305 has_context = %context.is_some(),
306 )
307 )]
308 async fn handle_transaction_status(
309 &self,
310 tx: TransactionRepoModel,
311 context: Option<StatusCheckContext>,
312 ) -> Result<TransactionRepoModel, TransactionError> {
313 match self {
314 NetworkTransaction::Evm(relayer) => {
315 relayer.handle_transaction_status(tx, context).await
316 }
317 NetworkTransaction::Solana(relayer) => {
318 relayer.handle_transaction_status(tx, context).await
319 }
320 NetworkTransaction::Stellar(relayer) => {
321 relayer.handle_transaction_status(tx, context).await
322 }
323 }
324 }
325
326 #[instrument(
336 level = "debug",
337 skip(self, tx),
338 fields(
339 request_id = ?crate::observability::request_id::get_request_id(),
340 tx_id = %tx.id,
341 relayer_id = %tx.relayer_id,
342 tx_status = ?tx.status,
343 network_type = ?tx.network_type,
344 )
345 )]
346 async fn cancel_transaction(
347 &self,
348 tx: TransactionRepoModel,
349 ) -> Result<TransactionRepoModel, TransactionError> {
350 match self {
351 NetworkTransaction::Evm(relayer) => relayer.cancel_transaction(tx).await,
352 NetworkTransaction::Solana(_) => solana_not_supported_transaction(),
353 NetworkTransaction::Stellar(relayer) => relayer.cancel_transaction(tx).await,
354 }
355 }
356
357 #[instrument(
368 level = "debug",
369 skip(self, old_tx, new_tx_request),
370 fields(
371 request_id = ?crate::observability::request_id::get_request_id(),
372 tx_id = %old_tx.id,
373 relayer_id = %old_tx.relayer_id,
374 tx_status = ?old_tx.status,
375 network_type = ?old_tx.network_type,
376 )
377 )]
378 async fn replace_transaction(
379 &self,
380 old_tx: TransactionRepoModel,
381 new_tx_request: NetworkTransactionRequest,
382 ) -> Result<TransactionRepoModel, TransactionError> {
383 match self {
384 NetworkTransaction::Evm(relayer) => {
385 relayer.replace_transaction(old_tx, new_tx_request).await
386 }
387 NetworkTransaction::Solana(_) => solana_not_supported_transaction(),
388 NetworkTransaction::Stellar(relayer) => {
389 relayer.replace_transaction(old_tx, new_tx_request).await
390 }
391 }
392 }
393
394 #[instrument(
404 level = "debug",
405 skip(self, tx),
406 fields(
407 request_id = ?crate::observability::request_id::get_request_id(),
408 tx_id = %tx.id,
409 relayer_id = %tx.relayer_id,
410 tx_status = ?tx.status,
411 network_type = ?tx.network_type,
412 )
413 )]
414 async fn sign_transaction(
415 &self,
416 tx: TransactionRepoModel,
417 ) -> Result<TransactionRepoModel, TransactionError> {
418 match self {
419 NetworkTransaction::Evm(relayer) => relayer.sign_transaction(tx).await,
420 NetworkTransaction::Solana(relayer) => relayer.sign_transaction(tx).await,
421 NetworkTransaction::Stellar(relayer) => relayer.sign_transaction(tx).await,
422 }
423 }
424
425 #[instrument(
436 level = "debug",
437 skip(self, tx),
438 fields(
439 request_id = ?crate::observability::request_id::get_request_id(),
440 tx_id = %tx.id,
441 relayer_id = %tx.relayer_id,
442 tx_status = ?tx.status,
443 network_type = ?tx.network_type,
444 )
445 )]
446 async fn validate_transaction(
447 &self,
448 tx: TransactionRepoModel,
449 ) -> Result<bool, TransactionError> {
450 match self {
451 NetworkTransaction::Evm(relayer) => relayer.validate_transaction(tx).await,
452 NetworkTransaction::Solana(relayer) => relayer.validate_transaction(tx).await,
453 NetworkTransaction::Stellar(relayer) => relayer.validate_transaction(tx).await,
454 }
455 }
456}
457
458#[allow(dead_code)]
460pub trait RelayerTransactionFactoryTrait {
461 fn create_transaction(
474 relayer: RelayerRepoModel,
475 relayer_repository: Arc<RelayerRepositoryStorage>,
476 transaction_repository: Arc<TransactionRepositoryStorage>,
477 job_producer: Arc<JobProducer>,
478 ) -> Result<NetworkTransaction, TransactionError>;
479}
480pub struct RelayerTransactionFactory;
482
483#[allow(dead_code)]
484impl RelayerTransactionFactory {
485 #[instrument(
500 level = "debug",
501 skip(
502 relayer,
503 signer,
504 relayer_repository,
505 network_repository,
506 transaction_repository,
507 transaction_counter_store,
508 job_producer
509 ),
510 fields(
511 request_id = ?crate::observability::request_id::get_request_id(),
512 relayer_id = %relayer.id,
513 network_type = ?relayer.network_type,
514 )
515 )]
516 pub async fn create_transaction(
517 relayer: RelayerRepoModel,
518 signer: SignerRepoModel,
519 relayer_repository: Arc<RelayerRepositoryStorage>,
520 network_repository: Arc<NetworkRepositoryStorage>,
521 transaction_repository: Arc<TransactionRepositoryStorage>,
522 transaction_counter_store: Arc<TransactionCounterRepositoryStorage>,
523 job_producer: Arc<JobProducer>,
524 ) -> Result<NetworkTransaction, TransactionError> {
525 match relayer.network_type {
526 NetworkType::Evm => {
527 let network_repo = network_repository
528 .get_by_name(NetworkType::Evm, &relayer.network)
529 .await
530 .ok()
531 .flatten()
532 .ok_or_else(|| {
533 TransactionError::NetworkConfiguration(format!(
534 "Network {} not found",
535 relayer.network
536 ))
537 })?;
538
539 let network = EvmNetwork::try_from(network_repo)
540 .map_err(|e| TransactionError::NetworkConfiguration(e.to_string()))?;
541
542 let evm_provider = get_network_provider(&network, relayer.custom_rpc_urls.clone())?;
543 let signer_service = EvmSignerFactory::create_evm_signer(signer.into()).await?;
544 let price_params_handler =
545 PriceParamsHandler::for_network(&network, evm_provider.clone());
546
547 let evm_gas_cache = GasPriceCache::global();
548
549 let cache = if let Some(cfg) = &network.gas_price_cache {
551 evm_gas_cache.configure_network(network.chain_id, cfg.clone());
552 Some(evm_gas_cache.clone())
553 } else {
554 if evm_gas_cache.has_configuration_for_network(network.chain_id) {
555 evm_gas_cache.remove_network(network.chain_id);
556 }
557 None
558 };
559
560 let gas_price_service =
561 EvmGasPriceService::new(evm_provider.clone(), network.clone(), cache);
562
563 let price_calculator =
564 evm::PriceCalculator::new(gas_price_service, price_params_handler);
565
566 Ok(NetworkTransaction::Evm(Box::new(
567 DefaultEvmTransaction::new(
568 relayer,
569 evm_provider,
570 relayer_repository,
571 network_repository,
572 transaction_repository,
573 transaction_counter_store,
574 job_producer,
575 price_calculator,
576 signer_service,
577 )?,
578 )))
579 }
580 NetworkType::Solana => {
581 let network_repo = network_repository
582 .get_by_name(NetworkType::Solana, &relayer.network)
583 .await
584 .ok()
585 .flatten()
586 .ok_or_else(|| {
587 TransactionError::NetworkConfiguration(format!(
588 "Network {} not found",
589 relayer.network
590 ))
591 })?;
592
593 let network = SolanaNetwork::try_from(network_repo)
594 .map_err(|e| TransactionError::NetworkConfiguration(e.to_string()))?;
595
596 let solana_provider = Arc::new(get_network_provider(
597 &network,
598 relayer.custom_rpc_urls.clone(),
599 )?);
600
601 let signer_service =
602 Arc::new(SolanaSignerFactory::create_solana_signer(&signer.into())?);
603
604 Ok(NetworkTransaction::Solana(SolanaRelayerTransaction::new(
605 relayer,
606 relayer_repository,
607 solana_provider,
608 transaction_repository,
609 job_producer,
610 signer_service,
611 )?))
612 }
613 NetworkType::Stellar => {
614 let stellar_signer = StellarSignerFactory::create_stellar_signer(&signer.into())?;
617 let signer_service = Arc::new(stellar_signer);
618
619 let network_repo = network_repository
620 .get_by_name(NetworkType::Stellar, &relayer.network)
621 .await
622 .ok()
623 .flatten()
624 .ok_or_else(|| {
625 TransactionError::NetworkConfiguration(format!(
626 "Network {} not found",
627 relayer.network
628 ))
629 })?;
630
631 let network = StellarNetwork::try_from(network_repo)
632 .map_err(|e| TransactionError::NetworkConfiguration(e.to_string()))?;
633
634 let stellar_provider =
635 get_network_provider(&network, relayer.custom_rpc_urls.clone())
636 .map_err(|e| TransactionError::NetworkConfiguration(e.to_string()))?;
637
638 let horizon_url = network.horizon_url.clone().unwrap_or_else(|| {
640 if network.is_testnet() {
641 STELLAR_HORIZON_TESTNET_URL.to_string()
642 } else {
643 STELLAR_HORIZON_MAINNET_URL.to_string()
644 }
645 });
646 let provider_arc = Arc::new(stellar_provider.clone());
647 let signer_arc = signer_service.clone();
649
650 let strategies = relayer
652 .policies
653 .get_stellar_policy()
654 .get_swap_config()
655 .and_then(|config| {
656 if config.strategies.is_empty() {
657 None
658 } else {
659 Some(config.strategies.clone())
660 }
661 })
662 .unwrap_or_else(|| vec![StellarSwapStrategy::OrderBook]);
663
664 let mut dex_services: Vec<DexServiceWrapper<_, _>> = Vec::new();
666 for strategy in &strategies {
667 match strategy {
668 StellarSwapStrategy::OrderBook => {
669 let order_book_service = Arc::new(
670 OrderBookService::new(
671 horizon_url.clone(),
672 provider_arc.clone(),
673 signer_arc.clone(),
674 )
675 .map_err(|e| {
676 TransactionError::NetworkConfiguration(format!(
677 "Failed to create OrderBook DEX service: {e}"
678 ))
679 })?,
680 );
681 dex_services.push(DexServiceWrapper::OrderBook(order_book_service));
682 }
683 StellarSwapStrategy::Soroswap => {
684 let is_testnet = network.is_testnet();
685 let network_label = if is_testnet { "TESTNET" } else { "MAINNET" };
686
687 let router_address =
688 crate::config::ServerConfig::resolve_stellar_soroswap_router_address(is_testnet)
689 .ok_or_else(|| {
690 eyre::eyre!(
691 "Soroswap router address not configured. Set STELLAR_{network_label}_SOROSWAP_ROUTER_ADDRESS env var."
692 )
693 })?;
694 let factory_address =
695 crate::config::ServerConfig::resolve_stellar_soroswap_factory_address(is_testnet)
696 .ok_or_else(|| {
697 eyre::eyre!(
698 "Soroswap factory address not configured. Set STELLAR_{network_label}_SOROSWAP_FACTORY_ADDRESS env var."
699 )
700 })?;
701 let native_wrapper_address =
702 crate::config::ServerConfig::resolve_stellar_soroswap_native_wrapper_address(is_testnet)
703 .ok_or_else(|| {
704 eyre::eyre!(
705 "Soroswap native wrapper address not configured. Set STELLAR_{network_label}_SOROSWAP_NATIVE_WRAPPER_ADDRESS env var."
706 )
707 })?;
708
709 let soroswap_service = Arc::new(SoroswapService::new(
710 router_address,
711 factory_address,
712 native_wrapper_address,
713 provider_arc.clone(),
714 network.passphrase.clone(),
715 ));
716 dex_services.push(DexServiceWrapper::Soroswap(soroswap_service));
717 }
718 }
719 }
720
721 let dex_service = Arc::new(StellarDexService::new(dex_services));
723
724 Ok(NetworkTransaction::Stellar(DefaultStellarTransaction::new(
725 relayer,
726 relayer_repository,
727 transaction_repository,
728 job_producer,
729 signer_service,
730 stellar_provider,
731 transaction_counter_store,
732 dex_service,
733 )?))
734 }
735 }
736 }
737}