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