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