1pub 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;
41pub 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
49macro_rules! collect_paged_skimmed_v2 {
51 ( $self:ident, $f: ident ) => {{
52 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 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 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 pub nym_api: nym_http_api_client::Client,
172 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#[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 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(
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 }
333
334#[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 #[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 #[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 #[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 #[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 pub async fn get_all_expanded_nodes(
455 &self,
456 ) -> Result<SemiSkimmedNodesWithMetadata, ValidatorClientError> {
457 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 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 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 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}