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