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