1use 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 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 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 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 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 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"); 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 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 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 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 assert!(result.get("result").is_some() || result.get("error").is_some());
1579 }
1580}