1use serde::{Deserialize, Serialize};
2use utoipa::ToSchema;
3
4mod json_rpc;
5pub use json_rpc::*;
6
7mod solana;
8pub use solana::*;
9
10mod stellar;
11pub use stellar::*;
12
13mod evm;
14pub use evm::*;
15
16mod error;
17pub use error::*;
18
19use crate::models::ApiError;
20
21#[derive(Debug, Serialize, Deserialize, ToSchema, PartialEq)]
22#[serde(untagged)]
23pub enum NetworkRpcResult {
24 Solana(SolanaRpcResult),
25 Stellar(StellarRpcResult),
26 Evm(EvmRpcResult),
27}
28
29#[derive(Debug, Serialize, Deserialize, ToSchema, PartialEq)]
30#[serde(untagged)]
31#[serde(deny_unknown_fields)]
32pub enum NetworkRpcRequest {
33 Solana(SolanaRpcRequest),
34 Stellar(StellarRpcRequest),
35 Evm(EvmRpcRequest),
36}
37
38pub fn convert_to_internal_rpc_request(
69 request: serde_json::Value,
70 network_type: &crate::models::NetworkType,
71) -> Result<JsonRpcRequest<NetworkRpcRequest>, ApiError> {
72 let jsonrpc = request
73 .get("jsonrpc")
74 .and_then(|v| v.as_str())
75 .unwrap_or("2.0")
76 .to_string();
77
78 let id = match request.get("id") {
79 Some(id_value) => match id_value {
80 serde_json::Value::String(s) => Some(JsonRpcId::String(s.clone())),
81 serde_json::Value::Number(n) => {
82 n.as_i64().map(JsonRpcId::Number).map(Some).ok_or_else(|| {
83 ApiError::BadRequest(
84 "Invalid 'id' field: must be a string, integer, or null".to_string(),
85 )
86 })?
87 }
88 serde_json::Value::Null => None,
89 _ => {
90 return Err(ApiError::BadRequest(
91 "Invalid 'id' field: must be a string, integer, or null".to_string(),
92 ))
93 }
94 },
95 None => Some(JsonRpcId::Number(1)), };
97
98 let method = request
99 .get("method")
100 .and_then(|v| v.as_str())
101 .ok_or_else(|| ApiError::BadRequest("Missing 'method' field".to_string()))?;
102
103 if method.is_empty() {
104 return Err(ApiError::BadRequest("Missing 'method' field".to_string()));
105 }
106
107 match network_type {
108 crate::models::NetworkType::Evm => {
109 let params = request
110 .get("params")
111 .cloned()
112 .unwrap_or(serde_json::Value::Null);
113
114 Ok(JsonRpcRequest {
115 jsonrpc,
116 params: NetworkRpcRequest::Evm(crate::models::EvmRpcRequest::RawRpcRequest {
117 method: method.to_string(),
118 params,
119 }),
120 id,
121 })
122 }
123 crate::models::NetworkType::Solana => {
124 let params = request
125 .get("params")
126 .cloned()
127 .unwrap_or(serde_json::Value::Null);
128
129 match crate::models::SolanaRpcMethod::from_string(method) {
132 Some(crate::models::SolanaRpcMethod::Generic(_)) | None => Ok(JsonRpcRequest {
133 jsonrpc,
134 params: NetworkRpcRequest::Solana(
135 crate::models::SolanaRpcRequest::RawRpcRequest {
136 method: method.to_string(),
137 params,
138 },
139 ),
140 id,
141 }),
142 Some(_) => {
144 let json = serde_json::json!({"method": method, "params": params});
145 let solana_request: crate::models::SolanaRpcRequest =
146 serde_json::from_value(json).map_err(|e| {
147 ApiError::BadRequest(format!("Invalid Solana RPC request: {e}"))
148 })?;
149
150 Ok(JsonRpcRequest {
151 jsonrpc,
152 params: NetworkRpcRequest::Solana(solana_request),
153 id,
154 })
155 }
156 }
157 }
158 crate::models::NetworkType::Stellar => {
159 let params = request
160 .get("params")
161 .cloned()
162 .unwrap_or(serde_json::Value::Null);
163
164 Ok(JsonRpcRequest {
165 jsonrpc,
166 params: NetworkRpcRequest::Stellar(
167 crate::models::StellarRpcRequest::RawRpcRequest {
168 method: method.to_string(),
169 params,
170 },
171 ),
172 id,
173 })
174 }
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use crate::models::{EvmRpcRequest, NetworkType, SolanaRpcRequest, StellarRpcRequest};
182 use serde_json::json;
183
184 #[test]
185 fn test_convert_evm_standard_request() {
186 let request = json!({
187 "jsonrpc": "2.0",
188 "method": "eth_getBalance",
189 "params": ["0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "latest"],
190 "id": 1
191 });
192
193 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm).unwrap();
194
195 assert_eq!(result.jsonrpc, "2.0");
196 assert_eq!(result.id, Some(JsonRpcId::Number(1)));
197
198 match result.params {
199 NetworkRpcRequest::Evm(EvmRpcRequest::RawRpcRequest { method, params }) => {
200 assert_eq!(method, "eth_getBalance");
201 assert_eq!(params[0], "0x742d35Cc6634C0532925a3b844Bc454e4438f44e");
202 assert_eq!(params[1], "latest");
203 }
204 _ => unreachable!("Expected EVM RawRpcRequest"),
205 }
206 }
207
208 #[test]
209 fn test_convert_evm_missing_method_field() {
210 let request = json!({
211 "jsonrpc": "2.0",
212 "params": ["0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "latest"],
213 "id": 1
214 });
215
216 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm);
217 assert!(result.is_err());
218 }
219
220 #[test]
221 fn test_convert_evm_with_defaults() {
222 let request = json!({
223 "method": "eth_blockNumber"
224 });
225
226 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm).unwrap();
227
228 assert_eq!(result.jsonrpc, "2.0");
229 assert_eq!(result.id, Some(JsonRpcId::Number(1)));
230
231 match result.params {
232 NetworkRpcRequest::Evm(EvmRpcRequest::RawRpcRequest { method, params }) => {
233 assert_eq!(method, "eth_blockNumber");
234 assert_eq!(params, serde_json::Value::Null);
235 }
236 _ => unreachable!("Expected EVM RawRpcRequest"),
237 }
238 }
239
240 #[test]
241 fn test_convert_evm_with_custom_jsonrpc_and_id() {
242 let request = json!({
243 "jsonrpc": "1.0",
244 "method": "eth_chainId",
245 "params": [],
246 "id": 42
247 });
248
249 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm).unwrap();
250
251 assert_eq!(result.jsonrpc, "1.0");
252 assert_eq!(result.id, Some(JsonRpcId::Number(42)));
253 }
254
255 #[test]
256 fn test_convert_evm_with_string_id() {
257 let request = json!({
258 "jsonrpc": "2.0",
259 "method": "eth_chainId",
260 "params": [],
261 "id": "test-id"
262 });
263
264 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm).unwrap();
265
266 assert_eq!(result.id, Some(JsonRpcId::String("test-id".to_string())));
268 }
269
270 #[test]
271 fn test_convert_evm_with_null_id() {
272 let request = json!({
273 "jsonrpc": "2.0",
274 "method": "eth_chainId",
275 "params": [],
276 "id": null
277 });
278
279 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm).unwrap();
280
281 assert_eq!(result.id, None);
283 }
284
285 #[test]
286 fn test_convert_evm_with_object_params() {
287 let request = json!({
288 "jsonrpc": "2.0",
289 "method": "eth_getTransactionByHash",
290 "params": {
291 "hash": "0x123",
292 "full": true
293 },
294 "id": 1
295 });
296
297 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm).unwrap();
298
299 match result.params {
300 NetworkRpcRequest::Evm(EvmRpcRequest::RawRpcRequest { method, params }) => {
301 assert_eq!(method, "eth_getTransactionByHash");
302 assert_eq!(params["hash"], "0x123");
303 assert_eq!(params["full"], true);
304 }
305 _ => unreachable!("Expected EVM RawRpcRequest"),
306 }
307 }
308
309 #[test]
310 fn test_convert_solana_fee_estimate_request() {
311 let request = json!({
312 "jsonrpc": "2.0",
313 "method": "feeEstimate",
314 "params": {
315 "transaction": "base64encodedtransaction",
316 "fee_token": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" },
318 "id": 1
319 });
320
321 let result = convert_to_internal_rpc_request(request, &NetworkType::Solana).unwrap();
322
323 assert_eq!(result.jsonrpc, "2.0");
324 assert_eq!(result.id, Some(JsonRpcId::Number(1)));
325
326 match result.params {
327 NetworkRpcRequest::Solana(solana_request) => {
328 match solana_request {
330 SolanaRpcRequest::FeeEstimate(_) => {}
331 _ => unreachable!("Expected FeeEstimate variant"),
332 }
333 }
334 _ => unreachable!("Expected Solana request"),
335 }
336 }
337
338 #[test]
339 fn test_convert_solana_get_supported_tokens_request() {
340 let request = json!({
341 "jsonrpc": "2.0",
342 "method": "getSupportedTokens",
343 "params": {},
344 "id": 2
345 });
346
347 let result = convert_to_internal_rpc_request(request, &NetworkType::Solana).unwrap();
348
349 assert_eq!(result.jsonrpc, "2.0");
350 assert_eq!(result.id, Some(JsonRpcId::Number(2)));
351
352 match result.params {
353 NetworkRpcRequest::Solana(solana_request) => match solana_request {
354 SolanaRpcRequest::GetSupportedTokens(_) => {}
355 _ => unreachable!("Expected GetSupportedTokens variant"),
356 },
357 _ => unreachable!("Expected Solana request"),
358 }
359 }
360
361 #[test]
362 fn test_convert_solana_transfer_transaction_request() {
363 let request = json!({
364 "jsonrpc": "2.0",
365 "method": "transferTransaction",
366 "params": {
367 "amount": 1000000,
368 "token": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "source": "source_address",
370 "destination": "destination_address"
371 },
372 "id": 3
373 });
374
375 let result = convert_to_internal_rpc_request(request, &NetworkType::Solana).unwrap();
376
377 match result.params {
378 NetworkRpcRequest::Solana(solana_request) => match solana_request {
379 SolanaRpcRequest::TransferTransaction(_) => {}
380 _ => unreachable!("Expected TransferTransaction variant"),
381 },
382 _ => unreachable!("Expected Solana request"),
383 }
384 }
385
386 #[test]
387 fn test_convert_solana_invalid_request() {
388 let request = json!({
389 "jsonrpc": "2.0",
390 "method": "",
391 "params": {},
392 "id": 1
393 });
394
395 let result = convert_to_internal_rpc_request(request, &NetworkType::Solana);
396 assert!(result.is_err());
397 }
398
399 #[test]
400 fn test_convert_solana_malformed_request() {
401 let request = json!({
402 "jsonrpc": "2.0",
403 "method": "feeEstimate",
404 "params": {
405 "invalid_field": "value"
406 },
407 "id": 1
408 });
409
410 let result = convert_to_internal_rpc_request(request, &NetworkType::Solana);
411 assert!(result.is_err());
412 }
413
414 #[test]
415 fn test_convert_solana_with_defaults() {
416 let request = json!({
417 "method": "getSupportedTokens",
418 "params": {}
419 });
420
421 let result = convert_to_internal_rpc_request(request, &NetworkType::Solana).unwrap();
422
423 assert_eq!(result.jsonrpc, "2.0");
424 assert_eq!(result.id, Some(JsonRpcId::Number(1)));
425 }
426
427 #[test]
428 fn test_convert_stellar_request() {
429 let request = json!({
430 "jsonrpc": "2.0",
431 "method": "test",
432 "params": "test_params",
433 "id": 1
434 });
435
436 let result = convert_to_internal_rpc_request(request, &NetworkType::Stellar).unwrap();
437
438 assert_eq!(result.jsonrpc, "2.0");
439 assert_eq!(result.id, Some(JsonRpcId::Number(1)));
440
441 match result.params {
442 NetworkRpcRequest::Stellar(stellar_request) => match stellar_request {
443 StellarRpcRequest::RawRpcRequest { method: _, params } => {
444 assert_eq!(params, serde_json::Value::String("test_params".to_string()));
445 }
446 },
447 _ => unreachable!("Expected Stellar request"),
448 }
449 }
450
451 #[test]
452 fn test_convert_stellar_invalid_request() {
453 let request = json!({
454 "jsonrpc": "2.0",
455 "method": "",
456 "params": {},
457 "id": 1
458 });
459
460 let result = convert_to_internal_rpc_request(request, &NetworkType::Stellar);
461 assert!(result.is_err());
462 }
463
464 #[test]
465 fn test_convert_stellar_with_defaults() {
466 let request = json!({
467 "method": "test",
468 "params": "default_test"
469 });
470
471 let result = convert_to_internal_rpc_request(request, &NetworkType::Stellar).unwrap();
472
473 assert_eq!(result.jsonrpc, "2.0");
474 assert_eq!(result.id, Some(JsonRpcId::Number(1)));
475 }
476
477 #[test]
478 fn test_convert_empty_request() {
479 let request = json!({});
480
481 let result_evm = convert_to_internal_rpc_request(request.clone(), &NetworkType::Evm);
482 assert!(result_evm.is_err());
483
484 let result_solana = convert_to_internal_rpc_request(request.clone(), &NetworkType::Solana);
485 assert!(result_solana.is_err());
486
487 let result_stellar = convert_to_internal_rpc_request(request, &NetworkType::Stellar);
488 assert!(result_stellar.is_err());
489 }
490
491 #[test]
492 fn test_convert_null_request() {
493 let request = serde_json::Value::Null;
494
495 let result_evm = convert_to_internal_rpc_request(request.clone(), &NetworkType::Evm);
496 assert!(result_evm.is_err());
497
498 let result_solana = convert_to_internal_rpc_request(request.clone(), &NetworkType::Solana);
499 assert!(result_solana.is_err());
500
501 let result_stellar = convert_to_internal_rpc_request(request, &NetworkType::Stellar);
502 assert!(result_stellar.is_err());
503 }
504
505 #[test]
506 fn test_convert_array_request() {
507 let request = json!([1, 2, 3]);
508
509 let result_evm = convert_to_internal_rpc_request(request.clone(), &NetworkType::Evm);
510 assert!(result_evm.is_err());
511
512 let result_solana = convert_to_internal_rpc_request(request.clone(), &NetworkType::Solana);
513 assert!(result_solana.is_err());
514
515 let result_stellar = convert_to_internal_rpc_request(request, &NetworkType::Stellar);
516 assert!(result_stellar.is_err());
517 }
518
519 #[test]
520 fn test_convert_solana_unknown_method_maps_to_raw_request() {
521 let request = serde_json::json!({
522 "jsonrpc": "2.0",
523 "id": 1,
524 "method": "getLatestBlockhash",
525 "params": [ { "commitment": "finalized" } ]
526 });
527
528 let result = convert_to_internal_rpc_request(request, &NetworkType::Solana).unwrap();
529
530 assert_eq!(result.jsonrpc, "2.0");
531 assert_eq!(result.id, Some(JsonRpcId::Number(1)));
532
533 match result.params {
534 NetworkRpcRequest::Solana(solana_request) => match solana_request {
535 crate::models::SolanaRpcRequest::RawRpcRequest { method, params } => {
536 assert_eq!(method, "getLatestBlockhash");
537 assert_eq!(params[0]["commitment"], "finalized");
538 }
539 _ => unreachable!("Expected RawRpcRequest variant for unknown method"),
540 },
541 _ => unreachable!("Expected Solana request"),
542 }
543 }
544
545 #[test]
546 fn test_convert_evm_non_string_method() {
547 let request = json!({
548 "jsonrpc": "2.0",
549 "method": 123,
550 "params": [],
551 "id": 1
552 });
553
554 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm);
555 assert!(result.is_err());
556 }
557
558 #[test]
559 fn test_convert_with_large_id() {
560 let request = json!({
561 "jsonrpc": "2.0",
562 "method": "eth_chainId",
563 "params": [],
564 "id": 18446744073709551615u64 });
566
567 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm);
568 assert!(result.is_err());
569
570 if let Err(crate::models::ApiError::BadRequest(msg)) = result {
571 assert!(msg.contains("Invalid 'id' field: must be a string, integer, or null"));
572 } else {
573 panic!("Expected BadRequest error");
574 }
575 }
576
577 #[test]
578 fn test_convert_with_zero_id() {
579 let request = json!({
580 "jsonrpc": "2.0",
581 "method": "eth_chainId",
582 "params": [],
583 "id": 0
584 });
585
586 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm).unwrap();
587 assert_eq!(result.id, Some(JsonRpcId::Number(0)));
588 }
589
590 #[test]
591 fn test_convert_evm_empty_method() {
592 let request = json!({
593 "jsonrpc": "2.0",
594 "method": "",
595 "params": [],
596 "id": 1
597 });
598
599 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm);
600
601 assert!(result.is_err());
602 }
603
604 #[test]
605 fn test_convert_evm_very_long_method() {
606 let long_method = "a".repeat(1000);
607 let request = json!({
608 "jsonrpc": "2.0",
609 "method": long_method,
610 "params": [],
611 "id": 1
612 });
613
614 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm).unwrap();
615
616 match result.params {
617 NetworkRpcRequest::Evm(EvmRpcRequest::RawRpcRequest { method, params: _ }) => {
618 assert_eq!(method, long_method);
619 }
620 _ => unreachable!("Expected EVM RawRpcRequest"),
621 }
622 }
623
624 #[test]
625 fn test_convert_with_invalid_id_type_boolean() {
626 let request = json!({
627 "jsonrpc": "2.0",
628 "method": "eth_chainId",
629 "params": [],
630 "id": true
631 });
632
633 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm);
634 assert!(result.is_err());
635
636 if let Err(crate::models::ApiError::BadRequest(msg)) = result {
637 assert!(msg.contains("Invalid 'id' field: must be a string, integer, or null"));
638 } else {
639 panic!("Expected BadRequest error");
640 }
641 }
642
643 #[test]
644 fn test_convert_with_invalid_id_type_array() {
645 let request = json!({
646 "jsonrpc": "2.0",
647 "method": "eth_chainId",
648 "params": [],
649 "id": [1, 2, 3]
650 });
651
652 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm);
653 assert!(result.is_err());
654
655 if let Err(crate::models::ApiError::BadRequest(msg)) = result {
656 assert!(msg.contains("Invalid 'id' field: must be a string, integer, or null"));
657 } else {
658 panic!("Expected BadRequest error");
659 }
660 }
661
662 #[test]
663 fn test_convert_with_invalid_id_type_object() {
664 let request = json!({
665 "jsonrpc": "2.0",
666 "method": "eth_chainId",
667 "params": [],
668 "id": {"nested": "object"}
669 });
670
671 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm);
672 assert!(result.is_err());
673
674 if let Err(crate::models::ApiError::BadRequest(msg)) = result {
675 assert!(msg.contains("Invalid 'id' field: must be a string, integer, or null"));
676 } else {
677 panic!("Expected BadRequest error");
678 }
679 }
680
681 #[test]
682 fn test_convert_with_fractional_id() {
683 let request = json!({
684 "jsonrpc": "2.0",
685 "method": "eth_chainId",
686 "params": [],
687 "id": 42.5
688 });
689
690 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm);
691 assert!(result.is_err());
692
693 if let Err(crate::models::ApiError::BadRequest(msg)) = result {
694 assert!(msg.contains("Invalid 'id' field: must be a string, integer, or null"));
695 } else {
696 panic!("Expected BadRequest error");
697 }
698 }
699}