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, NetworkRpcRequest,
24        NetworkRpcResult, NetworkTransactionRequest, NetworkType, RelayerError, RelayerRepoModel,
25        RelayerStatus, SignerRepoModel, StellarNetwork, TransactionError, TransactionRepoModel,
26    },
27    services::{get_network_provider, EvmSignerFactory, TransactionCounterService},
28};
29
30use async_trait::async_trait;
31use eyre::Result;
32
33mod evm;
34mod solana;
35mod stellar;
36mod util;
37
38pub use evm::*;
39pub use solana::*;
40pub use stellar::*;
41pub use util::*;
42
43/// The `Relayer` trait defines the core functionality required for a relayer
44/// in the system. Implementors of this trait are responsible for handling
45/// transaction requests, managing balances, and interacting with the network.
46#[async_trait]
47#[allow(dead_code)]
48pub trait Relayer {
49    /// Processes a transaction request and returns the result.
50    ///
51    /// # Arguments
52    ///
53    /// * `tx_request` - The transaction request to be processed.
54    ///
55    /// # Returns
56    ///
57    /// A `Result` containing a `TransactionRepoModel` on success, or a
58    /// `RelayerError` on failure.
59    async fn process_transaction_request(
60        &self,
61        tx_request: NetworkTransactionRequest,
62    ) -> Result<TransactionRepoModel, RelayerError>;
63
64    /// Retrieves the current balance of the relayer.
65    ///
66    /// # Returns
67    ///
68    /// A `Result` containing a `BalanceResponse` on success, or a
69    /// `RelayerError` on failure.
70    async fn get_balance(&self) -> Result<BalanceResponse, RelayerError>;
71
72    /// Deletes all pending transactions.
73    ///
74    /// # Returns
75    ///
76    /// A `Result` containing a `DeletePendingTransactionsResponse` with details
77    /// about which transactions were cancelled and which failed, or a `RelayerError` on failure.
78    async fn delete_pending_transactions(
79        &self,
80    ) -> Result<DeletePendingTransactionsResponse, RelayerError>;
81
82    /// Signs data using the relayer's credentials.
83    ///
84    /// # Arguments
85    ///
86    /// * `request` - The data to be signed.
87    ///
88    /// # Returns
89    ///
90    /// A `Result` containing a `SignDataResponse` on success, or a
91    /// `RelayerError` on failure.
92    async fn sign_data(&self, request: SignDataRequest) -> Result<SignDataResponse, RelayerError>;
93
94    /// Signs typed data using the relayer's credentials.
95    ///
96    /// # Arguments
97    ///
98    /// * `request` - The typed data to be signed.
99    ///
100    /// # Returns
101    ///
102    /// A `Result` containing a `SignDataResponse` on success, or a
103    /// `RelayerError` on failure.
104    async fn sign_typed_data(
105        &self,
106        request: SignTypedDataRequest,
107    ) -> Result<SignDataResponse, RelayerError>;
108
109    /// Executes a JSON-RPC request.
110    ///
111    /// # Arguments
112    ///
113    /// * `request` - The JSON-RPC request to be executed.
114    ///
115    /// # Returns
116    ///
117    /// A `Result` containing a `JsonRpcResponse` on success, or a
118    /// `RelayerError` on failure.
119    async fn rpc(
120        &self,
121        request: JsonRpcRequest<NetworkRpcRequest>,
122    ) -> Result<JsonRpcResponse<NetworkRpcResult>, RelayerError>;
123
124    /// Retrieves the current status of the relayer.
125    ///
126    /// # Returns
127    ///
128    /// A `Result` containing `RelayerStatus` on success, or a
129    /// `RelayerError` on failure.
130    async fn get_status(&self) -> Result<RelayerStatus, RelayerError>;
131
132    /// Initializes the relayer.
133    ///
134    /// # Returns
135    ///
136    /// A `Result` indicating success, or a `RelayerError` on failure.
137    async fn initialize_relayer(&self) -> Result<(), RelayerError>;
138
139    /// Validates that the relayer's balance meets the minimum required.
140    ///
141    /// # Returns
142    ///
143    /// A `Result` indicating success, or a `RelayerError` on failure.
144    async fn validate_min_balance(&self) -> Result<(), RelayerError>;
145}
146
147/// Solana Relayer Dex Trait
148/// Subset of methods for Solana relayer
149#[async_trait]
150#[allow(dead_code)]
151#[cfg_attr(test, automock)]
152pub trait SolanaRelayerDexTrait {
153    /// Handles a token swap request.
154    async fn handle_token_swap_request(
155        &self,
156        relayer_id: String,
157    ) -> Result<Vec<SwapResult>, RelayerError>;
158}
159
160/// Solana Relayer Trait
161/// Subset of methods for Solana relayer
162#[async_trait]
163#[allow(dead_code)]
164#[cfg_attr(test, automock)]
165pub trait SolanaRelayerTrait {
166    /// Retrieves the current balance of the relayer.
167    ///
168    /// # Returns
169    ///
170    /// A `Result` containing a `BalanceResponse` on success, or a
171    /// `RelayerError` on failure.
172    async fn get_balance(&self) -> Result<BalanceResponse, RelayerError>;
173
174    /// Executes a JSON-RPC request.
175    ///
176    /// # Arguments
177    ///
178    /// * `request` - The JSON-RPC request to be executed.
179    ///
180    /// # Returns
181    ///
182    /// A `Result` containing a `JsonRpcResponse` on success, or a
183    /// `RelayerError` on failure.
184    async fn rpc(
185        &self,
186        request: JsonRpcRequest<NetworkRpcRequest>,
187    ) -> Result<JsonRpcResponse<NetworkRpcResult>, RelayerError>;
188
189    /// Initializes the relayer.
190    ///
191    /// # Returns
192    ///
193    /// A `Result` indicating success, or a `RelayerError` on failure.
194    async fn initialize_relayer(&self) -> Result<(), RelayerError>;
195
196    /// Validates that the relayer's balance meets the minimum required.
197    ///
198    /// # Returns
199    ///
200    /// A `Result` indicating success, or a `RelayerError` on failure.
201    async fn validate_min_balance(&self) -> Result<(), RelayerError>;
202}
203
204pub enum NetworkRelayer<J: JobProducerTrait + 'static> {
205    Evm(DefaultEvmRelayer<J>),
206    Solana(DefaultSolanaRelayer<J>),
207    Stellar(DefaultStellarRelayer<J>),
208}
209
210#[async_trait]
211impl<J: JobProducerTrait + 'static> Relayer for NetworkRelayer<J> {
212    async fn process_transaction_request(
213        &self,
214        tx_request: NetworkTransactionRequest,
215    ) -> Result<TransactionRepoModel, RelayerError> {
216        match self {
217            NetworkRelayer::Evm(relayer) => relayer.process_transaction_request(tx_request).await,
218            NetworkRelayer::Solana(_) => solana_not_supported_relayer(),
219            NetworkRelayer::Stellar(relayer) => {
220                relayer.process_transaction_request(tx_request).await
221            }
222        }
223    }
224
225    async fn get_balance(&self) -> Result<BalanceResponse, RelayerError> {
226        match self {
227            NetworkRelayer::Evm(relayer) => relayer.get_balance().await,
228            NetworkRelayer::Solana(relayer) => relayer.get_balance().await,
229            NetworkRelayer::Stellar(relayer) => relayer.get_balance().await,
230        }
231    }
232
233    async fn delete_pending_transactions(
234        &self,
235    ) -> Result<DeletePendingTransactionsResponse, RelayerError> {
236        match self {
237            NetworkRelayer::Evm(relayer) => relayer.delete_pending_transactions().await,
238            NetworkRelayer::Solana(_) => solana_not_supported_relayer(),
239            NetworkRelayer::Stellar(relayer) => relayer.delete_pending_transactions().await,
240        }
241    }
242
243    async fn sign_data(&self, request: SignDataRequest) -> Result<SignDataResponse, RelayerError> {
244        match self {
245            NetworkRelayer::Evm(relayer) => relayer.sign_data(request).await,
246            NetworkRelayer::Solana(_) => solana_not_supported_relayer(),
247            NetworkRelayer::Stellar(relayer) => relayer.sign_data(request).await,
248        }
249    }
250
251    async fn sign_typed_data(
252        &self,
253        request: SignTypedDataRequest,
254    ) -> Result<SignDataResponse, RelayerError> {
255        match self {
256            NetworkRelayer::Evm(relayer) => relayer.sign_typed_data(request).await,
257            NetworkRelayer::Solana(_) => solana_not_supported_relayer(),
258            NetworkRelayer::Stellar(relayer) => relayer.sign_typed_data(request).await,
259        }
260    }
261
262    async fn rpc(
263        &self,
264        request: JsonRpcRequest<NetworkRpcRequest>,
265    ) -> Result<JsonRpcResponse<NetworkRpcResult>, RelayerError> {
266        match self {
267            NetworkRelayer::Evm(relayer) => relayer.rpc(request).await,
268            NetworkRelayer::Solana(relayer) => relayer.rpc(request).await,
269            NetworkRelayer::Stellar(relayer) => relayer.rpc(request).await,
270        }
271    }
272
273    async fn get_status(&self) -> Result<RelayerStatus, RelayerError> {
274        match self {
275            NetworkRelayer::Evm(relayer) => relayer.get_status().await,
276            NetworkRelayer::Solana(_) => solana_not_supported_relayer(),
277            NetworkRelayer::Stellar(relayer) => relayer.get_status().await,
278        }
279    }
280
281    async fn validate_min_balance(&self) -> Result<(), RelayerError> {
282        match self {
283            NetworkRelayer::Evm(relayer) => relayer.validate_min_balance().await,
284            NetworkRelayer::Solana(relayer) => relayer.validate_min_balance().await,
285            NetworkRelayer::Stellar(relayer) => relayer.validate_min_balance().await,
286        }
287    }
288
289    async fn initialize_relayer(&self) -> Result<(), RelayerError> {
290        match self {
291            NetworkRelayer::Evm(relayer) => relayer.initialize_relayer().await,
292            NetworkRelayer::Solana(relayer) => relayer.initialize_relayer().await,
293            NetworkRelayer::Stellar(relayer) => relayer.initialize_relayer().await,
294        }
295    }
296}
297
298#[async_trait]
299pub trait RelayerFactoryTrait<J: JobProducerTrait + 'static> {
300    async fn create_relayer(
301        relayer: RelayerRepoModel,
302        signer: SignerRepoModel,
303        state: &ThinData<AppState<J>>,
304    ) -> Result<NetworkRelayer<J>, RelayerError>;
305}
306
307pub struct RelayerFactory;
308
309#[async_trait]
310impl<J: JobProducerTrait + 'static> RelayerFactoryTrait<J> for RelayerFactory {
311    async fn create_relayer(
312        relayer: RelayerRepoModel,
313        signer: SignerRepoModel,
314        state: &ThinData<AppState<J>>,
315    ) -> Result<NetworkRelayer<J>, RelayerError> {
316        match relayer.network_type {
317            NetworkType::Evm => {
318                let network_repo = state
319                    .network_repository()
320                    .get(NetworkType::Evm, &relayer.network)
321                    .await
322                    .ok()
323                    .flatten()
324                    .ok_or_else(|| {
325                        RelayerError::NetworkConfiguration(format!(
326                            "Network {} not found",
327                            relayer.network
328                        ))
329                    })?;
330
331                let network = EvmNetwork::try_from(network_repo)?;
332
333                let evm_provider = get_network_provider(&network, relayer.custom_rpc_urls.clone())?;
334                let signer_service = EvmSignerFactory::create_evm_signer(signer).await?;
335                let transaction_counter_service = Arc::new(TransactionCounterService::new(
336                    relayer.id.clone(),
337                    relayer.address.clone(),
338                    state.transaction_counter_store(),
339                ));
340                let relayer = DefaultEvmRelayer::new(
341                    relayer,
342                    signer_service,
343                    evm_provider,
344                    network,
345                    state.relayer_repository(),
346                    state.network_repository(),
347                    state.transaction_repository(),
348                    transaction_counter_service,
349                    state.job_producer(),
350                )?;
351
352                Ok(NetworkRelayer::Evm(relayer))
353            }
354            NetworkType::Solana => {
355                let solana_relayer = create_solana_relayer(
356                    relayer,
357                    signer,
358                    state.relayer_repository(),
359                    state.network_repository(),
360                    state.transaction_repository(),
361                    state.job_producer(),
362                )
363                .await?;
364                Ok(NetworkRelayer::Solana(solana_relayer))
365            }
366            NetworkType::Stellar => {
367                let network_repo = state
368                    .network_repository()
369                    .get(NetworkType::Stellar, &relayer.network)
370                    .await
371                    .ok()
372                    .flatten()
373                    .ok_or_else(|| {
374                        RelayerError::NetworkConfiguration(format!(
375                            "Network {} not found",
376                            relayer.network
377                        ))
378                    })?;
379
380                let network = StellarNetwork::try_from(network_repo)?;
381
382                let stellar_provider =
383                    get_network_provider(&network, relayer.custom_rpc_urls.clone())
384                        .map_err(|e| RelayerError::NetworkConfiguration(e.to_string()))?;
385
386                let transaction_counter_service = Arc::new(TransactionCounterService::new(
387                    relayer.id.clone(),
388                    relayer.address.clone(),
389                    state.transaction_counter_store(),
390                ));
391
392                let relayer = DefaultStellarRelayer::<J>::new(
393                    relayer,
394                    stellar_provider,
395                    stellar::StellarRelayerDependencies::new(
396                        state.relayer_repository(),
397                        state.network_repository(),
398                        state.transaction_repository(),
399                        transaction_counter_service,
400                        state.job_producer(),
401                    ),
402                )
403                .await?;
404                Ok(NetworkRelayer::Stellar(relayer))
405            }
406        }
407    }
408}
409
410#[derive(Serialize, Deserialize, ToSchema)]
411pub struct SignDataRequest {
412    pub message: String,
413}
414
415#[derive(Serialize, Deserialize, ToSchema)]
416pub struct SignDataResponseEvm {
417    pub r: String,
418    pub s: String,
419    pub v: u8,
420    pub sig: String,
421}
422
423#[derive(Serialize, Deserialize, ToSchema)]
424pub struct SignDataResponseSolana {
425    pub signature: String,
426    pub public_key: String,
427}
428
429#[derive(Serialize, Deserialize, ToSchema)]
430#[serde(untagged)]
431pub enum SignDataResponse {
432    Evm(SignDataResponseEvm),
433    Solana(SignDataResponseSolana),
434}
435
436#[derive(Serialize, Deserialize, ToSchema)]
437pub struct SignTypedDataRequest {
438    pub domain_separator: String,
439    pub hash_struct_message: String,
440}
441
442#[derive(Debug, Serialize, Deserialize)]
443pub struct SignTransactionResponseEvm {
444    pub hash: String,
445    pub signature: EvmTransactionDataSignature,
446    pub raw: Vec<u8>,
447}
448
449#[derive(Debug, Serialize, Deserialize)]
450pub struct SignTransactionResponseStellar {
451    pub signature: DecoratedSignature,
452}
453
454#[derive(Debug, Serialize, Deserialize)]
455pub enum SignTransactionResponse {
456    Evm(SignTransactionResponseEvm),
457    Solana(Vec<u8>),
458    Stellar(SignTransactionResponseStellar),
459}
460
461impl SignTransactionResponse {
462    pub fn into_evm(self) -> Result<SignTransactionResponseEvm, TransactionError> {
463        match self {
464            SignTransactionResponse::Evm(e) => Ok(e),
465            _ => Err(TransactionError::InvalidType(
466                "Expected EVM signature".to_string(),
467            )),
468        }
469    }
470}
471
472#[derive(Debug, Serialize, ToSchema)]
473pub struct BalanceResponse {
474    pub balance: u128,
475    #[schema(example = "wei")]
476    pub unit: String,
477}
478
479#[derive(Serialize, Deserialize, ToSchema)]
480pub struct RelayerUpdateRequest {
481    #[schema(nullable = false)]
482    pub paused: Option<bool>,
483}