openzeppelin_relayer/services/plugins/
relayer_api.rs

1//! This module is responsible for handling the requests to the relayer API.
2//!
3//! It manages an internal API that mirrors the HTTP external API of the relayer.
4//!
5//! Supported methods:
6//! - `sendTransaction` - sends a transaction to the relayer.
7//!
8use crate::domain::{
9    get_network_relayer, get_relayer_by_id, get_transaction_by_id, Relayer, SignTransactionRequest,
10};
11use crate::jobs::JobProducerTrait;
12use crate::models::{
13    AppState, NetworkRepoModel, NetworkTransactionRequest, NotificationRepoModel, RelayerRepoModel,
14    SignerRepoModel, ThinDataAppState, TransactionRepoModel, TransactionResponse,
15};
16use crate::observability::request_id::set_request_id;
17use crate::repositories::{
18    ApiKeyRepositoryTrait, NetworkRepository, PluginRepositoryTrait, RelayerRepository, Repository,
19    TransactionCounterTrait, TransactionRepository,
20};
21use crate::services::plugins::PluginError;
22use actix_web::web;
23use async_trait::async_trait;
24use serde::{Deserialize, Serialize};
25use strum::Display;
26use tracing::instrument;
27
28#[cfg(test)]
29use mockall::automock;
30
31#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Display)]
32pub enum PluginMethod {
33    #[serde(rename = "sendTransaction")]
34    SendTransaction,
35    #[serde(rename = "getTransaction")]
36    GetTransaction,
37    #[serde(rename = "getRelayerStatus")]
38    GetRelayerStatus,
39    #[serde(rename = "signTransaction")]
40    SignTransaction,
41    #[serde(rename = "getRelayer")]
42    GetRelayer,
43}
44
45#[derive(Deserialize, Serialize, Clone, Debug)]
46#[serde(rename_all = "camelCase")]
47pub struct Request {
48    pub request_id: String,
49    pub relayer_id: String,
50    pub method: PluginMethod,
51    pub payload: serde_json::Value,
52    pub http_request_id: Option<String>,
53}
54
55#[derive(Deserialize, Serialize, Clone, Debug)]
56#[serde(rename_all = "camelCase")]
57pub struct GetTransactionRequest {
58    pub transaction_id: String,
59}
60
61#[derive(Serialize, Deserialize, Clone, Debug, Default)]
62#[serde(rename_all = "camelCase")]
63pub struct Response {
64    pub request_id: String,
65    pub result: Option<serde_json::Value>,
66    pub error: Option<String>,
67}
68
69#[async_trait]
70#[cfg_attr(test, automock)]
71pub trait RelayerApiTrait<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>: Send + Sync
72where
73    J: JobProducerTrait + 'static,
74    TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
75    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
76    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
77    NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
78    SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
79    TCR: TransactionCounterTrait + Send + Sync + 'static,
80    PR: PluginRepositoryTrait + Send + Sync + 'static,
81    AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
82{
83    async fn handle_request(
84        &self,
85        request: Request,
86        state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
87    ) -> Response;
88
89    async fn process_request(
90        &self,
91        request: Request,
92        state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
93    ) -> Result<Response, PluginError>;
94
95    async fn handle_send_transaction(
96        &self,
97        request: Request,
98        state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
99    ) -> Result<Response, PluginError>;
100
101    async fn handle_get_transaction(
102        &self,
103        request: Request,
104        state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
105    ) -> Result<Response, PluginError>;
106
107    async fn handle_get_relayer_status(
108        &self,
109        request: Request,
110        state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
111    ) -> Result<Response, PluginError>;
112
113    async fn handle_sign_transaction(
114        &self,
115        request: Request,
116        state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
117    ) -> Result<Response, PluginError>;
118    async fn handle_get_relayer_info(
119        &self,
120        request: Request,
121        state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
122    ) -> Result<Response, PluginError>;
123}
124
125#[derive(Default)]
126pub struct RelayerApi;
127
128impl RelayerApi {
129    #[instrument(name = "Plugin::handle_request", skip_all, fields(method = %request.method, relayer_id = %request.relayer_id, plugin_req_id = %request.request_id))]
130    pub async fn handle_request<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
131        &self,
132        request: Request,
133        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
134    ) -> Response
135    where
136        J: JobProducerTrait + 'static,
137        TR: TransactionRepository
138            + Repository<TransactionRepoModel, String>
139            + Send
140            + Sync
141            + 'static,
142        RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
143        NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
144        NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
145        SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
146        TCR: TransactionCounterTrait + Send + Sync + 'static,
147        PR: PluginRepositoryTrait + Send + Sync + 'static,
148        AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
149    {
150        // Restore original HTTP request id onto this span if provided
151        if let Some(http_rid) = request.http_request_id.clone() {
152            set_request_id(http_rid);
153        }
154
155        match self.process_request(request.clone(), state).await {
156            Ok(response) => response,
157            Err(e) => Response {
158                request_id: request.request_id,
159                result: None,
160                error: Some(e.to_string()),
161            },
162        }
163    }
164
165    async fn process_request<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
166        &self,
167        request: Request,
168        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
169    ) -> Result<Response, PluginError>
170    where
171        J: JobProducerTrait + 'static,
172        TR: TransactionRepository
173            + Repository<TransactionRepoModel, String>
174            + Send
175            + Sync
176            + 'static,
177        RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
178        NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
179        NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
180        SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
181        TCR: TransactionCounterTrait + Send + Sync + 'static,
182        PR: PluginRepositoryTrait + Send + Sync + 'static,
183        AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
184    {
185        match request.method {
186            PluginMethod::SendTransaction => self.handle_send_transaction(request, state).await,
187            PluginMethod::GetTransaction => self.handle_get_transaction(request, state).await,
188            PluginMethod::GetRelayerStatus => self.handle_get_relayer_status(request, state).await,
189            PluginMethod::SignTransaction => self.handle_sign_transaction(request, state).await,
190            PluginMethod::GetRelayer => self.handle_get_relayer_info(request, state).await,
191        }
192    }
193
194    async fn handle_send_transaction<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
195        &self,
196        request: Request,
197        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
198    ) -> Result<Response, PluginError>
199    where
200        J: JobProducerTrait + 'static,
201        TR: TransactionRepository
202            + Repository<TransactionRepoModel, String>
203            + Send
204            + Sync
205            + 'static,
206        RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
207        NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
208        NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
209        SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
210        TCR: TransactionCounterTrait + Send + Sync + 'static,
211        PR: PluginRepositoryTrait + Send + Sync + 'static,
212        AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
213    {
214        let relayer_repo_model = get_relayer_by_id(request.relayer_id.clone(), state)
215            .await
216            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
217
218        relayer_repo_model
219            .validate_active_state()
220            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
221
222        let network_relayer = get_network_relayer(request.relayer_id.clone(), state)
223            .await
224            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
225
226        let tx_request = NetworkTransactionRequest::from_json(
227            &relayer_repo_model.network_type,
228            request.payload.clone(),
229        )
230        .map_err(|e| PluginError::RelayerError(e.to_string()))?;
231
232        tx_request
233            .validate(&relayer_repo_model)
234            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
235
236        let transaction = network_relayer
237            .process_transaction_request(tx_request)
238            .await
239            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
240
241        let transaction_response: TransactionResponse = transaction.into();
242        let result = serde_json::to_value(transaction_response)
243            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
244
245        Ok(Response {
246            request_id: request.request_id,
247            result: Some(result),
248            error: None,
249        })
250    }
251
252    async fn handle_get_transaction<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
253        &self,
254        request: Request,
255        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
256    ) -> Result<Response, PluginError>
257    where
258        J: JobProducerTrait + 'static,
259        TR: TransactionRepository
260            + Repository<TransactionRepoModel, String>
261            + Send
262            + Sync
263            + 'static,
264        RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
265        NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
266        NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
267        SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
268        TCR: TransactionCounterTrait + Send + Sync + 'static,
269        PR: PluginRepositoryTrait + Send + Sync + 'static,
270        AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
271    {
272        // validation purpose only, checks if relayer exists
273        get_relayer_by_id(request.relayer_id.clone(), state)
274            .await
275            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
276
277        let get_transaction_request: GetTransactionRequest =
278            serde_json::from_value(request.payload)
279                .map_err(|e| PluginError::InvalidPayload(e.to_string()))?;
280
281        let transaction = get_transaction_by_id(get_transaction_request.transaction_id, state)
282            .await
283            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
284
285        let transaction_response: TransactionResponse = transaction.into();
286
287        let result = serde_json::to_value(transaction_response)
288            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
289
290        Ok(Response {
291            request_id: request.request_id,
292            result: Some(result),
293            error: None,
294        })
295    }
296
297    async fn handle_get_relayer_status<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
298        &self,
299        request: Request,
300        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
301    ) -> Result<Response, PluginError>
302    where
303        J: JobProducerTrait + 'static,
304        TR: TransactionRepository
305            + Repository<TransactionRepoModel, String>
306            + Send
307            + Sync
308            + 'static,
309        RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
310        NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
311        NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
312        SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
313        TCR: TransactionCounterTrait + Send + Sync + 'static,
314        PR: PluginRepositoryTrait + Send + Sync + 'static,
315        AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
316    {
317        let network_relayer = get_network_relayer(request.relayer_id.clone(), state)
318            .await
319            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
320
321        let status = network_relayer
322            .get_status()
323            .await
324            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
325
326        let result =
327            serde_json::to_value(status).map_err(|e| PluginError::RelayerError(e.to_string()))?;
328
329        Ok(Response {
330            request_id: request.request_id,
331            result: Some(result),
332            error: None,
333        })
334    }
335
336    async fn handle_sign_transaction<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
337        &self,
338        request: Request,
339        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
340    ) -> Result<Response, PluginError>
341    where
342        J: JobProducerTrait + 'static,
343        TR: TransactionRepository
344            + Repository<TransactionRepoModel, String>
345            + Send
346            + Sync
347            + 'static,
348        RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
349        NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
350        NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
351        SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
352        TCR: TransactionCounterTrait + Send + Sync + 'static,
353        PR: PluginRepositoryTrait + Send + Sync + 'static,
354        AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
355    {
356        let sign_request: SignTransactionRequest = serde_json::from_value(request.payload)
357            .map_err(|e| PluginError::InvalidPayload(e.to_string()))?;
358
359        let network_relayer = get_network_relayer(request.relayer_id.clone(), state)
360            .await
361            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
362
363        let response = network_relayer
364            .sign_transaction(&sign_request)
365            .await
366            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
367
368        let result =
369            serde_json::to_value(response).map_err(|e| PluginError::RelayerError(e.to_string()))?;
370
371        Ok(Response {
372            request_id: request.request_id,
373            result: Some(result),
374            error: None,
375        })
376    }
377
378    async fn handle_get_relayer_info<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
379        &self,
380        request: Request,
381        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
382    ) -> Result<Response, PluginError>
383    where
384        J: JobProducerTrait + 'static,
385        TR: TransactionRepository
386            + Repository<TransactionRepoModel, String>
387            + Send
388            + Sync
389            + 'static,
390        RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
391        NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
392        NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
393        SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
394        TCR: TransactionCounterTrait + Send + Sync + 'static,
395        PR: PluginRepositoryTrait + Send + Sync + 'static,
396        AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
397    {
398        let relayer = get_relayer_by_id(request.relayer_id.clone(), state)
399            .await
400            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
401        let relayer_response: crate::models::RelayerResponse = relayer.into();
402        let result = serde_json::to_value(relayer_response)
403            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
404        Ok(Response {
405            request_id: request.request_id,
406            result: Some(result),
407            error: None,
408        })
409    }
410}
411
412#[async_trait]
413impl<J, RR, TR, NR, NFR, SR, TCR, PR, AKR> RelayerApiTrait<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>
414    for RelayerApi
415where
416    J: JobProducerTrait + 'static,
417    TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
418    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
419    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
420    NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
421    SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
422    TCR: TransactionCounterTrait + Send + Sync + 'static,
423    PR: PluginRepositoryTrait + Send + Sync + 'static,
424    AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
425{
426    async fn handle_request(
427        &self,
428        request: Request,
429        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
430    ) -> Response {
431        self.handle_request(request, state).await
432    }
433
434    async fn process_request(
435        &self,
436        request: Request,
437        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
438    ) -> Result<Response, PluginError> {
439        self.process_request(request, state).await
440    }
441
442    async fn handle_send_transaction(
443        &self,
444        request: Request,
445        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
446    ) -> Result<Response, PluginError> {
447        self.handle_send_transaction(request, state).await
448    }
449
450    async fn handle_get_transaction(
451        &self,
452        request: Request,
453        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
454    ) -> Result<Response, PluginError> {
455        self.handle_get_transaction(request, state).await
456    }
457
458    async fn handle_get_relayer_status(
459        &self,
460        request: Request,
461        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
462    ) -> Result<Response, PluginError> {
463        self.handle_get_relayer_status(request, state).await
464    }
465
466    async fn handle_sign_transaction(
467        &self,
468        request: Request,
469        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
470    ) -> Result<Response, PluginError> {
471        self.handle_sign_transaction(request, state).await
472    }
473
474    async fn handle_get_relayer_info(
475        &self,
476        request: Request,
477        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
478    ) -> Result<Response, PluginError> {
479        self.handle_get_relayer_info(request, state).await
480    }
481}
482
483#[cfg(test)]
484mod tests {
485    use std::env;
486
487    use crate::utils::mocks::mockutils::{
488        create_mock_app_state, create_mock_evm_transaction_request, create_mock_network,
489        create_mock_relayer, create_mock_signer, create_mock_transaction,
490    };
491
492    use super::*;
493
494    fn setup_test_env() {
495        env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D"); // noboost
496        env::set_var("REDIS_URL", "redis://localhost:6379");
497        env::set_var("RPC_TIMEOUT_MS", "5000");
498    }
499
500    #[tokio::test]
501    async fn test_handle_request() {
502        setup_test_env();
503        let state = create_mock_app_state(
504            None,
505            Some(vec![create_mock_relayer("test".to_string(), false)]),
506            Some(vec![create_mock_signer()]),
507            Some(vec![create_mock_network()]),
508            None,
509            None,
510        )
511        .await;
512
513        let request = Request {
514            request_id: "test".to_string(),
515            relayer_id: "test".to_string(),
516            method: PluginMethod::SendTransaction,
517            payload: serde_json::json!(create_mock_evm_transaction_request()),
518            http_request_id: None,
519        };
520
521        let relayer_api = RelayerApi;
522        let response = relayer_api
523            .handle_request(request.clone(), &web::ThinData(state))
524            .await;
525
526        assert!(response.error.is_none());
527        assert!(response.result.is_some());
528    }
529
530    #[tokio::test]
531    async fn test_handle_request_error_paused_relayer() {
532        setup_test_env();
533        let paused = true;
534        let state = create_mock_app_state(
535            None,
536            Some(vec![create_mock_relayer("test".to_string(), paused)]),
537            Some(vec![create_mock_signer()]),
538            Some(vec![create_mock_network()]),
539            None,
540            None,
541        )
542        .await;
543
544        let request = Request {
545            request_id: "test".to_string(),
546            relayer_id: "test".to_string(),
547            method: PluginMethod::SendTransaction,
548            payload: serde_json::json!(create_mock_evm_transaction_request()),
549            http_request_id: None,
550        };
551
552        let relayer_api = RelayerApi;
553        let response = relayer_api
554            .handle_request(request.clone(), &web::ThinData(state))
555            .await;
556
557        assert!(response.error.is_some());
558        assert!(response.result.is_none());
559        assert_eq!(response.error.unwrap(), "Relayer error: Relayer is paused");
560    }
561
562    #[tokio::test]
563    async fn test_handle_request_using_trait() {
564        setup_test_env();
565        let state = create_mock_app_state(
566            None,
567            Some(vec![create_mock_relayer("test".to_string(), false)]),
568            Some(vec![create_mock_signer()]),
569            Some(vec![create_mock_network()]),
570            None,
571            None,
572        )
573        .await;
574
575        let request = Request {
576            request_id: "test".to_string(),
577            relayer_id: "test".to_string(),
578            method: PluginMethod::SendTransaction,
579            payload: serde_json::json!(create_mock_evm_transaction_request()),
580            http_request_id: None,
581        };
582
583        let relayer_api = RelayerApi;
584
585        let state = web::ThinData(state);
586
587        let response = RelayerApiTrait::handle_request(&relayer_api, request.clone(), &state).await;
588
589        assert!(response.error.is_none());
590        assert!(response.result.is_some());
591
592        let response =
593            RelayerApiTrait::process_request(&relayer_api, request.clone(), &state).await;
594
595        assert!(response.is_ok());
596
597        let response =
598            RelayerApiTrait::handle_send_transaction(&relayer_api, request.clone(), &state).await;
599
600        assert!(response.is_ok());
601    }
602
603    #[tokio::test]
604    async fn test_handle_get_transaction() {
605        setup_test_env();
606        let state = create_mock_app_state(
607            None,
608            Some(vec![create_mock_relayer("test".to_string(), false)]),
609            Some(vec![create_mock_signer()]),
610            Some(vec![create_mock_network()]),
611            None,
612            Some(vec![create_mock_transaction()]),
613        )
614        .await;
615
616        let request = Request {
617            request_id: "test".to_string(),
618            relayer_id: "test".to_string(),
619            method: PluginMethod::GetTransaction,
620            payload: serde_json::json!(GetTransactionRequest {
621                transaction_id: "test".to_string(),
622            }),
623            http_request_id: None,
624        };
625
626        let relayer_api = RelayerApi;
627        let response = relayer_api
628            .handle_request(request.clone(), &web::ThinData(state))
629            .await;
630
631        assert!(response.error.is_none());
632        assert!(response.result.is_some());
633    }
634
635    #[tokio::test]
636    async fn test_handle_get_transaction_error_relayer_not_found() {
637        setup_test_env();
638        let state = create_mock_app_state(
639            None,
640            None,
641            Some(vec![create_mock_signer()]),
642            Some(vec![create_mock_network()]),
643            None,
644            Some(vec![create_mock_transaction()]),
645        )
646        .await;
647
648        let request = Request {
649            request_id: "test".to_string(),
650            relayer_id: "test".to_string(),
651            method: PluginMethod::GetTransaction,
652            payload: serde_json::json!(GetTransactionRequest {
653                transaction_id: "test".to_string(),
654            }),
655            http_request_id: None,
656        };
657
658        let relayer_api = RelayerApi;
659        let response = relayer_api
660            .handle_request(request.clone(), &web::ThinData(state))
661            .await;
662
663        assert!(response.error.is_some());
664        let error = response.error.unwrap();
665        assert!(error.contains("Relayer with ID test not found"));
666    }
667
668    #[tokio::test]
669    async fn test_handle_get_transaction_error_transaction_not_found() {
670        setup_test_env();
671        let state = create_mock_app_state(
672            None,
673            Some(vec![create_mock_relayer("test".to_string(), false)]),
674            Some(vec![create_mock_signer()]),
675            Some(vec![create_mock_network()]),
676            None,
677            None,
678        )
679        .await;
680
681        let request = Request {
682            request_id: "test".to_string(),
683            relayer_id: "test".to_string(),
684            method: PluginMethod::GetTransaction,
685            payload: serde_json::json!(GetTransactionRequest {
686                transaction_id: "test".to_string(),
687            }),
688            http_request_id: None,
689        };
690
691        let relayer_api = RelayerApi;
692        let response = relayer_api
693            .handle_request(request.clone(), &web::ThinData(state))
694            .await;
695
696        assert!(response.error.is_some());
697        let error = response.error.unwrap();
698        assert!(error.contains("Transaction with ID test not found"));
699    }
700
701    #[tokio::test]
702    async fn test_handle_get_relayer_status_relayer_not_found() {
703        setup_test_env();
704        let state = create_mock_app_state(
705            None,
706            None,
707            Some(vec![create_mock_signer()]),
708            Some(vec![create_mock_network()]),
709            None,
710            None,
711        )
712        .await;
713
714        let request = Request {
715            request_id: "test".to_string(),
716            relayer_id: "test".to_string(),
717            method: PluginMethod::GetRelayerStatus,
718            payload: serde_json::json!({}),
719            http_request_id: None,
720        };
721
722        let relayer_api = RelayerApi;
723        let response = relayer_api
724            .handle_request(request.clone(), &web::ThinData(state))
725            .await;
726
727        assert!(response.error.is_some());
728        let error = response.error.unwrap();
729        assert!(error.contains("Relayer with ID test not found"));
730    }
731
732    #[tokio::test]
733    async fn test_handle_sign_transaction_evm_not_supported() {
734        setup_test_env();
735        let state = create_mock_app_state(
736            None,
737            Some(vec![create_mock_relayer("test".to_string(), false)]),
738            Some(vec![create_mock_signer()]),
739            Some(vec![create_mock_network()]),
740            None,
741            None,
742        )
743        .await;
744
745        let request = Request {
746            request_id: "test".to_string(),
747            relayer_id: "test".to_string(),
748            method: PluginMethod::SignTransaction,
749            payload: serde_json::json!({
750                "unsigned_xdr": "test_xdr"
751            }),
752            http_request_id: None,
753        };
754
755        let relayer_api = RelayerApi;
756        let response = relayer_api
757            .handle_request(request.clone(), &web::ThinData(state))
758            .await;
759
760        assert!(response.error.is_some());
761        let error = response.error.unwrap();
762        assert!(error.contains("sign_transaction not supported for EVM"));
763    }
764
765    #[tokio::test]
766    async fn test_handle_sign_transaction_invalid_payload() {
767        setup_test_env();
768        let state = create_mock_app_state(
769            None,
770            Some(vec![create_mock_relayer("test".to_string(), false)]),
771            Some(vec![create_mock_signer()]),
772            Some(vec![create_mock_network()]),
773            None,
774            None,
775        )
776        .await;
777
778        let request = Request {
779            request_id: "test".to_string(),
780            relayer_id: "test".to_string(),
781            method: PluginMethod::SignTransaction,
782            payload: serde_json::json!({"invalid": "payload"}),
783            http_request_id: None,
784        };
785
786        let relayer_api = RelayerApi;
787        let response = relayer_api
788            .handle_request(request.clone(), &web::ThinData(state))
789            .await;
790
791        assert!(response.error.is_some());
792        let error = response.error.unwrap();
793        assert!(error.contains("Invalid payload"));
794    }
795
796    #[tokio::test]
797    async fn test_handle_sign_transaction_relayer_not_found() {
798        setup_test_env();
799        let state = create_mock_app_state(
800            None,
801            None,
802            Some(vec![create_mock_signer()]),
803            Some(vec![create_mock_network()]),
804            None,
805            None,
806        )
807        .await;
808
809        let request = Request {
810            request_id: "test".to_string(),
811            relayer_id: "test".to_string(),
812            method: PluginMethod::SignTransaction,
813            payload: serde_json::json!({
814                "unsigned_xdr": "test_xdr"
815            }),
816            http_request_id: None,
817        };
818
819        let relayer_api = RelayerApi;
820        let response = relayer_api
821            .handle_request(request.clone(), &web::ThinData(state))
822            .await;
823
824        assert!(response.error.is_some());
825        let error = response.error.unwrap();
826        assert!(error.contains("Relayer with ID test not found"));
827    }
828
829    #[tokio::test]
830    async fn test_handle_get_relayer_info_success() {
831        setup_test_env();
832        let state = create_mock_app_state(
833            None,
834            Some(vec![create_mock_relayer("test".to_string(), false)]),
835            Some(vec![create_mock_signer()]),
836            Some(vec![create_mock_network()]),
837            None,
838            None,
839        )
840        .await;
841
842        let request = Request {
843            request_id: "test".to_string(),
844            relayer_id: "test".to_string(),
845            method: PluginMethod::GetRelayer,
846            payload: serde_json::json!({}),
847            http_request_id: None,
848        };
849
850        let relayer_api = RelayerApi;
851        let response = relayer_api
852            .handle_request(request.clone(), &web::ThinData(state))
853            .await;
854
855        assert!(response.error.is_none());
856        assert!(response.result.is_some());
857
858        let result = response.result.unwrap();
859        assert!(result.get("id").is_some());
860        assert!(result.get("name").is_some());
861        assert!(result.get("network").is_some());
862        assert!(result.get("address").is_some());
863    }
864
865    #[tokio::test]
866    async fn test_handle_get_relayer_info_relayer_not_found() {
867        setup_test_env();
868        let state = create_mock_app_state(
869            None,
870            None,
871            Some(vec![create_mock_signer()]),
872            Some(vec![create_mock_network()]),
873            None,
874            None,
875        )
876        .await;
877
878        let request = Request {
879            request_id: "test".to_string(),
880            relayer_id: "test".to_string(),
881            method: PluginMethod::GetRelayer,
882            payload: serde_json::json!({}),
883            http_request_id: None,
884        };
885
886        let relayer_api = RelayerApi;
887        let response = relayer_api
888            .handle_request(request.clone(), &web::ThinData(state))
889            .await;
890
891        assert!(response.error.is_some());
892        let error = response.error.unwrap();
893        assert!(error.contains("Relayer with ID test not found"));
894    }
895}