openzeppelin_relayer/domain/relayer/
mod.rs

1//! # Relayer Domain Module
2//!
3//! This module contains the core domain logic for the relayer service.
4//! It handles transaction submission, validation, and monitoring across
5//! different blockchain networks.
6//! ## Architecture
7//!
8//! The relayer domain is organized into network-specific implementations
9//! that share common interfaces for transaction handling and monitoring.
10
11use actix_web::web::ThinData;
12use serde::{Deserialize, Serialize};
13use std::sync::Arc;
14use utoipa::ToSchema;
15
16#[cfg(test)]
17use mockall::automock;
18
19use crate::{
20    jobs::JobProducerTrait,
21    models::{
22        AppState, DecoratedSignature, DeletePendingTransactionsResponse, EvmNetwork,
23        EvmTransactionDataSignature, JsonRpcRequest, JsonRpcResponse, NetworkRepoModel,
24        NetworkRpcRequest, NetworkRpcResult, NetworkTransactionRequest, NetworkType,
25        NotificationRepoModel, RelayerError, RelayerRepoModel, RelayerStatus, SignerRepoModel,
26        StellarNetwork, TransactionError, TransactionRepoModel,
27    },
28    repositories::{
29        ApiKeyRepositoryTrait, NetworkRepository, PluginRepositoryTrait, RelayerRepository,
30        Repository, TransactionCounterTrait, TransactionRepository,
31    },
32    services::{
33        get_network_provider, EvmSignerFactory, StellarSignerFactory, TransactionCounterService,
34    },
35};
36
37use async_trait::async_trait;
38use eyre::Result;
39
40mod evm;
41mod solana;
42mod stellar;
43mod util;
44
45pub use evm::*;
46pub use solana::*;
47pub use stellar::*;
48pub use util::*;
49
50/// The `Relayer` trait defines the core functionality required for a relayer
51/// in the system. Implementors of this trait are responsible for handling
52/// transaction requests, managing balances, and interacting with the network.
53#[async_trait]
54#[cfg_attr(test, automock)]
55#[allow(dead_code)]
56pub trait Relayer {
57    /// Processes a transaction request and returns the result.
58    ///
59    /// # Arguments
60    ///
61    /// * `tx_request` - The transaction request to be processed.
62    ///
63    /// # Returns
64    ///
65    /// A `Result` containing a `TransactionRepoModel` on success, or a
66    /// `RelayerError` on failure.
67    async fn process_transaction_request(
68        &self,
69        tx_request: NetworkTransactionRequest,
70    ) -> Result<TransactionRepoModel, RelayerError>;
71
72    /// Retrieves the current balance of the relayer.
73    ///
74    /// # Returns
75    ///
76    /// A `Result` containing a `BalanceResponse` on success, or a
77    /// `RelayerError` on failure.
78    async fn get_balance(&self) -> Result<BalanceResponse, RelayerError>;
79
80    /// Deletes all pending transactions.
81    ///
82    /// # Returns
83    ///
84    /// A `Result` containing a `DeletePendingTransactionsResponse` with details
85    /// about which transactions were cancelled and which failed, or a `RelayerError` on failure.
86    async fn delete_pending_transactions(
87        &self,
88    ) -> Result<DeletePendingTransactionsResponse, RelayerError>;
89
90    /// Signs data using the relayer's credentials.
91    ///
92    /// # Arguments
93    ///
94    /// * `request` - The data to be signed.
95    ///
96    /// # Returns
97    ///
98    /// A `Result` containing a `SignDataResponse` on success, or a
99    /// `RelayerError` on failure.
100    async fn sign_data(&self, request: SignDataRequest) -> Result<SignDataResponse, RelayerError>;
101
102    /// Signs typed data using the relayer's credentials.
103    ///
104    /// # Arguments
105    ///
106    /// * `request` - The typed data to be signed.
107    ///
108    /// # Returns
109    ///
110    /// A `Result` containing a `SignDataResponse` on success, or a
111    /// `RelayerError` on failure.
112    async fn sign_typed_data(
113        &self,
114        request: SignTypedDataRequest,
115    ) -> Result<SignDataResponse, RelayerError>;
116
117    /// Executes a JSON-RPC request.
118    ///
119    /// # Arguments
120    ///
121    /// * `request` - The JSON-RPC request to be executed.
122    ///
123    /// # Returns
124    ///
125    /// A `Result` containing a `JsonRpcResponse` on success, or a
126    /// `RelayerError` on failure.
127    async fn rpc(
128        &self,
129        request: JsonRpcRequest<NetworkRpcRequest>,
130    ) -> Result<JsonRpcResponse<NetworkRpcResult>, RelayerError>;
131
132    /// Retrieves the current status of the relayer.
133    ///
134    /// # Returns
135    ///
136    /// A `Result` containing `RelayerStatus` on success, or a
137    /// `RelayerError` on failure.
138    async fn get_status(&self) -> Result<RelayerStatus, RelayerError>;
139
140    /// Initializes the relayer.
141    ///
142    /// # Returns
143    ///
144    /// A `Result` indicating success, or a `RelayerError` on failure.
145    async fn initialize_relayer(&self) -> Result<(), RelayerError>;
146
147    /// Runs health checks on the relayer without side effects.
148    ///
149    /// This method performs all necessary health checks (RPC validation, balance checks, etc.)
150    /// and returns the results without updating any state or sending notifications.
151    ///
152    /// # Returns
153    ///
154    /// * `Ok(())` - All health checks passed
155    /// * `Err(Vec<HealthCheckFailure>)` - One or more health checks failed with specific reasons
156    async fn check_health(&self) -> Result<(), Vec<crate::models::HealthCheckFailure>>;
157
158    /// Validates that the relayer's balance meets the minimum required.
159    ///
160    /// # Returns
161    ///
162    /// A `Result` indicating success, or a `RelayerError` on failure.
163    async fn validate_min_balance(&self) -> Result<(), RelayerError>;
164
165    /// Signs a transaction using the relayer's credentials.
166    ///
167    /// # Arguments
168    ///
169    /// * `unsigned_xdr` - The unsigned transaction XDR string to be signed.
170    ///
171    /// # Returns
172    ///
173    /// A `Result` containing a `SignTransactionExternalResponse` on success, or a
174    /// `RelayerError` on failure.
175    async fn sign_transaction(
176        &self,
177        request: &SignTransactionRequest,
178    ) -> Result<SignTransactionExternalResponse, RelayerError>;
179}
180
181/// Solana Relayer Dex Trait
182/// Subset of methods for Solana relayer
183#[async_trait]
184#[allow(dead_code)]
185#[cfg_attr(test, automock)]
186pub trait SolanaRelayerDexTrait {
187    /// Handles a token swap request.
188    async fn handle_token_swap_request(
189        &self,
190        relayer_id: String,
191    ) -> Result<Vec<SwapResult>, RelayerError>;
192}
193
194/// Solana Relayer Trait
195/// Subset of methods for Solana relayer
196#[async_trait]
197#[allow(dead_code)]
198#[cfg_attr(test, automock)]
199pub trait SolanaRelayerTrait {
200    /// Retrieves the current balance of the relayer.
201    ///
202    /// # Returns
203    ///
204    /// A `Result` containing a `BalanceResponse` on success, or a
205    /// `RelayerError` on failure.
206    async fn get_balance(&self) -> Result<BalanceResponse, RelayerError>;
207
208    /// Executes a JSON-RPC request.
209    ///
210    /// # Arguments
211    ///
212    /// * `request` - The JSON-RPC request to be executed.
213    ///
214    /// # Returns
215    ///
216    /// A `Result` containing a `JsonRpcResponse` on success, or a
217    /// `RelayerError` on failure.
218    async fn rpc(
219        &self,
220        request: JsonRpcRequest<NetworkRpcRequest>,
221    ) -> Result<JsonRpcResponse<NetworkRpcResult>, RelayerError>;
222
223    /// Runs health checks on the relayer without side effects.
224    ///
225    /// # Returns
226    ///
227    /// * `Ok(())` - All health checks passed
228    /// * `Err(Vec<HealthCheckFailure>)` - One or more health checks failed
229    async fn check_health(&self) -> Result<(), Vec<crate::models::HealthCheckFailure>>;
230
231    /// Initializes the relayer.
232    ///
233    /// # Returns
234    ///
235    /// A `Result` indicating success, or a `RelayerError` on failure.
236    async fn initialize_relayer(&self) -> Result<(), RelayerError>;
237
238    /// Validates that the relayer's balance meets the minimum required.
239    ///
240    /// # Returns
241    ///
242    /// A `Result` indicating success, or a `RelayerError` on failure.
243    async fn validate_min_balance(&self) -> Result<(), RelayerError>;
244}
245
246pub enum NetworkRelayer<
247    J: JobProducerTrait + 'static,
248    T: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
249    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
250    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
251    TCR: TransactionCounterTrait + Send + Sync + 'static,
252> {
253    Evm(DefaultEvmRelayer<J, T, RR, NR, TCR>),
254    Solana(DefaultSolanaRelayer<J, T, RR, NR>),
255    Stellar(DefaultStellarRelayer<J, T, NR, RR, TCR>),
256}
257
258#[async_trait]
259impl<
260        J: JobProducerTrait + 'static,
261        T: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
262        RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
263        NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
264        TCR: TransactionCounterTrait + Send + Sync + 'static,
265    > Relayer for NetworkRelayer<J, T, RR, NR, TCR>
266{
267    async fn process_transaction_request(
268        &self,
269        tx_request: NetworkTransactionRequest,
270    ) -> Result<TransactionRepoModel, RelayerError> {
271        match self {
272            NetworkRelayer::Evm(relayer) => relayer.process_transaction_request(tx_request).await,
273            NetworkRelayer::Solana(_) => solana_not_supported_relayer(),
274            NetworkRelayer::Stellar(relayer) => {
275                relayer.process_transaction_request(tx_request).await
276            }
277        }
278    }
279
280    async fn get_balance(&self) -> Result<BalanceResponse, RelayerError> {
281        match self {
282            NetworkRelayer::Evm(relayer) => relayer.get_balance().await,
283            NetworkRelayer::Solana(relayer) => relayer.get_balance().await,
284            NetworkRelayer::Stellar(relayer) => relayer.get_balance().await,
285        }
286    }
287
288    async fn delete_pending_transactions(
289        &self,
290    ) -> Result<DeletePendingTransactionsResponse, RelayerError> {
291        match self {
292            NetworkRelayer::Evm(relayer) => relayer.delete_pending_transactions().await,
293            NetworkRelayer::Solana(_) => solana_not_supported_relayer(),
294            NetworkRelayer::Stellar(relayer) => relayer.delete_pending_transactions().await,
295        }
296    }
297
298    async fn sign_data(&self, request: SignDataRequest) -> Result<SignDataResponse, RelayerError> {
299        match self {
300            NetworkRelayer::Evm(relayer) => relayer.sign_data(request).await,
301            NetworkRelayer::Solana(_) => solana_not_supported_relayer(),
302            NetworkRelayer::Stellar(relayer) => relayer.sign_data(request).await,
303        }
304    }
305
306    async fn sign_typed_data(
307        &self,
308        request: SignTypedDataRequest,
309    ) -> Result<SignDataResponse, RelayerError> {
310        match self {
311            NetworkRelayer::Evm(relayer) => relayer.sign_typed_data(request).await,
312            NetworkRelayer::Solana(_) => solana_not_supported_relayer(),
313            NetworkRelayer::Stellar(relayer) => relayer.sign_typed_data(request).await,
314        }
315    }
316
317    async fn rpc(
318        &self,
319        request: JsonRpcRequest<NetworkRpcRequest>,
320    ) -> Result<JsonRpcResponse<NetworkRpcResult>, RelayerError> {
321        match self {
322            NetworkRelayer::Evm(relayer) => relayer.rpc(request).await,
323            NetworkRelayer::Solana(relayer) => relayer.rpc(request).await,
324            NetworkRelayer::Stellar(relayer) => relayer.rpc(request).await,
325        }
326    }
327
328    async fn get_status(&self) -> Result<RelayerStatus, RelayerError> {
329        match self {
330            NetworkRelayer::Evm(relayer) => relayer.get_status().await,
331            NetworkRelayer::Solana(_) => solana_not_supported_relayer(),
332            NetworkRelayer::Stellar(relayer) => relayer.get_status().await,
333        }
334    }
335
336    async fn validate_min_balance(&self) -> Result<(), RelayerError> {
337        match self {
338            NetworkRelayer::Evm(relayer) => relayer.validate_min_balance().await,
339            NetworkRelayer::Solana(relayer) => relayer.validate_min_balance().await,
340            NetworkRelayer::Stellar(relayer) => relayer.validate_min_balance().await,
341        }
342    }
343
344    async fn initialize_relayer(&self) -> Result<(), RelayerError> {
345        match self {
346            NetworkRelayer::Evm(relayer) => relayer.initialize_relayer().await,
347            NetworkRelayer::Solana(relayer) => relayer.initialize_relayer().await,
348            NetworkRelayer::Stellar(relayer) => relayer.initialize_relayer().await,
349        }
350    }
351
352    async fn check_health(&self) -> Result<(), Vec<crate::models::HealthCheckFailure>> {
353        match self {
354            NetworkRelayer::Evm(relayer) => relayer.check_health().await,
355            NetworkRelayer::Solana(relayer) => relayer.check_health().await,
356            NetworkRelayer::Stellar(relayer) => relayer.check_health().await,
357        }
358    }
359
360    async fn sign_transaction(
361        &self,
362        request: &SignTransactionRequest,
363    ) -> Result<SignTransactionExternalResponse, RelayerError> {
364        match self {
365            NetworkRelayer::Evm(_) => Err(RelayerError::NotSupported(
366                "sign_transaction not supported for EVM".to_string(),
367            )),
368            NetworkRelayer::Solana(_) => Err(RelayerError::NotSupported(
369                "sign_transaction not supported for Solana".to_string(),
370            )),
371            NetworkRelayer::Stellar(relayer) => relayer.sign_transaction(request).await,
372        }
373    }
374}
375
376#[async_trait]
377pub trait RelayerFactoryTrait<
378    J: JobProducerTrait + Send + Sync + 'static,
379    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
380    TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
381    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
382    NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
383    SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
384    TCR: TransactionCounterTrait + Send + Sync + 'static,
385    PR: PluginRepositoryTrait + Send + Sync + 'static,
386    AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
387>
388{
389    async fn create_relayer(
390        relayer: RelayerRepoModel,
391        signer: SignerRepoModel,
392        state: &ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
393    ) -> Result<NetworkRelayer<J, TR, RR, NR, TCR>, RelayerError>;
394}
395
396pub struct RelayerFactory;
397
398#[async_trait]
399impl<
400        J: JobProducerTrait + 'static,
401        TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
402        RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
403        NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
404        NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
405        SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
406        TCR: TransactionCounterTrait + Send + Sync + 'static,
407        PR: PluginRepositoryTrait + Send + Sync + 'static,
408        AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
409    > RelayerFactoryTrait<J, RR, TR, NR, NFR, SR, TCR, PR, AKR> for RelayerFactory
410{
411    async fn create_relayer(
412        relayer: RelayerRepoModel,
413        signer: SignerRepoModel,
414        state: &ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
415    ) -> Result<NetworkRelayer<J, TR, RR, NR, TCR>, RelayerError> {
416        match relayer.network_type {
417            NetworkType::Evm => {
418                let network_repo = state
419                    .network_repository()
420                    .get_by_name(NetworkType::Evm, &relayer.network)
421                    .await
422                    .ok()
423                    .flatten()
424                    .ok_or_else(|| {
425                        RelayerError::NetworkConfiguration(format!(
426                            "Network {} not found",
427                            relayer.network
428                        ))
429                    })?;
430
431                let network = EvmNetwork::try_from(network_repo)?;
432
433                let evm_provider = get_network_provider(&network, relayer.custom_rpc_urls.clone())?;
434                let signer_service = EvmSignerFactory::create_evm_signer(signer.into()).await?;
435                let transaction_counter_service = Arc::new(TransactionCounterService::new(
436                    relayer.id.clone(),
437                    relayer.address.clone(),
438                    state.transaction_counter_store(),
439                ));
440                let relayer = DefaultEvmRelayer::new(
441                    relayer,
442                    signer_service,
443                    evm_provider,
444                    network,
445                    state.relayer_repository(),
446                    state.network_repository(),
447                    state.transaction_repository(),
448                    transaction_counter_service,
449                    state.job_producer(),
450                )?;
451
452                Ok(NetworkRelayer::Evm(relayer))
453            }
454            NetworkType::Solana => {
455                let solana_relayer = create_solana_relayer(
456                    relayer,
457                    signer,
458                    state.relayer_repository(),
459                    state.network_repository(),
460                    state.transaction_repository(),
461                    state.job_producer(),
462                )
463                .await?;
464                Ok(NetworkRelayer::Solana(solana_relayer))
465            }
466            NetworkType::Stellar => {
467                let network_repo = state
468                    .network_repository()
469                    .get_by_name(NetworkType::Stellar, &relayer.network)
470                    .await
471                    .ok()
472                    .flatten()
473                    .ok_or_else(|| {
474                        RelayerError::NetworkConfiguration(format!(
475                            "Network {} not found",
476                            relayer.network
477                        ))
478                    })?;
479
480                let network = StellarNetwork::try_from(network_repo)?;
481
482                let stellar_provider =
483                    get_network_provider(&network, relayer.custom_rpc_urls.clone())
484                        .map_err(|e| RelayerError::NetworkConfiguration(e.to_string()))?;
485
486                let signer_service = StellarSignerFactory::create_stellar_signer(&signer.into())?;
487
488                let transaction_counter_service = Arc::new(TransactionCounterService::new(
489                    relayer.id.clone(),
490                    relayer.address.clone(),
491                    state.transaction_counter_store(),
492                ));
493
494                let relayer = DefaultStellarRelayer::<J, TR, NR, RR, TCR>::new(
495                    relayer,
496                    signer_service,
497                    stellar_provider,
498                    stellar::StellarRelayerDependencies::new(
499                        state.relayer_repository(),
500                        state.network_repository(),
501                        state.transaction_repository(),
502                        transaction_counter_service,
503                        state.job_producer(),
504                    ),
505                )
506                .await?;
507                Ok(NetworkRelayer::Stellar(relayer))
508            }
509        }
510    }
511}
512
513#[derive(Serialize, Deserialize, ToSchema)]
514pub struct SignDataRequest {
515    pub message: String,
516}
517
518#[derive(Serialize, Deserialize, ToSchema)]
519pub struct SignDataResponseEvm {
520    pub r: String,
521    pub s: String,
522    pub v: u8,
523    pub sig: String,
524}
525
526#[derive(Serialize, Deserialize, ToSchema)]
527pub struct SignDataResponseSolana {
528    pub signature: String,
529    pub public_key: String,
530}
531
532#[derive(Serialize, Deserialize, ToSchema)]
533#[serde(untagged)]
534pub enum SignDataResponse {
535    Evm(SignDataResponseEvm),
536    Solana(SignDataResponseSolana),
537}
538
539#[derive(Serialize, Deserialize, ToSchema)]
540pub struct SignTypedDataRequest {
541    pub domain_separator: String,
542    pub hash_struct_message: String,
543}
544
545#[derive(Debug, Serialize, Deserialize, ToSchema)]
546pub struct SignTransactionRequestStellar {
547    pub unsigned_xdr: String,
548}
549
550#[derive(Debug, Serialize, Deserialize, ToSchema)]
551#[serde(untagged)]
552pub enum SignTransactionRequest {
553    Stellar(SignTransactionRequestStellar),
554    Evm(Vec<u8>),
555    Solana(Vec<u8>),
556}
557
558#[derive(Debug, Serialize, Deserialize)]
559pub struct SignTransactionResponseEvm {
560    pub hash: String,
561    pub signature: EvmTransactionDataSignature,
562    pub raw: Vec<u8>,
563}
564
565#[derive(Debug, Serialize, Deserialize)]
566pub struct SignTransactionResponseStellar {
567    pub signature: DecoratedSignature,
568}
569
570#[derive(Debug, Serialize, Deserialize)]
571#[serde(rename_all = "camelCase")]
572pub struct SignXdrTransactionResponseStellar {
573    pub signed_xdr: String,
574    pub signature: DecoratedSignature,
575}
576
577#[derive(Debug, Serialize, Deserialize)]
578pub enum SignTransactionResponse {
579    Evm(SignTransactionResponseEvm),
580    Solana(Vec<u8>),
581    Stellar(SignTransactionResponseStellar),
582}
583
584#[derive(Debug, Serialize, Deserialize, ToSchema)]
585#[serde(rename_all = "camelCase")]
586#[schema(as = SignTransactionResponseStellar)]
587pub struct SignTransactionExternalResponseStellar {
588    pub signed_xdr: String,
589    pub signature: String,
590}
591
592#[derive(Debug, Serialize, Deserialize, ToSchema)]
593#[serde(untagged)]
594#[schema(as = SignTransactionResponse)]
595pub enum SignTransactionExternalResponse {
596    Stellar(SignTransactionExternalResponseStellar),
597    Evm(Vec<u8>),
598    Solana(Vec<u8>),
599}
600
601impl SignTransactionResponse {
602    pub fn into_evm(self) -> Result<SignTransactionResponseEvm, TransactionError> {
603        match self {
604            SignTransactionResponse::Evm(e) => Ok(e),
605            _ => Err(TransactionError::InvalidType(
606                "Expected EVM signature".to_string(),
607            )),
608        }
609    }
610}
611
612#[derive(Debug, Serialize, ToSchema)]
613pub struct BalanceResponse {
614    pub balance: u128,
615    #[schema(example = "wei")]
616    pub unit: String,
617}