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