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    convert_to_internal_rpc_request, AppState, JsonRpcRequest, NetworkRepoModel, NetworkRpcRequest,
14    NetworkTransactionRequest, NotificationRepoModel, RelayerRepoModel, SignerRepoModel,
15    ThinDataAppState, TransactionRepoModel, TransactionResponse,
16};
17use crate::observability::request_id::set_request_id;
18use crate::repositories::{
19    ApiKeyRepositoryTrait, NetworkRepository, PluginRepositoryTrait, RelayerRepository, Repository,
20    TransactionCounterTrait, TransactionRepository,
21};
22use crate::services::plugins::PluginError;
23use actix_web::web;
24use async_trait::async_trait;
25use serde::{Deserialize, Serialize};
26use strum::Display;
27use tracing::instrument;
28
29#[cfg(test)]
30use mockall::automock;
31
32#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Display)]
33pub enum PluginMethod {
34    #[serde(rename = "sendTransaction")]
35    SendTransaction,
36    #[serde(rename = "getTransaction")]
37    GetTransaction,
38    #[serde(rename = "getRelayerStatus")]
39    GetRelayerStatus,
40    #[serde(rename = "signTransaction")]
41    SignTransaction,
42    #[serde(rename = "getRelayer")]
43    GetRelayer,
44    #[serde(rename = "rpc")]
45    Rpc,
46}
47
48#[derive(Deserialize, Serialize, Clone, Debug)]
49#[serde(rename_all = "camelCase")]
50pub struct Request {
51    pub request_id: String,
52    pub relayer_id: String,
53    pub method: PluginMethod,
54    pub payload: serde_json::Value,
55    pub http_request_id: Option<String>,
56}
57
58#[derive(Deserialize, Serialize, Clone, Debug)]
59#[serde(rename_all = "camelCase")]
60pub struct GetTransactionRequest {
61    pub transaction_id: String,
62}
63
64#[derive(Serialize, Deserialize, Clone, Debug, Default)]
65#[serde(rename_all = "camelCase")]
66pub struct Response {
67    pub request_id: String,
68    pub result: Option<serde_json::Value>,
69    pub error: Option<String>,
70}
71
72#[async_trait]
73#[cfg_attr(test, automock)]
74pub trait RelayerApiTrait<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>: Send + Sync
75where
76    J: JobProducerTrait + 'static,
77    TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
78    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
79    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
80    NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
81    SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
82    TCR: TransactionCounterTrait + Send + Sync + 'static,
83    PR: PluginRepositoryTrait + Send + Sync + 'static,
84    AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
85{
86    async fn handle_request(
87        &self,
88        request: Request,
89        state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
90    ) -> Response;
91
92    async fn process_request(
93        &self,
94        request: Request,
95        state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
96    ) -> Result<Response, PluginError>;
97
98    async fn handle_send_transaction(
99        &self,
100        request: Request,
101        state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
102    ) -> Result<Response, PluginError>;
103
104    async fn handle_get_transaction(
105        &self,
106        request: Request,
107        state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
108    ) -> Result<Response, PluginError>;
109
110    async fn handle_get_relayer_status(
111        &self,
112        request: Request,
113        state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
114    ) -> Result<Response, PluginError>;
115
116    async fn handle_sign_transaction(
117        &self,
118        request: Request,
119        state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
120    ) -> Result<Response, PluginError>;
121    async fn handle_get_relayer_info(
122        &self,
123        request: Request,
124        state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
125    ) -> Result<Response, PluginError>;
126    async fn handle_rpc_request(
127        &self,
128        request: Request,
129        state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
130    ) -> Result<Response, PluginError>;
131}
132
133#[derive(Default)]
134pub struct RelayerApi;
135
136impl RelayerApi {
137    #[instrument(name = "Plugin::handle_request", skip_all, fields(method = %request.method, relayer_id = %request.relayer_id, plugin_req_id = %request.request_id))]
138    pub async fn handle_request<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
139        &self,
140        request: Request,
141        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
142    ) -> Response
143    where
144        J: JobProducerTrait + 'static,
145        TR: TransactionRepository
146            + Repository<TransactionRepoModel, String>
147            + Send
148            + Sync
149            + 'static,
150        RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
151        NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
152        NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
153        SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
154        TCR: TransactionCounterTrait + Send + Sync + 'static,
155        PR: PluginRepositoryTrait + Send + Sync + 'static,
156        AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
157    {
158        // Restore original HTTP request id onto this span if provided
159        if let Some(http_rid) = request.http_request_id.clone() {
160            set_request_id(http_rid);
161        }
162
163        match self.process_request(request.clone(), state).await {
164            Ok(response) => response,
165            Err(e) => Response {
166                request_id: request.request_id,
167                result: None,
168                error: Some(e.to_string()),
169            },
170        }
171    }
172
173    async fn process_request<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
174        &self,
175        request: Request,
176        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
177    ) -> Result<Response, PluginError>
178    where
179        J: JobProducerTrait + 'static,
180        TR: TransactionRepository
181            + Repository<TransactionRepoModel, String>
182            + Send
183            + Sync
184            + 'static,
185        RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
186        NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
187        NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
188        SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
189        TCR: TransactionCounterTrait + Send + Sync + 'static,
190        PR: PluginRepositoryTrait + Send + Sync + 'static,
191        AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
192    {
193        match request.method {
194            PluginMethod::SendTransaction => self.handle_send_transaction(request, state).await,
195            PluginMethod::GetTransaction => self.handle_get_transaction(request, state).await,
196            PluginMethod::GetRelayerStatus => self.handle_get_relayer_status(request, state).await,
197            PluginMethod::SignTransaction => self.handle_sign_transaction(request, state).await,
198            PluginMethod::GetRelayer => self.handle_get_relayer_info(request, state).await,
199            PluginMethod::Rpc => self.handle_rpc_request(request, state).await,
200        }
201    }
202
203    async fn handle_send_transaction<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
204        &self,
205        request: Request,
206        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
207    ) -> Result<Response, PluginError>
208    where
209        J: JobProducerTrait + 'static,
210        TR: TransactionRepository
211            + Repository<TransactionRepoModel, String>
212            + Send
213            + Sync
214            + 'static,
215        RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
216        NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
217        NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
218        SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
219        TCR: TransactionCounterTrait + Send + Sync + 'static,
220        PR: PluginRepositoryTrait + Send + Sync + 'static,
221        AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
222    {
223        let relayer_repo_model = get_relayer_by_id(request.relayer_id.clone(), state)
224            .await
225            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
226
227        relayer_repo_model
228            .validate_active_state()
229            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
230
231        let network_relayer = get_network_relayer(request.relayer_id.clone(), state)
232            .await
233            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
234
235        let tx_request = NetworkTransactionRequest::from_json(
236            &relayer_repo_model.network_type,
237            request.payload.clone(),
238        )
239        .map_err(|e| PluginError::RelayerError(e.to_string()))?;
240
241        tx_request
242            .validate(&relayer_repo_model)
243            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
244
245        let transaction = network_relayer
246            .process_transaction_request(tx_request)
247            .await
248            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
249
250        let transaction_response: TransactionResponse = transaction.into();
251        let result = serde_json::to_value(transaction_response)
252            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
253
254        Ok(Response {
255            request_id: request.request_id,
256            result: Some(result),
257            error: None,
258        })
259    }
260
261    async fn handle_get_transaction<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
262        &self,
263        request: Request,
264        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
265    ) -> Result<Response, PluginError>
266    where
267        J: JobProducerTrait + 'static,
268        TR: TransactionRepository
269            + Repository<TransactionRepoModel, String>
270            + Send
271            + Sync
272            + 'static,
273        RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
274        NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
275        NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
276        SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
277        TCR: TransactionCounterTrait + Send + Sync + 'static,
278        PR: PluginRepositoryTrait + Send + Sync + 'static,
279        AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
280    {
281        // validation purpose only, checks if relayer exists
282        get_relayer_by_id(request.relayer_id.clone(), state)
283            .await
284            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
285
286        let get_transaction_request: GetTransactionRequest =
287            serde_json::from_value(request.payload)
288                .map_err(|e| PluginError::InvalidPayload(e.to_string()))?;
289
290        let transaction = get_transaction_by_id(get_transaction_request.transaction_id, state)
291            .await
292            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
293
294        let transaction_response: TransactionResponse = transaction.into();
295
296        let result = serde_json::to_value(transaction_response)
297            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
298
299        Ok(Response {
300            request_id: request.request_id,
301            result: Some(result),
302            error: None,
303        })
304    }
305
306    async fn handle_get_relayer_status<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
307        &self,
308        request: Request,
309        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
310    ) -> Result<Response, PluginError>
311    where
312        J: JobProducerTrait + 'static,
313        TR: TransactionRepository
314            + Repository<TransactionRepoModel, String>
315            + Send
316            + Sync
317            + 'static,
318        RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
319        NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
320        NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
321        SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
322        TCR: TransactionCounterTrait + Send + Sync + 'static,
323        PR: PluginRepositoryTrait + Send + Sync + 'static,
324        AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
325    {
326        let network_relayer = get_network_relayer(request.relayer_id.clone(), state)
327            .await
328            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
329
330        let status = network_relayer
331            .get_status()
332            .await
333            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
334
335        let result =
336            serde_json::to_value(status).map_err(|e| PluginError::RelayerError(e.to_string()))?;
337
338        Ok(Response {
339            request_id: request.request_id,
340            result: Some(result),
341            error: None,
342        })
343    }
344
345    async fn handle_sign_transaction<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
346        &self,
347        request: Request,
348        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
349    ) -> Result<Response, PluginError>
350    where
351        J: JobProducerTrait + 'static,
352        TR: TransactionRepository
353            + Repository<TransactionRepoModel, String>
354            + Send
355            + Sync
356            + 'static,
357        RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
358        NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
359        NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
360        SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
361        TCR: TransactionCounterTrait + Send + Sync + 'static,
362        PR: PluginRepositoryTrait + Send + Sync + 'static,
363        AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
364    {
365        let sign_request: SignTransactionRequest = serde_json::from_value(request.payload)
366            .map_err(|e| PluginError::InvalidPayload(e.to_string()))?;
367
368        let network_relayer = get_network_relayer(request.relayer_id.clone(), state)
369            .await
370            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
371
372        let response = network_relayer
373            .sign_transaction(&sign_request)
374            .await
375            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
376
377        let result =
378            serde_json::to_value(response).map_err(|e| PluginError::RelayerError(e.to_string()))?;
379
380        Ok(Response {
381            request_id: request.request_id,
382            result: Some(result),
383            error: None,
384        })
385    }
386
387    async fn handle_get_relayer_info<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
388        &self,
389        request: Request,
390        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
391    ) -> Result<Response, PluginError>
392    where
393        J: JobProducerTrait + 'static,
394        TR: TransactionRepository
395            + Repository<TransactionRepoModel, String>
396            + Send
397            + Sync
398            + 'static,
399        RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
400        NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
401        NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
402        SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
403        TCR: TransactionCounterTrait + Send + Sync + 'static,
404        PR: PluginRepositoryTrait + Send + Sync + 'static,
405        AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
406    {
407        let relayer = get_relayer_by_id(request.relayer_id.clone(), state)
408            .await
409            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
410        let relayer_response: crate::models::RelayerResponse = relayer.into();
411        let result = serde_json::to_value(relayer_response)
412            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
413        Ok(Response {
414            request_id: request.request_id,
415            result: Some(result),
416            error: None,
417        })
418    }
419
420    async fn handle_rpc_request<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
421        &self,
422        request: Request,
423        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
424    ) -> Result<Response, PluginError>
425    where
426        J: JobProducerTrait + 'static,
427        TR: TransactionRepository
428            + Repository<TransactionRepoModel, String>
429            + Send
430            + Sync
431            + 'static,
432        RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
433        NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
434        NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
435        SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
436        TCR: TransactionCounterTrait + Send + Sync + 'static,
437        PR: PluginRepositoryTrait + Send + Sync + 'static,
438        AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
439    {
440        let relayer_repo_model = get_relayer_by_id(request.relayer_id.clone(), state)
441            .await
442            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
443
444        relayer_repo_model
445            .validate_active_state()
446            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
447
448        let network_relayer = get_network_relayer(request.relayer_id.clone(), state)
449            .await
450            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
451
452        // Use the network type from relayer_repo_model to parse the request with correct type context
453        let network_rpc_request: JsonRpcRequest<NetworkRpcRequest> =
454            convert_to_internal_rpc_request(request.payload, &relayer_repo_model.network_type)
455                .map_err(|e| PluginError::InvalidPayload(e.to_string()))?;
456
457        let result = network_relayer.rpc(network_rpc_request).await;
458
459        match result {
460            Ok(json_rpc_response) => {
461                let result_value = serde_json::to_value(json_rpc_response)
462                    .map_err(|e| PluginError::RelayerError(e.to_string()))?;
463                Ok(Response {
464                    request_id: request.request_id,
465                    result: Some(result_value),
466                    error: None,
467                })
468            }
469            Err(e) => Ok(Response {
470                request_id: request.request_id,
471                result: None,
472                error: Some(e.to_string()),
473            }),
474        }
475    }
476}
477
478#[async_trait]
479impl<J, RR, TR, NR, NFR, SR, TCR, PR, AKR> RelayerApiTrait<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>
480    for RelayerApi
481where
482    J: JobProducerTrait + 'static,
483    TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
484    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
485    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
486    NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
487    SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
488    TCR: TransactionCounterTrait + Send + Sync + 'static,
489    PR: PluginRepositoryTrait + Send + Sync + 'static,
490    AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
491{
492    async fn handle_request(
493        &self,
494        request: Request,
495        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
496    ) -> Response {
497        self.handle_request(request, state).await
498    }
499
500    async fn process_request(
501        &self,
502        request: Request,
503        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
504    ) -> Result<Response, PluginError> {
505        self.process_request(request, state).await
506    }
507
508    async fn handle_send_transaction(
509        &self,
510        request: Request,
511        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
512    ) -> Result<Response, PluginError> {
513        self.handle_send_transaction(request, state).await
514    }
515
516    async fn handle_get_transaction(
517        &self,
518        request: Request,
519        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
520    ) -> Result<Response, PluginError> {
521        self.handle_get_transaction(request, state).await
522    }
523
524    async fn handle_get_relayer_status(
525        &self,
526        request: Request,
527        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
528    ) -> Result<Response, PluginError> {
529        self.handle_get_relayer_status(request, state).await
530    }
531
532    async fn handle_sign_transaction(
533        &self,
534        request: Request,
535        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
536    ) -> Result<Response, PluginError> {
537        self.handle_sign_transaction(request, state).await
538    }
539
540    async fn handle_get_relayer_info(
541        &self,
542        request: Request,
543        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
544    ) -> Result<Response, PluginError> {
545        self.handle_get_relayer_info(request, state).await
546    }
547
548    async fn handle_rpc_request(
549        &self,
550        request: Request,
551        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
552    ) -> Result<Response, PluginError> {
553        self.handle_rpc_request(request, state).await
554    }
555}
556
557#[cfg(test)]
558mod tests {
559    use std::env;
560
561    use crate::utils::mocks::mockutils::{
562        create_mock_app_state, create_mock_evm_transaction_request, create_mock_network,
563        create_mock_relayer, create_mock_signer, create_mock_transaction,
564    };
565
566    use super::*;
567
568    fn setup_test_env() {
569        env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D"); // noboost
570        env::set_var("REDIS_URL", "redis://localhost:6379");
571        env::set_var("RPC_TIMEOUT_MS", "5000");
572    }
573
574    #[tokio::test]
575    async fn test_handle_request() {
576        setup_test_env();
577        let state = create_mock_app_state(
578            None,
579            Some(vec![create_mock_relayer("test".to_string(), false)]),
580            Some(vec![create_mock_signer()]),
581            Some(vec![create_mock_network()]),
582            None,
583            None,
584        )
585        .await;
586
587        let request = Request {
588            request_id: "test".to_string(),
589            relayer_id: "test".to_string(),
590            method: PluginMethod::SendTransaction,
591            payload: serde_json::json!(create_mock_evm_transaction_request()),
592            http_request_id: None,
593        };
594
595        let relayer_api = RelayerApi;
596        let response = relayer_api
597            .handle_request(request.clone(), &web::ThinData(state))
598            .await;
599
600        assert!(response.error.is_none());
601        assert!(response.result.is_some());
602    }
603
604    #[tokio::test]
605    async fn test_handle_request_error_paused_relayer() {
606        setup_test_env();
607        let paused = true;
608        let state = create_mock_app_state(
609            None,
610            Some(vec![create_mock_relayer("test".to_string(), paused)]),
611            Some(vec![create_mock_signer()]),
612            Some(vec![create_mock_network()]),
613            None,
614            None,
615        )
616        .await;
617
618        let request = Request {
619            request_id: "test".to_string(),
620            relayer_id: "test".to_string(),
621            method: PluginMethod::SendTransaction,
622            payload: serde_json::json!(create_mock_evm_transaction_request()),
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_some());
632        assert!(response.result.is_none());
633        assert_eq!(response.error.unwrap(), "Relayer error: Relayer is paused");
634    }
635
636    #[tokio::test]
637    async fn test_handle_request_using_trait() {
638        setup_test_env();
639        let state = create_mock_app_state(
640            None,
641            Some(vec![create_mock_relayer("test".to_string(), false)]),
642            Some(vec![create_mock_signer()]),
643            Some(vec![create_mock_network()]),
644            None,
645            None,
646        )
647        .await;
648
649        let request = Request {
650            request_id: "test".to_string(),
651            relayer_id: "test".to_string(),
652            method: PluginMethod::SendTransaction,
653            payload: serde_json::json!(create_mock_evm_transaction_request()),
654            http_request_id: None,
655        };
656
657        let relayer_api = RelayerApi;
658
659        let state = web::ThinData(state);
660
661        let response = RelayerApiTrait::handle_request(&relayer_api, request.clone(), &state).await;
662
663        assert!(response.error.is_none());
664        assert!(response.result.is_some());
665
666        let response =
667            RelayerApiTrait::process_request(&relayer_api, request.clone(), &state).await;
668
669        assert!(response.is_ok());
670
671        let response =
672            RelayerApiTrait::handle_send_transaction(&relayer_api, request.clone(), &state).await;
673
674        assert!(response.is_ok());
675    }
676
677    #[tokio::test]
678    async fn test_handle_get_transaction() {
679        setup_test_env();
680        let state = create_mock_app_state(
681            None,
682            Some(vec![create_mock_relayer("test".to_string(), false)]),
683            Some(vec![create_mock_signer()]),
684            Some(vec![create_mock_network()]),
685            None,
686            Some(vec![create_mock_transaction()]),
687        )
688        .await;
689
690        let request = Request {
691            request_id: "test".to_string(),
692            relayer_id: "test".to_string(),
693            method: PluginMethod::GetTransaction,
694            payload: serde_json::json!(GetTransactionRequest {
695                transaction_id: "test".to_string(),
696            }),
697            http_request_id: None,
698        };
699
700        let relayer_api = RelayerApi;
701        let response = relayer_api
702            .handle_request(request.clone(), &web::ThinData(state))
703            .await;
704
705        assert!(response.error.is_none());
706        assert!(response.result.is_some());
707    }
708
709    #[tokio::test]
710    async fn test_handle_get_transaction_error_relayer_not_found() {
711        setup_test_env();
712        let state = create_mock_app_state(
713            None,
714            None,
715            Some(vec![create_mock_signer()]),
716            Some(vec![create_mock_network()]),
717            None,
718            Some(vec![create_mock_transaction()]),
719        )
720        .await;
721
722        let request = Request {
723            request_id: "test".to_string(),
724            relayer_id: "test".to_string(),
725            method: PluginMethod::GetTransaction,
726            payload: serde_json::json!(GetTransactionRequest {
727                transaction_id: "test".to_string(),
728            }),
729            http_request_id: None,
730        };
731
732        let relayer_api = RelayerApi;
733        let response = relayer_api
734            .handle_request(request.clone(), &web::ThinData(state))
735            .await;
736
737        assert!(response.error.is_some());
738        let error = response.error.unwrap();
739        assert!(error.contains("Relayer with ID test not found"));
740    }
741
742    #[tokio::test]
743    async fn test_handle_get_transaction_error_transaction_not_found() {
744        setup_test_env();
745        let state = create_mock_app_state(
746            None,
747            Some(vec![create_mock_relayer("test".to_string(), false)]),
748            Some(vec![create_mock_signer()]),
749            Some(vec![create_mock_network()]),
750            None,
751            None,
752        )
753        .await;
754
755        let request = Request {
756            request_id: "test".to_string(),
757            relayer_id: "test".to_string(),
758            method: PluginMethod::GetTransaction,
759            payload: serde_json::json!(GetTransactionRequest {
760                transaction_id: "test".to_string(),
761            }),
762            http_request_id: None,
763        };
764
765        let relayer_api = RelayerApi;
766        let response = relayer_api
767            .handle_request(request.clone(), &web::ThinData(state))
768            .await;
769
770        assert!(response.error.is_some());
771        let error = response.error.unwrap();
772        assert!(error.contains("Transaction with ID test not found"));
773    }
774
775    #[tokio::test]
776    async fn test_handle_get_relayer_status_relayer_not_found() {
777        setup_test_env();
778        let state = create_mock_app_state(
779            None,
780            None,
781            Some(vec![create_mock_signer()]),
782            Some(vec![create_mock_network()]),
783            None,
784            None,
785        )
786        .await;
787
788        let request = Request {
789            request_id: "test".to_string(),
790            relayer_id: "test".to_string(),
791            method: PluginMethod::GetRelayerStatus,
792            payload: serde_json::json!({}),
793            http_request_id: None,
794        };
795
796        let relayer_api = RelayerApi;
797        let response = relayer_api
798            .handle_request(request.clone(), &web::ThinData(state))
799            .await;
800
801        assert!(response.error.is_some());
802        let error = response.error.unwrap();
803        assert!(error.contains("Relayer with ID test not found"));
804    }
805
806    #[tokio::test]
807    async fn test_handle_sign_transaction_evm_not_supported() {
808        setup_test_env();
809        let state = create_mock_app_state(
810            None,
811            Some(vec![create_mock_relayer("test".to_string(), false)]),
812            Some(vec![create_mock_signer()]),
813            Some(vec![create_mock_network()]),
814            None,
815            None,
816        )
817        .await;
818
819        let request = Request {
820            request_id: "test".to_string(),
821            relayer_id: "test".to_string(),
822            method: PluginMethod::SignTransaction,
823            payload: serde_json::json!({
824                "unsigned_xdr": "test_xdr"
825            }),
826            http_request_id: None,
827        };
828
829        let relayer_api = RelayerApi;
830        let response = relayer_api
831            .handle_request(request.clone(), &web::ThinData(state))
832            .await;
833
834        assert!(response.error.is_some());
835        let error = response.error.unwrap();
836        assert!(error.contains("sign_transaction not supported for EVM"));
837    }
838
839    #[tokio::test]
840    async fn test_handle_sign_transaction_invalid_payload() {
841        setup_test_env();
842        let state = create_mock_app_state(
843            None,
844            Some(vec![create_mock_relayer("test".to_string(), false)]),
845            Some(vec![create_mock_signer()]),
846            Some(vec![create_mock_network()]),
847            None,
848            None,
849        )
850        .await;
851
852        let request = Request {
853            request_id: "test".to_string(),
854            relayer_id: "test".to_string(),
855            method: PluginMethod::SignTransaction,
856            payload: serde_json::json!({"invalid": "payload"}),
857            http_request_id: None,
858        };
859
860        let relayer_api = RelayerApi;
861        let response = relayer_api
862            .handle_request(request.clone(), &web::ThinData(state))
863            .await;
864
865        assert!(response.error.is_some());
866        let error = response.error.unwrap();
867        assert!(error.contains("Invalid payload"));
868    }
869
870    #[tokio::test]
871    async fn test_handle_sign_transaction_relayer_not_found() {
872        setup_test_env();
873        let state = create_mock_app_state(
874            None,
875            None,
876            Some(vec![create_mock_signer()]),
877            Some(vec![create_mock_network()]),
878            None,
879            None,
880        )
881        .await;
882
883        let request = Request {
884            request_id: "test".to_string(),
885            relayer_id: "test".to_string(),
886            method: PluginMethod::SignTransaction,
887            payload: serde_json::json!({
888                "unsigned_xdr": "test_xdr"
889            }),
890            http_request_id: None,
891        };
892
893        let relayer_api = RelayerApi;
894        let response = relayer_api
895            .handle_request(request.clone(), &web::ThinData(state))
896            .await;
897
898        assert!(response.error.is_some());
899        let error = response.error.unwrap();
900        assert!(error.contains("Relayer with ID test not found"));
901    }
902
903    #[tokio::test]
904    async fn test_handle_get_relayer_info_success() {
905        setup_test_env();
906        let state = create_mock_app_state(
907            None,
908            Some(vec![create_mock_relayer("test".to_string(), false)]),
909            Some(vec![create_mock_signer()]),
910            Some(vec![create_mock_network()]),
911            None,
912            None,
913        )
914        .await;
915
916        let request = Request {
917            request_id: "test".to_string(),
918            relayer_id: "test".to_string(),
919            method: PluginMethod::GetRelayer,
920            payload: serde_json::json!({}),
921            http_request_id: None,
922        };
923
924        let relayer_api = RelayerApi;
925        let response = relayer_api
926            .handle_request(request.clone(), &web::ThinData(state))
927            .await;
928
929        assert!(response.error.is_none());
930        assert!(response.result.is_some());
931
932        let result = response.result.unwrap();
933        assert!(result.get("id").is_some());
934        assert!(result.get("name").is_some());
935        assert!(result.get("network").is_some());
936        assert!(result.get("address").is_some());
937    }
938
939    #[tokio::test]
940    async fn test_handle_get_relayer_info_relayer_not_found() {
941        setup_test_env();
942        let state = create_mock_app_state(
943            None,
944            None,
945            Some(vec![create_mock_signer()]),
946            Some(vec![create_mock_network()]),
947            None,
948            None,
949        )
950        .await;
951
952        let request = Request {
953            request_id: "test".to_string(),
954            relayer_id: "test".to_string(),
955            method: PluginMethod::GetRelayer,
956            payload: serde_json::json!({}),
957            http_request_id: None,
958        };
959
960        let relayer_api = RelayerApi;
961        let response = relayer_api
962            .handle_request(request.clone(), &web::ThinData(state))
963            .await;
964
965        assert!(response.error.is_some());
966        let error = response.error.unwrap();
967        assert!(error.contains("Relayer with ID test not found"));
968    }
969
970    #[tokio::test]
971    async fn test_handle_rpc_request_evm_success() {
972        setup_test_env();
973        let state = create_mock_app_state(
974            None,
975            Some(vec![create_mock_relayer("test".to_string(), false)]),
976            Some(vec![create_mock_signer()]),
977            Some(vec![create_mock_network()]),
978            None,
979            None,
980        )
981        .await;
982
983        let request = Request {
984            request_id: "test-rpc-1".to_string(),
985            relayer_id: "test".to_string(),
986            method: PluginMethod::Rpc,
987            payload: serde_json::json!({
988                "jsonrpc": "2.0",
989                "method": "eth_blockNumber",
990                "params": [],
991                "id": 1
992            }),
993            http_request_id: None,
994        };
995
996        let relayer_api = RelayerApi;
997        let response = relayer_api
998            .handle_request(request.clone(), &web::ThinData(state))
999            .await;
1000
1001        assert!(response.error.is_none());
1002        assert!(response.result.is_some());
1003        let result = response.result.unwrap();
1004        assert!(result.get("jsonrpc").is_some());
1005    }
1006
1007    #[tokio::test]
1008    async fn test_handle_rpc_request_invalid_payload() {
1009        setup_test_env();
1010        let state = create_mock_app_state(
1011            None,
1012            Some(vec![create_mock_relayer("test".to_string(), false)]),
1013            Some(vec![create_mock_signer()]),
1014            Some(vec![create_mock_network()]),
1015            None,
1016            None,
1017        )
1018        .await;
1019
1020        let request = Request {
1021            request_id: "test-rpc-2".to_string(),
1022            relayer_id: "test".to_string(),
1023            method: PluginMethod::Rpc,
1024            payload: serde_json::json!({
1025                "invalid": "payload"
1026            }),
1027            http_request_id: None,
1028        };
1029
1030        let relayer_api = RelayerApi;
1031        let response = relayer_api
1032            .handle_request(request.clone(), &web::ThinData(state))
1033            .await;
1034
1035        assert!(response.error.is_some());
1036        let error = response.error.unwrap();
1037        assert!(error.contains("Invalid payload") || error.contains("Missing 'method' field"));
1038    }
1039
1040    #[tokio::test]
1041    async fn test_handle_rpc_request_relayer_not_found() {
1042        setup_test_env();
1043        let state = create_mock_app_state(
1044            None,
1045            None,
1046            Some(vec![create_mock_signer()]),
1047            Some(vec![create_mock_network()]),
1048            None,
1049            None,
1050        )
1051        .await;
1052
1053        let request = Request {
1054            request_id: "test-rpc-3".to_string(),
1055            relayer_id: "nonexistent".to_string(),
1056            method: PluginMethod::Rpc,
1057            payload: serde_json::json!({
1058                "jsonrpc": "2.0",
1059                "method": "eth_blockNumber",
1060                "params": [],
1061                "id": 1
1062            }),
1063            http_request_id: None,
1064        };
1065
1066        let relayer_api = RelayerApi;
1067        let response = relayer_api
1068            .handle_request(request.clone(), &web::ThinData(state))
1069            .await;
1070
1071        assert!(response.error.is_some());
1072        let error = response.error.unwrap();
1073        assert!(error.contains("Relayer with ID nonexistent not found"));
1074    }
1075
1076    #[tokio::test]
1077    async fn test_handle_rpc_request_paused_relayer() {
1078        setup_test_env();
1079        let paused = true;
1080        let state = create_mock_app_state(
1081            None,
1082            Some(vec![create_mock_relayer("test".to_string(), paused)]),
1083            Some(vec![create_mock_signer()]),
1084            Some(vec![create_mock_network()]),
1085            None,
1086            None,
1087        )
1088        .await;
1089
1090        let request = Request {
1091            request_id: "test-rpc-4".to_string(),
1092            relayer_id: "test".to_string(),
1093            method: PluginMethod::Rpc,
1094            payload: serde_json::json!({
1095                "jsonrpc": "2.0",
1096                "method": "eth_blockNumber",
1097                "params": [],
1098                "id": 1
1099            }),
1100            http_request_id: None,
1101        };
1102
1103        let relayer_api = RelayerApi;
1104        let response = relayer_api
1105            .handle_request(request.clone(), &web::ThinData(state))
1106            .await;
1107
1108        assert!(response.error.is_some());
1109        let error = response.error.unwrap();
1110        assert!(error.contains("Relayer is paused"));
1111    }
1112
1113    #[tokio::test]
1114    async fn test_handle_rpc_request_with_string_id() {
1115        setup_test_env();
1116        let state = create_mock_app_state(
1117            None,
1118            Some(vec![create_mock_relayer("test".to_string(), false)]),
1119            Some(vec![create_mock_signer()]),
1120            Some(vec![create_mock_network()]),
1121            None,
1122            None,
1123        )
1124        .await;
1125
1126        let request = Request {
1127            request_id: "test-rpc-5".to_string(),
1128            relayer_id: "test".to_string(),
1129            method: PluginMethod::Rpc,
1130            payload: serde_json::json!({
1131                "jsonrpc": "2.0",
1132                "method": "eth_chainId",
1133                "params": [],
1134                "id": "custom-string-id"
1135            }),
1136            http_request_id: None,
1137        };
1138
1139        let relayer_api = RelayerApi;
1140        let response = relayer_api
1141            .handle_request(request.clone(), &web::ThinData(state))
1142            .await;
1143
1144        assert!(response.error.is_none());
1145        assert!(response.result.is_some());
1146        let result = response.result.unwrap();
1147        assert_eq!(result.get("id").unwrap(), "custom-string-id");
1148    }
1149
1150    #[tokio::test]
1151    async fn test_handle_rpc_request_with_null_id() {
1152        setup_test_env();
1153        let state = create_mock_app_state(
1154            None,
1155            Some(vec![create_mock_relayer("test".to_string(), false)]),
1156            Some(vec![create_mock_signer()]),
1157            Some(vec![create_mock_network()]),
1158            None,
1159            None,
1160        )
1161        .await;
1162
1163        let request = Request {
1164            request_id: "test-rpc-6".to_string(),
1165            relayer_id: "test".to_string(),
1166            method: PluginMethod::Rpc,
1167            payload: serde_json::json!({
1168                "jsonrpc": "2.0",
1169                "method": "eth_chainId",
1170                "params": [],
1171                "id": null
1172            }),
1173            http_request_id: None,
1174        };
1175
1176        let relayer_api = RelayerApi;
1177        let response = relayer_api
1178            .handle_request(request.clone(), &web::ThinData(state))
1179            .await;
1180
1181        assert!(response.error.is_none());
1182        assert!(response.result.is_some());
1183    }
1184
1185    #[tokio::test]
1186    async fn test_handle_rpc_request_with_array_params() {
1187        setup_test_env();
1188        let state = create_mock_app_state(
1189            None,
1190            Some(vec![create_mock_relayer("test".to_string(), false)]),
1191            Some(vec![create_mock_signer()]),
1192            Some(vec![create_mock_network()]),
1193            None,
1194            None,
1195        )
1196        .await;
1197
1198        let request = Request {
1199            request_id: "test-rpc-7".to_string(),
1200            relayer_id: "test".to_string(),
1201            method: PluginMethod::Rpc,
1202            payload: serde_json::json!({
1203                "jsonrpc": "2.0",
1204                "method": "eth_getBalance",
1205                "params": ["0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "latest"],
1206                "id": 1
1207            }),
1208            http_request_id: None,
1209        };
1210
1211        let relayer_api = RelayerApi;
1212        let response = relayer_api
1213            .handle_request(request.clone(), &web::ThinData(state))
1214            .await;
1215
1216        assert!(response.error.is_none());
1217        assert!(response.result.is_some());
1218    }
1219
1220    #[tokio::test]
1221    async fn test_handle_rpc_request_with_object_params() {
1222        setup_test_env();
1223        let state = create_mock_app_state(
1224            None,
1225            Some(vec![create_mock_relayer("test".to_string(), false)]),
1226            Some(vec![create_mock_signer()]),
1227            Some(vec![create_mock_network()]),
1228            None,
1229            None,
1230        )
1231        .await;
1232
1233        let request = Request {
1234            request_id: "test-rpc-8".to_string(),
1235            relayer_id: "test".to_string(),
1236            method: PluginMethod::Rpc,
1237            payload: serde_json::json!({
1238                "jsonrpc": "2.0",
1239                "method": "eth_call",
1240                "params": {
1241                    "to": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
1242                    "data": "0x"
1243                },
1244                "id": 1
1245            }),
1246            http_request_id: None,
1247        };
1248
1249        let relayer_api = RelayerApi;
1250        let response = relayer_api
1251            .handle_request(request.clone(), &web::ThinData(state))
1252            .await;
1253
1254        assert!(response.error.is_none());
1255        assert!(response.result.is_some());
1256    }
1257
1258    #[tokio::test]
1259    async fn test_handle_rpc_request_missing_method() {
1260        setup_test_env();
1261        let state = create_mock_app_state(
1262            None,
1263            Some(vec![create_mock_relayer("test".to_string(), false)]),
1264            Some(vec![create_mock_signer()]),
1265            Some(vec![create_mock_network()]),
1266            None,
1267            None,
1268        )
1269        .await;
1270
1271        let request = Request {
1272            request_id: "test-rpc-9".to_string(),
1273            relayer_id: "test".to_string(),
1274            method: PluginMethod::Rpc,
1275            payload: serde_json::json!({
1276                "jsonrpc": "2.0",
1277                "params": [],
1278                "id": 1
1279            }),
1280            http_request_id: None,
1281        };
1282
1283        let relayer_api = RelayerApi;
1284        let response = relayer_api
1285            .handle_request(request.clone(), &web::ThinData(state))
1286            .await;
1287
1288        assert!(response.error.is_some());
1289        let error = response.error.unwrap();
1290        assert!(error.contains("Missing 'method' field") || error.contains("Invalid payload"));
1291    }
1292
1293    #[tokio::test]
1294    async fn test_handle_rpc_request_empty_method() {
1295        setup_test_env();
1296        let state = create_mock_app_state(
1297            None,
1298            Some(vec![create_mock_relayer("test".to_string(), false)]),
1299            Some(vec![create_mock_signer()]),
1300            Some(vec![create_mock_network()]),
1301            None,
1302            None,
1303        )
1304        .await;
1305
1306        let request = Request {
1307            request_id: "test-rpc-10".to_string(),
1308            relayer_id: "test".to_string(),
1309            method: PluginMethod::Rpc,
1310            payload: serde_json::json!({
1311                "jsonrpc": "2.0",
1312                "method": "",
1313                "params": [],
1314                "id": 1
1315            }),
1316            http_request_id: None,
1317        };
1318
1319        let relayer_api = RelayerApi;
1320        let response = relayer_api
1321            .handle_request(request.clone(), &web::ThinData(state))
1322            .await;
1323
1324        // Empty method may be handled by the convert function or the provider
1325        // Either way, there should be an error or the response should indicate a problem
1326        assert!(
1327            response.error.is_some()
1328                || (response.result.is_some()
1329                    && response.result.as_ref().unwrap().get("error").is_some())
1330        );
1331    }
1332
1333    #[tokio::test]
1334    async fn test_handle_rpc_request_with_http_request_id() {
1335        setup_test_env();
1336        let state = create_mock_app_state(
1337            None,
1338            Some(vec![create_mock_relayer("test".to_string(), false)]),
1339            Some(vec![create_mock_signer()]),
1340            Some(vec![create_mock_network()]),
1341            None,
1342            None,
1343        )
1344        .await;
1345
1346        let request = Request {
1347            request_id: "test-rpc-11".to_string(),
1348            relayer_id: "test".to_string(),
1349            method: PluginMethod::Rpc,
1350            payload: serde_json::json!({
1351                "jsonrpc": "2.0",
1352                "method": "eth_blockNumber",
1353                "params": [],
1354                "id": 1
1355            }),
1356            http_request_id: Some("http-req-123".to_string()),
1357        };
1358
1359        let relayer_api = RelayerApi;
1360        let response = relayer_api
1361            .handle_request(request.clone(), &web::ThinData(state))
1362            .await;
1363
1364        assert!(response.error.is_none());
1365        assert!(response.result.is_some());
1366        assert_eq!(response.request_id, "test-rpc-11");
1367    }
1368
1369    #[tokio::test]
1370    async fn test_handle_rpc_request_default_jsonrpc_version() {
1371        setup_test_env();
1372        let state = create_mock_app_state(
1373            None,
1374            Some(vec![create_mock_relayer("test".to_string(), false)]),
1375            Some(vec![create_mock_signer()]),
1376            Some(vec![create_mock_network()]),
1377            None,
1378            None,
1379        )
1380        .await;
1381
1382        let request = Request {
1383            request_id: "test-rpc-12".to_string(),
1384            relayer_id: "test".to_string(),
1385            method: PluginMethod::Rpc,
1386            payload: serde_json::json!({
1387                "method": "eth_blockNumber",
1388                "params": [],
1389                "id": 1
1390            }),
1391            http_request_id: None,
1392        };
1393
1394        let relayer_api = RelayerApi;
1395        let response = relayer_api
1396            .handle_request(request.clone(), &web::ThinData(state))
1397            .await;
1398
1399        // Should either succeed or return a JSON-RPC formatted response
1400        if response.error.is_none() {
1401            assert!(response.result.is_some());
1402            let result = response.result.unwrap();
1403            assert_eq!(result.get("jsonrpc").unwrap(), "2.0");
1404        } else {
1405            // If there's an error, it's still valid since we're testing default version behavior
1406            assert!(response.error.is_some());
1407        }
1408    }
1409
1410    #[tokio::test]
1411    async fn test_handle_rpc_request_custom_jsonrpc_version() {
1412        setup_test_env();
1413        let state = create_mock_app_state(
1414            None,
1415            Some(vec![create_mock_relayer("test".to_string(), false)]),
1416            Some(vec![create_mock_signer()]),
1417            Some(vec![create_mock_network()]),
1418            None,
1419            None,
1420        )
1421        .await;
1422
1423        let request = Request {
1424            request_id: "test-rpc-13".to_string(),
1425            relayer_id: "test".to_string(),
1426            method: PluginMethod::Rpc,
1427            payload: serde_json::json!({
1428                "jsonrpc": "1.0",
1429                "method": "eth_blockNumber",
1430                "params": [],
1431                "id": 1
1432            }),
1433            http_request_id: None,
1434        };
1435
1436        let relayer_api = RelayerApi;
1437        let response = relayer_api
1438            .handle_request(request.clone(), &web::ThinData(state))
1439            .await;
1440
1441        assert!(response.error.is_none());
1442        assert!(response.result.is_some());
1443    }
1444
1445    #[tokio::test]
1446    async fn test_handle_rpc_request_result_structure() {
1447        setup_test_env();
1448        let state = create_mock_app_state(
1449            None,
1450            Some(vec![create_mock_relayer("test".to_string(), false)]),
1451            Some(vec![create_mock_signer()]),
1452            Some(vec![create_mock_network()]),
1453            None,
1454            None,
1455        )
1456        .await;
1457
1458        let request = Request {
1459            request_id: "test-rpc-14".to_string(),
1460            relayer_id: "test".to_string(),
1461            method: PluginMethod::Rpc,
1462            payload: serde_json::json!({
1463                "jsonrpc": "2.0",
1464                "method": "eth_blockNumber",
1465                "params": [],
1466                "id": 42
1467            }),
1468            http_request_id: None,
1469        };
1470
1471        let relayer_api = RelayerApi;
1472        let response = relayer_api
1473            .handle_request(request.clone(), &web::ThinData(state))
1474            .await;
1475
1476        assert!(response.error.is_none());
1477        assert!(response.result.is_some());
1478        assert_eq!(response.request_id, "test-rpc-14");
1479
1480        let result = response.result.unwrap();
1481        assert!(result.get("jsonrpc").is_some());
1482        assert!(result.get("id").is_some());
1483        // Should have either result or error field
1484        assert!(result.get("result").is_some() || result.get("error").is_some());
1485    }
1486}