1use crate::nym_api::error::NymAPIError;
5use crate::nym_api::routes::{ecash, CORE_STATUS_COUNT, SINCE_ARG};
6use crate::nym_nodes::SkimmedNodesWithMetadata;
7use crate::ValidatorClientError;
8use async_trait::async_trait;
9use nym_api_requests::ecash::models::{
10 AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
11 BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashSignerStatusResponse,
12 EcashTicketVerificationResponse, IssuedTicketbooksChallengeCommitmentRequest,
13 IssuedTicketbooksChallengeCommitmentResponse, IssuedTicketbooksDataRequest,
14 IssuedTicketbooksDataResponse, IssuedTicketbooksForCountResponse, IssuedTicketbooksForResponse,
15 VerifyEcashTicketBody,
16};
17use nym_api_requests::ecash::VerificationKeyResponse;
18
19use nym_api_requests::models::node_families::NodeFamily;
20use nym_api_requests::models::{
21 AnnotationResponseV1, AnnotationResponseV2, ApiHealthResponse, BinaryBuildInformationOwned,
22 ChainBlocksStatusResponse, ChainStatusResponse, KeyRotationInfoResponse,
23 NodePerformanceResponse, NodeRefreshBody, PerformanceHistoryResponse, RewardedSetResponse,
24 SignerInformationResponse,
25};
26use nym_api_requests::pagination::PaginatedResponse;
27use nym_http_api_client::{ApiClient, NO_PARAMS};
28use nym_mixnet_contract_common::{IdentityKeyRef, NodeId, NymNodeDetails};
29use std::net::IpAddr;
30use time::format_description::BorrowedFormatItem;
31use time::Date;
32use tracing::instrument;
33
34use nym_api_requests::models::described::v1::NymNodeDescriptionV1;
35use nym_api_requests::models::described::v2::NymNodeDescriptionV2;
36use nym_api_requests::models::v3::{
37 KnownNetworkMonitorResponse, StressTestBatchSubmission, StressTestBatchSubmissionResponse,
38};
39pub use nym_api_requests::{
40 ecash::{
41 models::SpentCredentialsResponse, BlindSignRequestBody, BlindedSignatureResponse,
42 PartialCoinIndicesSignatureResponse, PartialExpirationDateSignatureResponse,
43 VerifyEcashCredentialBody,
44 },
45 models::{
46 GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse,
47 MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse,
48 MixnodeUptimeHistoryResponse, StakeSaturationResponse, UptimeResponse,
49 },
50 nym_nodes::{
51 CachedNodesResponse, NodesByAddressesRequestBody, NodesByAddressesResponse,
52 PaginatedCachedNodesResponseV1, PaginatedCachedNodesResponseV2, SemiSkimmedNodeV1,
53 SemiSkimmedNodeV3, SemiSkimmedNodesWithMetadata, SkimmedNodeV1,
54 },
55 NymNetworkDetailsResponse,
56};
57pub use nym_coconut_dkg_common::types::EpochId;
58
59pub mod error;
60pub mod routes;
61
62pub fn rfc_3339_date() -> Vec<BorrowedFormatItem<'static>> {
63 time::format_description::parse("[year]-[month]-[day]").unwrap()
64}
65
66#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
67#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
68pub trait NymApiClientExt: ApiClient {
69 fn api_url(&self) -> &url::Url;
71
72 async fn health(&self) -> Result<ApiHealthResponse, NymAPIError> {
73 self.get_json(
74 &[
75 routes::V1_API_VERSION,
76 routes::API_STATUS_ROUTES,
77 routes::HEALTH,
78 ],
79 NO_PARAMS,
80 )
81 .await
82 }
83
84 #[instrument(level = "debug", skip(self))]
85 async fn build_information(&self) -> Result<BinaryBuildInformationOwned, NymAPIError> {
86 self.get_json(
87 &[
88 routes::V1_API_VERSION,
89 routes::API_STATUS_ROUTES,
90 routes::BUILD_INFORMATION,
91 ],
92 NO_PARAMS,
93 )
94 .await
95 }
96
97 #[tracing::instrument(level = "debug", skip_all)]
98 async fn get_node_performance_history(
99 &self,
100 node_id: NodeId,
101 page: Option<u32>,
102 per_page: Option<u32>,
103 ) -> Result<PerformanceHistoryResponse, NymAPIError> {
104 let mut params = Vec::new();
105
106 if let Some(page) = page {
107 params.push(("page", page.to_string()))
108 }
109
110 if let Some(per_page) = per_page {
111 params.push(("per_page", per_page.to_string()))
112 }
113
114 self.get_json(
115 &[
116 routes::V1_API_VERSION,
117 routes::NYM_NODES_ROUTES,
118 routes::NYM_NODES_PERFORMANCE_HISTORY,
119 &*node_id.to_string(),
120 ],
121 ¶ms,
122 )
123 .await
124 }
125
126 #[tracing::instrument(level = "debug", skip_all)]
127 #[deprecated(note = "use .get_nodes_described_v2 instead")]
128 async fn get_nodes_described(
129 &self,
130 page: Option<u32>,
131 per_page: Option<u32>,
132 ) -> Result<PaginatedResponse<NymNodeDescriptionV1>, NymAPIError> {
133 let mut params = Vec::new();
134
135 if let Some(page) = page {
136 params.push(("page", page.to_string()))
137 }
138
139 if let Some(per_page) = per_page {
140 params.push(("per_page", per_page.to_string()))
141 }
142
143 self.get_json(
144 &[
145 routes::V1_API_VERSION,
146 routes::NYM_NODES_ROUTES,
147 routes::NYM_NODES_DESCRIBED,
148 ],
149 ¶ms,
150 )
151 .await
152 }
153
154 #[tracing::instrument(level = "debug", skip_all)]
155 async fn get_nodes_described_v2(
156 &self,
157 page: Option<u32>,
158 per_page: Option<u32>,
159 ) -> Result<PaginatedResponse<NymNodeDescriptionV2>, NymAPIError> {
160 let mut params = Vec::new();
161
162 if let Some(page) = page {
163 params.push(("page", page.to_string()))
164 }
165
166 if let Some(per_page) = per_page {
167 params.push(("per_page", per_page.to_string()))
168 }
169
170 self.get_json(
171 &[
172 routes::V2_API_VERSION,
173 routes::NYM_NODES_ROUTES,
174 routes::NYM_NODES_DESCRIBED,
175 ],
176 ¶ms,
177 )
178 .await
179 }
180
181 async fn get_current_rewarded_set(&self) -> Result<RewardedSetResponse, NymAPIError> {
182 self.get_rewarded_set().await
183 }
184
185 async fn get_all_basic_nodes_with_metadata(
186 &self,
187 ) -> Result<SkimmedNodesWithMetadata, NymAPIError> {
188 let mut page = 0;
190 let res = self
191 .get_basic_nodes_v2(false, Some(page), None, true)
192 .await?;
193 let mut nodes = res.nodes.data;
194 let metadata = res.metadata;
195
196 if res.nodes.pagination.total == nodes.len() {
197 return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
198 }
199
200 page += 1;
201
202 loop {
203 let mut res = self
204 .get_basic_nodes_v2(false, Some(page), None, true)
205 .await?;
206
207 if !metadata.consistency_check(&res.metadata) {
208 return Err(NymAPIError::InternalResponseInconsistency {
210 url: self.api_url().clone(),
211 details: "Inconsistent paged metadata".to_string(),
212 });
213 }
214
215 nodes.append(&mut res.nodes.data);
216 if nodes.len() >= res.nodes.pagination.total {
217 break;
218 } else {
219 page += 1
220 }
221 }
222
223 Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
224 }
225
226 async fn get_all_basic_active_mixing_assigned_nodes_with_metadata(
227 &self,
228 ) -> Result<SkimmedNodesWithMetadata, NymAPIError> {
229 let mut page = 0;
231 let res = self
232 .get_basic_active_mixing_assigned_nodes_v2(false, Some(page), None, false)
233 .await?;
234
235 let metadata = res.metadata;
236 let mut nodes = res.nodes.data;
237
238 if res.nodes.pagination.total == nodes.len() {
239 return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
240 }
241
242 page += 1;
243
244 loop {
245 let res = self
246 .get_basic_active_mixing_assigned_nodes_v2(false, Some(page), None, false)
247 .await?;
248
249 if !metadata.consistency_check(&res.metadata) {
250 return Err(NymAPIError::InternalResponseInconsistency {
251 url: self.api_url().clone(),
252 details: "Inconsistent paged metadata".to_string(),
253 });
254 }
255
256 nodes.append(&mut res.nodes.data.clone());
257
258 if nodes.len() >= res.nodes.pagination.total {
260 break;
261 } else {
262 page += 1;
263 }
264 }
265
266 Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
267 }
268
269 async fn get_all_basic_entry_assigned_nodes_with_metadata(
270 &self,
271 ) -> Result<SkimmedNodesWithMetadata, NymAPIError> {
272 let mut page = 0;
274 let res = self
275 .get_basic_entry_assigned_nodes_v2(false, Some(page), None, false)
276 .await?;
277
278 let metadata = res.metadata;
279 let mut nodes = res.nodes.data;
280
281 if res.nodes.pagination.total == nodes.len() {
282 return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
283 }
284
285 page += 1;
286
287 loop {
288 let res = self
289 .get_basic_entry_assigned_nodes_v2(false, Some(page), None, false)
290 .await?;
291
292 if !metadata.consistency_check(&res.metadata) {
293 return Err(NymAPIError::InternalResponseInconsistency {
294 url: self.api_url().clone(),
295 details: "Inconsistent paged metadata".to_string(),
296 });
297 }
298
299 nodes.append(&mut res.nodes.data.clone());
300
301 if nodes.len() >= res.nodes.pagination.total {
303 break;
304 } else {
305 page += 1;
306 }
307 }
308
309 Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
310 }
311
312 #[deprecated(note = "use .get_all_described_nodes_v2 instead")]
313 #[allow(deprecated)]
314 async fn get_all_described_nodes(&self) -> Result<Vec<NymNodeDescriptionV1>, NymAPIError> {
315 let mut page = 0;
317 let mut descriptions = Vec::new();
318
319 loop {
320 let mut res = self.get_nodes_described(Some(page), None).await?;
321
322 descriptions.append(&mut res.data);
323 if descriptions.len() < res.pagination.total {
324 page += 1
325 } else {
326 break;
327 }
328 }
329
330 Ok(descriptions)
331 }
332
333 async fn get_all_described_nodes_v2(&self) -> Result<Vec<NymNodeDescriptionV2>, NymAPIError> {
334 let mut page = 0;
336 let mut descriptions = Vec::new();
337
338 loop {
339 let mut res = self.get_nodes_described_v2(Some(page), None).await?;
340
341 descriptions.append(&mut res.data);
342 if descriptions.len() < res.pagination.total {
343 page += 1
344 } else {
345 break;
346 }
347 }
348
349 Ok(descriptions)
350 }
351
352 #[tracing::instrument(level = "debug", skip_all)]
353 async fn get_nym_nodes(
354 &self,
355 page: Option<u32>,
356 per_page: Option<u32>,
357 ) -> Result<PaginatedResponse<NymNodeDetails>, NymAPIError> {
358 let mut params = Vec::new();
359
360 if let Some(page) = page {
361 params.push(("page", page.to_string()))
362 }
363
364 if let Some(per_page) = per_page {
365 params.push(("per_page", per_page.to_string()))
366 }
367
368 self.get_json(
369 &[
370 routes::V1_API_VERSION,
371 routes::NYM_NODES_ROUTES,
372 routes::NYM_NODES_BONDED,
373 ],
374 ¶ms,
375 )
376 .await
377 }
378
379 async fn get_all_bonded_nym_nodes(&self) -> Result<Vec<NymNodeDetails>, ValidatorClientError> {
380 let mut page = 0;
382 let mut bonds = Vec::new();
383
384 loop {
385 let mut res = self.get_nym_nodes(Some(page), None).await?;
386
387 bonds.append(&mut res.data);
388 if bonds.len() < res.pagination.total {
389 page += 1
390 } else {
391 break;
392 }
393 }
394
395 Ok(bonds)
396 }
397
398 #[tracing::instrument(level = "debug", skip_all)]
399 async fn get_node_families(
400 &self,
401 page: Option<u32>,
402 per_page: Option<u32>,
403 ) -> Result<PaginatedResponse<NodeFamily>, NymAPIError> {
404 let mut params = Vec::new();
405 if let Some(page) = page {
406 params.push(("page", page.to_string()))
407 }
408 if let Some(per_page) = per_page {
409 params.push(("per_page", per_page.to_string()))
410 }
411 self.get_json(
412 &[routes::V1_API_VERSION, routes::NODE_FAMILIES_ROUTES],
413 ¶ms,
414 )
415 .await
416 }
417
418 async fn get_all_node_families(&self) -> Result<Vec<NodeFamily>, NymAPIError> {
419 let mut page = 0;
421 let mut families = Vec::new();
422
423 loop {
424 let mut res = self.get_node_families(Some(page), None).await?;
425
426 families.append(&mut res.data);
427 if families.len() < res.pagination.total {
428 page += 1
429 } else {
430 break;
431 }
432 }
433
434 Ok(families)
435 }
436
437 #[deprecated]
438 #[tracing::instrument(level = "debug", skip_all)]
439 async fn get_basic_mixnodes(&self) -> Result<CachedNodesResponse<SkimmedNodeV1>, NymAPIError> {
440 self.get_json(
441 &[
442 routes::V1_API_VERSION,
443 "unstable",
444 routes::NYM_NODES_ROUTES,
445 "mixnodes",
446 "skimmed",
447 ],
448 NO_PARAMS,
449 )
450 .await
451 }
452
453 #[deprecated]
454 #[instrument(level = "debug", skip(self))]
455 async fn get_basic_gateways(&self) -> Result<CachedNodesResponse<SkimmedNodeV1>, NymAPIError> {
456 self.get_json(
457 &[
458 routes::V1_API_VERSION,
459 "unstable",
460 routes::NYM_NODES_ROUTES,
461 "gateways",
462 "skimmed",
463 ],
464 NO_PARAMS,
465 )
466 .await
467 }
468
469 #[instrument(level = "debug", skip(self))]
470 async fn get_rewarded_set(&self) -> Result<RewardedSetResponse, NymAPIError> {
471 self.get_json(
472 &[
473 routes::V1_API_VERSION,
474 routes::NYM_NODES_ROUTES,
475 routes::NYM_NODES_REWARDED_SET,
476 ],
477 NO_PARAMS,
478 )
479 .await
480 }
481
482 #[deprecated(note = "use get_basic_entry_assigned_nodes_v2")]
485 #[instrument(level = "debug", skip(self))]
486 async fn get_basic_entry_assigned_nodes(
487 &self,
488 no_legacy: bool,
489 page: Option<u32>,
490 per_page: Option<u32>,
491 use_bincode: bool,
492 ) -> Result<PaginatedCachedNodesResponseV1<SkimmedNodeV1>, NymAPIError> {
493 let mut params = Vec::new();
494
495 if no_legacy {
496 params.push(("no_legacy", "true".to_string()))
497 }
498
499 if let Some(page) = page {
500 params.push(("page", page.to_string()))
501 }
502
503 if let Some(per_page) = per_page {
504 params.push(("per_page", per_page.to_string()))
505 }
506
507 if use_bincode {
508 params.push(("output", "bincode".to_string()))
509 }
510
511 self.get_response(
512 &[
513 routes::V1_API_VERSION,
514 "unstable",
515 routes::NYM_NODES_ROUTES,
516 "skimmed",
517 "entry-gateways",
518 "all",
519 ],
520 ¶ms,
521 )
522 .await
523 }
524
525 #[instrument(level = "debug", skip(self))]
528 async fn get_basic_entry_assigned_nodes_v2(
529 &self,
530 no_legacy: bool,
531 page: Option<u32>,
532 per_page: Option<u32>,
533 use_bincode: bool,
534 ) -> Result<PaginatedCachedNodesResponseV2<SkimmedNodeV1>, NymAPIError> {
535 let mut params = Vec::new();
536
537 if no_legacy {
538 params.push(("no_legacy", "true".to_string()))
539 }
540
541 if let Some(page) = page {
542 params.push(("page", page.to_string()))
543 }
544
545 if let Some(per_page) = per_page {
546 params.push(("per_page", per_page.to_string()))
547 }
548
549 if use_bincode {
550 params.push(("output", "bincode".to_string()))
551 }
552
553 self.get_response(
554 &[
555 routes::V2_API_VERSION,
556 "unstable",
557 routes::NYM_NODES_ROUTES,
558 "skimmed",
559 "entry-gateways",
560 ],
561 ¶ms,
562 )
563 .await
564 }
565
566 #[deprecated(note = "use get_basic_active_mixing_assigned_nodes_v2")]
569 #[instrument(level = "debug", skip(self))]
570 async fn get_basic_active_mixing_assigned_nodes(
571 &self,
572 no_legacy: bool,
573 page: Option<u32>,
574 per_page: Option<u32>,
575 use_bincode: bool,
576 ) -> Result<PaginatedCachedNodesResponseV1<SkimmedNodeV1>, NymAPIError> {
577 let mut params = Vec::new();
578
579 if no_legacy {
580 params.push(("no_legacy", "true".to_string()))
581 }
582
583 if let Some(page) = page {
584 params.push(("page", page.to_string()))
585 }
586
587 if let Some(per_page) = per_page {
588 params.push(("per_page", per_page.to_string()))
589 }
590
591 if use_bincode {
592 params.push(("output", "bincode".to_string()))
593 }
594
595 self.get_response(
596 &[
597 routes::V1_API_VERSION,
598 "unstable",
599 routes::NYM_NODES_ROUTES,
600 "skimmed",
601 "mixnodes",
602 "active",
603 ],
604 ¶ms,
605 )
606 .await
607 }
608
609 #[instrument(level = "debug", skip(self))]
612 async fn get_basic_active_mixing_assigned_nodes_v2(
613 &self,
614 no_legacy: bool,
615 page: Option<u32>,
616 per_page: Option<u32>,
617 use_bincode: bool,
618 ) -> Result<PaginatedCachedNodesResponseV2<SkimmedNodeV1>, NymAPIError> {
619 let mut params = Vec::new();
620
621 if no_legacy {
622 params.push(("no_legacy", "true".to_string()))
623 }
624
625 if let Some(page) = page {
626 params.push(("page", page.to_string()))
627 }
628
629 if let Some(per_page) = per_page {
630 params.push(("per_page", per_page.to_string()))
631 }
632
633 if use_bincode {
634 params.push(("output", "bincode".to_string()))
635 }
636
637 self.get_response(
638 &[
639 routes::V2_API_VERSION,
640 "unstable",
641 routes::NYM_NODES_ROUTES,
642 "skimmed",
643 "mixnodes",
644 "active",
645 ],
646 ¶ms,
647 )
648 .await
649 }
650
651 #[deprecated(note = "use get_basic_mixing_capable_nodes_v2")]
654 #[instrument(level = "debug", skip(self))]
655 async fn get_basic_mixing_capable_nodes(
656 &self,
657 no_legacy: bool,
658 page: Option<u32>,
659 per_page: Option<u32>,
660 use_bincode: bool,
661 ) -> Result<PaginatedCachedNodesResponseV1<SkimmedNodeV1>, NymAPIError> {
662 let mut params = Vec::new();
663
664 if no_legacy {
665 params.push(("no_legacy", "true".to_string()))
666 }
667
668 if let Some(page) = page {
669 params.push(("page", page.to_string()))
670 }
671
672 if let Some(per_page) = per_page {
673 params.push(("per_page", per_page.to_string()))
674 }
675
676 if use_bincode {
677 params.push(("output", "bincode".to_string()))
678 }
679
680 self.get_response(
681 &[
682 routes::V1_API_VERSION,
683 "unstable",
684 routes::NYM_NODES_ROUTES,
685 "skimmed",
686 "mixnodes",
687 "all",
688 ],
689 ¶ms,
690 )
691 .await
692 }
693
694 #[instrument(level = "debug", skip(self))]
697 async fn get_basic_mixing_capable_nodes_v2(
698 &self,
699 no_legacy: bool,
700 page: Option<u32>,
701 per_page: Option<u32>,
702 use_bincode: bool,
703 ) -> Result<PaginatedCachedNodesResponseV2<SkimmedNodeV1>, NymAPIError> {
704 let mut params = Vec::new();
705
706 if no_legacy {
707 params.push(("no_legacy", "true".to_string()))
708 }
709
710 if let Some(page) = page {
711 params.push(("page", page.to_string()))
712 }
713
714 if let Some(per_page) = per_page {
715 params.push(("per_page", per_page.to_string()))
716 }
717
718 if use_bincode {
719 params.push(("output", "bincode".to_string()))
720 }
721
722 self.get_response(
723 &[
724 routes::V2_API_VERSION,
725 "unstable",
726 routes::NYM_NODES_ROUTES,
727 "skimmed",
728 "mixnodes",
729 "all",
730 ],
731 ¶ms,
732 )
733 .await
734 }
735
736 #[deprecated(note = "use get_basic_nodes_v2")]
737 #[instrument(level = "debug", skip(self))]
738 async fn get_basic_nodes(
739 &self,
740 no_legacy: bool,
741 page: Option<u32>,
742 per_page: Option<u32>,
743 use_bincode: bool,
744 ) -> Result<PaginatedCachedNodesResponseV1<SkimmedNodeV1>, NymAPIError> {
745 let mut params = Vec::new();
746
747 if no_legacy {
748 params.push(("no_legacy", "true".to_string()))
749 }
750
751 if let Some(page) = page {
752 params.push(("page", page.to_string()))
753 }
754
755 if let Some(per_page) = per_page {
756 params.push(("per_page", per_page.to_string()))
757 }
758
759 if use_bincode {
760 params.push(("output", "bincode".to_string()))
761 }
762
763 self.get_response(
764 &[
765 routes::V1_API_VERSION,
766 "unstable",
767 routes::NYM_NODES_ROUTES,
768 "skimmed",
769 ],
770 ¶ms,
771 )
772 .await
773 }
774
775 #[instrument(level = "debug", skip(self))]
776 async fn get_basic_nodes_v2(
777 &self,
778 no_legacy: bool,
779 page: Option<u32>,
780 per_page: Option<u32>,
781 use_bincode: bool,
782 ) -> Result<PaginatedCachedNodesResponseV2<SkimmedNodeV1>, NymAPIError> {
783 let mut params = Vec::new();
784
785 if no_legacy {
786 params.push(("no_legacy", "true".to_string()))
787 }
788
789 if let Some(page) = page {
790 params.push(("page", page.to_string()))
791 }
792
793 if let Some(per_page) = per_page {
794 params.push(("per_page", per_page.to_string()))
795 }
796
797 if use_bincode {
798 params.push(("output", "bincode".to_string()))
799 }
800
801 self.get_response(
802 &[
803 routes::V2_API_VERSION,
804 "unstable",
805 routes::NYM_NODES_ROUTES,
806 "skimmed",
807 ],
808 ¶ms,
809 )
810 .await
811 }
812
813 #[instrument(level = "debug", skip(self))]
814 async fn get_expanded_nodes(
815 &self,
816 no_legacy: bool,
817 page: Option<u32>,
818 per_page: Option<u32>,
819 ) -> Result<PaginatedCachedNodesResponseV2<SemiSkimmedNodeV1>, NymAPIError> {
820 let mut params = Vec::new();
821
822 if no_legacy {
823 params.push(("no_legacy", "true".to_string()))
824 }
825
826 if let Some(page) = page {
827 params.push(("page", page.to_string()))
828 }
829
830 if let Some(per_page) = per_page {
831 params.push(("per_page", per_page.to_string()))
832 }
833
834 self.get_json(
835 &[
836 routes::V2_API_VERSION,
837 "unstable",
838 routes::NYM_NODES_ROUTES,
839 "semi-skimmed",
840 ],
841 ¶ms,
842 )
843 .await
844 }
845
846 #[instrument(level = "debug", skip(self))]
847 async fn get_expanded_nodes_v3(
848 &self,
849 use_bincode: bool,
850 ) -> Result<PaginatedCachedNodesResponseV2<SemiSkimmedNodeV3>, NymAPIError> {
851 let mut params = Vec::new();
852
853 if use_bincode {
854 params.push(("output", "bincode".to_string()))
855 }
856
857 self.get_response("/v3/unstable/nym-nodes/semi-skimmed", ¶ms)
858 .await
859 }
860
861 #[deprecated]
862 #[instrument(level = "debug", skip(self))]
863 async fn get_mixnode_report(
864 &self,
865 mix_id: NodeId,
866 ) -> Result<MixnodeStatusReportResponse, NymAPIError> {
867 self.get_json(
868 &[
869 routes::V1_API_VERSION,
870 routes::STATUS,
871 routes::MIXNODE,
872 &mix_id.to_string(),
873 routes::REPORT,
874 ],
875 NO_PARAMS,
876 )
877 .await
878 }
879
880 #[deprecated]
881 #[instrument(level = "debug", skip(self))]
882 async fn get_gateway_report(
883 &self,
884 identity: IdentityKeyRef<'_>,
885 ) -> Result<GatewayStatusReportResponse, NymAPIError> {
886 self.get_json(
887 &[
888 routes::V1_API_VERSION,
889 routes::STATUS,
890 routes::GATEWAY,
891 identity,
892 routes::REPORT,
893 ],
894 NO_PARAMS,
895 )
896 .await
897 }
898
899 #[deprecated]
900 #[instrument(level = "debug", skip(self))]
901 async fn get_mixnode_history(
902 &self,
903 mix_id: NodeId,
904 ) -> Result<MixnodeUptimeHistoryResponse, NymAPIError> {
905 self.get_json(
906 &[
907 routes::V1_API_VERSION,
908 routes::STATUS,
909 routes::MIXNODE,
910 &mix_id.to_string(),
911 routes::HISTORY,
912 ],
913 NO_PARAMS,
914 )
915 .await
916 }
917
918 #[deprecated]
919 #[instrument(level = "debug", skip(self))]
920 async fn get_gateway_history(
921 &self,
922 identity: IdentityKeyRef<'_>,
923 ) -> Result<GatewayUptimeHistoryResponse, NymAPIError> {
924 self.get_json(
925 &[
926 routes::V1_API_VERSION,
927 routes::STATUS,
928 routes::GATEWAY,
929 identity,
930 routes::HISTORY,
931 ],
932 NO_PARAMS,
933 )
934 .await
935 }
936
937 #[deprecated]
938 #[instrument(level = "debug", skip(self))]
939 async fn get_gateway_core_status_count(
940 &self,
941 identity: IdentityKeyRef<'_>,
942 since: Option<i64>,
943 ) -> Result<GatewayCoreStatusResponse, NymAPIError> {
944 if let Some(since) = since {
945 self.get_json(
946 &[
947 routes::V1_API_VERSION,
948 routes::STATUS_ROUTES,
949 routes::GATEWAY,
950 identity,
951 CORE_STATUS_COUNT,
952 ],
953 &[(SINCE_ARG, since.to_string())],
954 )
955 .await
956 } else {
957 self.get_json(
958 &[
959 routes::V1_API_VERSION,
960 routes::STATUS_ROUTES,
961 routes::GATEWAY,
962 identity,
963 ],
964 NO_PARAMS,
965 )
966 .await
967 }
968 }
969
970 #[deprecated]
971 #[instrument(level = "debug", skip(self))]
972 async fn get_mixnode_core_status_count(
973 &self,
974 mix_id: NodeId,
975 since: Option<i64>,
976 ) -> Result<MixnodeCoreStatusResponse, NymAPIError> {
977 if let Some(since) = since {
978 self.get_json(
979 &[
980 routes::V1_API_VERSION,
981 routes::STATUS_ROUTES,
982 routes::MIXNODE,
983 &mix_id.to_string(),
984 CORE_STATUS_COUNT,
985 ],
986 &[(SINCE_ARG, since.to_string())],
987 )
988 .await
989 } else {
990 self.get_json(
991 &[
992 routes::V1_API_VERSION,
993 routes::STATUS_ROUTES,
994 routes::MIXNODE,
995 &mix_id.to_string(),
996 CORE_STATUS_COUNT,
997 ],
998 NO_PARAMS,
999 )
1000 .await
1001 }
1002 }
1003
1004 #[instrument(level = "debug", skip(self))]
1005 async fn get_current_node_performance(
1006 &self,
1007 node_id: NodeId,
1008 ) -> Result<NodePerformanceResponse, NymAPIError> {
1009 self.get_json(
1010 &[
1011 routes::V1_API_VERSION,
1012 routes::NYM_NODES_ROUTES,
1013 routes::NYM_NODES_PERFORMANCE,
1014 &node_id.to_string(),
1015 ],
1016 NO_PARAMS,
1017 )
1018 .await
1019 }
1020
1021 async fn get_node_annotation(
1022 &self,
1023 node_id: NodeId,
1024 ) -> Result<AnnotationResponseV1, NymAPIError> {
1025 self.get_json(
1026 &[
1027 routes::V1_API_VERSION,
1028 routes::NYM_NODES_ROUTES,
1029 routes::NYM_NODES_ANNOTATION,
1030 &node_id.to_string(),
1031 ],
1032 NO_PARAMS,
1033 )
1034 .await
1035 }
1036
1037 async fn get_node_annotation_v2(
1038 &self,
1039 node_id: NodeId,
1040 ) -> Result<AnnotationResponseV2, NymAPIError> {
1041 self.get_json(
1042 &[
1043 routes::V2_API_VERSION,
1044 routes::NYM_NODES_ROUTES,
1045 routes::NYM_NODES_ANNOTATION,
1046 &node_id.to_string(),
1047 ],
1048 NO_PARAMS,
1049 )
1050 .await
1051 }
1052
1053 #[deprecated]
1054 async fn get_mixnode_avg_uptime(&self, mix_id: NodeId) -> Result<UptimeResponse, NymAPIError> {
1055 self.get_json(
1056 &[
1057 routes::V1_API_VERSION,
1058 routes::STATUS_ROUTES,
1059 routes::MIXNODE,
1060 &mix_id.to_string(),
1061 routes::AVG_UPTIME,
1062 ],
1063 NO_PARAMS,
1064 )
1065 .await
1066 }
1067
1068 #[instrument(level = "debug", skip(self, request_body))]
1069 async fn blind_sign(
1070 &self,
1071 request_body: &BlindSignRequestBody,
1072 ) -> Result<BlindedSignatureResponse, NymAPIError> {
1073 self.post_json(
1074 &[
1075 routes::V1_API_VERSION,
1076 routes::ECASH_ROUTES,
1077 routes::ECASH_BLIND_SIGN,
1078 ],
1079 NO_PARAMS,
1080 request_body,
1081 )
1082 .await
1083 }
1084
1085 #[instrument(level = "debug", skip(self, request_body))]
1086 async fn verify_ecash_ticket(
1087 &self,
1088 request_body: &VerifyEcashTicketBody,
1089 ) -> Result<EcashTicketVerificationResponse, NymAPIError> {
1090 self.post_json(
1091 &[
1092 routes::V1_API_VERSION,
1093 routes::ECASH_ROUTES,
1094 routes::VERIFY_ECASH_TICKET,
1095 ],
1096 NO_PARAMS,
1097 request_body,
1098 )
1099 .await
1100 }
1101
1102 #[instrument(level = "debug", skip(self, request_body))]
1103 async fn batch_redeem_ecash_tickets(
1104 &self,
1105 request_body: &BatchRedeemTicketsBody,
1106 ) -> Result<EcashBatchTicketRedemptionResponse, NymAPIError> {
1107 self.post_json(
1108 &[
1109 routes::V1_API_VERSION,
1110 routes::ECASH_ROUTES,
1111 routes::BATCH_REDEEM_ECASH_TICKETS,
1112 ],
1113 NO_PARAMS,
1114 request_body,
1115 )
1116 .await
1117 }
1118
1119 #[instrument(level = "debug", skip(self))]
1120 async fn partial_expiration_date_signatures(
1121 &self,
1122 expiration_date: Option<Date>,
1123 epoch_id: Option<EpochId>,
1124 ) -> Result<PartialExpirationDateSignatureResponse, NymAPIError> {
1125 let mut params = match expiration_date {
1126 None => Vec::new(),
1127 Some(exp) => vec![(
1128 ecash::EXPIRATION_DATE_PARAM,
1129 exp.format(&rfc_3339_date()).unwrap(),
1130 )],
1131 };
1132
1133 if let Some(epoch_id) = epoch_id {
1134 params.push((ecash::EPOCH_ID_PARAM, epoch_id.to_string()));
1135 }
1136
1137 self.get_json(
1138 &[
1139 routes::V1_API_VERSION,
1140 routes::ECASH_ROUTES,
1141 routes::PARTIAL_EXPIRATION_DATE_SIGNATURES,
1142 ],
1143 ¶ms,
1144 )
1145 .await
1146 }
1147
1148 #[instrument(level = "debug", skip(self))]
1149 async fn partial_coin_indices_signatures(
1150 &self,
1151 epoch_id: Option<EpochId>,
1152 ) -> Result<PartialCoinIndicesSignatureResponse, NymAPIError> {
1153 let params = match epoch_id {
1154 None => Vec::new(),
1155 Some(epoch_id) => vec![(ecash::EPOCH_ID_PARAM, epoch_id.to_string())],
1156 };
1157
1158 self.get_json(
1159 &[
1160 routes::V1_API_VERSION,
1161 routes::ECASH_ROUTES,
1162 routes::PARTIAL_COIN_INDICES_SIGNATURES,
1163 ],
1164 ¶ms,
1165 )
1166 .await
1167 }
1168
1169 #[instrument(level = "debug", skip(self))]
1170 async fn global_expiration_date_signatures(
1171 &self,
1172 expiration_date: Option<Date>,
1173 epoch_id: Option<EpochId>,
1174 ) -> Result<AggregatedExpirationDateSignatureResponse, NymAPIError> {
1175 let mut params = match expiration_date {
1176 None => Vec::new(),
1177 Some(exp) => vec![(
1178 ecash::EXPIRATION_DATE_PARAM,
1179 exp.format(&rfc_3339_date()).unwrap(),
1180 )],
1181 };
1182
1183 if let Some(epoch_id) = epoch_id {
1184 params.push((ecash::EPOCH_ID_PARAM, epoch_id.to_string()));
1185 }
1186
1187 self.get_json(
1188 &[
1189 routes::V1_API_VERSION,
1190 routes::ECASH_ROUTES,
1191 routes::GLOBAL_EXPIRATION_DATE_SIGNATURES,
1192 ],
1193 ¶ms,
1194 )
1195 .await
1196 }
1197
1198 #[instrument(level = "debug", skip(self))]
1199 async fn global_coin_indices_signatures(
1200 &self,
1201 epoch_id: Option<EpochId>,
1202 ) -> Result<AggregatedCoinIndicesSignatureResponse, NymAPIError> {
1203 let params = match epoch_id {
1204 None => Vec::new(),
1205 Some(epoch_id) => vec![(ecash::EPOCH_ID_PARAM, epoch_id.to_string())],
1206 };
1207
1208 self.get_json(
1209 &[
1210 routes::V1_API_VERSION,
1211 routes::ECASH_ROUTES,
1212 routes::GLOBAL_COIN_INDICES_SIGNATURES,
1213 ],
1214 ¶ms,
1215 )
1216 .await
1217 }
1218
1219 #[instrument(level = "debug", skip(self))]
1220 async fn master_verification_key(
1221 &self,
1222 epoch_id: Option<EpochId>,
1223 ) -> Result<VerificationKeyResponse, NymAPIError> {
1224 let params = match epoch_id {
1225 None => Vec::new(),
1226 Some(epoch_id) => vec![(ecash::EPOCH_ID_PARAM, epoch_id.to_string())],
1227 };
1228 self.get_json(
1229 &[
1230 routes::V1_API_VERSION,
1231 routes::ECASH_ROUTES,
1232 ecash::MASTER_VERIFICATION_KEY,
1233 ],
1234 ¶ms,
1235 )
1236 .await
1237 }
1238
1239 #[instrument(level = "debug", skip(self))]
1240 async fn force_refresh_describe_cache(
1241 &self,
1242 request: &NodeRefreshBody,
1243 ) -> Result<(), NymAPIError> {
1244 self.post_json(
1245 &[
1246 routes::V1_API_VERSION,
1247 routes::NYM_NODES_ROUTES,
1248 routes::NYM_NODES_REFRESH_DESCRIBED,
1249 ],
1250 NO_PARAMS,
1251 request,
1252 )
1253 .await
1254 }
1255
1256 #[instrument(level = "debug", skip(self))]
1257 async fn issued_ticketbooks_for(
1258 &self,
1259 expiration_date: Date,
1260 ) -> Result<IssuedTicketbooksForResponse, NymAPIError> {
1261 self.get_json(
1262 &[
1263 routes::V1_API_VERSION,
1264 routes::ECASH_ROUTES,
1265 routes::ECASH_ISSUED_TICKETBOOKS_FOR,
1266 &expiration_date.to_string(),
1267 ],
1268 NO_PARAMS,
1269 )
1270 .await
1271 }
1272
1273 #[instrument(level = "debug", skip(self))]
1274 async fn issued_ticketbooks_for_count(
1275 &self,
1276 expiration_date: Date,
1277 ) -> Result<IssuedTicketbooksForCountResponse, NymAPIError> {
1278 self.get_json(
1279 &[
1280 routes::V1_API_VERSION,
1281 routes::ECASH_ROUTES,
1282 routes::ECASH_ISSUED_TICKETBOOKS_FOR_COUNT,
1283 &expiration_date.to_string(),
1284 ],
1285 NO_PARAMS,
1286 )
1287 .await
1288 }
1289
1290 #[instrument(level = "debug", skip(self))]
1291 async fn issued_ticketbooks_challenge_commitment(
1292 &self,
1293 request: &IssuedTicketbooksChallengeCommitmentRequest,
1294 ) -> Result<IssuedTicketbooksChallengeCommitmentResponse, NymAPIError> {
1295 self.post_json(
1296 &[
1297 routes::V1_API_VERSION,
1298 routes::ECASH_ROUTES,
1299 routes::ECASH_ISSUED_TICKETBOOKS_CHALLENGE_COMMITMENT,
1300 ],
1301 NO_PARAMS,
1302 request,
1303 )
1304 .await
1305 }
1306
1307 #[instrument(level = "debug", skip(self))]
1308 async fn issued_ticketbooks_data(
1309 &self,
1310 request: &IssuedTicketbooksDataRequest,
1311 ) -> Result<IssuedTicketbooksDataResponse, NymAPIError> {
1312 self.post_json(
1313 &[
1314 routes::V1_API_VERSION,
1315 routes::ECASH_ROUTES,
1316 routes::ECASH_ISSUED_TICKETBOOKS_DATA,
1317 ],
1318 NO_PARAMS,
1319 request,
1320 )
1321 .await
1322 }
1323
1324 async fn nodes_by_addresses(
1325 &self,
1326 addresses: Vec<IpAddr>,
1327 ) -> Result<NodesByAddressesResponse, NymAPIError> {
1328 self.post_json(
1329 &[
1330 routes::V1_API_VERSION,
1331 "unstable",
1332 routes::NYM_NODES_ROUTES,
1333 routes::nym_nodes::BY_ADDRESSES,
1334 ],
1335 NO_PARAMS,
1336 &NodesByAddressesRequestBody { addresses },
1337 )
1338 .await
1339 }
1340
1341 #[instrument(level = "debug", skip(self))]
1342 async fn get_network_details(&self) -> Result<NymNetworkDetailsResponse, NymAPIError> {
1343 self.get_json(
1344 &[routes::V1_API_VERSION, routes::NETWORK, routes::DETAILS],
1345 NO_PARAMS,
1346 )
1347 .await
1348 }
1349
1350 #[instrument(level = "debug", skip(self))]
1351 async fn get_chain_status(&self) -> Result<ChainStatusResponse, NymAPIError> {
1352 self.get_json(
1353 &[
1354 routes::V1_API_VERSION,
1355 routes::NETWORK,
1356 routes::CHAIN_STATUS,
1357 ],
1358 NO_PARAMS,
1359 )
1360 .await
1361 }
1362
1363 async fn get_chain_blocks_status(&self) -> Result<ChainBlocksStatusResponse, NymAPIError> {
1364 self.get_json("/v1/network/chain-blocks-status", NO_PARAMS)
1365 .await
1366 }
1367
1368 #[instrument(level = "debug", skip(self))]
1369 async fn get_signer_status(&self) -> Result<EcashSignerStatusResponse, NymAPIError> {
1370 self.get_json("/v1/ecash/signer-status", NO_PARAMS).await
1371 }
1372
1373 #[instrument(level = "debug", skip(self))]
1374 async fn get_signer_information(&self) -> Result<SignerInformationResponse, NymAPIError> {
1375 self.get_json("/v1/api-status/signer-information", NO_PARAMS)
1376 .await
1377 }
1378
1379 #[instrument(level = "debug", skip(self))]
1380 async fn get_key_rotation_info(&self) -> Result<KeyRotationInfoResponse, NymAPIError> {
1381 self.get_json(
1382 &[
1383 routes::V1_API_VERSION,
1384 routes::EPOCH,
1385 routes::KEY_ROTATION_INFO,
1386 ],
1387 NO_PARAMS,
1388 )
1389 .await
1390 }
1391
1392 fn change_base_urls(&mut self, urls: Vec<url::Url>);
1394
1395 async fn get_all_expanded_nodes(&self) -> Result<SemiSkimmedNodesWithMetadata, NymAPIError> {
1397 let mut page = 0;
1399
1400 let res = self.get_expanded_nodes(false, Some(page), None).await?;
1401 let mut nodes = res.nodes.data;
1402 let metadata = res.metadata;
1403
1404 if res.nodes.pagination.total == nodes.len() {
1405 return Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata));
1406 }
1407
1408 page += 1;
1409
1410 loop {
1411 let mut res = self.get_expanded_nodes(false, Some(page), None).await?;
1412
1413 nodes.append(&mut res.nodes.data);
1414 if nodes.len() < res.nodes.pagination.total {
1415 page += 1
1416 } else {
1417 break;
1418 }
1419 }
1420
1421 Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata))
1422 }
1423
1424 #[instrument(level = "debug", skip(self))]
1429 async fn get_known_network_monitor(
1430 &self,
1431 identity_key: IdentityKeyRef<'_>,
1432 ) -> Result<KnownNetworkMonitorResponse, NymAPIError> {
1433 self.get_json(
1434 &[
1435 routes::V3_API_VERSION,
1436 routes::NYM_NODES_ROUTES,
1437 routes::STRESS_TESTING,
1438 routes::STRESS_TESTING_KNOWN_MONITORS,
1439 identity_key,
1440 ],
1441 NO_PARAMS,
1442 )
1443 .await
1444 }
1445
1446 #[instrument(level = "debug", skip(self, request))]
1454 async fn submit_stress_testing_results(
1455 &self,
1456 request: &StressTestBatchSubmission,
1457 ) -> Result<StressTestBatchSubmissionResponse, NymAPIError> {
1458 self.post_json(
1459 &[
1460 routes::V3_API_VERSION,
1461 routes::NYM_NODES_ROUTES,
1462 routes::STRESS_TESTING,
1463 routes::STRESS_TESTING_BATCH_SUBMIT,
1464 ],
1465 NO_PARAMS,
1466 request,
1467 )
1468 .await
1469 }
1470}
1471
1472#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
1474#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
1475impl NymApiClientExt for nym_http_api_client::Client {
1476 fn api_url(&self) -> &url::Url {
1477 self.current_url().as_ref()
1478 }
1479
1480 fn change_base_urls(&mut self, urls: Vec<url::Url>) {
1481 self.change_base_urls(urls.into_iter().map(|u| u.into()).collect());
1482 }
1483}