openzeppelin_relayer/domain/transaction/mod.rs
1//! This module defines the core transaction handling logic for different blockchain networks,
2//! including Ethereum (EVM), Solana, and Stellar. It provides a unified interface for preparing,
3//! submitting, handling, canceling, replacing, signing, and validating transactions across these
4//! networks. The module also includes a factory for creating network-specific transaction handlers
5//! based on relayer and repository information.
6//!
7//! The main components of this module are:
8//! - `Transaction` trait: Defines the operations for handling transactions.
9//! - `NetworkTransaction` enum: Represents a transaction for different network types.
10//! - `RelayerTransactionFactory`: A factory for creating network transactions.
11//!
12//! The module leverages async traits to handle asynchronous operations and uses the `eyre` crate
13//! for error handling.
14use crate::{
15 constants::{STELLAR_HORIZON_MAINNET_URL, STELLAR_HORIZON_TESTNET_URL},
16 jobs::JobProducer,
17 models::{
18 EvmNetwork, NetworkTransactionRequest, NetworkType, RelayerRepoModel, SignerRepoModel,
19 SolanaNetwork, StellarNetwork, 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::OrderBookService,
33 },
34};
35use async_trait::async_trait;
36use eyre::Result;
37#[cfg(test)]
38use mockall::automock;
39use std::sync::Arc;
40
41pub mod common;
42pub mod evm;
43pub mod solana;
44pub mod stellar;
45
46mod util;
47pub use util::*;
48
49// Explicit re-exports to avoid ambiguous glob re-exports
50pub use common::is_final_state;
51pub use common::*;
52pub use evm::{ensure_status, ensure_status_one_of, DefaultEvmTransaction, EvmRelayerTransaction};
53pub use solana::{DefaultSolanaTransaction, SolanaRelayerTransaction};
54pub use stellar::{DefaultStellarTransaction, StellarRelayerTransaction};
55
56/// A trait that defines the operations for handling transactions across different networks.
57#[cfg_attr(test, automock)]
58#[async_trait]
59#[allow(dead_code)]
60pub trait Transaction {
61 /// Prepares a transaction for submission.
62 ///
63 /// # Arguments
64 ///
65 /// * `tx` - A `TransactionRepoModel` representing the transaction to be prepared.
66 ///
67 /// # Returns
68 ///
69 /// A `Result` containing the prepared `TransactionRepoModel` or a `TransactionError`.
70 async fn prepare_transaction(
71 &self,
72 tx: TransactionRepoModel,
73 ) -> Result<TransactionRepoModel, TransactionError>;
74
75 /// Submits a transaction to the network.
76 ///
77 /// # Arguments
78 ///
79 /// * `tx` - A `TransactionRepoModel` representing the transaction to be submitted.
80 ///
81 /// # Returns
82 ///
83 /// A `Result` containing the submitted `TransactionRepoModel` or a `TransactionError`.
84 async fn submit_transaction(
85 &self,
86 tx: TransactionRepoModel,
87 ) -> Result<TransactionRepoModel, TransactionError>;
88
89 /// Resubmits a transaction with updated parameters.
90 ///
91 /// # Arguments
92 ///
93 /// * `tx` - A `TransactionRepoModel` representing the transaction to be resubmitted.
94 ///
95 /// # Returns
96 ///
97 /// A `Result` containing the resubmitted `TransactionRepoModel` or a `TransactionError`.
98 async fn resubmit_transaction(
99 &self,
100 tx: TransactionRepoModel,
101 ) -> Result<TransactionRepoModel, TransactionError>;
102
103 /// Handles the status of a transaction.
104 ///
105 /// # Arguments
106 ///
107 /// * `tx` - A `TransactionRepoModel` representing the transaction whose status is to be
108 /// handled.
109 ///
110 /// # Returns
111 ///
112 /// A `Result` containing the updated `TransactionRepoModel` or a `TransactionError`.
113 async fn handle_transaction_status(
114 &self,
115 tx: TransactionRepoModel,
116 ) -> Result<TransactionRepoModel, TransactionError>;
117
118 /// Cancels a transaction.
119 ///
120 /// # Arguments
121 ///
122 /// * `tx` - A `TransactionRepoModel` representing the transaction to be canceled.
123 ///
124 /// # Returns
125 ///
126 /// A `Result` containing the canceled `TransactionRepoModel` or a `TransactionError`.
127 async fn cancel_transaction(
128 &self,
129 tx: TransactionRepoModel,
130 ) -> Result<TransactionRepoModel, TransactionError>;
131
132 /// Replaces a transaction with a new one.
133 ///
134 /// # Arguments
135 ///
136 /// * `old_tx` - A `TransactionRepoModel` representing the transaction to be replaced.
137 /// * `new_tx_request` - A `NetworkTransactionRequest` representing the new transaction data.
138 ///
139 /// # Returns
140 ///
141 /// A `Result` containing the new `TransactionRepoModel` or a `TransactionError`.
142 async fn replace_transaction(
143 &self,
144 old_tx: TransactionRepoModel,
145 new_tx_request: NetworkTransactionRequest,
146 ) -> Result<TransactionRepoModel, TransactionError>;
147
148 /// Signs a transaction.
149 ///
150 /// # Arguments
151 ///
152 /// * `tx` - A `TransactionRepoModel` representing the transaction to be signed.
153 ///
154 /// # Returns
155 ///
156 /// A `Result` containing the signed `TransactionRepoModel` or a `TransactionError`.
157 async fn sign_transaction(
158 &self,
159 tx: TransactionRepoModel,
160 ) -> Result<TransactionRepoModel, TransactionError>;
161
162 /// Validates a transaction.
163 ///
164 /// # Arguments
165 ///
166 /// * `tx` - A `TransactionRepoModel` representing the transaction to be validated.
167 ///
168 /// # Returns
169 ///
170 /// A `Result` containing a boolean indicating the validity of the transaction or a
171 /// `TransactionError`.
172 async fn validate_transaction(
173 &self,
174 tx: TransactionRepoModel,
175 ) -> Result<bool, TransactionError>;
176}
177
178/// An enum representing a transaction for different network types.
179pub enum NetworkTransaction {
180 Evm(Box<DefaultEvmTransaction>),
181 Solana(DefaultSolanaTransaction),
182 Stellar(DefaultStellarTransaction),
183}
184
185#[async_trait]
186impl Transaction for NetworkTransaction {
187 /// Prepares a transaction for submission based on the network type.
188 ///
189 /// # Arguments
190 ///
191 /// * `tx` - A `TransactionRepoModel` representing the transaction to be prepared.
192 ///
193 /// # Returns
194 ///
195 /// A `Result` containing the prepared `TransactionRepoModel` or a `TransactionError`.
196 async fn prepare_transaction(
197 &self,
198 tx: TransactionRepoModel,
199 ) -> Result<TransactionRepoModel, TransactionError> {
200 match self {
201 NetworkTransaction::Evm(relayer) => relayer.prepare_transaction(tx).await,
202 NetworkTransaction::Solana(relayer) => relayer.prepare_transaction(tx).await,
203 NetworkTransaction::Stellar(relayer) => relayer.prepare_transaction(tx).await,
204 }
205 }
206
207 /// Submits a transaction to the network based on the network type.
208 ///
209 /// # Arguments
210 ///
211 /// * `tx` - A `TransactionRepoModel` representing the transaction to be submitted.
212 ///
213 /// # Returns
214 ///
215 /// A `Result` containing the submitted `TransactionRepoModel` or a `TransactionError`.
216 async fn submit_transaction(
217 &self,
218 tx: TransactionRepoModel,
219 ) -> Result<TransactionRepoModel, TransactionError> {
220 match self {
221 NetworkTransaction::Evm(relayer) => relayer.submit_transaction(tx).await,
222 NetworkTransaction::Solana(relayer) => relayer.submit_transaction(tx).await,
223 NetworkTransaction::Stellar(relayer) => relayer.submit_transaction(tx).await,
224 }
225 }
226 /// Resubmits a transaction with updated parameters based on the network type.
227 ///
228 /// # Arguments
229 ///
230 /// * `tx` - A `TransactionRepoModel` representing the transaction to be resubmitted.
231 ///
232 /// # Returns
233 ///
234 /// A `Result` containing the resubmitted `TransactionRepoModel` or a `TransactionError`.
235 async fn resubmit_transaction(
236 &self,
237 tx: TransactionRepoModel,
238 ) -> Result<TransactionRepoModel, TransactionError> {
239 match self {
240 NetworkTransaction::Evm(relayer) => relayer.resubmit_transaction(tx).await,
241 NetworkTransaction::Solana(relayer) => relayer.resubmit_transaction(tx).await,
242 NetworkTransaction::Stellar(relayer) => relayer.resubmit_transaction(tx).await,
243 }
244 }
245
246 /// Handles the status of a transaction based on the network type.
247 ///
248 /// # Arguments
249 ///
250 /// * `tx` - A `TransactionRepoModel` representing the transaction whose status is to be
251 /// handled.
252 ///
253 /// # Returns
254 ///
255 /// A `Result` containing the updated `TransactionRepoModel` or a `TransactionError`.
256 async fn handle_transaction_status(
257 &self,
258 tx: TransactionRepoModel,
259 ) -> Result<TransactionRepoModel, TransactionError> {
260 match self {
261 NetworkTransaction::Evm(relayer) => relayer.handle_transaction_status(tx).await,
262 NetworkTransaction::Solana(relayer) => relayer.handle_transaction_status(tx).await,
263 NetworkTransaction::Stellar(relayer) => relayer.handle_transaction_status(tx).await,
264 }
265 }
266
267 /// Cancels a transaction based on the network type.
268 ///
269 /// # Arguments
270 ///
271 /// * `tx` - A `TransactionRepoModel` representing the transaction to be canceled.
272 ///
273 /// # Returns
274 ///
275 /// A `Result` containing the canceled `TransactionRepoModel` or a `TransactionError`.
276 async fn cancel_transaction(
277 &self,
278 tx: TransactionRepoModel,
279 ) -> Result<TransactionRepoModel, TransactionError> {
280 match self {
281 NetworkTransaction::Evm(relayer) => relayer.cancel_transaction(tx).await,
282 NetworkTransaction::Solana(_) => solana_not_supported_transaction(),
283 NetworkTransaction::Stellar(relayer) => relayer.cancel_transaction(tx).await,
284 }
285 }
286
287 /// Replaces a transaction with a new one based on the network type.
288 ///
289 /// # Arguments
290 ///
291 /// * `old_tx` - A `TransactionRepoModel` representing the transaction to be replaced.
292 /// * `new_tx_request` - A `NetworkTransactionRequest` representing the new transaction data.
293 ///
294 /// # Returns
295 ///
296 /// A `Result` containing the new `TransactionRepoModel` or a `TransactionError`.
297 async fn replace_transaction(
298 &self,
299 old_tx: TransactionRepoModel,
300 new_tx_request: NetworkTransactionRequest,
301 ) -> Result<TransactionRepoModel, TransactionError> {
302 match self {
303 NetworkTransaction::Evm(relayer) => {
304 relayer.replace_transaction(old_tx, new_tx_request).await
305 }
306 NetworkTransaction::Solana(_) => solana_not_supported_transaction(),
307 NetworkTransaction::Stellar(relayer) => {
308 relayer.replace_transaction(old_tx, new_tx_request).await
309 }
310 }
311 }
312
313 /// Signs a transaction based on the network type.
314 ///
315 /// # Arguments
316 ///
317 /// * `tx` - A `TransactionRepoModel` representing the transaction to be signed.
318 ///
319 /// # Returns
320 ///
321 /// A `Result` containing the signed `TransactionRepoModel` or a `TransactionError`.
322 async fn sign_transaction(
323 &self,
324 tx: TransactionRepoModel,
325 ) -> Result<TransactionRepoModel, TransactionError> {
326 match self {
327 NetworkTransaction::Evm(relayer) => relayer.sign_transaction(tx).await,
328 NetworkTransaction::Solana(relayer) => relayer.sign_transaction(tx).await,
329 NetworkTransaction::Stellar(relayer) => relayer.sign_transaction(tx).await,
330 }
331 }
332
333 /// Validates a transaction based on the network type.
334 ///
335 /// # Arguments
336 ///
337 /// * `tx` - A `TransactionRepoModel` representing the transaction to be validated.
338 ///
339 /// # Returns
340 ///
341 /// A `Result` containing a boolean indicating the validity of the transaction or a
342 /// `TransactionError`.
343 async fn validate_transaction(
344 &self,
345 tx: TransactionRepoModel,
346 ) -> Result<bool, TransactionError> {
347 match self {
348 NetworkTransaction::Evm(relayer) => relayer.validate_transaction(tx).await,
349 NetworkTransaction::Solana(relayer) => relayer.validate_transaction(tx).await,
350 NetworkTransaction::Stellar(relayer) => relayer.validate_transaction(tx).await,
351 }
352 }
353}
354
355/// A trait for creating network transactions.
356#[allow(dead_code)]
357pub trait RelayerTransactionFactoryTrait {
358 /// Creates a network transaction based on the relayer and repository information.
359 ///
360 /// # Arguments
361 ///
362 /// * `relayer` - A `RelayerRepoModel` representing the relayer.
363 /// * `relayer_repository` - An `Arc` to the `RelayerRepositoryStorage`.
364 /// * `transaction_repository` - An `Arc` to the `TransactionRepositoryStorage`.
365 /// * `job_producer` - An `Arc` to the `JobProducer`.
366 ///
367 /// # Returns
368 ///
369 /// A `Result` containing the created `NetworkTransaction` or a `TransactionError`.
370 fn create_transaction(
371 relayer: RelayerRepoModel,
372 relayer_repository: Arc<RelayerRepositoryStorage>,
373 transaction_repository: Arc<TransactionRepositoryStorage>,
374 job_producer: Arc<JobProducer>,
375 ) -> Result<NetworkTransaction, TransactionError>;
376}
377/// A factory for creating relayer transactions.
378pub struct RelayerTransactionFactory;
379
380#[allow(dead_code)]
381impl RelayerTransactionFactory {
382 /// Creates a network transaction based on the relayer, signer, and repository information.
383 ///
384 /// # Arguments
385 ///
386 /// * `relayer` - A `RelayerRepoModel` representing the relayer.
387 /// * `signer` - A `SignerRepoModel` representing the signer.
388 /// * `relayer_repository` - An `Arc` to the `RelayerRepositoryStorage`.
389 /// * `transaction_repository` - An `Arc` to the `InMemoryTransactionRepository`.
390 /// * `transaction_counter_store` - An `Arc` to the `InMemoryTransactionCounter`.
391 /// * `job_producer` - An `Arc` to the `JobProducer`.
392 ///
393 /// # Returns
394 ///
395 /// A `Result` containing the created `NetworkTransaction` or a `TransactionError`.
396 pub async fn create_transaction(
397 relayer: RelayerRepoModel,
398 signer: SignerRepoModel,
399 relayer_repository: Arc<RelayerRepositoryStorage>,
400 network_repository: Arc<NetworkRepositoryStorage>,
401 transaction_repository: Arc<TransactionRepositoryStorage>,
402 transaction_counter_store: Arc<TransactionCounterRepositoryStorage>,
403 job_producer: Arc<JobProducer>,
404 ) -> Result<NetworkTransaction, TransactionError> {
405 match relayer.network_type {
406 NetworkType::Evm => {
407 let network_repo = network_repository
408 .get_by_name(NetworkType::Evm, &relayer.network)
409 .await
410 .ok()
411 .flatten()
412 .ok_or_else(|| {
413 TransactionError::NetworkConfiguration(format!(
414 "Network {} not found",
415 relayer.network
416 ))
417 })?;
418
419 let network = EvmNetwork::try_from(network_repo)
420 .map_err(|e| TransactionError::NetworkConfiguration(e.to_string()))?;
421
422 let evm_provider = get_network_provider(&network, relayer.custom_rpc_urls.clone())?;
423 let signer_service = EvmSignerFactory::create_evm_signer(signer.into()).await?;
424 let price_params_handler =
425 PriceParamsHandler::for_network(&network, evm_provider.clone());
426
427 let evm_gas_cache = GasPriceCache::global();
428
429 // Use the global cache if gas price caching is enabled
430 let cache = if let Some(cfg) = &network.gas_price_cache {
431 evm_gas_cache.configure_network(network.chain_id, cfg.clone());
432 Some(evm_gas_cache.clone())
433 } else {
434 if evm_gas_cache.has_configuration_for_network(network.chain_id) {
435 evm_gas_cache.remove_network(network.chain_id);
436 }
437 None
438 };
439
440 let gas_price_service =
441 EvmGasPriceService::new(evm_provider.clone(), network.clone(), cache);
442
443 let price_calculator =
444 evm::PriceCalculator::new(gas_price_service, price_params_handler);
445
446 Ok(NetworkTransaction::Evm(Box::new(
447 DefaultEvmTransaction::new(
448 relayer,
449 evm_provider,
450 relayer_repository,
451 network_repository,
452 transaction_repository,
453 transaction_counter_store,
454 job_producer,
455 price_calculator,
456 signer_service,
457 )?,
458 )))
459 }
460 NetworkType::Solana => {
461 let network_repo = network_repository
462 .get_by_name(NetworkType::Solana, &relayer.network)
463 .await
464 .ok()
465 .flatten()
466 .ok_or_else(|| {
467 TransactionError::NetworkConfiguration(format!(
468 "Network {} not found",
469 relayer.network
470 ))
471 })?;
472
473 let network = SolanaNetwork::try_from(network_repo)
474 .map_err(|e| TransactionError::NetworkConfiguration(e.to_string()))?;
475
476 let solana_provider = Arc::new(get_network_provider(
477 &network,
478 relayer.custom_rpc_urls.clone(),
479 )?);
480
481 let signer_service =
482 Arc::new(SolanaSignerFactory::create_solana_signer(&signer.into())?);
483
484 Ok(NetworkTransaction::Solana(SolanaRelayerTransaction::new(
485 relayer,
486 relayer_repository,
487 solana_provider,
488 transaction_repository,
489 job_producer,
490 signer_service,
491 )?))
492 }
493 NetworkType::Stellar => {
494 // Create signer once and wrap in Arc, then clone Arc for both uses
495 // Arc implements Clone (cheap reference count increment)
496 let stellar_signer = StellarSignerFactory::create_stellar_signer(&signer.into())?;
497 let signer_service = Arc::new(stellar_signer);
498
499 let network_repo = network_repository
500 .get_by_name(NetworkType::Stellar, &relayer.network)
501 .await
502 .ok()
503 .flatten()
504 .ok_or_else(|| {
505 TransactionError::NetworkConfiguration(format!(
506 "Network {} not found",
507 relayer.network
508 ))
509 })?;
510
511 let network = StellarNetwork::try_from(network_repo)
512 .map_err(|e| TransactionError::NetworkConfiguration(e.to_string()))?;
513
514 let stellar_provider =
515 get_network_provider(&network, relayer.custom_rpc_urls.clone())
516 .map_err(|e| TransactionError::NetworkConfiguration(e.to_string()))?;
517
518 // Create DEX service for swap operations and validations using Horizon API
519 let horizon_url = network.horizon_url.clone().unwrap_or_else(|| {
520 if network.is_testnet() {
521 STELLAR_HORIZON_TESTNET_URL.to_string()
522 } else {
523 STELLAR_HORIZON_MAINNET_URL.to_string()
524 }
525 });
526 let provider_arc = Arc::new(stellar_provider.clone());
527 // Clone Arc for DEX service (cheap - just increments reference count)
528 let signer_arc = signer_service.clone();
529 let dex_service = Arc::new(
530 OrderBookService::new(horizon_url, provider_arc, signer_arc).map_err(|e| {
531 TransactionError::NetworkConfiguration(format!(
532 "Failed to create DEX service: {e}",
533 ))
534 })?,
535 );
536
537 Ok(NetworkTransaction::Stellar(DefaultStellarTransaction::new(
538 relayer,
539 relayer_repository,
540 transaction_repository,
541 job_producer,
542 signer_service,
543 stellar_provider,
544 transaction_counter_store,
545 dex_service,
546 )?))
547 }
548 }
549 }
550}