1#![allow(clippy::field_reassign_with_default)]
6
7use crate::constants::{TOKEN_SUPPLY, UNIT_DELEGATION_BASE};
8use crate::error::MixnetContractError;
9use crate::helpers::IntoBaseDecimal;
10use crate::nym_node::Role;
11use crate::reward_params::{NodeRewardingParameters, RewardingParams};
12use crate::rewarding::RewardDistribution;
13use crate::rewarding::helpers::truncate_reward;
14use crate::{
15 Delegation, EpochEventId, EpochId, IdentityKey, IntervalEventId, NodeId, OperatingCostRange,
16 Percent, ProfitMarginRange, SphinxKey,
17};
18use cosmwasm_schema::cw_serde;
19use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128, to_json_string};
20use schemars::JsonSchema;
21use serde::{Deserialize, Serialize};
22use serde_repr::{Deserialize_repr, Serialize_repr};
23
24#[cw_serde]
26#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
27pub struct MixNodeDetails {
28 pub bond_information: MixNodeBond,
30
31 pub rewarding_details: NodeRewarding,
33
34 #[serde(default)]
36 pub pending_changes: PendingMixNodeChanges,
37}
38
39impl MixNodeDetails {
40 pub fn new(
41 bond_information: MixNodeBond,
42 rewarding_details: NodeRewarding,
43 pending_changes: PendingMixNodeChanges,
44 ) -> Self {
45 MixNodeDetails {
46 bond_information,
47 rewarding_details,
48 pending_changes,
49 }
50 }
51
52 pub fn mix_id(&self) -> NodeId {
53 self.bond_information.mix_id
54 }
55
56 pub fn is_unbonding(&self) -> bool {
57 self.bond_information.is_unbonding
58 }
59
60 pub fn original_pledge(&self) -> &Coin {
61 &self.bond_information.original_pledge
62 }
63
64 pub fn pending_operator_reward(&self) -> Coin {
65 let pledge = self.original_pledge();
66 self.rewarding_details.pending_operator_reward(pledge)
67 }
68
69 pub fn pending_detailed_operator_reward(&self) -> StdResult<Decimal> {
70 let pledge = self.original_pledge();
71 self.rewarding_details
72 .pending_detailed_operator_reward(pledge)
73 }
74
75 pub fn total_stake(&self) -> Decimal {
76 self.rewarding_details.node_bond()
77 }
78
79 pub fn pending_pledge_change(&self) -> Option<EpochEventId> {
80 self.pending_changes.pledge_change
81 }
82}
83
84#[cw_serde]
86#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
87pub struct NodeRewarding {
88 pub cost_params: NodeCostParams,
90
91 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
93 pub operator: Decimal,
94
95 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
97 pub delegates: Decimal,
98
99 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
101 pub total_unit_reward: Decimal,
102
103 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
105 pub unit_delegation: Decimal,
106
107 pub last_rewarded_epoch: EpochId,
110
111 pub unique_delegations: u32,
115}
116
117impl NodeRewarding {
118 pub fn initialise_new(
119 cost_params: NodeCostParams,
120 initial_pledge: &Coin,
121 current_epoch: EpochId,
122 ) -> Result<Self, MixnetContractError> {
123 assert!(
124 initial_pledge.amount <= TOKEN_SUPPLY,
125 "pledge cannot be larger than the token supply"
126 );
127
128 Ok(NodeRewarding {
129 cost_params,
130 operator: initial_pledge.amount.into_base_decimal()?,
131 delegates: Decimal::zero(),
132 total_unit_reward: Decimal::zero(),
133 unit_delegation: UNIT_DELEGATION_BASE,
134 last_rewarded_epoch: current_epoch,
135 unique_delegations: 0,
136 })
137 }
138
139 pub fn normalise_cost_function(
140 &mut self,
141 allowed_profit_margin: ProfitMarginRange,
142 allowed_operating_cost: OperatingCostRange,
143 ) {
144 self.normalise_profit_margin(allowed_profit_margin);
145 self.normalise_operating_cost(allowed_operating_cost)
146 }
147
148 pub fn normalise_profit_margin(&mut self, allowed_range: ProfitMarginRange) {
149 self.cost_params.profit_margin_percent =
150 allowed_range.normalise(self.cost_params.profit_margin_percent)
151 }
152
153 pub fn normalise_operating_cost(&mut self, allowed_range: OperatingCostRange) {
154 self.cost_params.interval_operating_cost.amount =
155 allowed_range.normalise(self.cost_params.interval_operating_cost.amount)
156 }
157
158 pub fn still_bonded(&self) -> bool {
163 self.operator != Decimal::zero()
164 }
165
166 pub fn pending_operator_reward(&self, original_pledge: &Coin) -> Coin {
167 let reward_with_pledge = truncate_reward(self.operator, &original_pledge.denom);
168 Coin {
169 denom: reward_with_pledge.denom,
170 amount: reward_with_pledge.amount - original_pledge.amount,
171 }
172 }
173
174 #[allow(clippy::panic)]
179 pub fn pending_detailed_operator_reward(&self, original_pledge: &Coin) -> StdResult<Decimal> {
180 let initial_dec = original_pledge.amount.into_base_decimal()?;
181 if initial_dec > self.operator {
182 panic!(
183 "seems slashing has occurred while it has not been implemented nor accounted for!"
184 )
185 }
186 Ok(self.operator - initial_dec)
187 }
188
189 pub fn operator_pledge_with_reward(&self, denom: impl Into<String>) -> Coin {
190 truncate_reward(self.operator, denom)
191 }
192
193 pub fn pending_delegator_reward(&self, delegation: &Delegation) -> StdResult<Coin> {
194 let delegator_reward = self.determine_delegation_reward(delegation)?;
195 Ok(truncate_reward(delegator_reward, &delegation.amount.denom))
196 }
197
198 #[allow(clippy::panic)]
203 pub fn withdraw_operator_reward(
204 &mut self,
205 original_pledge: &Coin,
206 ) -> Result<Coin, MixnetContractError> {
207 let initial_dec = original_pledge.amount.into_base_decimal()?;
208 if initial_dec > self.operator {
209 panic!(
210 "seems slashing has occurred while it has not been implemented nor accounted for!"
211 )
212 }
213 let diff = self.operator - initial_dec;
214 self.operator = initial_dec;
215
216 Ok(truncate_reward(diff, &original_pledge.denom))
217 }
218
219 pub fn withdraw_delegator_reward(
220 &mut self,
221 delegation: &mut Delegation,
222 ) -> Result<Coin, MixnetContractError> {
223 let reward = self.determine_delegation_reward(delegation)?;
224 self.decrease_delegates_decimal(reward)?;
225
226 delegation.cumulative_reward_ratio = self.full_reward_ratio();
227 Ok(truncate_reward(reward, &delegation.amount.denom))
228 }
229
230 pub fn node_bond(&self) -> Decimal {
231 self.operator + self.delegates
232 }
233
234 pub fn pledge_saturation(&self, reward_params: &RewardingParams) -> Decimal {
236 if self.operator > reward_params.interval.stake_saturation_point {
238 Decimal::one()
239 } else {
240 self.operator / reward_params.interval.stake_saturation_point
241 }
242 }
243
244 pub fn bond_saturation(&self, reward_params: &RewardingParams) -> Decimal {
246 if self.node_bond() > reward_params.interval.stake_saturation_point {
248 Decimal::one()
249 } else {
250 self.node_bond() / reward_params.interval.stake_saturation_point
251 }
252 }
253
254 pub fn uncapped_bond_saturation(&self, reward_params: &RewardingParams) -> Decimal {
255 self.node_bond() / reward_params.interval.stake_saturation_point
256 }
257
258 pub fn node_reward(
259 &self,
260 global_params: &RewardingParams,
261 node_params: NodeRewardingParameters,
262 ) -> Decimal {
263 let work = node_params.work_factor;
264 let alpha = global_params.interval.sybil_resistance;
265
266 global_params.interval.epoch_reward_budget
267 * node_params.performance
268 * self.bond_saturation(global_params)
269 * (work
270 + alpha.value() * self.pledge_saturation(global_params)
271 / global_params.dec_rewarded_set_size())
272 / (Decimal::one() + alpha.value())
273 }
274
275 pub fn determine_reward_split(
276 &self,
277 node_reward: Decimal,
278 node_performance: Percent,
279 epochs_in_interval: u32,
281 ) -> RewardDistribution {
282 let node_cost =
283 self.cost_params.epoch_operating_cost(epochs_in_interval) * node_performance;
284
285 if node_reward > node_cost {
287 let profit = node_reward - node_cost;
288 let profit_margin = self.cost_params.profit_margin_percent.value();
289 let one = Decimal::one();
290
291 let operator_share = self.operator / self.node_bond();
292
293 let operator = profit * (profit_margin + (one - profit_margin) * operator_share);
294 let delegates = profit - operator;
295
296 debug_assert_eq!(operator + delegates + node_cost, node_reward);
297
298 RewardDistribution {
299 operator: operator + node_cost,
300 delegates,
301 }
302 } else {
303 RewardDistribution {
304 operator: node_reward,
305 delegates: Decimal::zero(),
306 }
307 }
308 }
309
310 pub fn calculate_epoch_reward(
311 &self,
312 reward_params: &RewardingParams,
313 node_params: NodeRewardingParameters,
314 epochs_in_interval: u32,
315 ) -> RewardDistribution {
316 let node_reward = self.node_reward(reward_params, node_params);
317 self.determine_reward_split(node_reward, node_params.performance, epochs_in_interval)
318 }
319
320 pub fn distribute_rewards(
321 &mut self,
322 distribution: RewardDistribution,
323 absolute_epoch_id: EpochId,
324 ) {
325 let unit_delegation_reward = distribution.delegates
326 * self.delegator_share(self.unit_delegation + self.total_unit_reward);
327
328 self.operator += distribution.operator;
329 self.delegates += distribution.delegates;
330
331 self.total_unit_reward += unit_delegation_reward;
333 self.last_rewarded_epoch = absolute_epoch_id;
334 }
335
336 pub fn epoch_rewarding(
337 &mut self,
338 reward_params: &RewardingParams,
339 node_params: NodeRewardingParameters,
340 epochs_in_interval: u32,
341 absolute_epoch_id: EpochId,
342 ) {
343 let reward_distribution =
344 self.calculate_epoch_reward(reward_params, node_params, epochs_in_interval);
345 self.distribute_rewards(reward_distribution, absolute_epoch_id)
346 }
347
348 pub fn determine_delegation_reward(&self, delegation: &Delegation) -> StdResult<Decimal> {
349 let starting_ratio = delegation.cumulative_reward_ratio;
350 let ending_ratio = self.full_reward_ratio();
351 let adjust = starting_ratio + self.unit_delegation;
352
353 Ok((ending_ratio - starting_ratio) * delegation.dec_amount()? / adjust)
354 }
355
356 pub fn add_base_delegation(&mut self, amount: Uint128) -> Result<(), MixnetContractError> {
358 self.increase_delegates_uint128(amount)?;
359 self.unique_delegations += 1;
360 Ok(())
361 }
362
363 pub fn increase_operator_uint128(
364 &mut self,
365 amount: Uint128,
366 ) -> Result<(), MixnetContractError> {
367 self.operator += amount.into_base_decimal()?;
368 Ok(())
369 }
370
371 pub fn decrease_operator_uint128(
373 &mut self,
374 amount: Uint128,
375 ) -> Result<(), MixnetContractError> {
376 let amount_decimal = amount.into_base_decimal()?;
377 if self.operator < amount_decimal {
378 return Err(MixnetContractError::OverflowDecimalSubtraction {
379 minuend: self.operator,
380 subtrahend: amount_decimal,
381 });
382 }
383 self.operator -= amount_decimal;
384 Ok(())
385 }
386
387 pub fn increase_delegates_uint128(
388 &mut self,
389 amount: Uint128,
390 ) -> Result<(), MixnetContractError> {
391 self.delegates += amount.into_base_decimal()?;
392 Ok(())
393 }
394
395 pub fn remove_delegation_uint128(
399 &mut self,
400 amount: Uint128,
401 ) -> Result<(), MixnetContractError> {
402 self.decrease_delegates_uint128(amount)?;
403 self.decrement_unique_delegations()
404 }
405
406 pub fn decrease_delegates_uint128(
407 &mut self,
408 amount: Uint128,
409 ) -> Result<(), MixnetContractError> {
410 let amount_dec = amount.into_base_decimal()?;
411 self.decrease_delegates_decimal(amount_dec)
412 }
413
414 fn decrement_unique_delegations(&mut self) -> Result<(), MixnetContractError> {
415 if self.unique_delegations == 0 {
416 return Err(MixnetContractError::OverflowSubtraction {
417 minuend: 0,
418 subtrahend: 1,
419 });
420 }
421 self.unique_delegations -= 1;
422 Ok(())
423 }
424
425 pub fn remove_delegation_decimal(
427 &mut self,
428 amount: Decimal,
429 ) -> Result<(), MixnetContractError> {
430 self.decrease_delegates_decimal(amount)?;
431 self.decrement_unique_delegations()?;
432
433 if self.unique_delegations == 0 {
436 self.operator += self.delegates;
437 self.delegates = Decimal::zero();
438 }
439 Ok(())
440 }
441
442 pub fn undelegate(&mut self, delegation: &Delegation) -> Result<Coin, MixnetContractError> {
443 let reward = self.determine_delegation_reward(delegation)?;
444 let full_amount = reward + delegation.dec_amount()?;
445 self.remove_delegation_decimal(full_amount)?;
446 Ok(truncate_reward(full_amount, &delegation.amount.denom))
447 }
448
449 pub fn decrease_delegates_decimal(
450 &mut self,
451 amount: Decimal,
452 ) -> Result<(), MixnetContractError> {
453 if self.delegates < amount {
454 return Err(MixnetContractError::OverflowDecimalSubtraction {
455 minuend: self.delegates,
456 subtrahend: amount,
457 });
458 }
459
460 self.delegates -= amount;
461 Ok(())
462 }
463
464 pub fn decrease_operator_decimal(
465 &mut self,
466 amount: Decimal,
467 ) -> Result<(), MixnetContractError> {
468 if self.operator < amount {
469 return Err(MixnetContractError::OverflowDecimalSubtraction {
470 minuend: self.operator,
471 subtrahend: amount,
472 });
473 }
474
475 self.operator -= amount;
476 Ok(())
477 }
478
479 pub fn full_reward_ratio(&self) -> Decimal {
480 self.total_unit_reward }
482
483 pub fn delegator_share(&self, amount: Decimal) -> Decimal {
484 if self.delegates.is_zero() {
485 Decimal::zero()
486 } else {
487 amount / self.delegates
488 }
489 }
490
491 pub fn clear_operator(&self) -> NodeRewarding {
493 let mut zeroed = self.clone();
494 zeroed.operator = Decimal::zero();
495 zeroed
496 }
497}
498
499#[derive(
503 ::cosmwasm_schema::serde::Serialize,
504 ::cosmwasm_schema::serde::Deserialize,
505 ::std::clone::Clone,
506 ::std::fmt::Debug,
507 ::std::cmp::PartialEq,
508 ::cosmwasm_schema::schemars::JsonSchema,
509)]
510#[schemars(crate = "::cosmwasm_schema::schemars")]
511#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
512pub struct MixNodeBond {
513 pub mix_id: NodeId,
515
516 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
518 pub owner: Addr,
519
520 #[cfg_attr(feature = "utoipa", schema(value_type = crate::CoinSchema))]
522 pub original_pledge: Coin,
523
524 pub mix_node: MixNode,
529
530 #[cfg_attr(feature = "utoipa", schema(value_type = Option<String>))]
533 pub proxy: Option<Addr>,
534
535 pub bonding_height: u64,
537
538 pub is_unbonding: bool,
541}
542
543impl MixNodeBond {
544 pub fn identity(&self) -> &str {
545 &self.mix_node.identity_key
546 }
547
548 pub fn original_pledge(&self) -> &Coin {
549 &self.original_pledge
550 }
551
552 pub fn owner(&self) -> &Addr {
553 &self.owner
554 }
555
556 pub fn mix_node(&self) -> &MixNode {
557 &self.mix_node
558 }
559}
560
561#[cw_serde]
563#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
564#[cfg_attr(
565 feature = "generate-ts",
566 ts(export, export_to = "ts-packages/types/src/types/rust/Mixnode.ts")
567)]
568#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
569pub struct MixNode {
570 pub host: String,
572
573 pub mix_port: u16,
575
576 pub verloc_port: u16,
578
579 pub http_api_port: u16,
581
582 pub sphinx_key: SphinxKey,
584
585 pub identity_key: IdentityKey,
587
588 pub version: String,
590}
591
592#[cw_serde]
595#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
596pub struct NodeCostParams {
597 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
599 pub profit_margin_percent: Percent,
600
601 #[cfg_attr(feature = "utoipa", schema(value_type = crate::CoinSchema))]
603 pub interval_operating_cost: Coin,
604}
605
606impl NodeCostParams {
607 pub fn to_inline_json(&self) -> String {
608 to_json_string(self).unwrap_or_else(|_| "serialisation failure".into())
609 }
610}
611
612impl NodeCostParams {
613 pub fn epoch_operating_cost(&self, epochs_in_interval: u32) -> Decimal {
614 Decimal::from_ratio(self.interval_operating_cost.amount, epochs_in_interval)
615 }
616}
617
618#[derive(
619 Copy,
620 Clone,
621 Debug,
622 PartialEq,
623 Eq,
624 PartialOrd,
625 Ord,
626 Hash,
627 Serialize_repr,
628 Deserialize_repr,
629 JsonSchema,
630)]
631#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
632#[repr(u8)]
633pub enum LegacyMixLayer {
634 One = 1,
635 Two = 2,
636 Three = 3,
637}
638
639impl From<LegacyMixLayer> for Role {
640 fn from(layer: LegacyMixLayer) -> Self {
641 match layer {
642 LegacyMixLayer::One => Role::Layer1,
643 LegacyMixLayer::Two => Role::Layer2,
644 LegacyMixLayer::Three => Role::Layer3,
645 }
646 }
647}
648
649impl From<LegacyMixLayer> for String {
650 fn from(layer: LegacyMixLayer) -> Self {
651 (layer as u8).to_string()
652 }
653}
654
655impl TryFrom<u8> for LegacyMixLayer {
656 type Error = MixnetContractError;
657
658 fn try_from(i: u8) -> Result<LegacyMixLayer, MixnetContractError> {
659 match i {
660 1 => Ok(LegacyMixLayer::One),
661 2 => Ok(LegacyMixLayer::Two),
662 3 => Ok(LegacyMixLayer::Three),
663 _ => Err(MixnetContractError::InvalidLayer(i)),
664 }
665 }
666}
667
668impl From<LegacyMixLayer> for u8 {
669 fn from(layer: LegacyMixLayer) -> u8 {
670 match layer {
671 LegacyMixLayer::One => 1,
672 LegacyMixLayer::Two => 2,
673 LegacyMixLayer::Three => 3,
674 }
675 }
676}
677
678#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
679#[cfg_attr(
680 feature = "generate-ts",
681 ts(
682 export,
683 export_to = "ts-packages/types/src/types/rust/PendingMixnodeChanges.ts"
684 )
685)]
686#[derive(
689 ::cosmwasm_schema::serde::Serialize,
690 ::cosmwasm_schema::serde::Deserialize,
691 ::std::clone::Clone,
692 ::std::fmt::Debug,
693 ::std::cmp::PartialEq,
694 ::cosmwasm_schema::schemars::JsonSchema,
695 Default,
696 Copy,
697)]
698#[schemars(crate = "::cosmwasm_schema::schemars")]
699#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
700pub struct PendingMixNodeChanges {
701 pub pledge_change: Option<EpochEventId>,
702
703 #[serde(default)]
704 pub cost_params_change: Option<IntervalEventId>,
705}
706
707#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
708#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
709pub struct LegacyPendingMixNodeChanges {
710 #[cfg_attr(feature = "utoipa", schema(value_type = Option<u32>))]
711 pub pledge_change: Option<EpochEventId>,
712}
713
714impl From<PendingMixNodeChanges> for LegacyPendingMixNodeChanges {
715 fn from(value: PendingMixNodeChanges) -> Self {
716 LegacyPendingMixNodeChanges {
717 pledge_change: value.pledge_change,
718 }
719 }
720}
721
722impl PendingMixNodeChanges {
723 pub fn new_empty() -> PendingMixNodeChanges {
724 PendingMixNodeChanges {
725 pledge_change: None,
726 cost_params_change: None,
727 }
728 }
729}
730
731#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
733#[cfg_attr(
734 feature = "generate-ts",
735 ts(
736 export,
737 export_to = "ts-packages/types/src/types/rust/UnbondedMixnode.ts"
738 )
739)]
740#[cw_serde]
741pub struct UnbondedMixnode {
742 pub identity_key: IdentityKey,
744
745 #[cfg_attr(feature = "generate-ts", ts(type = "string"))]
747 pub owner: Addr,
748
749 #[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
752 pub proxy: Option<Addr>,
753
754 #[cfg_attr(feature = "generate-ts", ts(type = "number"))]
756 pub unbonding_height: u64,
757}
758
759#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
760#[cfg_attr(
761 feature = "generate-ts",
762 ts(
763 export,
764 export_to = "ts-packages/types/src/types/rust/MixNodeConfigUpdate.ts"
765 )
766)]
767#[cw_serde]
768pub struct MixNodeConfigUpdate {
769 pub host: String,
770 pub mix_port: u16,
771 pub verloc_port: u16,
772 pub http_api_port: u16,
773 pub version: String,
774}
775
776impl MixNodeConfigUpdate {
777 pub fn to_inline_json(&self) -> String {
778 to_json_string(self).unwrap_or_else(|_| "serialisation failure".into())
779 }
780}
781
782#[cw_serde]
784pub struct PagedMixnodeBondsResponse {
785 pub nodes: Vec<MixNodeBond>,
787
788 pub per_page: usize,
791
792 pub start_next_after: Option<NodeId>,
794}
795
796impl PagedMixnodeBondsResponse {
797 pub fn new(nodes: Vec<MixNodeBond>, per_page: usize, start_next_after: Option<NodeId>) -> Self {
798 PagedMixnodeBondsResponse {
799 nodes,
800 per_page,
801 start_next_after,
802 }
803 }
804}
805
806#[cw_serde]
808pub struct PagedMixnodesDetailsResponse {
809 pub nodes: Vec<MixNodeDetails>,
813
814 pub per_page: usize,
817
818 pub start_next_after: Option<NodeId>,
820}
821
822impl PagedMixnodesDetailsResponse {
823 pub fn new(
824 nodes: Vec<MixNodeDetails>,
825 per_page: usize,
826 start_next_after: Option<NodeId>,
827 ) -> Self {
828 PagedMixnodesDetailsResponse {
829 nodes,
830 per_page,
831 start_next_after,
832 }
833 }
834}
835
836#[cw_serde]
838pub struct PagedUnbondedMixnodesResponse {
839 pub nodes: Vec<(NodeId, UnbondedMixnode)>,
841
842 pub per_page: usize,
845
846 pub start_next_after: Option<NodeId>,
848}
849
850impl PagedUnbondedMixnodesResponse {
851 pub fn new(
852 nodes: Vec<(NodeId, UnbondedMixnode)>,
853 per_page: usize,
854 start_next_after: Option<NodeId>,
855 ) -> Self {
856 PagedUnbondedMixnodesResponse {
857 nodes,
858 per_page,
859 start_next_after,
860 }
861 }
862}
863
864#[cw_serde]
866pub struct MixOwnershipResponse {
867 pub address: Addr,
869
870 pub mixnode_details: Option<MixNodeDetails>,
872}
873
874#[cw_serde]
876pub struct MixnodeDetailsResponse {
877 pub mix_id: NodeId,
879
880 pub mixnode_details: Option<MixNodeDetails>,
882}
883
884#[cw_serde]
886pub struct MixnodeDetailsByIdentityResponse {
887 pub identity_key: IdentityKey,
889
890 pub mixnode_details: Option<MixNodeDetails>,
892}
893
894#[cw_serde]
896pub struct MixnodeRewardingDetailsResponse {
897 pub mix_id: NodeId,
899
900 pub rewarding_details: Option<NodeRewarding>,
902}
903
904#[cw_serde]
906pub struct UnbondedMixnodeResponse {
907 pub mix_id: NodeId,
909
910 pub unbonded_info: Option<UnbondedMixnode>,
912}
913
914#[cw_serde]
916pub struct MixStakeSaturationResponse {
917 pub mix_id: NodeId,
919
920 pub current_saturation: Option<Decimal>,
923
924 pub uncapped_saturation: Option<Decimal>,
928}