1use std::{
3 collections::HashMap,
4 io::{self, Error, ErrorKind},
5};
6
7use crate::{
8 codec::serde::hex_0x_utxo::Hex0xUtxo,
9 ids::{self, node},
10 jsonrpc,
11 key::bls,
12 platformvm, txs,
13};
14use serde::{Deserialize, Serialize};
15use serde_with::{serde_as, DisplayFromStr};
16
17#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
19pub struct IssueTxRequest {
20 pub jsonrpc: String,
21 pub id: u32,
22
23 pub method: String,
24
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub params: Option<IssueTxParams>,
27}
28
29impl Default for IssueTxRequest {
30 fn default() -> Self {
31 Self {
32 jsonrpc: String::from(super::DEFAULT_VERSION),
33 id: super::DEFAULT_ID,
34 method: String::new(),
35 params: None,
36 }
37 }
38}
39
40impl IssueTxRequest {
41 pub fn encode_json(&self) -> io::Result<String> {
42 serde_json::to_string(&self)
43 .map_err(|e| Error::new(ErrorKind::Other, format!("failed to serialize JSON {}", e)))
44 }
45}
46
47#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
48#[serde(rename_all = "camelCase")]
49pub struct IssueTxParams {
50 pub tx: String,
51 pub encoding: String,
52}
53
54#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
55pub struct IssueTxResponse {
56 pub jsonrpc: String,
57 pub id: u32,
58
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub result: Option<IssueTxResult>,
61
62 #[serde(skip_serializing_if = "Option::is_none")]
63 pub error: Option<super::ResponseError>,
64}
65
66impl Default for IssueTxResponse {
67 fn default() -> Self {
68 Self {
69 jsonrpc: "2.0".to_string(),
70 id: 1,
71 result: None,
72 error: None,
73 }
74 }
75}
76
77#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
79pub struct IssueTxResult {
80 #[serde(rename = "txID")]
81 pub tx_id: ids::Id,
82}
83
84impl Default for IssueTxResult {
85 fn default() -> Self {
86 Self {
87 tx_id: ids::Id::empty(),
88 }
89 }
90}
91
92#[test]
94fn test_issue_tx() {
95 use std::str::FromStr;
96
97 let resp: IssueTxResponse = serde_json::from_str(
98 "
99
100 {
101 \"jsonrpc\": \"2.0\",
102 \"result\": {
103 \"txID\": \"G3BuH6ytQ2averrLxJJugjWZHTRubzCrUZEXoheG5JMqL5ccY\"
104 },
105 \"id\": 1
106 }
107
108 ",
109 )
110 .unwrap();
111
112 let expected = IssueTxResponse {
113 jsonrpc: "2.0".to_string(),
114 id: 1,
115 result: Some(IssueTxResult {
116 tx_id: ids::Id::from_str("G3BuH6ytQ2averrLxJJugjWZHTRubzCrUZEXoheG5JMqL5ccY").unwrap(),
117 }),
118 error: None,
119 };
120 assert_eq!(resp, expected);
121}
122
123#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
125pub struct GetTxResponse {
126 pub jsonrpc: String,
127 pub id: u32,
128
129 #[serde(skip_serializing_if = "Option::is_none")]
130 pub result: Option<GetTxResult>,
131
132 #[serde(skip_serializing_if = "Option::is_none")]
133 pub error: Option<jsonrpc::ResponseError>,
134}
135
136impl Default for GetTxResponse {
137 fn default() -> Self {
138 Self {
139 jsonrpc: "2.0".to_string(),
140 id: 1,
141 result: None,
142 error: None,
143 }
144 }
145}
146
147#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Default)]
149pub struct GetTxResult {
150 pub tx: platformvm::txs::Tx,
151 pub encoding: String,
152}
153
154#[test]
156fn test_get_tx() {
157 let parsed_resp: GetTxResponse = serde_json::from_str(
158 "
159
160{
161 \"jsonrpc\": \"2.0\",
162 \"result\": {
163 \"tx\": {
164 \"unsignedTx\": {
165 \"networkID\": 1000000,
166 \"blockchainID\": \"11111111111111111111111111111111LpoYY\",
167 \"outputs\": [
168 {
169 \"assetID\": \"u8aaQ7MxyW32iHuP2xMXgYPrWYAsSbh8RJV9C6p1UeuGvqR3\",
170 \"fxID\": \"spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ\",
171 \"output\": {
172 \"addresses\": [
173 \"P-custom12szthht8tnl455u4mz3ns3nvvkel8ezvw2n8cx\"
174 ],
175 \"amount\": 245952587549460688,
176 \"locktime\": 0,
177 \"threshold\": 1
178 }
179 }
180 ],
181 \"inputs\": [
182 {
183 \"txID\": \"nN5QsURgEpM8D3e9q8FonS4EE13mnaBDtnQmgSwwUfBZ6FSW1\",
184 \"outputIndex\": 0,
185 \"assetID\": \"u8aaQ7MxyW32iHuP2xMXgYPrWYAsSbh8RJV9C6p1UeuGvqR3\",
186 \"fxID\": \"spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ\",
187 \"input\": {
188 \"amount\": 245952587649460688,
189 \"signatureIndices\": [
190 0
191 ]
192 }
193 }
194 ],
195 \"memo\": \"0x\",
196 \"owner\": {
197 \"addresses\": [
198 \"P-custom12szthht8tnl455u4mz3ns3nvvkel8ezvw2n8cx\"
199 ],
200 \"locktime\": 0,
201 \"threshold\": 1
202 }
203 },
204 \"credentials\": [
205 {
206 \"signatures\": [
207 \"0xcb356822dc8990672b5777ec50b57da91baf572240e7d4e9e38f26ec9dbdfd8e376fdc5f30769b842668cd8d81bd71db926dfbe326585137d363566ee500369f01\"
208 ]
209 }
210 ]
211 },
212 \"encoding\": \"json\"
213 },
214 \"id\": 1
215}
216
217",
218 )
219 .unwrap();
220
221 assert_eq!(parsed_resp.jsonrpc, "2.0");
222 assert_eq!(parsed_resp.result.clone().unwrap().encoding, "json");
223}
224
225#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
227pub struct GetTxStatusResponse {
228 pub jsonrpc: String,
229 pub id: u32,
230
231 #[serde(skip_serializing_if = "Option::is_none")]
232 pub result: Option<GetTxStatusResult>,
233
234 #[serde(skip_serializing_if = "Option::is_none")]
235 pub error: Option<jsonrpc::ResponseError>,
236}
237
238impl Default for GetTxStatusResponse {
239 fn default() -> Self {
240 Self {
241 jsonrpc: "2.0".to_string(),
242 id: 1,
243 result: None,
244 error: None,
245 }
246 }
247}
248
249#[serde_as]
251#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
252pub struct GetTxStatusResult {
253 #[serde_as(as = "DisplayFromStr")]
254 pub status: platformvm::txs::status::Status,
255}
256
257impl Default for GetTxStatusResult {
258 fn default() -> Self {
259 Self {
260 status: platformvm::txs::status::Status::Unknown(String::new()),
261 }
262 }
263}
264
265#[test]
267fn test_get_tx_status() {
268 let resp: GetTxStatusResponse = serde_json::from_str(
269 "
270
271{
272 \"jsonrpc\": \"2.0\",
273 \"result\": {
274 \"status\": \"Committed\"
275 },
276 \"id\": 1
277}
278
279",
280 )
281 .unwrap();
282
283 let expected = GetTxStatusResponse {
284 jsonrpc: "2.0".to_string(),
285 id: 1,
286 result: Some(GetTxStatusResult {
287 status: platformvm::txs::status::Status::Committed,
288 }),
289 error: None,
290 };
291 assert_eq!(resp, expected);
292}
293
294#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
296pub struct GetHeightResponse {
297 pub jsonrpc: String,
298 pub id: u32,
299
300 #[serde(skip_serializing_if = "Option::is_none")]
301 pub result: Option<GetHeightResult>,
302
303 #[serde(skip_serializing_if = "Option::is_none")]
304 pub error: Option<jsonrpc::ResponseError>,
305}
306
307#[serde_as]
309#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Default)]
310pub struct GetHeightResult {
311 #[serde_as(as = "DisplayFromStr")]
312 pub height: u64,
313}
314
315#[test]
317fn test_get_height() {
318 let resp: GetHeightResponse = serde_json::from_str(
320 "
321
322{
323 \"jsonrpc\": \"2.0\",
324 \"result\": {
325 \"height\": \"0\"
326 },
327 \"id\": 1
328}
329
330",
331 )
332 .unwrap();
333
334 let expected = GetHeightResponse {
335 jsonrpc: "2.0".to_string(),
336 id: 1,
337 result: Some(GetHeightResult { height: 0 }),
338 error: None,
339 };
340 assert_eq!(resp, expected);
341}
342
343#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
345pub struct GetUtxosRequest {
346 pub jsonrpc: String,
347 pub id: u32,
348
349 pub method: String,
350
351 #[serde(skip_serializing_if = "Option::is_none")]
352 pub params: Option<GetUtxosParams>,
353}
354
355impl Default for GetUtxosRequest {
356 fn default() -> Self {
357 Self {
358 jsonrpc: String::from(super::DEFAULT_VERSION),
359 id: super::DEFAULT_ID,
360 method: String::new(),
361 params: None,
362 }
363 }
364}
365
366impl GetUtxosRequest {
367 pub fn encode_json(&self) -> io::Result<String> {
368 serde_json::to_string(&self)
369 .map_err(|e| Error::new(ErrorKind::Other, format!("failed to serialize JSON {}", e)))
370 }
371}
372
373#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
375#[serde(rename_all = "camelCase")]
376pub struct GetUtxosParams {
377 pub addresses: Vec<String>,
378 pub limit: u32,
379 pub encoding: String,
380}
381
382#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
384pub struct GetUtxosResponse {
385 pub jsonrpc: String,
386 pub id: u32,
387
388 #[serde(skip_serializing_if = "Option::is_none")]
389 pub result: Option<GetUtxosResult>,
390
391 #[serde(skip_serializing_if = "Option::is_none")]
392 pub error: Option<super::ResponseError>,
393}
394
395#[serde_as]
397#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
398#[serde(rename_all = "camelCase")]
399#[derive(Default)]
400pub struct GetUtxosResult {
401 #[serde_as(as = "DisplayFromStr")]
402 pub num_fetched: u32,
403
404 #[serde_as(as = "Option<Vec<Hex0xUtxo>>")]
405 #[serde(skip_serializing_if = "Option::is_none")]
406 pub utxos: Option<Vec<txs::utxo::Utxo>>,
407
408 #[serde(skip_serializing_if = "Option::is_none")]
409 pub end_index: Option<super::EndIndex>,
410 #[serde(skip_serializing_if = "Option::is_none")]
411 pub encoding: Option<String>,
412}
413
414#[test]
416fn test_get_utxos_empty() {
417 let resp: GetUtxosResponse = serde_json::from_str(
419 "
420
421{
422 \"jsonrpc\": \"2.0\",
423 \"result\": {
424 \"numFetched\": \"0\",
425 \"utxos\": [],
426 \"endIndex\": {
427 \"address\": \"P-custom152qlr6zunz7nw2kc4lfej3cn3wk46u3002k4w5\",
428 \"utxo\": \"11111111111111111111111111111111LpoYY\"
429 },
430 \"encoding\":\"hex\"
431 },
432 \"id\": 1
433}
434
435",
436 )
437 .unwrap();
438
439 let expected = GetUtxosResponse {
440 jsonrpc: "2.0".to_string(),
441 id: 1,
442 result: Some(GetUtxosResult {
443 num_fetched: 0,
444 utxos: Some(Vec::new()),
445 end_index: Some(super::EndIndex {
446 address: String::from("P-custom152qlr6zunz7nw2kc4lfej3cn3wk46u3002k4w5"),
447 utxo: String::from("11111111111111111111111111111111LpoYY"),
448 }),
449 encoding: Some(String::from("hex")),
450 }),
451 error: None,
452 };
453 assert_eq!(resp, expected);
454}
455
456#[test]
458fn test_get_utxos_non_empty() {
459 let resp: GetUtxosResponse = serde_json::from_str(
461 "
462
463{
464 \"jsonrpc\": \"2.0\",
465 \"result\": {
466 \"numFetched\": \"1\",
467 \"utxos\": [
468 \"0x000000000000000000000000000000000000000000000000000000000000000000000000000088eec2e099c6a528e689618e8721e04ae85ea574c7a15a7968644d14d54780140000000702c68af0bb1400000000000000000000000000010000000165844a05405f3662c1928142c6c2a783ef871de939b564db\"
469 ],
470 \"endIndex\": {
471 \"address\": \"X-avax1x459sj0ssujguq723cljfty4jlae28evjzt7xz\",
472 \"utxo\": \"LUC1cmcxnfNR9LdkACS2ccGKLEK7SYqB4gLLTycQfg1koyfSq\"
473 },
474 \"encoding\": \"hex\"
475 },
476 \"id\": 1
477}
478
479",
480 )
481 .unwrap();
482
483 let raw_utxo = String::from("0x000000000000000000000000000000000000000000000000000000000000000000000000000088eec2e099c6a528e689618e8721e04ae85ea574c7a15a7968644d14d54780140000000702c68af0bb1400000000000000000000000000010000000165844a05405f3662c1928142c6c2a783ef871de939b564db");
484 let utxo = txs::utxo::Utxo::from_hex(&raw_utxo).unwrap();
485
486 let expected = GetUtxosResponse {
487 jsonrpc: "2.0".to_string(),
488 id: 1,
489 result: Some(GetUtxosResult {
490 num_fetched: 1,
491 utxos: Some(vec![utxo]),
492 end_index: Some(super::EndIndex {
493 address: String::from("X-avax1x459sj0ssujguq723cljfty4jlae28evjzt7xz"),
494 utxo: String::from("LUC1cmcxnfNR9LdkACS2ccGKLEK7SYqB4gLLTycQfg1koyfSq"),
495 }),
496 encoding: Some(String::from("hex")),
497 }),
498 error: None,
499 };
500 assert_eq!(resp, expected);
501}
502
503#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
505pub struct GetBalanceResponse {
506 pub jsonrpc: String,
507 pub id: u32,
508
509 #[serde(skip_serializing_if = "Option::is_none")]
510 pub result: Option<GetBalanceResult>,
511
512 #[serde(skip_serializing_if = "Option::is_none")]
513 pub error: Option<jsonrpc::ResponseError>,
514}
515
516#[serde_as]
518#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Default)]
519pub struct GetBalanceResult {
520 #[serde_as(as = "DisplayFromStr")]
521 pub balance: u64,
522 #[serde_as(as = "DisplayFromStr")]
523 pub unlocked: u64,
524
525 #[serde(rename = "lockedStakeable", skip_serializing_if = "Option::is_none")]
526 #[serde_as(as = "Option<DisplayFromStr>")]
527 pub locked_stakeable: Option<u64>,
528
529 #[serde(skip_serializing_if = "Option::is_none")]
530 #[serde_as(as = "Option<HashMap<_, DisplayFromStr>>")]
531 pub balances: Option<HashMap<String, u64>>,
532 #[serde(skip_serializing_if = "Option::is_none")]
533 #[serde_as(as = "Option<HashMap<_, DisplayFromStr>>")]
534 pub unlockeds: Option<HashMap<String, u64>>,
535
536 #[serde(rename = "lockedNotStakeable", skip_serializing_if = "Option::is_none")]
537 #[serde_as(as = "Option<DisplayFromStr>")]
538 pub locked_not_stakeable: Option<u64>,
539
540 #[serde(rename = "utxoIDs", skip_serializing_if = "Option::is_none")]
541 pub utxo_ids: Option<Vec<txs::utxo::Id>>,
542}
543
544#[test]
546fn test_get_balance() {
547 use crate::ids;
548 use std::str::FromStr;
549
550 let resp: GetBalanceResponse = serde_json::from_str(
552 "
553
554{
555 \"jsonrpc\": \"2.0\",
556 \"result\": {
557 \"balance\": \"20000000000000000\",
558 \"unlocked\": \"10000000000000000\",
559 \"lockedStakeable\": \"10000000000000000\",
560 \"lockedNotStakeable\": \"0\",
561 \"balances\": {
562 \"2ZKbwERx36B5WrYesQGAeTV4NTo4dx6j8svkjwAEix89ZPencR\": \"147573952589676412\"
563 },
564 \"unlockeds\": {
565 \"2ZKbwERx36B5WrYesQGAeTV4NTo4dx6j8svkjwAEix89ZPencR\": \"147573952589676412\"
566 },
567 \"utxoIDs\": [
568 {
569 \"txID\": \"11111111111111111111111111111111LpoYY\",
570 \"outputIndex\": 1
571 },
572 {
573 \"txID\": \"11111111111111111111111111111111LpoYY\",
574 \"outputIndex\": 0
575 }
576 ]
577 },
578 \"id\": 1
579}
580
581",
582 )
583 .unwrap();
584
585 let mut h = HashMap::new();
586 h.insert(
587 "2ZKbwERx36B5WrYesQGAeTV4NTo4dx6j8svkjwAEix89ZPencR".to_string(),
588 147573952589676412_u64,
589 );
590
591 let expected = GetBalanceResponse {
592 jsonrpc: "2.0".to_string(),
593 id: 1,
594 result: Some(GetBalanceResult {
595 balance: 20000000000000000,
596 unlocked: 10000000000000000,
597
598 locked_stakeable: Some(10000000000000000),
599 locked_not_stakeable: Some(0),
600
601 balances: Some(h.clone()),
602 unlockeds: Some(h.clone()),
603
604 utxo_ids: Some(vec![
605 txs::utxo::Id {
606 tx_id: ids::Id::from_str("11111111111111111111111111111111LpoYY").unwrap(),
607 output_index: 1,
608 ..txs::utxo::Id::default()
609 },
610 txs::utxo::Id {
611 tx_id: ids::Id::from_str("11111111111111111111111111111111LpoYY").unwrap(),
612 output_index: 0,
613 ..txs::utxo::Id::default()
614 },
615 ]),
616 }),
617 error: None,
618 };
619 assert_eq!(resp, expected);
620}
621
622#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
624pub struct GetCurrentValidatorsResponse {
625 pub jsonrpc: String,
626 pub id: u32,
627
628 #[serde(skip_serializing_if = "Option::is_none")]
629 pub result: Option<GetCurrentValidatorsResult>,
630
631 #[serde(skip_serializing_if = "Option::is_none")]
632 pub error: Option<jsonrpc::ResponseError>,
633}
634
635impl Default for GetCurrentValidatorsResponse {
636 fn default() -> Self {
637 Self {
638 jsonrpc: "2.0".to_string(),
639 id: 1,
640 result: Some(GetCurrentValidatorsResult::default()),
641 error: None,
642 }
643 }
644}
645
646#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Default)]
649pub struct GetCurrentValidatorsResult {
650 #[serde(skip_serializing_if = "Option::is_none")]
651 pub validators: Option<Vec<ApiPrimaryValidator>>,
652}
653
654#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
656pub struct GetPendingValidatorsResponse {
657 pub jsonrpc: String,
658 pub id: u32,
659
660 #[serde(skip_serializing_if = "Option::is_none")]
661 pub result: Option<GetPendingValidatorsResult>,
662
663 #[serde(skip_serializing_if = "Option::is_none")]
664 pub error: Option<jsonrpc::ResponseError>,
665}
666
667impl Default for GetPendingValidatorsResponse {
668 fn default() -> Self {
669 Self {
670 jsonrpc: "2.0".to_string(),
671 id: 1,
672 result: Some(GetPendingValidatorsResult::default()),
673 error: None,
674 }
675 }
676}
677
678#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone)]
682pub struct GetPendingValidatorsResult {
683 pub validators: Vec<ApiPrimaryValidator>,
684 pub delegators: Vec<ApiPrimaryDelegator>,
685}
686
687#[serde_as]
691#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
692#[serde(rename_all = "camelCase")]
693pub struct ApiPrimaryValidator {
694 #[serde(rename = "txID")]
695 pub tx_id: ids::Id,
696
697 #[serde_as(as = "DisplayFromStr")]
698 pub start_time: u64,
699 #[serde_as(as = "DisplayFromStr")]
700 pub end_time: u64,
701
702 #[serde_as(as = "Option<DisplayFromStr>")]
704 #[serde(skip_serializing_if = "Option::is_none")]
705 pub weight: Option<u64>,
706
707 #[serde_as(as = "Option<DisplayFromStr>")]
709 #[serde(skip_serializing_if = "Option::is_none")]
710 pub stake_amount: Option<u64>,
711
712 #[serde(rename = "nodeID")]
713 pub node_id: node::Id,
714
715 #[serde(skip_serializing_if = "Option::is_none")]
717 pub validation_reward_owner: Option<ApiOwner>,
718 #[serde(skip_serializing_if = "Option::is_none")]
720 pub delegation_reward_owner: Option<ApiOwner>,
721
722 #[serde_as(as = "Option<DisplayFromStr>")]
724 pub potential_reward: Option<u64>,
725 #[serde_as(as = "Option<DisplayFromStr>")]
727 #[serde(skip_serializing_if = "Option::is_none")]
728 pub delegation_fee: Option<f32>,
729
730 #[serde_as(as = "Option<DisplayFromStr>")]
732 #[serde(skip_serializing_if = "Option::is_none")]
733 pub uptime: Option<f32>,
734 pub connected: bool,
735
736 #[serde(skip_serializing_if = "Option::is_none")]
738 pub signer: Option<bls::ProofOfPossession>,
739
740 #[serde_as(as = "Option<DisplayFromStr>")]
742 #[serde(skip_serializing_if = "Option::is_none")]
743 pub delegator_count: Option<u64>,
744 #[serde_as(as = "Option<DisplayFromStr>")]
746 #[serde(skip_serializing_if = "Option::is_none")]
747 pub delegator_weight: Option<u64>,
748
749 #[serde(skip_serializing_if = "Option::is_none")]
751 pub delegators: Option<Vec<ApiPrimaryDelegator>>,
752}
753
754impl Default for ApiPrimaryValidator {
755 fn default() -> Self {
756 Self {
757 tx_id: ids::Id::empty(),
758 start_time: 0,
759 end_time: 0,
760 weight: None,
761 stake_amount: None,
762 node_id: node::Id::empty(),
763 validation_reward_owner: None,
764 delegation_reward_owner: None,
765 potential_reward: None,
766 delegation_fee: None,
767 uptime: None,
768 connected: false,
769 signer: None,
770 delegator_count: None,
771 delegator_weight: None,
772 delegators: None,
773 }
774 }
775}
776
777#[serde_as]
779#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Default)]
780pub struct ApiOwner {
781 #[serde_as(as = "DisplayFromStr")]
782 pub locktime: u64,
783 #[serde_as(as = "DisplayFromStr")]
784 pub threshold: u32,
785 pub addresses: Vec<String>,
786}
787
788#[serde_as]
792#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
793#[serde(rename_all = "camelCase")]
794pub struct ApiPrimaryDelegator {
795 #[serde(rename = "txID")]
796 pub tx_id: ids::Id,
797
798 #[serde_as(as = "DisplayFromStr")]
799 pub start_time: u64,
800 #[serde_as(as = "DisplayFromStr")]
801 pub end_time: u64,
802
803 #[serde_as(as = "DisplayFromStr")]
804 pub stake_amount: u64,
805
806 #[serde(rename = "nodeID")]
807 pub node_id: node::Id,
808
809 #[serde(skip_serializing_if = "Option::is_none")]
810 pub reward_owner: Option<ApiOwner>,
811
812 #[serde_as(as = "Option<DisplayFromStr>")]
813 #[serde(rename = "potentialReward", skip_serializing_if = "Option::is_none")]
814 pub potential_reward: Option<u64>,
815}
816
817impl Default for ApiPrimaryDelegator {
818 fn default() -> Self {
819 Self {
820 tx_id: ids::Id::empty(),
821 start_time: 0,
822 end_time: 0,
823 stake_amount: 0,
824 node_id: node::Id::empty(),
825 reward_owner: None,
826 potential_reward: None,
827 }
828 }
829}
830
831#[test]
833fn test_get_current_validators() {
834 use std::str::FromStr;
835
836 let resp: GetCurrentValidatorsResponse = serde_json::from_str(
838 "
839{
840 \"jsonrpc\": \"2.0\",
841 \"result\": {
842 \"validators\": [
843 {
844 \"txID\": \"KPkPo9EerKZhSwrA8NfLTVWsgr16Ntu8Ei4ci7GF7t75szrcQ\",
845 \"startTime\": \"1648312635\",
846 \"endTime\": \"1679843235\",
847 \"stakeAmount\": \"100000000000000000\",
848 \"nodeID\": \"NodeID-5wVq6KkSK3p4wQFmiVHCDq2zdg8unchaE\",
849 \"validationRewardOwner\": {
850 \"locktime\": \"0\",
851 \"threshold\": \"1\",
852 \"addresses\": [
853 \"P-custom1vkzy5p2qtumx9svjs9pvds48s0hcw80f962vrs\"
854 ]
855 },
856 \"delegationRewardOwner\": {
857 \"locktime\": \"0\",
858 \"threshold\": \"1\",
859 \"addresses\": [
860 \"P-custom1vkzy5p2qtumx9svjs9pvds48s0hcw80f962vrs\"
861 ]
862 },
863 \"potentialReward\": \"79984390135364555\",
864 \"delegationFee\": \"6.2500\",
865 \"uptime\": \"100.0000\",
866 \"connected\": true,
867 \"delegatorCount\": \"0\",
868 \"delegatorWeight\": \"0\",
869 \"delegators\": null
870 },
871 {
872 \"txID\": \"EjKZm5eEajWu151cfPms7PvMjyaQk36qTSz1MfnZRU5x5bNxz\",
873 \"startTime\": \"1648312635\",
874 \"endTime\": \"1679848635\",
875 \"stakeAmount\": \"100000000000000000\",
876 \"nodeID\": \"NodeID-JLR7d6z9cwCbkoPcPsnjkm6gq4xz7c4oT\",
877 \"validationRewardOwner\": {
878 \"locktime\": \"0\",
879 \"threshold\": \"1\",
880 \"addresses\": [
881 \"P-custom1vkzy5p2qtumx9svjs9pvds48s0hcw80f962vrs\"
882 ]
883 },
884 \"delegationRewardOwner\": {
885 \"locktime\": \"0\",
886 \"threshold\": \"1\",
887 \"addresses\": [
888 \"P-custom1vkzy5p2qtumx9svjs9pvds48s0hcw80f962vrs\"
889 ]
890 },
891 \"potentialReward\": \"77148186230865960\",
892 \"delegationFee\": \"6.2500\",
893 \"uptime\": \"100.0000\",
894 \"connected\": true,
895 \"delegatorCount\": \"0\",
896 \"delegatorWeight\": \"0\",
897 \"delegators\": null
898 }
899 ]
900 },
901 \"id\": 1
902}
903
904",
905 )
906 .unwrap();
907
908 let expected = GetCurrentValidatorsResponse {
909 jsonrpc: "2.0".to_string(),
910 id: 1,
911 result: Some(GetCurrentValidatorsResult {
912 validators: Some(<Vec<ApiPrimaryValidator>>::from([
913 ApiPrimaryValidator {
914 tx_id: ids::Id::from_str("KPkPo9EerKZhSwrA8NfLTVWsgr16Ntu8Ei4ci7GF7t75szrcQ")
915 .unwrap(),
916 start_time: 1648312635,
917 end_time: 1679843235,
918 stake_amount: Some(100000000000000000),
919 node_id: node::Id::from_str("NodeID-5wVq6KkSK3p4wQFmiVHCDq2zdg8unchaE")
920 .unwrap(),
921 validation_reward_owner: Some(ApiOwner {
922 locktime: 0,
923 threshold: 1,
924 addresses: vec![
925 "P-custom1vkzy5p2qtumx9svjs9pvds48s0hcw80f962vrs".to_string()
926 ],
927 }),
928 delegation_reward_owner: Some(ApiOwner {
929 locktime: 0,
930 threshold: 1,
931 addresses: vec![
932 "P-custom1vkzy5p2qtumx9svjs9pvds48s0hcw80f962vrs".to_string()
933 ],
934 }),
935 potential_reward: Some(79984390135364555),
936 delegation_fee: Some(6.25),
937 uptime: Some(100.0),
938 connected: true,
939 delegator_count: Some(0),
940 delegator_weight: Some(0),
941 ..ApiPrimaryValidator::default()
942 },
943 ApiPrimaryValidator {
944 tx_id: ids::Id::from_str("EjKZm5eEajWu151cfPms7PvMjyaQk36qTSz1MfnZRU5x5bNxz")
945 .unwrap(),
946
947 start_time: 1648312635,
948 end_time: 1679848635,
949 stake_amount: Some(100000000000000000),
950 node_id: node::Id::from_str("NodeID-JLR7d6z9cwCbkoPcPsnjkm6gq4xz7c4oT")
951 .unwrap(),
952 validation_reward_owner: Some(ApiOwner {
953 locktime: 0,
954 threshold: 1,
955 addresses: vec![
956 "P-custom1vkzy5p2qtumx9svjs9pvds48s0hcw80f962vrs".to_string()
957 ],
958 }),
959 delegation_reward_owner: Some(ApiOwner {
960 locktime: 0,
961 threshold: 1,
962 addresses: vec![
963 "P-custom1vkzy5p2qtumx9svjs9pvds48s0hcw80f962vrs".to_string()
964 ],
965 }),
966 potential_reward: Some(77148186230865960),
967 delegation_fee: Some(6.25),
968 uptime: Some(100.0),
969 connected: true,
970 delegator_count: Some(0),
971 delegator_weight: Some(0),
972 ..ApiPrimaryValidator::default()
973 },
974 ])),
975 }),
976 error: None,
977 };
978 assert_eq!(resp, expected);
979}
980
981#[test]
983
984fn test_get_pending_validators() {
985 use std::str::FromStr;
986
987 let resp: GetPendingValidatorsResponse = serde_json::from_str(
989 r#"
990{
991 "jsonrpc": "2.0",
992 "result": {
993 "validators": [
994 {
995 "txID": "2NNkpYTGfTFLSGXJcHtVv6drwVU2cczhmjK2uhvwDyxwsjzZMm",
996 "startTime": "1600368632",
997 "endTime": "1602960455",
998 "stakeAmount": "200000000000",
999 "nodeID": "NodeID-5mb46qkSBj81k9g9e4VFjGGSbaaSLFRzD",
1000 "delegationFee": "10.0000",
1001 "connected": false
1002 }
1003 ],
1004 "delegators": [
1005 {
1006 "txID": "Bbai8nzGVcyn2VmeYcbS74zfjJLjDacGNVuzuvAQkHn1uWfoV",
1007 "startTime": "1600368523",
1008 "endTime": "1602960342",
1009 "stakeAmount": "20000000000",
1010 "nodeID": "NodeID-7Xhw2mDxuDS44j42TCB6U5579esbSt3Lg"
1011 }
1012 ]
1013 },
1014 "id": 1
1015}
1016 "#,
1017 )
1018 .unwrap();
1019
1020 let expected = GetPendingValidatorsResponse {
1021 jsonrpc: "2.0".to_string(),
1022 id: 1,
1023 result: Some(GetPendingValidatorsResult {
1024 validators: <Vec<ApiPrimaryValidator>>::from([ApiPrimaryValidator {
1025 tx_id: ids::Id::from_str("2NNkpYTGfTFLSGXJcHtVv6drwVU2cczhmjK2uhvwDyxwsjzZMm")
1026 .unwrap(),
1027 start_time: 1600368632,
1028 end_time: 1602960455,
1029 stake_amount: Some(200000000000),
1030 node_id: node::Id::from_str("NodeID-5mb46qkSBj81k9g9e4VFjGGSbaaSLFRzD").unwrap(),
1031 delegation_fee: Some(10.0),
1032 connected: false,
1033 ..ApiPrimaryValidator::default()
1034 }]),
1035 delegators: <Vec<ApiPrimaryDelegator>>::from([ApiPrimaryDelegator {
1036 tx_id: ids::Id::from_str("Bbai8nzGVcyn2VmeYcbS74zfjJLjDacGNVuzuvAQkHn1uWfoV")
1037 .unwrap(),
1038 start_time: 1600368523,
1039 end_time: 1602960342,
1040 stake_amount: 20000000000,
1041 node_id: node::Id::from_str("NodeID-7Xhw2mDxuDS44j42TCB6U5579esbSt3Lg").unwrap(),
1042 ..ApiPrimaryDelegator::default()
1043 }]),
1044 }),
1045 error: None,
1046 };
1047 assert_eq!(resp, expected);
1048}
1049
1050#[serde_as]
1052#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Default)]
1053pub struct ApiUtxo {
1054 #[serde_as(as = "DisplayFromStr")]
1055 pub locktime: u64,
1056 #[serde_as(as = "DisplayFromStr")]
1057 pub amount: u64,
1058
1059 pub address: String,
1060 pub message: Option<String>,
1061}
1062
1063#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
1065pub struct GetSubnetsResponse {
1066 pub jsonrpc: String,
1067 pub id: u32,
1068
1069 #[serde(skip_serializing_if = "Option::is_none")]
1070 pub result: Option<GetSubnetsResult>,
1071
1072 #[serde(skip_serializing_if = "Option::is_none")]
1073 pub error: Option<jsonrpc::ResponseError>,
1074}
1075
1076#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Default)]
1078pub struct GetSubnetsResult {
1079 #[serde(skip_serializing_if = "Option::is_none")]
1080 pub subnets: Option<Vec<Subnet>>,
1081}
1082
1083#[serde_as]
1084#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
1085pub struct Subnet {
1086 pub id: ids::Id,
1087
1088 #[serde(rename = "controlKeys", skip_serializing_if = "Option::is_none")]
1089 pub control_keys: Option<Vec<ids::short::Id>>,
1090
1091 #[serde_as(as = "DisplayFromStr")]
1092 pub threshold: u32,
1093}
1094
1095impl Default for Subnet {
1096 fn default() -> Self {
1097 Self {
1098 id: ids::Id::empty(),
1099 control_keys: None,
1100 threshold: 0,
1101 }
1102 }
1103}
1104
1105#[test]
1107fn test_get_subnets() {
1108 use crate::ids;
1109 use std::str::FromStr;
1110
1111 let resp: GetSubnetsResponse = serde_json::from_str(
1113 "
1114
1115{
1116 \"jsonrpc\": \"2.0\",
1117 \"result\": {
1118 \"subnets\": [
1119 {
1120 \"id\": \"hW8Ma7dLMA7o4xmJf3AXBbo17bXzE7xnThUd3ypM4VAWo1sNJ\",
1121 \"controlKeys\": [
1122 \"KNjXsaA1sZsaKCD1cd85YXauDuxshTes2\",
1123 \"Aiz4eEt5xv9t4NCnAWaQJFNz5ABqLtJkR\"
1124 ],
1125 \"threshold\": \"2\"
1126 }
1127 ]
1128 },
1129 \"id\": 1
1130}
1131
1132",
1133 )
1134 .unwrap();
1135
1136 let expected = GetSubnetsResponse {
1137 jsonrpc: "2.0".to_string(),
1138 id: 1,
1139 result: Some(GetSubnetsResult {
1140 subnets: Some(vec![Subnet {
1141 id: ids::Id::from_str("hW8Ma7dLMA7o4xmJf3AXBbo17bXzE7xnThUd3ypM4VAWo1sNJ").unwrap(),
1142 control_keys: Some(vec![
1143 ids::short::Id::from_str("KNjXsaA1sZsaKCD1cd85YXauDuxshTes2").unwrap(),
1144 ids::short::Id::from_str("Aiz4eEt5xv9t4NCnAWaQJFNz5ABqLtJkR").unwrap(),
1145 ]),
1146 threshold: 2,
1147 }]),
1148 }),
1149 error: None,
1150 };
1151 assert_eq!(resp, expected);
1152}
1153
1154#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
1156pub struct GetBlockchainsResponse {
1157 pub jsonrpc: String,
1158 pub id: u32,
1159
1160 #[serde(skip_serializing_if = "Option::is_none")]
1161 pub result: Option<GetBlockchainsResult>,
1162
1163 #[serde(skip_serializing_if = "Option::is_none")]
1164 pub error: Option<jsonrpc::ResponseError>,
1165}
1166
1167#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Default)]
1169pub struct GetBlockchainsResult {
1170 #[serde(skip_serializing_if = "Option::is_none")]
1171 pub blockchains: Option<Vec<Blockchain>>,
1172}
1173
1174#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, PartialOrd, Ord, Hash)]
1175pub struct Blockchain {
1176 pub id: ids::Id,
1177 pub name: String,
1178 #[serde(rename = "subnetID")]
1179 pub subnet_id: ids::Id,
1180 #[serde(rename = "vmID")]
1181 pub vm_id: ids::Id,
1182}
1183
1184impl Default for Blockchain {
1185 fn default() -> Self {
1186 Self {
1187 id: ids::Id::empty(),
1188 name: String::new(),
1189 subnet_id: ids::Id::empty(),
1190 vm_id: ids::Id::empty(),
1191 }
1192 }
1193}
1194
1195#[test]
1197fn test_get_blockchains() {
1198 use crate::ids;
1199 use std::str::FromStr;
1200
1201 let resp: GetBlockchainsResponse = serde_json::from_str(
1203 "
1204
1205{
1206 \"jsonrpc\": \"2.0\",
1207 \"result\": {
1208 \"blockchains\": [
1209 {
1210 \"id\": \"yC7LZEwfaYhEvreNm48iGMtASiukPaT4899N9df2rkhWUzDeQ\",
1211 \"name\": \"subnetevm\",
1212 \"subnetID\": \"268FZNRqAUwE4baMPkEQj48gnbGz34pgocWZvHQaUpnwb9Cp7i\",
1213 \"vmID\": \"srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy\"
1214 },
1215 {
1216 \"id\": \"27sJotKt62mibBwKFAwxvCKn8KEbdvk4Bn3nFSVJSZebrxMfdU\",
1217 \"name\": \"C-Chain\",
1218 \"subnetID\": \"11111111111111111111111111111111LpoYY\",
1219 \"vmID\": \"mgj786NP7uDwBCcq6YwThhaN8FLyybkCa4zBWTQbNgmK6k9A6\"
1220 },
1221 {
1222 \"id\": \"2oYMBNV4eNHyqk2fjjV5nVQLDbtmNJzq5s3qs3Lo6ftnC6FByM\",
1223 \"name\": \"X-Chain\",
1224 \"subnetID\": \"11111111111111111111111111111111LpoYY\",
1225 \"vmID\": \"jvYyfQTxGMJLuGWa55kdP2p2zSUYsQ5Raupu4TW34ZAUBAbtq\"
1226 }
1227 ]
1228 },
1229 \"id\": 1
1230}
1231
1232",
1233 )
1234 .unwrap();
1235
1236 let expected = GetBlockchainsResponse {
1237 jsonrpc: "2.0".to_string(),
1238 id: 1,
1239 result: Some(GetBlockchainsResult {
1240 blockchains: Some(vec![
1241 Blockchain {
1242 id: ids::Id::from_str("yC7LZEwfaYhEvreNm48iGMtASiukPaT4899N9df2rkhWUzDeQ")
1243 .unwrap(),
1244 name: String::from("subnetevm"),
1245 subnet_id: ids::Id::from_str(
1246 "268FZNRqAUwE4baMPkEQj48gnbGz34pgocWZvHQaUpnwb9Cp7i",
1247 )
1248 .unwrap(),
1249 vm_id: ids::Id::from_str("srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy")
1250 .unwrap(),
1251 },
1252 Blockchain {
1253 id: ids::Id::from_str("27sJotKt62mibBwKFAwxvCKn8KEbdvk4Bn3nFSVJSZebrxMfdU")
1254 .unwrap(),
1255 name: String::from("C-Chain"),
1256 subnet_id: ids::Id::empty(),
1257 vm_id: ids::Id::from_str("mgj786NP7uDwBCcq6YwThhaN8FLyybkCa4zBWTQbNgmK6k9A6")
1258 .unwrap(),
1259 },
1260 Blockchain {
1261 id: ids::Id::from_str("2oYMBNV4eNHyqk2fjjV5nVQLDbtmNJzq5s3qs3Lo6ftnC6FByM")
1262 .unwrap(),
1263 name: String::from("X-Chain"),
1264 subnet_id: ids::Id::empty(),
1265 vm_id: ids::Id::from_str("jvYyfQTxGMJLuGWa55kdP2p2zSUYsQ5Raupu4TW34ZAUBAbtq")
1266 .unwrap(),
1267 },
1268 ]),
1269 }),
1270 error: None,
1271 };
1272 assert_eq!(resp, expected);
1273}
1274
1275#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
1277pub struct GetBlockchainStatusRequest {
1278 pub jsonrpc: String,
1279 pub id: u32,
1280
1281 pub method: String,
1282
1283 #[serde(skip_serializing_if = "Option::is_none")]
1284 pub params: Option<GetBlockchainStatusParams>,
1285}
1286
1287impl Default for GetBlockchainStatusRequest {
1288 fn default() -> Self {
1289 Self {
1290 jsonrpc: String::from(super::DEFAULT_VERSION),
1291 id: super::DEFAULT_ID,
1292 method: String::new(),
1293 params: None,
1294 }
1295 }
1296}
1297
1298impl GetBlockchainStatusRequest {
1299 pub fn encode_json(&self) -> io::Result<String> {
1300 serde_json::to_string(&self)
1301 .map_err(|e| Error::new(ErrorKind::Other, format!("failed to serialize JSON {}", e)))
1302 }
1303}
1304
1305#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
1306pub struct GetBlockchainStatusParams {
1307 #[serde(rename = "blockchainID")]
1308 pub blockchain_id: ids::Id,
1309}
1310
1311#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
1312pub struct GetBlockchainStatusResponse {
1313 pub jsonrpc: String,
1314 pub id: u32,
1315
1316 #[serde(skip_serializing_if = "Option::is_none")]
1317 pub result: Option<GetBlockchainStatusResult>,
1318
1319 #[serde(skip_serializing_if = "Option::is_none")]
1320 pub error: Option<super::ResponseError>,
1321}
1322
1323impl Default for GetBlockchainStatusResponse {
1324 fn default() -> Self {
1325 Self {
1326 jsonrpc: "2.0".to_string(),
1327 id: 1,
1328 result: None,
1329 error: None,
1330 }
1331 }
1332}
1333
1334#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Default)]
1336pub struct GetBlockchainStatusResult {
1337 pub status: String,
1338}
1339
1340#[test]
1342fn test_get_blockchain_status() {
1343 let resp: GetBlockchainStatusResponse = serde_json::from_str(
1344 "
1345
1346 {
1347 \"jsonrpc\": \"2.0\",
1348 \"result\": {
1349 \"status\": \"Created\"
1350 },
1351 \"id\": 1
1352 }
1353
1354 ",
1355 )
1356 .unwrap();
1357
1358 let expected = GetBlockchainStatusResponse {
1359 jsonrpc: "2.0".to_string(),
1360 id: 1,
1361 result: Some(GetBlockchainStatusResult {
1362 status: String::from("Created"),
1363 }),
1364 error: None,
1365 };
1366 assert_eq!(resp, expected);
1367}