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, JsonRpcRequest, NetworkRepoModel, NetworkRpcRequest,
15    NetworkTransactionRequest, NotificationRepoModel, RelayerRepoModel, SignerRepoModel,
16    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 network_relayer = get_network_relayer(request.relayer_id.clone(), state)
375            .await
376            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
377
378        let status = network_relayer
379            .get_status()
380            .await
381            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
382
383        let result =
384            serde_json::to_value(status).map_err(|e| PluginError::RelayerError(e.to_string()))?;
385
386        Ok(Response {
387            request_id: request.request_id,
388            result: Some(result),
389            error: None,
390        })
391    }
392
393    #[instrument(
394        name = "Plugin::handle_sign_transaction",
395        skip_all,
396        fields(
397            method = %request.method,
398            relayer_id = %request.relayer_id,
399            plugin_req_id = %request.http_request_id.as_ref().unwrap_or(&request.request_id)
400        )
401    )]
402    async fn handle_sign_transaction<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
403        &self,
404        request: Request,
405        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
406    ) -> Result<Response, PluginError>
407    where
408        J: JobProducerTrait + 'static,
409        TR: TransactionRepository
410            + Repository<TransactionRepoModel, String>
411            + Send
412            + Sync
413            + 'static,
414        RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
415        NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
416        NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
417        SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
418        TCR: TransactionCounterTrait + Send + Sync + 'static,
419        PR: PluginRepositoryTrait + Send + Sync + 'static,
420        AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
421    {
422        let sign_request: SignTransactionRequest = serde_json::from_value(request.payload)
423            .map_err(|e| PluginError::InvalidPayload(e.to_string()))?;
424
425        let network_relayer = get_network_relayer(request.relayer_id.clone(), state)
426            .await
427            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
428
429        let response = network_relayer
430            .sign_transaction(&sign_request)
431            .await
432            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
433
434        let result =
435            serde_json::to_value(response).map_err(|e| PluginError::RelayerError(e.to_string()))?;
436
437        Ok(Response {
438            request_id: request.request_id,
439            result: Some(result),
440            error: None,
441        })
442    }
443
444    #[instrument(
445        name = "Plugin::handle_get_relayer_info",
446        skip_all,
447        fields(
448            method = %request.method,
449            relayer_id = %request.relayer_id,
450            plugin_req_id = %request.http_request_id.as_ref().unwrap_or(&request.request_id)
451        )
452    )]
453    async fn handle_get_relayer_info<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
454        &self,
455        request: Request,
456        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
457    ) -> Result<Response, PluginError>
458    where
459        J: JobProducerTrait + 'static,
460        TR: TransactionRepository
461            + Repository<TransactionRepoModel, String>
462            + Send
463            + Sync
464            + 'static,
465        RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
466        NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
467        NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
468        SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
469        TCR: TransactionCounterTrait + Send + Sync + 'static,
470        PR: PluginRepositoryTrait + Send + Sync + 'static,
471        AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
472    {
473        let relayer = get_relayer_by_id(request.relayer_id.clone(), state)
474            .await
475            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
476        let relayer_response: crate::models::RelayerResponse = relayer.into();
477        let result = serde_json::to_value(relayer_response)
478            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
479        Ok(Response {
480            request_id: request.request_id,
481            result: Some(result),
482            error: None,
483        })
484    }
485
486    #[instrument(
487        name = "Plugin::handle_rpc_request",
488        skip_all,
489        fields(
490            method = %request.method,
491            relayer_id = %request.relayer_id,
492            plugin_req_id = %request.http_request_id.as_ref().unwrap_or(&request.request_id)
493        )
494    )]
495    async fn handle_rpc_request<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
496        &self,
497        request: Request,
498        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
499    ) -> Result<Response, PluginError>
500    where
501        J: JobProducerTrait + 'static,
502        TR: TransactionRepository
503            + Repository<TransactionRepoModel, String>
504            + Send
505            + Sync
506            + 'static,
507        RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
508        NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
509        NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
510        SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
511        TCR: TransactionCounterTrait + Send + Sync + 'static,
512        PR: PluginRepositoryTrait + Send + Sync + 'static,
513        AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
514    {
515        let relayer_repo_model = get_relayer_by_id(request.relayer_id.clone(), state)
516            .await
517            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
518
519        relayer_repo_model
520            .validate_active_state()
521            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
522
523        // Use get_network_relayer_by_model to avoid duplicate Redis lookup
524        let network_relayer = get_network_relayer_by_model(relayer_repo_model.clone(), state)
525            .await
526            .map_err(|e| PluginError::RelayerError(e.to_string()))?;
527
528        // Use the network type from relayer_repo_model to parse the request with correct type context
529        let network_rpc_request: JsonRpcRequest<NetworkRpcRequest> =
530            convert_to_internal_rpc_request(request.payload, &relayer_repo_model.network_type)
531                .map_err(|e| PluginError::InvalidPayload(e.to_string()))?;
532
533        let result = network_relayer.rpc(network_rpc_request).await;
534
535        match result {
536            Ok(json_rpc_response) => {
537                let result_value = serde_json::to_value(json_rpc_response)
538                    .map_err(|e| PluginError::RelayerError(e.to_string()))?;
539                Ok(Response {
540                    request_id: request.request_id,
541                    result: Some(result_value),
542                    error: None,
543                })
544            }
545            Err(e) => Ok(Response {
546                request_id: request.request_id,
547                result: None,
548                error: Some(e.to_string()),
549            }),
550        }
551    }
552}
553
554#[async_trait]
555impl<J, RR, TR, NR, NFR, SR, TCR, PR, AKR> RelayerApiTrait<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>
556    for RelayerApi
557where
558    J: JobProducerTrait + 'static,
559    TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
560    RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
561    NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
562    NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
563    SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
564    TCR: TransactionCounterTrait + Send + Sync + 'static,
565    PR: PluginRepositoryTrait + Send + Sync + 'static,
566    AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
567{
568    async fn handle_request(
569        &self,
570        request: Request,
571        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
572    ) -> Response {
573        self.handle_request(request, state).await
574    }
575
576    async fn process_request(
577        &self,
578        request: Request,
579        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
580    ) -> Result<Response, PluginError> {
581        self.process_request(request, state).await
582    }
583
584    async fn handle_send_transaction(
585        &self,
586        request: Request,
587        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
588    ) -> Result<Response, PluginError> {
589        self.handle_send_transaction(request, state).await
590    }
591
592    async fn handle_get_transaction(
593        &self,
594        request: Request,
595        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
596    ) -> Result<Response, PluginError> {
597        self.handle_get_transaction(request, state).await
598    }
599
600    async fn handle_get_relayer_status(
601        &self,
602        request: Request,
603        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
604    ) -> Result<Response, PluginError> {
605        self.handle_get_relayer_status(request, state).await
606    }
607
608    async fn handle_sign_transaction(
609        &self,
610        request: Request,
611        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
612    ) -> Result<Response, PluginError> {
613        self.handle_sign_transaction(request, state).await
614    }
615
616    async fn handle_get_relayer_info(
617        &self,
618        request: Request,
619        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
620    ) -> Result<Response, PluginError> {
621        self.handle_get_relayer_info(request, state).await
622    }
623
624    async fn handle_rpc_request(
625        &self,
626        request: Request,
627        state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
628    ) -> Result<Response, PluginError> {
629        self.handle_rpc_request(request, state).await
630    }
631}
632
633#[cfg(test)]
634mod tests {
635    use std::env;
636
637    use crate::utils::mocks::mockutils::{
638        create_mock_app_state, create_mock_evm_transaction_request, create_mock_network,
639        create_mock_relayer, create_mock_signer, create_mock_transaction,
640    };
641
642    use super::*;
643
644    fn setup_test_env() {
645        env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D"); // noboost
646        env::set_var("REDIS_URL", "redis://localhost:6379");
647        env::set_var("RPC_TIMEOUT_MS", "5000");
648    }
649
650    #[tokio::test]
651    async fn test_handle_request() {
652        setup_test_env();
653        let state = create_mock_app_state(
654            None,
655            Some(vec![create_mock_relayer("test".to_string(), false)]),
656            Some(vec![create_mock_signer()]),
657            Some(vec![create_mock_network()]),
658            None,
659            None,
660        )
661        .await;
662
663        let request = Request {
664            request_id: "test".to_string(),
665            relayer_id: "test".to_string(),
666            method: PluginMethod::SendTransaction,
667            payload: serde_json::json!(create_mock_evm_transaction_request()),
668            http_request_id: None,
669        };
670
671        let relayer_api = RelayerApi;
672        let response = relayer_api
673            .handle_request(request.clone(), &web::ThinData(state))
674            .await;
675
676        assert!(response.error.is_none());
677        assert!(response.result.is_some());
678    }
679
680    #[tokio::test]
681    async fn test_handle_request_error_paused_relayer() {
682        setup_test_env();
683        let paused = true;
684        let state = create_mock_app_state(
685            None,
686            Some(vec![create_mock_relayer("test".to_string(), paused)]),
687            Some(vec![create_mock_signer()]),
688            Some(vec![create_mock_network()]),
689            None,
690            None,
691        )
692        .await;
693
694        let request = Request {
695            request_id: "test".to_string(),
696            relayer_id: "test".to_string(),
697            method: PluginMethod::SendTransaction,
698            payload: serde_json::json!(create_mock_evm_transaction_request()),
699            http_request_id: None,
700        };
701
702        let relayer_api = RelayerApi;
703        let response = relayer_api
704            .handle_request(request.clone(), &web::ThinData(state))
705            .await;
706
707        assert!(response.error.is_some());
708        assert!(response.result.is_none());
709        assert_eq!(response.error.unwrap(), "Relayer error: Relayer is paused");
710    }
711
712    #[tokio::test]
713    async fn test_handle_request_using_trait() {
714        setup_test_env();
715        let state = create_mock_app_state(
716            None,
717            Some(vec![create_mock_relayer("test".to_string(), false)]),
718            Some(vec![create_mock_signer()]),
719            Some(vec![create_mock_network()]),
720            None,
721            None,
722        )
723        .await;
724
725        let request = Request {
726            request_id: "test".to_string(),
727            relayer_id: "test".to_string(),
728            method: PluginMethod::SendTransaction,
729            payload: serde_json::json!(create_mock_evm_transaction_request()),
730            http_request_id: None,
731        };
732
733        let relayer_api = RelayerApi;
734
735        let state = web::ThinData(state);
736
737        let response = RelayerApiTrait::handle_request(&relayer_api, request.clone(), &state).await;
738
739        assert!(response.error.is_none());
740        assert!(response.result.is_some());
741
742        let response =
743            RelayerApiTrait::process_request(&relayer_api, request.clone(), &state).await;
744
745        assert!(response.is_ok());
746
747        let response =
748            RelayerApiTrait::handle_send_transaction(&relayer_api, request.clone(), &state).await;
749
750        assert!(response.is_ok());
751    }
752
753    #[tokio::test]
754    async fn test_handle_get_transaction() {
755        setup_test_env();
756        let state = create_mock_app_state(
757            None,
758            Some(vec![create_mock_relayer("test".to_string(), false)]),
759            Some(vec![create_mock_signer()]),
760            Some(vec![create_mock_network()]),
761            None,
762            Some(vec![create_mock_transaction()]),
763        )
764        .await;
765
766        let request = Request {
767            request_id: "test".to_string(),
768            relayer_id: "test".to_string(),
769            method: PluginMethod::GetTransaction,
770            payload: serde_json::json!(GetTransactionRequest {
771                transaction_id: "test".to_string(),
772            }),
773            http_request_id: None,
774        };
775
776        let relayer_api = RelayerApi;
777        let response = relayer_api
778            .handle_request(request.clone(), &web::ThinData(state))
779            .await;
780
781        assert!(response.error.is_none());
782        assert!(response.result.is_some());
783    }
784
785    #[tokio::test]
786    async fn test_handle_get_transaction_error_relayer_not_found() {
787        setup_test_env();
788        let state = create_mock_app_state(
789            None,
790            None,
791            Some(vec![create_mock_signer()]),
792            Some(vec![create_mock_network()]),
793            None,
794            Some(vec![create_mock_transaction()]),
795        )
796        .await;
797
798        let request = Request {
799            request_id: "test".to_string(),
800            relayer_id: "test".to_string(),
801            method: PluginMethod::GetTransaction,
802            payload: serde_json::json!(GetTransactionRequest {
803                transaction_id: "test".to_string(),
804            }),
805            http_request_id: None,
806        };
807
808        let relayer_api = RelayerApi;
809        let response = relayer_api
810            .handle_request(request.clone(), &web::ThinData(state))
811            .await;
812
813        assert!(response.error.is_some());
814        let error = response.error.unwrap();
815        assert!(error.contains("Relayer with ID test not found"));
816    }
817
818    #[tokio::test]
819    async fn test_handle_get_transaction_error_transaction_not_found() {
820        setup_test_env();
821        let state = create_mock_app_state(
822            None,
823            Some(vec![create_mock_relayer("test".to_string(), false)]),
824            Some(vec![create_mock_signer()]),
825            Some(vec![create_mock_network()]),
826            None,
827            None,
828        )
829        .await;
830
831        let request = Request {
832            request_id: "test".to_string(),
833            relayer_id: "test".to_string(),
834            method: PluginMethod::GetTransaction,
835            payload: serde_json::json!(GetTransactionRequest {
836                transaction_id: "test".to_string(),
837            }),
838            http_request_id: None,
839        };
840
841        let relayer_api = RelayerApi;
842        let response = relayer_api
843            .handle_request(request.clone(), &web::ThinData(state))
844            .await;
845
846        assert!(response.error.is_some());
847        let error = response.error.unwrap();
848        assert!(error.contains("Transaction with ID test not found"));
849    }
850
851    #[tokio::test]
852    async fn test_handle_get_relayer_status_relayer_not_found() {
853        setup_test_env();
854        let state = create_mock_app_state(
855            None,
856            None,
857            Some(vec![create_mock_signer()]),
858            Some(vec![create_mock_network()]),
859            None,
860            None,
861        )
862        .await;
863
864        let request = Request {
865            request_id: "test".to_string(),
866            relayer_id: "test".to_string(),
867            method: PluginMethod::GetRelayerStatus,
868            payload: serde_json::json!({}),
869            http_request_id: None,
870        };
871
872        let relayer_api = RelayerApi;
873        let response = relayer_api
874            .handle_request(request.clone(), &web::ThinData(state))
875            .await;
876
877        assert!(response.error.is_some());
878        let error = response.error.unwrap();
879        assert!(error.contains("Relayer with ID test not found"));
880    }
881
882    #[tokio::test]
883    async fn test_handle_sign_transaction_evm_not_supported() {
884        setup_test_env();
885        let state = create_mock_app_state(
886            None,
887            Some(vec![create_mock_relayer("test".to_string(), false)]),
888            Some(vec![create_mock_signer()]),
889            Some(vec![create_mock_network()]),
890            None,
891            None,
892        )
893        .await;
894
895        let request = Request {
896            request_id: "test".to_string(),
897            relayer_id: "test".to_string(),
898            method: PluginMethod::SignTransaction,
899            payload: serde_json::json!({
900                "unsigned_xdr": "test_xdr"
901            }),
902            http_request_id: None,
903        };
904
905        let relayer_api = RelayerApi;
906        let response = relayer_api
907            .handle_request(request.clone(), &web::ThinData(state))
908            .await;
909
910        assert!(response.error.is_some());
911        let error = response.error.unwrap();
912        assert!(error.contains("sign_transaction not supported for EVM"));
913    }
914
915    #[tokio::test]
916    async fn test_handle_sign_transaction_invalid_payload() {
917        setup_test_env();
918        let state = create_mock_app_state(
919            None,
920            Some(vec![create_mock_relayer("test".to_string(), false)]),
921            Some(vec![create_mock_signer()]),
922            Some(vec![create_mock_network()]),
923            None,
924            None,
925        )
926        .await;
927
928        let request = Request {
929            request_id: "test".to_string(),
930            relayer_id: "test".to_string(),
931            method: PluginMethod::SignTransaction,
932            payload: serde_json::json!({"invalid": "payload"}),
933            http_request_id: None,
934        };
935
936        let relayer_api = RelayerApi;
937        let response = relayer_api
938            .handle_request(request.clone(), &web::ThinData(state))
939            .await;
940
941        assert!(response.error.is_some());
942        let error = response.error.unwrap();
943        assert!(error.contains("Invalid payload"));
944    }
945
946    #[tokio::test]
947    async fn test_handle_sign_transaction_relayer_not_found() {
948        setup_test_env();
949        let state = create_mock_app_state(
950            None,
951            None,
952            Some(vec![create_mock_signer()]),
953            Some(vec![create_mock_network()]),
954            None,
955            None,
956        )
957        .await;
958
959        let request = Request {
960            request_id: "test".to_string(),
961            relayer_id: "test".to_string(),
962            method: PluginMethod::SignTransaction,
963            payload: serde_json::json!({
964                "unsigned_xdr": "test_xdr"
965            }),
966            http_request_id: None,
967        };
968
969        let relayer_api = RelayerApi;
970        let response = relayer_api
971            .handle_request(request.clone(), &web::ThinData(state))
972            .await;
973
974        assert!(response.error.is_some());
975        let error = response.error.unwrap();
976        assert!(error.contains("Relayer with ID test not found"));
977    }
978
979    #[tokio::test]
980    async fn test_handle_get_relayer_info_success() {
981        setup_test_env();
982        let state = create_mock_app_state(
983            None,
984            Some(vec![create_mock_relayer("test".to_string(), false)]),
985            Some(vec![create_mock_signer()]),
986            Some(vec![create_mock_network()]),
987            None,
988            None,
989        )
990        .await;
991
992        let request = Request {
993            request_id: "test".to_string(),
994            relayer_id: "test".to_string(),
995            method: PluginMethod::GetRelayer,
996            payload: serde_json::json!({}),
997            http_request_id: None,
998        };
999
1000        let relayer_api = RelayerApi;
1001        let response = relayer_api
1002            .handle_request(request.clone(), &web::ThinData(state))
1003            .await;
1004
1005        assert!(response.error.is_none());
1006        assert!(response.result.is_some());
1007
1008        let result = response.result.unwrap();
1009        assert!(result.get("id").is_some());
1010        assert!(result.get("name").is_some());
1011        assert!(result.get("network").is_some());
1012        assert!(result.get("address").is_some());
1013    }
1014
1015    #[tokio::test]
1016    async fn test_handle_get_relayer_info_relayer_not_found() {
1017        setup_test_env();
1018        let state = create_mock_app_state(
1019            None,
1020            None,
1021            Some(vec![create_mock_signer()]),
1022            Some(vec![create_mock_network()]),
1023            None,
1024            None,
1025        )
1026        .await;
1027
1028        let request = Request {
1029            request_id: "test".to_string(),
1030            relayer_id: "test".to_string(),
1031            method: PluginMethod::GetRelayer,
1032            payload: serde_json::json!({}),
1033            http_request_id: None,
1034        };
1035
1036        let relayer_api = RelayerApi;
1037        let response = relayer_api
1038            .handle_request(request.clone(), &web::ThinData(state))
1039            .await;
1040
1041        assert!(response.error.is_some());
1042        let error = response.error.unwrap();
1043        assert!(error.contains("Relayer with ID test not found"));
1044    }
1045
1046    #[tokio::test]
1047    async fn test_handle_rpc_request_evm_success() {
1048        setup_test_env();
1049        let state = create_mock_app_state(
1050            None,
1051            Some(vec![create_mock_relayer("test".to_string(), false)]),
1052            Some(vec![create_mock_signer()]),
1053            Some(vec![create_mock_network()]),
1054            None,
1055            None,
1056        )
1057        .await;
1058
1059        let request = Request {
1060            request_id: "test-rpc-1".to_string(),
1061            relayer_id: "test".to_string(),
1062            method: PluginMethod::Rpc,
1063            payload: serde_json::json!({
1064                "jsonrpc": "2.0",
1065                "method": "eth_blockNumber",
1066                "params": [],
1067                "id": 1
1068            }),
1069            http_request_id: None,
1070        };
1071
1072        let relayer_api = RelayerApi;
1073        let response = relayer_api
1074            .handle_request(request.clone(), &web::ThinData(state))
1075            .await;
1076
1077        assert!(response.error.is_none());
1078        assert!(response.result.is_some());
1079        let result = response.result.unwrap();
1080        assert!(result.get("jsonrpc").is_some());
1081    }
1082
1083    #[tokio::test]
1084    async fn test_handle_rpc_request_invalid_payload() {
1085        setup_test_env();
1086        let state = create_mock_app_state(
1087            None,
1088            Some(vec![create_mock_relayer("test".to_string(), false)]),
1089            Some(vec![create_mock_signer()]),
1090            Some(vec![create_mock_network()]),
1091            None,
1092            None,
1093        )
1094        .await;
1095
1096        let request = Request {
1097            request_id: "test-rpc-2".to_string(),
1098            relayer_id: "test".to_string(),
1099            method: PluginMethod::Rpc,
1100            payload: serde_json::json!({
1101                "invalid": "payload"
1102            }),
1103            http_request_id: None,
1104        };
1105
1106        let relayer_api = RelayerApi;
1107        let response = relayer_api
1108            .handle_request(request.clone(), &web::ThinData(state))
1109            .await;
1110
1111        assert!(response.error.is_some());
1112        let error = response.error.unwrap();
1113        assert!(error.contains("Invalid payload") || error.contains("Missing 'method' field"));
1114    }
1115
1116    #[tokio::test]
1117    async fn test_handle_rpc_request_relayer_not_found() {
1118        setup_test_env();
1119        let state = create_mock_app_state(
1120            None,
1121            None,
1122            Some(vec![create_mock_signer()]),
1123            Some(vec![create_mock_network()]),
1124            None,
1125            None,
1126        )
1127        .await;
1128
1129        let request = Request {
1130            request_id: "test-rpc-3".to_string(),
1131            relayer_id: "nonexistent".to_string(),
1132            method: PluginMethod::Rpc,
1133            payload: serde_json::json!({
1134                "jsonrpc": "2.0",
1135                "method": "eth_blockNumber",
1136                "params": [],
1137                "id": 1
1138            }),
1139            http_request_id: None,
1140        };
1141
1142        let relayer_api = RelayerApi;
1143        let response = relayer_api
1144            .handle_request(request.clone(), &web::ThinData(state))
1145            .await;
1146
1147        assert!(response.error.is_some());
1148        let error = response.error.unwrap();
1149        assert!(error.contains("Relayer with ID nonexistent not found"));
1150    }
1151
1152    #[tokio::test]
1153    async fn test_handle_rpc_request_paused_relayer() {
1154        setup_test_env();
1155        let paused = true;
1156        let state = create_mock_app_state(
1157            None,
1158            Some(vec![create_mock_relayer("test".to_string(), paused)]),
1159            Some(vec![create_mock_signer()]),
1160            Some(vec![create_mock_network()]),
1161            None,
1162            None,
1163        )
1164        .await;
1165
1166        let request = Request {
1167            request_id: "test-rpc-4".to_string(),
1168            relayer_id: "test".to_string(),
1169            method: PluginMethod::Rpc,
1170            payload: serde_json::json!({
1171                "jsonrpc": "2.0",
1172                "method": "eth_blockNumber",
1173                "params": [],
1174                "id": 1
1175            }),
1176            http_request_id: None,
1177        };
1178
1179        let relayer_api = RelayerApi;
1180        let response = relayer_api
1181            .handle_request(request.clone(), &web::ThinData(state))
1182            .await;
1183
1184        assert!(response.error.is_some());
1185        let error = response.error.unwrap();
1186        assert!(error.contains("Relayer is paused"));
1187    }
1188
1189    #[tokio::test]
1190    async fn test_handle_rpc_request_with_string_id() {
1191        setup_test_env();
1192        let state = create_mock_app_state(
1193            None,
1194            Some(vec![create_mock_relayer("test".to_string(), false)]),
1195            Some(vec![create_mock_signer()]),
1196            Some(vec![create_mock_network()]),
1197            None,
1198            None,
1199        )
1200        .await;
1201
1202        let request = Request {
1203            request_id: "test-rpc-5".to_string(),
1204            relayer_id: "test".to_string(),
1205            method: PluginMethod::Rpc,
1206            payload: serde_json::json!({
1207                "jsonrpc": "2.0",
1208                "method": "eth_chainId",
1209                "params": [],
1210                "id": "custom-string-id"
1211            }),
1212            http_request_id: None,
1213        };
1214
1215        let relayer_api = RelayerApi;
1216        let response = relayer_api
1217            .handle_request(request.clone(), &web::ThinData(state))
1218            .await;
1219
1220        assert!(response.error.is_none());
1221        assert!(response.result.is_some());
1222        let result = response.result.unwrap();
1223        assert_eq!(result.get("id").unwrap(), "custom-string-id");
1224    }
1225
1226    #[tokio::test]
1227    async fn test_handle_rpc_request_with_null_id() {
1228        setup_test_env();
1229        let state = create_mock_app_state(
1230            None,
1231            Some(vec![create_mock_relayer("test".to_string(), false)]),
1232            Some(vec![create_mock_signer()]),
1233            Some(vec![create_mock_network()]),
1234            None,
1235            None,
1236        )
1237        .await;
1238
1239        let request = Request {
1240            request_id: "test-rpc-6".to_string(),
1241            relayer_id: "test".to_string(),
1242            method: PluginMethod::Rpc,
1243            payload: serde_json::json!({
1244                "jsonrpc": "2.0",
1245                "method": "eth_chainId",
1246                "params": [],
1247                "id": null
1248            }),
1249            http_request_id: None,
1250        };
1251
1252        let relayer_api = RelayerApi;
1253        let response = relayer_api
1254            .handle_request(request.clone(), &web::ThinData(state))
1255            .await;
1256
1257        assert!(response.error.is_none());
1258        assert!(response.result.is_some());
1259    }
1260
1261    #[tokio::test]
1262    async fn test_handle_rpc_request_with_array_params() {
1263        setup_test_env();
1264        let state = create_mock_app_state(
1265            None,
1266            Some(vec![create_mock_relayer("test".to_string(), false)]),
1267            Some(vec![create_mock_signer()]),
1268            Some(vec![create_mock_network()]),
1269            None,
1270            None,
1271        )
1272        .await;
1273
1274        let request = Request {
1275            request_id: "test-rpc-7".to_string(),
1276            relayer_id: "test".to_string(),
1277            method: PluginMethod::Rpc,
1278            payload: serde_json::json!({
1279                "jsonrpc": "2.0",
1280                "method": "eth_getBalance",
1281                "params": ["0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "latest"],
1282                "id": 1
1283            }),
1284            http_request_id: None,
1285        };
1286
1287        let relayer_api = RelayerApi;
1288        let response = relayer_api
1289            .handle_request(request.clone(), &web::ThinData(state))
1290            .await;
1291
1292        assert!(response.error.is_none());
1293        assert!(response.result.is_some());
1294    }
1295
1296    #[tokio::test]
1297    async fn test_handle_rpc_request_with_object_params() {
1298        setup_test_env();
1299        let state = create_mock_app_state(
1300            None,
1301            Some(vec![create_mock_relayer("test".to_string(), false)]),
1302            Some(vec![create_mock_signer()]),
1303            Some(vec![create_mock_network()]),
1304            None,
1305            None,
1306        )
1307        .await;
1308
1309        let request = Request {
1310            request_id: "test-rpc-8".to_string(),
1311            relayer_id: "test".to_string(),
1312            method: PluginMethod::Rpc,
1313            payload: serde_json::json!({
1314                "jsonrpc": "2.0",
1315                "method": "eth_call",
1316                "params": {
1317                    "to": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
1318                    "data": "0x"
1319                },
1320                "id": 1
1321            }),
1322            http_request_id: None,
1323        };
1324
1325        let relayer_api = RelayerApi;
1326        let response = relayer_api
1327            .handle_request(request.clone(), &web::ThinData(state))
1328            .await;
1329
1330        assert!(response.error.is_none());
1331        assert!(response.result.is_some());
1332    }
1333
1334    #[tokio::test]
1335    async fn test_handle_rpc_request_missing_method() {
1336        setup_test_env();
1337        let state = create_mock_app_state(
1338            None,
1339            Some(vec![create_mock_relayer("test".to_string(), false)]),
1340            Some(vec![create_mock_signer()]),
1341            Some(vec![create_mock_network()]),
1342            None,
1343            None,
1344        )
1345        .await;
1346
1347        let request = Request {
1348            request_id: "test-rpc-9".to_string(),
1349            relayer_id: "test".to_string(),
1350            method: PluginMethod::Rpc,
1351            payload: serde_json::json!({
1352                "jsonrpc": "2.0",
1353                "params": [],
1354                "id": 1
1355            }),
1356            http_request_id: None,
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_some());
1365        let error = response.error.unwrap();
1366        assert!(error.contains("Missing 'method' field") || error.contains("Invalid payload"));
1367    }
1368
1369    #[tokio::test]
1370    async fn test_handle_rpc_request_empty_method() {
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-10".to_string(),
1384            relayer_id: "test".to_string(),
1385            method: PluginMethod::Rpc,
1386            payload: serde_json::json!({
1387                "jsonrpc": "2.0",
1388                "method": "",
1389                "params": [],
1390                "id": 1
1391            }),
1392            http_request_id: None,
1393        };
1394
1395        let relayer_api = RelayerApi;
1396        let response = relayer_api
1397            .handle_request(request.clone(), &web::ThinData(state))
1398            .await;
1399
1400        // Empty method may be handled by the convert function or the provider
1401        // Either way, there should be an error or the response should indicate a problem
1402        assert!(
1403            response.error.is_some()
1404                || (response.result.is_some()
1405                    && response.result.as_ref().unwrap().get("error").is_some())
1406        );
1407    }
1408
1409    #[tokio::test]
1410    async fn test_handle_rpc_request_with_http_request_id() {
1411        setup_test_env();
1412        let state = create_mock_app_state(
1413            None,
1414            Some(vec![create_mock_relayer("test".to_string(), false)]),
1415            Some(vec![create_mock_signer()]),
1416            Some(vec![create_mock_network()]),
1417            None,
1418            None,
1419        )
1420        .await;
1421
1422        let request = Request {
1423            request_id: "test-rpc-11".to_string(),
1424            relayer_id: "test".to_string(),
1425            method: PluginMethod::Rpc,
1426            payload: serde_json::json!({
1427                "jsonrpc": "2.0",
1428                "method": "eth_blockNumber",
1429                "params": [],
1430                "id": 1
1431            }),
1432            http_request_id: Some("http-req-123".to_string()),
1433        };
1434
1435        let relayer_api = RelayerApi;
1436        let response = relayer_api
1437            .handle_request(request.clone(), &web::ThinData(state))
1438            .await;
1439
1440        assert!(response.error.is_none());
1441        assert!(response.result.is_some());
1442        assert_eq!(response.request_id, "test-rpc-11");
1443    }
1444
1445    #[tokio::test]
1446    async fn test_handle_rpc_request_default_jsonrpc_version() {
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-12".to_string(),
1460            relayer_id: "test".to_string(),
1461            method: PluginMethod::Rpc,
1462            payload: serde_json::json!({
1463                "method": "eth_blockNumber",
1464                "params": [],
1465                "id": 1
1466            }),
1467            http_request_id: None,
1468        };
1469
1470        let relayer_api = RelayerApi;
1471        let response = relayer_api
1472            .handle_request(request.clone(), &web::ThinData(state))
1473            .await;
1474
1475        // Should either succeed or return a JSON-RPC formatted response
1476        if response.error.is_none() {
1477            assert!(response.result.is_some());
1478            let result = response.result.unwrap();
1479            assert_eq!(result.get("jsonrpc").unwrap(), "2.0");
1480        } else {
1481            // If there's an error, it's still valid since we're testing default version behavior
1482            assert!(response.error.is_some());
1483        }
1484    }
1485
1486    #[tokio::test]
1487    async fn test_handle_rpc_request_custom_jsonrpc_version() {
1488        setup_test_env();
1489        let state = create_mock_app_state(
1490            None,
1491            Some(vec![create_mock_relayer("test".to_string(), false)]),
1492            Some(vec![create_mock_signer()]),
1493            Some(vec![create_mock_network()]),
1494            None,
1495            None,
1496        )
1497        .await;
1498
1499        let request = Request {
1500            request_id: "test-rpc-13".to_string(),
1501            relayer_id: "test".to_string(),
1502            method: PluginMethod::Rpc,
1503            payload: serde_json::json!({
1504                "jsonrpc": "1.0",
1505                "method": "eth_blockNumber",
1506                "params": [],
1507                "id": 1
1508            }),
1509            http_request_id: None,
1510        };
1511
1512        let relayer_api = RelayerApi;
1513        let response = relayer_api
1514            .handle_request(request.clone(), &web::ThinData(state))
1515            .await;
1516
1517        assert!(response.error.is_none());
1518        assert!(response.result.is_some());
1519    }
1520
1521    #[tokio::test]
1522    async fn test_handle_rpc_request_result_structure() {
1523        setup_test_env();
1524        let state = create_mock_app_state(
1525            None,
1526            Some(vec![create_mock_relayer("test".to_string(), false)]),
1527            Some(vec![create_mock_signer()]),
1528            Some(vec![create_mock_network()]),
1529            None,
1530            None,
1531        )
1532        .await;
1533
1534        let request = Request {
1535            request_id: "test-rpc-14".to_string(),
1536            relayer_id: "test".to_string(),
1537            method: PluginMethod::Rpc,
1538            payload: serde_json::json!({
1539                "jsonrpc": "2.0",
1540                "method": "eth_blockNumber",
1541                "params": [],
1542                "id": 42
1543            }),
1544            http_request_id: None,
1545        };
1546
1547        let relayer_api = RelayerApi;
1548        let response = relayer_api
1549            .handle_request(request.clone(), &web::ThinData(state))
1550            .await;
1551
1552        assert!(response.error.is_none());
1553        assert!(response.result.is_some());
1554        assert_eq!(response.request_id, "test-rpc-14");
1555
1556        let result = response.result.unwrap();
1557        assert!(result.get("jsonrpc").is_some());
1558        assert!(result.get("id").is_some());
1559        // Should have either result or error field
1560        assert!(result.get("result").is_some() || result.get("error").is_some());
1561    }
1562}