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