Skip to main content

nym_validator_client/
client.rs

1// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4pub use crate::nym_api::NymApiClientExt;
5use crate::nyxd::{self, NyxdClient};
6use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
7use crate::signing::signer::{NoSigner, OfflineSigner};
8use crate::{
9    DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient, ValidatorClientError,
10};
11use nym_api_requests::ecash::models::{
12    AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
13    BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashTicketVerificationResponse,
14    IssuedTicketbooksChallengeCommitmentRequest, IssuedTicketbooksChallengeCommitmentResponse,
15    IssuedTicketbooksDataRequest, IssuedTicketbooksDataResponse, IssuedTicketbooksForCountResponse,
16    IssuedTicketbooksForResponse, VerifyEcashTicketBody,
17};
18use nym_api_requests::ecash::{
19    BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
20    PartialExpirationDateSignatureResponse, VerificationKeyResponse,
21};
22use nym_api_requests::models::described::v1::NymNodeDescriptionV1;
23use nym_api_requests::models::described::v2::NymNodeDescriptionV2;
24use nym_api_requests::models::{
25    ApiHealthResponse, GatewayCoreStatusResponse, HistoricalPerformanceResponse,
26    MixnodeCoreStatusResponse,
27};
28use nym_api_requests::nym_nodes::{
29    NodesByAddressesResponse, SemiSkimmedNodesWithMetadata, SkimmedNodeV1, SkimmedNodesWithMetadata,
30};
31use nym_coconut_dkg_common::types::EpochId;
32use nym_http_api_client::UserAgent;
33use nym_mixnet_contract_common::EpochRewardedSet;
34pub use nym_mixnet_contract_common::{
35    mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, NodeId, NymNodeDetails,
36};
37use nym_network_defaults::NymNetworkDetails;
38use std::net::IpAddr;
39use time::Date;
40use url::Url;
41// re-export the type to not break existing imports
42pub use crate::coconut::EcashApiClient;
43
44#[cfg(feature = "http-client")]
45use crate::rpc::http_client;
46#[cfg(feature = "http-client")]
47use crate::{DirectSigningHttpRpcValidatorClient, HttpRpcClient, QueryHttpRpcValidatorClient};
48
49// a simple helper macro to define to repeatedly call a paged query until a full response is constructed
50macro_rules! collect_paged_skimmed_v2 {
51    ( $self:ident, $f: ident ) => {{
52        // unroll first loop iteration in order to obtain the metadata
53        let mut page = 0;
54        let res = $self
55            .nym_api
56            .$f(false, Some(page), None, $self.use_bincode)
57            .await?;
58        let mut nodes = res.nodes.data;
59        let metadata = res.metadata;
60
61        if res.nodes.pagination.total == nodes.len() {
62            return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
63        }
64
65        page += 1;
66
67        loop {
68            let mut res = $self
69                .nym_api
70                .$f(false, Some(page), None, $self.use_bincode)
71                .await?;
72
73            if !metadata.consistency_check(&res.metadata) {
74                return Err(ValidatorClientError::InconsistentPagedMetadata);
75            }
76
77            nodes.append(&mut res.nodes.data);
78            if nodes.len() < res.nodes.pagination.total {
79                page += 1
80            } else {
81                break;
82            }
83        }
84
85        Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
86    }};
87}
88
89#[must_use]
90#[derive(Debug, Clone)]
91pub struct Config {
92    api_url: Url,
93    nyxd_url: Url,
94
95    // TODO: until refactored, this is a dead field under some features
96    nyxd_config: nyxd::Config,
97}
98
99impl TryFrom<NymNetworkDetails> for Config {
100    type Error = ValidatorClientError;
101
102    fn try_from(value: NymNetworkDetails) -> Result<Self, Self::Error> {
103        Config::try_from_nym_network_details(&value)
104    }
105}
106
107impl Config {
108    pub fn new(nyxd_url: Url, api_url: Url, nyxd_config: nyxd::Config) -> Self {
109        Config {
110            api_url,
111            nyxd_url,
112            nyxd_config,
113        }
114    }
115
116    pub fn try_from_nym_network_details(
117        details: &NymNetworkDetails,
118    ) -> Result<Self, ValidatorClientError> {
119        let mut api_url = details
120            .endpoints
121            .iter()
122            .filter_map(|d| d.api_url.as_ref())
123            .map(|url| Url::parse(url))
124            .collect::<Result<Vec<_>, _>>()?;
125
126        if let Some(nym_api_urls) = details.nym_api_urls.as_ref() {
127            api_url.extend(
128                nym_api_urls
129                    .iter()
130                    .map(|url| url.url.parse())
131                    .collect::<Result<Vec<_>, _>>()?,
132            );
133        }
134
135        if api_url.is_empty() {
136            return Err(ValidatorClientError::NoAPIUrlAvailable);
137        }
138
139        Ok(Config {
140            api_url: api_url.pop().unwrap(),
141            nyxd_url: details.endpoints[0]
142                .nyxd_url
143                .parse()
144                .map_err(ValidatorClientError::MalformedUrlProvided)?,
145            nyxd_config: nyxd::Config::try_from_nym_network_details(details)?,
146        })
147    }
148
149    // TODO: this method shouldn't really exist as all information should be included immediately
150    // via `from_nym_network_details`, but it's here for, you guessed it, legacy compatibility
151    pub fn with_urls(mut self, nyxd_url: Url, api_url: Url) -> Self {
152        self.nyxd_url = nyxd_url;
153        self.api_url = api_url;
154        self
155    }
156
157    pub fn with_nyxd_url(mut self, nyxd_url: Url) -> Self {
158        self.nyxd_url = nyxd_url;
159        self
160    }
161
162    pub fn with_simulated_gas_multiplier(mut self, gas_multiplier: f32) -> Self {
163        self.nyxd_config.simulated_gas_multiplier = gas_multiplier;
164        self
165    }
166}
167
168pub struct Client<C, S = NoSigner> {
169    // ideally they would have been read-only, but unfortunately rust doesn't have such features
170    // #[deprecated(note = "please use `nym_api_client` instead")]
171    pub nym_api: nym_http_api_client::Client,
172    // pub nym_api_client: NymApiClient,
173    pub nyxd: NyxdClient<C, S>,
174}
175
176#[cfg(feature = "http-client")]
177impl Client<HttpRpcClient, DirectSecp256k1HdWallet> {
178    pub fn new_signing(
179        config: Config,
180        mnemonic: bip39::Mnemonic,
181    ) -> Result<DirectSigningHttpRpcValidatorClient, ValidatorClientError> {
182        let rpc_client = http_client(config.nyxd_url.as_str())?;
183        let prefix = &config.nyxd_config.chain_details.bech32_account_prefix;
184        let wallet = DirectSecp256k1HdWallet::checked_from_mnemonic(prefix, mnemonic)?;
185
186        Ok(Self::new_signing_with_rpc_client(
187            config, rpc_client, wallet,
188        ))
189    }
190
191    pub fn change_nyxd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> {
192        self.nyxd.change_endpoint(new_endpoint.as_ref())?;
193        Ok(())
194    }
195}
196
197#[allow(deprecated)]
198impl Client<crate::ReqwestRpcClient, DirectSecp256k1HdWallet> {
199    pub fn new_reqwest_signing(
200        config: Config,
201        mnemonic: bip39::Mnemonic,
202    ) -> DirectSigningReqwestRpcValidatorClient {
203        let rpc_client = crate::ReqwestRpcClient::new(config.nyxd_url.clone());
204        let prefix = &config.nyxd_config.chain_details.bech32_account_prefix;
205        let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
206
207        Self::new_signing_with_rpc_client(config, rpc_client, wallet)
208    }
209}
210
211#[cfg(feature = "http-client")]
212impl Client<HttpRpcClient> {
213    pub fn new_query(config: Config) -> Result<QueryHttpRpcValidatorClient, ValidatorClientError> {
214        let rpc_client = http_client(config.nyxd_url.as_str())?;
215        Ok(Self::new_with_rpc_client(config, rpc_client))
216    }
217
218    pub fn change_nyxd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> {
219        self.nyxd = NyxdClient::connect(self.nyxd.current_config().clone(), new_endpoint.as_ref())?;
220        Ok(())
221    }
222}
223
224#[allow(deprecated)]
225impl Client<crate::ReqwestRpcClient> {
226    pub fn new_reqwest_query(config: Config) -> QueryReqwestRpcValidatorClient {
227        let rpc_client = crate::ReqwestRpcClient::new(config.nyxd_url.clone());
228        Self::new_with_rpc_client(config, rpc_client)
229    }
230}
231
232impl<C> Client<C> {
233    pub fn new_with_rpc_client(config: Config, rpc_client: C) -> Self {
234        let nym_api_client = nym_http_api_client::Client::new(config.api_url.clone(), None);
235
236        Client {
237            nym_api: nym_api_client,
238            nyxd: NyxdClient::new(config.nyxd_config, rpc_client),
239        }
240    }
241}
242
243impl<C, S> Client<C, S> {
244    pub fn new_signing_with_rpc_client(config: Config, rpc_client: C, signer: S) -> Self
245    where
246        S: OfflineSigner,
247    {
248        let nym_api_client = nym_http_api_client::Client::new(config.api_url.clone(), None);
249
250        Client {
251            nym_api: nym_api_client,
252            nyxd: NyxdClient::new_signing(config.nyxd_config, rpc_client, signer),
253        }
254    }
255}
256
257// validator-api wrappers
258// we have to allow the use of deprecated method here as they're calling the deprecated trait methods
259#[allow(deprecated)]
260impl<C, S> Client<C, S> {
261    pub fn api_url(&self) -> &Url {
262        self.nym_api.current_url().as_ref()
263    }
264
265    pub fn change_nym_api(&mut self, new_endpoint: Url) {
266        self.nym_api.change_base_urls(vec![new_endpoint.into()])
267    }
268
269    pub async fn get_full_node_performance_history(
270        &self,
271        node_id: NodeId,
272    ) -> Result<Vec<HistoricalPerformanceResponse>, ValidatorClientError> {
273        // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
274        let mut page = 0;
275        let mut history = Vec::new();
276
277        loop {
278            let mut res = self
279                .nym_api
280                .get_node_performance_history(node_id, Some(page), None)
281                .await?;
282
283            history.append(&mut res.history.data);
284            if history.len() < res.history.pagination.total {
285                page += 1
286            } else {
287                break;
288            }
289        }
290
291        Ok(history)
292    }
293
294    #[deprecated(note = "use get_all_cached_described_nodes_v2 instead")]
295    pub async fn get_all_cached_described_nodes(
296        &self,
297    ) -> Result<Vec<NymNodeDescriptionV1>, ValidatorClientError> {
298        Ok(self.nym_api.get_all_described_nodes().await?)
299    }
300
301    pub async fn get_all_cached_described_nodes_v2(
302        &self,
303    ) -> Result<Vec<NymNodeDescriptionV2>, ValidatorClientError> {
304        Ok(self.nym_api.get_all_described_nodes_v2().await?)
305    }
306
307    pub async fn get_all_cached_bonded_nym_nodes(
308        &self,
309    ) -> Result<Vec<NymNodeDetails>, ValidatorClientError> {
310        self.nym_api.get_all_bonded_nym_nodes().await
311    }
312
313    pub async fn blind_sign(
314        &self,
315        request_body: &BlindSignRequestBody,
316    ) -> Result<BlindedSignatureResponse, ValidatorClientError> {
317        Ok(self.nym_api.blind_sign(request_body).await?)
318    }
319}
320
321/// DEPRECATED: Use nym_http_api_client::Client with from_network() or with_bincode() instead
322#[deprecated(
323    since = "1.2.0",
324    note = "Use nym_http_api_client::Client::from_network() or ClientBuilder::with_bincode() instead"
325)]
326#[derive(Clone)]
327pub struct NymApiClient {
328    pub use_bincode: bool,
329    pub nym_api: nym_http_api_client::Client,
330    // TODO: perhaps if we really need it at some (currently I don't see any reasons for it)
331    // we could re-implement the communication with the REST API on port 1317
332}
333
334// we have to allow the use of deprecated method here as they're calling the deprecated trait methods
335#[allow(deprecated)]
336impl NymApiClient {
337    #[cfg(not(target_arch = "wasm32"))]
338    pub fn new_with_timeout(api_url: Url, timeout: std::time::Duration) -> Self {
339        let nym_api = nym_http_api_client::Client::new(api_url, Some(timeout));
340
341        NymApiClient {
342            use_bincode: true,
343            nym_api,
344        }
345    }
346
347    #[must_use]
348    pub fn with_bincode(mut self, use_bincode: bool) -> Self {
349        self.use_bincode = use_bincode;
350        self
351    }
352
353    pub fn new_with_user_agent(api_url: Url, user_agent: impl Into<UserAgent>) -> Self {
354        let nym_api = nym_http_api_client::Client::builder(api_url)
355            .expect("invalid api url")
356            .with_user_agent(user_agent.into())
357            .build()
358            .expect("failed to build nym api client");
359
360        NymApiClient {
361            use_bincode: false,
362            nym_api,
363        }
364    }
365
366    pub fn api_url(&self) -> &Url {
367        self.nym_api.current_url().as_ref()
368    }
369
370    pub fn change_nym_api(&mut self, new_endpoint: Url) {
371        self.nym_api.change_base_urls(vec![new_endpoint.into()]);
372    }
373
374    #[deprecated(note = "use get_all_basic_active_mixing_assigned_nodes instead")]
375    pub async fn get_basic_mixnodes(&self) -> Result<Vec<SkimmedNodeV1>, ValidatorClientError> {
376        Ok(self.nym_api.get_basic_mixnodes().await?.nodes)
377    }
378
379    #[deprecated(note = "use get_all_basic_entry_assigned_nodes instead")]
380    pub async fn get_basic_gateways(&self) -> Result<Vec<SkimmedNodeV1>, ValidatorClientError> {
381        Ok(self.nym_api.get_basic_gateways().await?.nodes)
382    }
383
384    pub async fn get_current_rewarded_set(&self) -> Result<EpochRewardedSet, ValidatorClientError> {
385        Ok(self.nym_api.get_rewarded_set().await?.into())
386    }
387
388    /// retrieve basic information for nodes are capable of operating as an entry gateway
389    /// this includes legacy gateways and nym-nodes
390    #[deprecated(note = "use get_all_basic_entry_assigned_nodes_with_metadata instead")]
391    pub async fn get_all_basic_entry_assigned_nodes(
392        &self,
393    ) -> Result<Vec<SkimmedNodeV1>, ValidatorClientError> {
394        self.get_all_basic_entry_assigned_nodes_with_metadata()
395            .await
396            .map(|res| res.nodes)
397    }
398
399    pub async fn get_all_basic_entry_assigned_nodes_with_metadata(
400        &self,
401    ) -> Result<SkimmedNodesWithMetadata, ValidatorClientError> {
402        collect_paged_skimmed_v2!(self, get_basic_entry_assigned_nodes_v2)
403    }
404
405    /// retrieve basic information for nodes that got assigned 'mixing' node in this epoch
406    /// this includes legacy mixnodes and nym-nodes
407    #[deprecated(note = "use get_all_basic_active_mixing_assigned_nodes_with_metadata instead")]
408    pub async fn get_all_basic_active_mixing_assigned_nodes(
409        &self,
410    ) -> Result<Vec<SkimmedNodeV1>, ValidatorClientError> {
411        self.get_all_basic_active_mixing_assigned_nodes_with_metadata()
412            .await
413            .map(|res| res.nodes)
414    }
415
416    pub async fn get_all_basic_active_mixing_assigned_nodes_with_metadata(
417        &self,
418    ) -> Result<SkimmedNodesWithMetadata, ValidatorClientError> {
419        collect_paged_skimmed_v2!(self, get_basic_active_mixing_assigned_nodes_v2)
420    }
421
422    /// retrieve basic information for nodes are capable of operating as a mixnode
423    /// this includes legacy mixnodes and nym-nodes
424    #[deprecated(note = "use get_all_basic_mixing_capable_nodes_with_metadata instead")]
425    pub async fn get_all_basic_mixing_capable_nodes(
426        &self,
427    ) -> Result<Vec<SkimmedNodeV1>, ValidatorClientError> {
428        self.get_all_basic_mixing_capable_nodes_with_metadata()
429            .await
430            .map(|res| res.nodes)
431    }
432
433    pub async fn get_all_basic_mixing_capable_nodes_with_metadata(
434        &self,
435    ) -> Result<SkimmedNodesWithMetadata, ValidatorClientError> {
436        collect_paged_skimmed_v2!(self, get_basic_mixing_capable_nodes_v2)
437    }
438
439    /// retrieve basic information for all bonded nodes on the network
440    #[deprecated(note = "use get_all_basic_nodes_with_metadata instead")]
441    pub async fn get_all_basic_nodes(&self) -> Result<Vec<SkimmedNodeV1>, ValidatorClientError> {
442        self.get_all_basic_nodes_with_metadata()
443            .await
444            .map(|res| res.nodes)
445    }
446
447    pub async fn get_all_basic_nodes_with_metadata(
448        &self,
449    ) -> Result<SkimmedNodesWithMetadata, ValidatorClientError> {
450        collect_paged_skimmed_v2!(self, get_basic_nodes_v2)
451    }
452
453    /// retrieve expanded information for all bonded nodes on the network
454    pub async fn get_all_expanded_nodes(
455        &self,
456    ) -> Result<SemiSkimmedNodesWithMetadata, ValidatorClientError> {
457        // Unroll the first iteration to get the metadata
458        let mut page = 0;
459
460        let res = self
461            .nym_api
462            .get_expanded_nodes(false, Some(page), None)
463            .await?;
464        let mut nodes = res.nodes.data;
465        let metadata = res.metadata;
466
467        if res.nodes.pagination.total == nodes.len() {
468            return Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata));
469        }
470
471        page += 1;
472
473        loop {
474            let mut res = self
475                .nym_api
476                .get_expanded_nodes(false, Some(page), None)
477                .await?;
478
479            nodes.append(&mut res.nodes.data);
480            if nodes.len() < res.nodes.pagination.total {
481                page += 1
482            } else {
483                break;
484            }
485        }
486
487        Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata))
488    }
489
490    pub async fn health(&self) -> Result<ApiHealthResponse, ValidatorClientError> {
491        Ok(self.nym_api.health().await?)
492    }
493
494    #[deprecated(note = "use .get_all_described_nodes_v2 instead")]
495    pub async fn get_all_described_nodes(
496        &self,
497    ) -> Result<Vec<NymNodeDescriptionV1>, ValidatorClientError> {
498        // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
499        let mut page = 0;
500        let mut descriptions = Vec::new();
501
502        loop {
503            let mut res = self.nym_api.get_nodes_described(Some(page), None).await?;
504
505            descriptions.append(&mut res.data);
506            if descriptions.len() < res.pagination.total {
507                page += 1
508            } else {
509                break;
510            }
511        }
512
513        Ok(descriptions)
514    }
515
516    pub async fn get_all_described_nodes_v2(
517        &self,
518    ) -> Result<Vec<NymNodeDescriptionV2>, ValidatorClientError> {
519        // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
520        let mut page = 0;
521        let mut descriptions = Vec::new();
522
523        loop {
524            let mut res = self
525                .nym_api
526                .get_nodes_described_v2(Some(page), None)
527                .await?;
528
529            descriptions.append(&mut res.data);
530            if descriptions.len() < res.pagination.total {
531                page += 1
532            } else {
533                break;
534            }
535        }
536
537        Ok(descriptions)
538    }
539
540    pub async fn get_all_bonded_nym_nodes(
541        &self,
542    ) -> Result<Vec<NymNodeDetails>, ValidatorClientError> {
543        // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
544        let mut page = 0;
545        let mut bonds = Vec::new();
546
547        loop {
548            let mut res = self.nym_api.get_nym_nodes(Some(page), None).await?;
549
550            bonds.append(&mut res.data);
551            if bonds.len() < res.pagination.total {
552                page += 1
553            } else {
554                break;
555            }
556        }
557
558        Ok(bonds)
559    }
560
561    #[deprecated]
562    pub async fn get_gateway_core_status_count(
563        &self,
564        identity: IdentityKeyRef<'_>,
565        since: Option<i64>,
566    ) -> Result<GatewayCoreStatusResponse, ValidatorClientError> {
567        Ok(self
568            .nym_api
569            .get_gateway_core_status_count(identity, since)
570            .await?)
571    }
572
573    #[deprecated]
574    pub async fn get_mixnode_core_status_count(
575        &self,
576        mix_id: NodeId,
577        since: Option<i64>,
578    ) -> Result<MixnodeCoreStatusResponse, ValidatorClientError> {
579        Ok(self
580            .nym_api
581            .get_mixnode_core_status_count(mix_id, since)
582            .await?)
583    }
584
585    pub async fn blind_sign(
586        &self,
587        request_body: &BlindSignRequestBody,
588    ) -> Result<BlindedSignatureResponse, ValidatorClientError> {
589        Ok(self.nym_api.blind_sign(request_body).await?)
590    }
591
592    pub async fn verify_ecash_ticket(
593        &self,
594        request_body: &VerifyEcashTicketBody,
595    ) -> Result<EcashTicketVerificationResponse, ValidatorClientError> {
596        Ok(self.nym_api.verify_ecash_ticket(request_body).await?)
597    }
598
599    pub async fn batch_redeem_ecash_tickets(
600        &self,
601        request_body: &BatchRedeemTicketsBody,
602    ) -> Result<EcashBatchTicketRedemptionResponse, ValidatorClientError> {
603        Ok(self
604            .nym_api
605            .batch_redeem_ecash_tickets(request_body)
606            .await?)
607    }
608
609    pub async fn partial_expiration_date_signatures(
610        &self,
611        expiration_date: Option<Date>,
612        epoch_id: Option<EpochId>,
613    ) -> Result<PartialExpirationDateSignatureResponse, ValidatorClientError> {
614        Ok(self
615            .nym_api
616            .partial_expiration_date_signatures(expiration_date, epoch_id)
617            .await?)
618    }
619
620    pub async fn partial_coin_indices_signatures(
621        &self,
622        epoch_id: Option<EpochId>,
623    ) -> Result<PartialCoinIndicesSignatureResponse, ValidatorClientError> {
624        Ok(self
625            .nym_api
626            .partial_coin_indices_signatures(epoch_id)
627            .await?)
628    }
629
630    pub async fn global_expiration_date_signatures(
631        &self,
632        expiration_date: Option<Date>,
633        epoch_id: Option<EpochId>,
634    ) -> Result<AggregatedExpirationDateSignatureResponse, ValidatorClientError> {
635        Ok(self
636            .nym_api
637            .global_expiration_date_signatures(expiration_date, epoch_id)
638            .await?)
639    }
640
641    pub async fn global_coin_indices_signatures(
642        &self,
643        epoch_id: Option<EpochId>,
644    ) -> Result<AggregatedCoinIndicesSignatureResponse, ValidatorClientError> {
645        Ok(self
646            .nym_api
647            .global_coin_indices_signatures(epoch_id)
648            .await?)
649    }
650
651    pub async fn master_verification_key(
652        &self,
653        epoch_id: Option<EpochId>,
654    ) -> Result<VerificationKeyResponse, ValidatorClientError> {
655        Ok(self.nym_api.master_verification_key(epoch_id).await?)
656    }
657
658    pub async fn issued_ticketbooks_for(
659        &self,
660        expiration_date: Date,
661    ) -> Result<IssuedTicketbooksForResponse, ValidatorClientError> {
662        Ok(self.nym_api.issued_ticketbooks_for(expiration_date).await?)
663    }
664
665    pub async fn issued_ticketbooks_for_count(
666        &self,
667        expiration_date: Date,
668    ) -> Result<IssuedTicketbooksForCountResponse, ValidatorClientError> {
669        Ok(self
670            .nym_api
671            .issued_ticketbooks_for_count(expiration_date)
672            .await?)
673    }
674
675    pub async fn issued_ticketbooks_challenge_commitment(
676        &self,
677        request: &IssuedTicketbooksChallengeCommitmentRequest,
678    ) -> Result<IssuedTicketbooksChallengeCommitmentResponse, ValidatorClientError> {
679        Ok(self
680            .nym_api
681            .issued_ticketbooks_challenge_commitment(request)
682            .await?)
683    }
684
685    pub async fn issued_ticketbooks_data(
686        &self,
687        request: &IssuedTicketbooksDataRequest,
688    ) -> Result<IssuedTicketbooksDataResponse, ValidatorClientError> {
689        Ok(self.nym_api.issued_ticketbooks_data(request).await?)
690    }
691
692    pub async fn nodes_by_addresses(
693        &self,
694        addresses: Vec<IpAddr>,
695    ) -> Result<NodesByAddressesResponse, ValidatorClientError> {
696        Ok(self.nym_api.nodes_by_addresses(addresses).await?)
697    }
698}