1use crate::{GAS_PRICE_AMOUNT, mainnet};
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use std::ops::Not;
8use url::Url;
9
10#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)]
11#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
12pub struct ChainDetails {
13 pub bech32_account_prefix: String,
14 pub mix_denom: DenomDetailsOwned,
15 pub stake_denom: DenomDetailsOwned,
16}
17
18#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)]
19#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
20pub struct NymContracts {
21 pub mixnet_contract_address: Option<String>,
22 pub vesting_contract_address: Option<String>,
23 #[serde(default)]
24 pub performance_contract_address: Option<String>,
25 pub ecash_contract_address: Option<String>,
26 pub group_contract_address: Option<String>,
27 pub multisig_contract_address: Option<String>,
28 pub coconut_dkg_contract_address: Option<String>,
29}
30
31#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)]
34#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
35pub struct NymNetworkDetails {
36 pub network_name: String,
37 pub chain_details: ChainDetails,
38 pub endpoints: Vec<ValidatorDetails>,
39 pub contracts: NymContracts,
40 pub nym_vpn_api_url: Option<String>,
41 pub nym_api_urls: Option<Vec<ApiUrl>>,
42 pub nym_vpn_api_urls: Option<Vec<ApiUrl>>,
43}
44
45#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)]
46#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
47pub struct ApiUrl {
48 pub url: String,
52 pub front_hosts: Option<Vec<String>>,
56}
57
58#[derive(Copy, Clone)]
59pub struct ApiUrlConst<'a> {
60 pub url: &'a str,
61 pub front_hosts: Option<&'a [&'a str]>,
62}
63
64impl From<ApiUrlConst<'_>> for ApiUrl {
65 fn from(value: ApiUrlConst) -> Self {
66 ApiUrl {
67 url: value.url.to_string(),
68 front_hosts: value
69 .front_hosts
70 .map(|slice| slice.iter().map(|s| s.to_string()).collect()),
71 }
72 }
73}
74
75impl Default for NymNetworkDetails {
77 fn default() -> Self {
78 NymNetworkDetails::new_mainnet()
79 }
80}
81
82impl NymNetworkDetails {
83 pub fn new_empty() -> Self {
84 NymNetworkDetails {
85 network_name: Default::default(),
86 chain_details: ChainDetails {
87 bech32_account_prefix: Default::default(),
88 mix_denom: DenomDetailsOwned {
89 base: Default::default(),
90 display: Default::default(),
91 display_exponent: Default::default(),
92 },
93 stake_denom: DenomDetailsOwned {
94 base: Default::default(),
95 display: Default::default(),
96 display_exponent: Default::default(),
97 },
98 },
99 endpoints: Default::default(),
100 contracts: Default::default(),
101 nym_vpn_api_url: Default::default(),
102 nym_api_urls: Default::default(),
103 nym_vpn_api_urls: Default::default(),
104 }
105 }
106
107 #[cfg(feature = "env")]
108 pub fn new_from_env() -> Self {
109 use crate::var_names;
110 use std::env::{VarError, var};
111 use std::ffi::OsStr;
112
113 fn get_optional_env<K: AsRef<OsStr>>(env: K) -> Option<String> {
114 match var(env) {
115 Ok(var) => {
116 if var.is_empty() {
117 None
118 } else {
119 Some(var)
120 }
121 }
122 Err(VarError::NotPresent) => None,
123 err => panic!("Unable to set: {err:?}"),
124 }
125 }
126
127 let nym_api = var(var_names::NYM_API).expect("nym api not set");
128
129 NymNetworkDetails::new_empty()
130 .with_network_name(var(var_names::NETWORK_NAME).expect("network name not set"))
131 .with_bech32_account_prefix(
132 var(var_names::BECH32_PREFIX).expect("bech32 prefix not set"),
133 )
134 .with_mix_denom(DenomDetailsOwned {
135 base: var(var_names::MIX_DENOM).expect("mix denomination base not set"),
136 display: var(var_names::MIX_DENOM_DISPLAY)
137 .expect("mix denomination display not set"),
138 display_exponent: var(var_names::DENOMS_EXPONENT)
139 .expect("denomination exponent not set")
140 .parse()
141 .expect("denomination exponent is not u32"),
142 })
143 .with_stake_denom(DenomDetailsOwned {
144 base: var(var_names::STAKE_DENOM).expect("stake denomination base not set"),
145 display: var(var_names::STAKE_DENOM_DISPLAY)
146 .expect("stake denomination display not set"),
147 display_exponent: var(var_names::DENOMS_EXPONENT)
148 .expect("denomination exponent not set")
149 .parse()
150 .expect("denomination exponent is not u32"),
151 })
152 .with_additional_validator_endpoint(ValidatorDetails::new(
153 var(var_names::NYXD).expect("nyxd validator not set"),
154 Some(nym_api.clone()),
155 get_optional_env(var_names::NYXD_WEBSOCKET),
156 ))
157 .with_mixnet_contract(get_optional_env(var_names::MIXNET_CONTRACT_ADDRESS))
158 .with_vesting_contract(get_optional_env(var_names::VESTING_CONTRACT_ADDRESS))
159 .with_ecash_contract(get_optional_env(var_names::ECASH_CONTRACT_ADDRESS))
160 .with_group_contract(get_optional_env(var_names::GROUP_CONTRACT_ADDRESS))
161 .with_multisig_contract(get_optional_env(var_names::MULTISIG_CONTRACT_ADDRESS))
162 .with_coconut_dkg_contract(get_optional_env(var_names::COCONUT_DKG_CONTRACT_ADDRESS))
163 .with_nym_vpn_api_url(get_optional_env(var_names::NYM_VPN_API))
164 .with_nym_api_urls(Some(vec![ApiUrl {
165 url: nym_api,
166 front_hosts: None,
167 }]))
168 }
169
170 pub fn new_mainnet() -> Self {
171 fn parse_optional_str(raw: &str) -> Option<String> {
172 raw.is_empty().not().then(|| raw.into())
173 }
174
175 NymNetworkDetails {
177 network_name: mainnet::NETWORK_NAME.into(),
178 chain_details: ChainDetails {
179 bech32_account_prefix: mainnet::BECH32_PREFIX.into(),
180 mix_denom: mainnet::MIX_DENOM.into(),
181 stake_denom: mainnet::STAKE_DENOM.into(),
182 },
183 endpoints: mainnet::validators(),
184 contracts: NymContracts {
185 mixnet_contract_address: parse_optional_str(mainnet::MIXNET_CONTRACT_ADDRESS),
186 vesting_contract_address: parse_optional_str(mainnet::VESTING_CONTRACT_ADDRESS),
187 performance_contract_address: parse_optional_str(
188 mainnet::PERFORMANCE_CONTRACT_ADDRESS,
189 ),
190 ecash_contract_address: parse_optional_str(mainnet::ECASH_CONTRACT_ADDRESS),
191 group_contract_address: parse_optional_str(mainnet::GROUP_CONTRACT_ADDRESS),
192 multisig_contract_address: parse_optional_str(mainnet::MULTISIG_CONTRACT_ADDRESS),
193 coconut_dkg_contract_address: parse_optional_str(
194 mainnet::COCONUT_DKG_CONTRACT_ADDRESS,
195 ),
196 },
197 nym_vpn_api_url: parse_optional_str(mainnet::NYM_VPN_API),
198 nym_api_urls: Some(mainnet::NYM_APIS.iter().copied().map(Into::into).collect()),
199 nym_vpn_api_urls: Some(
200 mainnet::NYM_VPN_APIS
201 .iter()
202 .copied()
203 .map(Into::into)
204 .collect(),
205 ),
206 }
207 }
208
209 #[rustfmt::skip]
210 #[cfg(feature = "env")]
211 pub fn export_to_env(self) {
212 use crate::var_names;
213 use std::env::set_var;
214
215 fn set_optional_var(var_name: &str, value: Option<String>) {
216 if let Some(value) = value {
217 unsafe {set_var(var_name, value)}
218 }
219 }
220 unsafe {
221 set_var(var_names::NETWORK_NAME, self.network_name);
222 set_var(var_names::BECH32_PREFIX, self.chain_details.bech32_account_prefix);
223
224 set_var(var_names::MIX_DENOM, self.chain_details.mix_denom.base);
225 set_var(var_names::MIX_DENOM_DISPLAY, self.chain_details.mix_denom.display);
226
227 set_var(var_names::STAKE_DENOM, self.chain_details.stake_denom.base);
228 set_var(var_names::STAKE_DENOM_DISPLAY, self.chain_details.stake_denom.display);
229
230 set_var(var_names::DENOMS_EXPONENT, self.chain_details.mix_denom.display_exponent.to_string());
231
232 if let Some(e) = self.endpoints.first() {
233 set_var(var_names::NYXD, e.nyxd_url.clone());
234 set_optional_var(var_names::NYM_API, e.api_url.clone());
235 set_optional_var(var_names::NYXD_WEBSOCKET, e.websocket_url.clone());
236 }
237
238 set_optional_var(var_names::MIXNET_CONTRACT_ADDRESS, self.contracts.mixnet_contract_address);
239 set_optional_var(var_names::VESTING_CONTRACT_ADDRESS, self.contracts.vesting_contract_address);
240 set_optional_var(var_names::ECASH_CONTRACT_ADDRESS, self.contracts.ecash_contract_address);
241 set_optional_var(var_names::GROUP_CONTRACT_ADDRESS, self.contracts.group_contract_address);
242 set_optional_var(var_names::MULTISIG_CONTRACT_ADDRESS, self.contracts.multisig_contract_address);
243 set_optional_var(var_names::COCONUT_DKG_CONTRACT_ADDRESS, self.contracts.coconut_dkg_contract_address);
244
245 set_optional_var(var_names::NYM_VPN_API, self.nym_vpn_api_url);
246 }
247
248
249 }
250
251 pub fn default_gas_price_amount(&self) -> f64 {
252 GAS_PRICE_AMOUNT
253 }
254
255 #[must_use]
256 pub fn with_network_name(mut self, network_name: String) -> Self {
257 self.network_name = network_name;
258 self
259 }
260
261 #[must_use]
262 pub fn with_chain_details(mut self, chain_details: ChainDetails) -> Self {
263 self.chain_details = chain_details;
264 self
265 }
266
267 #[must_use]
268 pub fn with_bech32_account_prefix<S: Into<String>>(mut self, prefix: S) -> Self {
269 self.chain_details.bech32_account_prefix = prefix.into();
270 self
271 }
272
273 #[must_use]
274 pub fn with_mix_denom(mut self, mix_denom: DenomDetailsOwned) -> Self {
275 self.chain_details.mix_denom = mix_denom;
276 self
277 }
278
279 #[must_use]
280 pub fn with_stake_denom(mut self, stake_denom: DenomDetailsOwned) -> Self {
281 self.chain_details.stake_denom = stake_denom;
282 self
283 }
284
285 #[must_use]
286 pub fn with_base_mix_denom<S: Into<String>>(mut self, base_mix_denom: S) -> Self {
287 self.chain_details.mix_denom = DenomDetailsOwned::base_only(base_mix_denom.into());
288 self
289 }
290
291 #[must_use]
292 pub fn with_base_stake_denom<S: Into<String>>(mut self, base_stake_denom: S) -> Self {
293 self.chain_details.stake_denom = DenomDetailsOwned::base_only(base_stake_denom.into());
294 self
295 }
296
297 #[must_use]
298 pub fn with_additional_validator_endpoint(mut self, endpoint: ValidatorDetails) -> Self {
299 self.endpoints.push(endpoint);
300 self
301 }
302
303 #[must_use]
304 pub fn with_validator_endpoint(mut self, endpoint: ValidatorDetails) -> Self {
305 self.endpoints = vec![endpoint];
306 self
307 }
308
309 #[must_use]
310 pub fn with_contracts(mut self, contracts: NymContracts) -> Self {
311 self.contracts = contracts;
312 self
313 }
314
315 #[must_use]
316 pub fn with_mixnet_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
317 self.contracts.mixnet_contract_address = contract.map(Into::into);
318 self
319 }
320
321 #[must_use]
322 pub fn with_vesting_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
323 self.contracts.vesting_contract_address = contract.map(Into::into);
324 self
325 }
326
327 #[must_use]
328 pub fn with_ecash_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
329 self.contracts.ecash_contract_address = contract.map(Into::into);
330 self
331 }
332
333 #[must_use]
334 pub fn with_group_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
335 self.contracts.group_contract_address = contract.map(Into::into);
336 self
337 }
338
339 #[must_use]
340 pub fn with_multisig_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
341 self.contracts.multisig_contract_address = contract.map(Into::into);
342 self
343 }
344
345 #[must_use]
346 pub fn with_coconut_dkg_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
347 self.contracts.coconut_dkg_contract_address = contract.map(Into::into);
348 self
349 }
350
351 #[must_use]
352 pub fn with_nym_vpn_api_url<S: Into<String>>(mut self, endpoint: Option<S>) -> Self {
353 self.nym_vpn_api_url = endpoint.map(Into::into);
354 self
355 }
356
357 #[must_use]
358 pub fn with_nym_api_urls(mut self, urls: Option<Vec<ApiUrl>>) -> Self {
359 self.nym_api_urls = urls;
360 self
361 }
362
363 pub fn nym_vpn_api_url(&self) -> Option<Url> {
364 self.nym_vpn_api_url.as_ref().map(|url| {
365 url.parse()
366 .expect("the provided nym-vpn api url is invalid!")
367 })
368 }
369}
370
371#[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)]
372pub struct DenomDetails {
373 pub base: &'static str,
374 pub display: &'static str,
375 pub display_exponent: u32,
377}
378
379impl DenomDetails {
380 pub const fn new(base: &'static str, display: &'static str, display_exponent: u32) -> Self {
381 DenomDetails {
382 base,
383 display,
384 display_exponent,
385 }
386 }
387}
388
389#[derive(Debug, Serialize, Deserialize, Hash, Clone, PartialEq, Eq, JsonSchema)]
390#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
391pub struct DenomDetailsOwned {
392 pub base: String,
393 pub display: String,
394 pub display_exponent: u32,
396}
397
398impl From<DenomDetails> for DenomDetailsOwned {
399 fn from(details: DenomDetails) -> Self {
400 DenomDetailsOwned {
401 base: details.base.to_owned(),
402 display: details.display.to_owned(),
403 display_exponent: details.display_exponent,
404 }
405 }
406}
407
408impl DenomDetailsOwned {
409 pub fn base_only(base: String) -> Self {
410 DenomDetailsOwned {
411 base: base.clone(),
412 display: base,
413 display_exponent: 0,
414 }
415 }
416}
417
418#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)]
419#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
420pub struct ValidatorDetails {
421 pub nyxd_url: String,
423 pub websocket_url: Option<String>,
425
426 pub api_url: Option<String>,
429 }
431
432impl ValidatorDetails {
433 pub fn new<S: Into<String>>(nyxd_url: S, api_url: Option<S>, websocket_url: Option<S>) -> Self {
434 ValidatorDetails {
435 nyxd_url: nyxd_url.into(),
436 websocket_url: websocket_url.map(Into::into),
437 api_url: api_url.map(Into::into),
438 }
439 }
440
441 pub fn new_nyxd_only<S: Into<String>>(nyxd_url: S) -> Self {
442 ValidatorDetails {
443 nyxd_url: nyxd_url.into(),
444 websocket_url: None,
445 api_url: None,
446 }
447 }
448
449 pub fn nyxd_url(&self) -> Url {
450 self.nyxd_url
451 .parse()
452 .expect("the provided nyxd url is invalid!")
453 }
454
455 pub fn api_url(&self) -> Option<Url> {
456 self.api_url
457 .as_ref()
458 .map(|url| url.parse().expect("the provided api url is invalid!"))
459 }
460
461 pub fn websocket_url(&self) -> Option<Url> {
462 self.websocket_url
463 .as_ref()
464 .map(|url| url.parse().expect("the provided websocket url is invalid!"))
465 }
466}