// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::nyxd::{self, NyxdClient}; use crate::signing::direct_wallet::DirectSecp256k1HdWallet; use crate::signing::signer::{NoSigner, OfflineSigner}; use crate::{ nym_api, DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient, ReqwestRpcClient, ValidatorClientError, }; use nym_api_requests::coconut::{ BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, }; use nym_api_requests::models::MixNodeBondAnnotated; use nym_api_requests::models::{ GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse, RewardEstimationResponse, StakeSaturationResponse, }; use nym_network_defaults::NymNetworkDetails; use url::Url; pub use nym_mixnet_contract_common::{ mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, MixId, }; // re-export the type to not break existing imports pub use crate::coconut::CoconutApiClient; #[cfg(feature = "http-client")] use crate::{DirectSigningHttpRpcValidatorClient, HttpRpcClient, QueryHttpRpcValidatorClient}; #[must_use] #[derive(Debug, Clone)] pub struct Config { api_url: Url, nyxd_url: Url, // TODO: until refactored, this is a dead field under some features nyxd_config: nyxd::Config, } impl Config { pub fn try_from_nym_network_details( details: &NymNetworkDetails, ) -> Result { let mut api_url = details .endpoints .iter() .filter_map(|d| d.api_url.as_ref()) .map(|url| Url::parse(url)) .collect::, _>>()?; if api_url.is_empty() { return Err(ValidatorClientError::NoAPIUrlAvailable); } Ok(Config { api_url: api_url.pop().unwrap(), nyxd_url: details.endpoints[0] .nyxd_url .parse() .map_err(ValidatorClientError::MalformedUrlProvided)?, nyxd_config: nyxd::Config::try_from_nym_network_details(details)?, }) } // TODO: this method shouldn't really exist as all information should be included immediately // via `from_nym_network_details`, but it's here for, you guessed it, legacy compatibility pub fn with_urls(mut self, nyxd_url: Url, api_url: Url) -> Self { self.nyxd_url = nyxd_url; self.api_url = api_url; self } pub fn with_nyxd_url(mut self, nyxd_url: Url) -> Self { self.nyxd_url = nyxd_url; self } pub fn with_simulated_gas_multiplier(mut self, gas_multiplier: f32) -> Self { self.nyxd_config.simulated_gas_multiplier = gas_multiplier; self } } pub struct Client { // ideally they would have been read-only, but unfortunately rust doesn't have such features pub nym_api: nym_api::Client, pub nyxd: NyxdClient, } #[cfg(feature = "http-client")] impl Client { pub fn new_signing( config: Config, mnemonic: bip39::Mnemonic, ) -> Result { let rpc_client = HttpRpcClient::new(config.nyxd_url.as_str())?; let prefix = &config.nyxd_config.chain_details.bech32_account_prefix; let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic); Ok(Self::new_signing_with_rpc_client( config, rpc_client, wallet, )) } pub fn change_nyxd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> { self.nyxd.change_endpoint(new_endpoint.as_ref())?; Ok(()) } } impl Client { pub fn new_reqwest_signing( config: Config, mnemonic: bip39::Mnemonic, ) -> DirectSigningReqwestRpcValidatorClient { let rpc_client = ReqwestRpcClient::new(config.nyxd_url.clone()); let prefix = &config.nyxd_config.chain_details.bech32_account_prefix; let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic); Self::new_signing_with_rpc_client(config, rpc_client, wallet) } } #[cfg(feature = "http-client")] impl Client { pub fn new_query(config: Config) -> Result { let rpc_client = HttpRpcClient::new(config.nyxd_url.as_str())?; Ok(Self::new_with_rpc_client(config, rpc_client)) } pub fn change_nyxd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> { self.nyxd = NyxdClient::connect(self.nyxd.current_config().clone(), new_endpoint.as_ref())?; Ok(()) } } impl Client { pub fn new_reqwest_query(config: Config) -> QueryReqwestRpcValidatorClient { let rpc_client = ReqwestRpcClient::new(config.nyxd_url.clone()); Self::new_with_rpc_client(config, rpc_client) } } impl Client { pub fn new_with_rpc_client(config: Config, rpc_client: C) -> Self { let nym_api_client = nym_api::Client::new(config.api_url.clone()); Client { nym_api: nym_api_client, nyxd: NyxdClient::new(config.nyxd_config, rpc_client), } } } impl Client { pub fn new_signing_with_rpc_client(config: Config, rpc_client: C, signer: S) -> Self where S: OfflineSigner, { let nym_api_client = nym_api::Client::new(config.api_url.clone()); Client { nym_api: nym_api_client, nyxd: NyxdClient::new_signing(config.nyxd_config, rpc_client, signer), } } } // validator-api wrappers impl Client { pub fn change_nym_api(&mut self, new_endpoint: Url) { self.nym_api.change_url(new_endpoint) } pub async fn get_cached_mixnodes(&self) -> Result, ValidatorClientError> { Ok(self.nym_api.get_mixnodes().await?) } pub async fn get_cached_mixnodes_detailed( &self, ) -> Result, ValidatorClientError> { Ok(self.nym_api.get_mixnodes_detailed().await?) } pub async fn get_cached_mixnodes_detailed_unfiltered( &self, ) -> Result, ValidatorClientError> { Ok(self.nym_api.get_mixnodes_detailed_unfiltered().await?) } pub async fn get_cached_rewarded_mixnodes( &self, ) -> Result, ValidatorClientError> { Ok(self.nym_api.get_rewarded_mixnodes().await?) } pub async fn get_cached_rewarded_mixnodes_detailed( &self, ) -> Result, ValidatorClientError> { Ok(self.nym_api.get_rewarded_mixnodes_detailed().await?) } pub async fn get_cached_active_mixnodes( &self, ) -> Result, ValidatorClientError> { Ok(self.nym_api.get_active_mixnodes().await?) } pub async fn get_cached_active_mixnodes_detailed( &self, ) -> Result, ValidatorClientError> { Ok(self.nym_api.get_active_mixnodes_detailed().await?) } pub async fn get_cached_gateways(&self) -> Result, ValidatorClientError> { Ok(self.nym_api.get_gateways().await?) } pub async fn blind_sign( &self, request_body: &BlindSignRequestBody, ) -> Result { Ok(self.nym_api.blind_sign(request_body).await?) } } #[derive(Clone)] pub struct NymApiClient { pub nym_api: nym_api::Client, // TODO: perhaps if we really need it at some (currently I don't see any reasons for it) // we could re-implement the communication with the REST API on port 1317 } impl NymApiClient { pub fn new(api_url: Url) -> Self { let nym_api = nym_api::Client::new(api_url); NymApiClient { nym_api } } pub fn change_nym_api(&mut self, new_endpoint: Url) { self.nym_api.change_url(new_endpoint); } pub async fn get_cached_active_mixnodes( &self, ) -> Result, ValidatorClientError> { Ok(self.nym_api.get_active_mixnodes().await?) } pub async fn get_cached_rewarded_mixnodes( &self, ) -> Result, ValidatorClientError> { Ok(self.nym_api.get_rewarded_mixnodes().await?) } pub async fn get_cached_mixnodes(&self) -> Result, ValidatorClientError> { Ok(self.nym_api.get_mixnodes().await?) } pub async fn get_cached_gateways(&self) -> Result, ValidatorClientError> { Ok(self.nym_api.get_gateways().await?) } pub async fn get_gateway_core_status_count( &self, identity: IdentityKeyRef<'_>, since: Option, ) -> Result { Ok(self .nym_api .get_gateway_core_status_count(identity, since) .await?) } pub async fn get_mixnode_core_status_count( &self, mix_id: MixId, since: Option, ) -> Result { Ok(self .nym_api .get_mixnode_core_status_count(mix_id, since) .await?) } pub async fn get_mixnode_status( &self, mix_id: MixId, ) -> Result { Ok(self.nym_api.get_mixnode_status(mix_id).await?) } pub async fn get_mixnode_reward_estimation( &self, mix_id: MixId, ) -> Result { Ok(self.nym_api.get_mixnode_reward_estimation(mix_id).await?) } pub async fn get_mixnode_stake_saturation( &self, mix_id: MixId, ) -> Result { Ok(self.nym_api.get_mixnode_stake_saturation(mix_id).await?) } pub async fn blind_sign( &self, request_body: &BlindSignRequestBody, ) -> Result { Ok(self.nym_api.blind_sign(request_body).await?) } pub async fn verify_bandwidth_credential( &self, request_body: &VerifyCredentialBody, ) -> Result { Ok(self .nym_api .verify_bandwidth_credential(request_body) .await?) } }