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 delegations_with_reward(&self, denom: impl Into<String>) -> Coin {
194 truncate_reward(self.delegates, denom)
195 }
196
197 pub fn pending_delegator_reward(&self, delegation: &Delegation) -> StdResult<Coin> {
198 let delegator_reward = self.determine_delegation_reward(delegation)?;
199 Ok(truncate_reward(delegator_reward, &delegation.amount.denom))
200 }
201
202 #[allow(clippy::panic)]
207 pub fn withdraw_operator_reward(
208 &mut self,
209 original_pledge: &Coin,
210 ) -> Result<Coin, MixnetContractError> {
211 let initial_dec = original_pledge.amount.into_base_decimal()?;
212 if initial_dec > self.operator {
213 panic!(
214 "seems slashing has occurred while it has not been implemented nor accounted for!"
215 )
216 }
217 let diff = self.operator - initial_dec;
218 self.operator = initial_dec;
219
220 Ok(truncate_reward(diff, &original_pledge.denom))
221 }
222
223 pub fn withdraw_delegator_reward(
224 &mut self,
225 delegation: &mut Delegation,
226 ) -> Result<Coin, MixnetContractError> {
227 let reward = self.determine_delegation_reward(delegation)?;
228 self.decrease_delegates_decimal(reward)?;
229
230 delegation.cumulative_reward_ratio = self.full_reward_ratio();
231 Ok(truncate_reward(reward, &delegation.amount.denom))
232 }
233
234 pub fn node_bond(&self) -> Decimal {
235 self.operator + self.delegates
236 }
237
238 pub fn pledge_saturation(&self, reward_params: &RewardingParams) -> Decimal {
240 if self.operator > reward_params.interval.stake_saturation_point {
242 Decimal::one()
243 } else {
244 self.operator / reward_params.interval.stake_saturation_point
245 }
246 }
247
248 pub fn bond_saturation(&self, reward_params: &RewardingParams) -> Decimal {
250 if self.node_bond() > reward_params.interval.stake_saturation_point {
252 Decimal::one()
253 } else {
254 self.node_bond() / reward_params.interval.stake_saturation_point
255 }
256 }
257
258 pub fn uncapped_bond_saturation(&self, reward_params: &RewardingParams) -> Decimal {
259 self.node_bond() / reward_params.interval.stake_saturation_point
260 }
261
262 pub fn node_reward(
263 &self,
264 global_params: &RewardingParams,
265 node_params: NodeRewardingParameters,
266 ) -> Decimal {
267 let work = node_params.work_factor;
268 let alpha = global_params.interval.sybil_resistance;
269
270 global_params.interval.epoch_reward_budget
271 * node_params.performance
272 * self.bond_saturation(global_params)
273 * (work
274 + alpha.value() * self.pledge_saturation(global_params)
275 / global_params.dec_rewarded_set_size())
276 / (Decimal::one() + alpha.value())
277 }
278
279 pub fn determine_reward_split(
280 &self,
281 node_reward: Decimal,
282 node_performance: Percent,
283 epochs_in_interval: u32,
285 ) -> RewardDistribution {
286 let node_cost =
287 self.cost_params.epoch_operating_cost(epochs_in_interval) * node_performance;
288
289 if node_reward > node_cost {
291 let profit = node_reward - node_cost;
292 let profit_margin = self.cost_params.profit_margin_percent.value();
293 let one = Decimal::one();
294
295 let operator_share = self.operator / self.node_bond();
296
297 let operator = profit * (profit_margin + (one - profit_margin) * operator_share);
298 let delegates = profit - operator;
299
300 debug_assert_eq!(operator + delegates + node_cost, node_reward);
301
302 RewardDistribution {
303 operator: operator + node_cost,
304 delegates,
305 }
306 } else {
307 RewardDistribution {
308 operator: node_reward,
309 delegates: Decimal::zero(),
310 }
311 }
312 }
313
314 pub fn calculate_epoch_reward(
315 &self,
316 reward_params: &RewardingParams,
317 node_params: NodeRewardingParameters,
318 epochs_in_interval: u32,
319 ) -> RewardDistribution {
320 let node_reward = self.node_reward(reward_params, node_params);
321 self.determine_reward_split(node_reward, node_params.performance, epochs_in_interval)
322 }
323
324 pub fn distribute_rewards(
325 &mut self,
326 distribution: RewardDistribution,
327 absolute_epoch_id: EpochId,
328 ) {
329 let unit_delegation_reward = distribution.delegates
330 * self.delegator_share(self.unit_delegation + self.total_unit_reward);
331
332 self.operator += distribution.operator;
333 self.delegates += distribution.delegates;
334
335 self.total_unit_reward += unit_delegation_reward;
337 self.last_rewarded_epoch = absolute_epoch_id;
338 }
339
340 pub fn epoch_rewarding(
341 &mut self,
342 reward_params: &RewardingParams,
343 node_params: NodeRewardingParameters,
344 epochs_in_interval: u32,
345 absolute_epoch_id: EpochId,
346 ) {
347 let reward_distribution =
348 self.calculate_epoch_reward(reward_params, node_params, epochs_in_interval);
349 self.distribute_rewards(reward_distribution, absolute_epoch_id)
350 }
351
352 pub fn determine_delegation_reward(&self, delegation: &Delegation) -> StdResult<Decimal> {
353 let starting_ratio = delegation.cumulative_reward_ratio;
354 let ending_ratio = self.full_reward_ratio();
355 let adjust = starting_ratio + self.unit_delegation;
356
357 Ok((ending_ratio - starting_ratio) * delegation.dec_amount()? / adjust)
358 }
359
360 pub fn add_base_delegation(&mut self, amount: Uint128) -> Result<(), MixnetContractError> {
362 self.increase_delegates_uint128(amount)?;
363 self.unique_delegations += 1;
364 Ok(())
365 }
366
367 pub fn increase_operator_uint128(
368 &mut self,
369 amount: Uint128,
370 ) -> Result<(), MixnetContractError> {
371 self.operator += amount.into_base_decimal()?;
372 Ok(())
373 }
374
375 pub fn decrease_operator_uint128(
377 &mut self,
378 amount: Uint128,
379 ) -> Result<(), MixnetContractError> {
380 let amount_decimal = amount.into_base_decimal()?;
381 if self.operator < amount_decimal {
382 return Err(MixnetContractError::OverflowDecimalSubtraction {
383 minuend: self.operator,
384 subtrahend: amount_decimal,
385 });
386 }
387 self.operator -= amount_decimal;
388 Ok(())
389 }
390
391 pub fn increase_delegates_uint128(
392 &mut self,
393 amount: Uint128,
394 ) -> Result<(), MixnetContractError> {
395 self.delegates += amount.into_base_decimal()?;
396 Ok(())
397 }
398
399 pub fn remove_delegation_uint128(
403 &mut self,
404 amount: Uint128,
405 ) -> Result<(), MixnetContractError> {
406 self.decrease_delegates_uint128(amount)?;
407 self.decrement_unique_delegations()
408 }
409
410 pub fn decrease_delegates_uint128(
411 &mut self,
412 amount: Uint128,
413 ) -> Result<(), MixnetContractError> {
414 let amount_dec = amount.into_base_decimal()?;
415 self.decrease_delegates_decimal(amount_dec)
416 }
417
418 fn decrement_unique_delegations(&mut self) -> Result<(), MixnetContractError> {
419 if self.unique_delegations == 0 {
420 return Err(MixnetContractError::OverflowSubtraction {
421 minuend: 0,
422 subtrahend: 1,
423 });
424 }
425 self.unique_delegations -= 1;
426 Ok(())
427 }
428
429 pub fn remove_delegation_decimal(
431 &mut self,
432 amount: Decimal,
433 ) -> Result<(), MixnetContractError> {
434 self.decrease_delegates_decimal(amount)?;
435 self.decrement_unique_delegations()?;
436
437 if self.unique_delegations == 0 {
440 self.operator += self.delegates;
441 self.delegates = Decimal::zero();
442 }
443 Ok(())
444 }
445
446 pub fn undelegate(&mut self, delegation: &Delegation) -> Result<Coin, MixnetContractError> {
447 let reward = self.determine_delegation_reward(delegation)?;
448 let full_amount = reward + delegation.dec_amount()?;
449 self.remove_delegation_decimal(full_amount)?;
450 Ok(truncate_reward(full_amount, &delegation.amount.denom))
451 }
452
453 pub fn decrease_delegates_decimal(
454 &mut self,
455 amount: Decimal,
456 ) -> Result<(), MixnetContractError> {
457 if self.delegates < amount {
458 return Err(MixnetContractError::OverflowDecimalSubtraction {
459 minuend: self.delegates,
460 subtrahend: amount,
461 });
462 }
463
464 self.delegates -= amount;
465 Ok(())
466 }
467
468 pub fn decrease_operator_decimal(
469 &mut self,
470 amount: Decimal,
471 ) -> Result<(), MixnetContractError> {
472 if self.operator < amount {
473 return Err(MixnetContractError::OverflowDecimalSubtraction {
474 minuend: self.operator,
475 subtrahend: amount,
476 });
477 }
478
479 self.operator -= amount;
480 Ok(())
481 }
482
483 pub fn full_reward_ratio(&self) -> Decimal {
484 self.total_unit_reward }
486
487 pub fn delegator_share(&self, amount: Decimal) -> Decimal {
488 if self.delegates.is_zero() {
489 Decimal::zero()
490 } else {
491 amount / self.delegates
492 }
493 }
494
495 pub fn clear_operator(&self) -> NodeRewarding {
497 let mut zeroed = self.clone();
498 zeroed.operator = Decimal::zero();
499 zeroed
500 }
501}
502
503#[derive(
507 ::cosmwasm_schema::serde::Serialize,
508 ::cosmwasm_schema::serde::Deserialize,
509 ::std::clone::Clone,
510 ::std::fmt::Debug,
511 ::std::cmp::PartialEq,
512 ::cosmwasm_schema::schemars::JsonSchema,
513)]
514#[schemars(crate = "::cosmwasm_schema::schemars")]
515#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
516pub struct MixNodeBond {
517 pub mix_id: NodeId,
519
520 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
522 pub owner: Addr,
523
524 #[cfg_attr(feature = "utoipa", schema(value_type = crate::CoinSchema))]
526 pub original_pledge: Coin,
527
528 pub mix_node: MixNode,
533
534 #[cfg_attr(feature = "utoipa", schema(value_type = Option<String>))]
537 pub proxy: Option<Addr>,
538
539 pub bonding_height: u64,
541
542 pub is_unbonding: bool,
545}
546
547impl MixNodeBond {
548 pub fn identity(&self) -> &str {
549 &self.mix_node.identity_key
550 }
551
552 pub fn original_pledge(&self) -> &Coin {
553 &self.original_pledge
554 }
555
556 pub fn owner(&self) -> &Addr {
557 &self.owner
558 }
559
560 pub fn mix_node(&self) -> &MixNode {
561 &self.mix_node
562 }
563}
564
565#[cw_serde]
567#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
568#[cfg_attr(
569 feature = "generate-ts",
570 ts(export, export_to = "ts-packages/types/src/types/rust/Mixnode.ts")
571)]
572#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
573pub struct MixNode {
574 pub host: String,
576
577 pub mix_port: u16,
579
580 pub verloc_port: u16,
582
583 pub http_api_port: u16,
585
586 pub sphinx_key: SphinxKey,
588
589 pub identity_key: IdentityKey,
591
592 pub version: String,
594}
595
596#[cw_serde]
599#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
600pub struct NodeCostParams {
601 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
603 pub profit_margin_percent: Percent,
604
605 #[cfg_attr(feature = "utoipa", schema(value_type = crate::CoinSchema))]
607 pub interval_operating_cost: Coin,
608}
609
610impl NodeCostParams {
611 pub fn to_inline_json(&self) -> String {
612 to_json_string(self).unwrap_or_else(|_| "serialisation failure".into())
613 }
614}
615
616impl NodeCostParams {
617 pub fn epoch_operating_cost(&self, epochs_in_interval: u32) -> Decimal {
618 Decimal::from_ratio(self.interval_operating_cost.amount, epochs_in_interval)
619 }
620}
621
622#[derive(
623 Copy,
624 Clone,
625 Debug,
626 PartialEq,
627 Eq,
628 PartialOrd,
629 Ord,
630 Hash,
631 Serialize_repr,
632 Deserialize_repr,
633 JsonSchema,
634)]
635#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
636#[repr(u8)]
637pub enum LegacyMixLayer {
638 One = 1,
639 Two = 2,
640 Three = 3,
641}
642
643impl From<LegacyMixLayer> for Role {
644 fn from(layer: LegacyMixLayer) -> Self {
645 match layer {
646 LegacyMixLayer::One => Role::Layer1,
647 LegacyMixLayer::Two => Role::Layer2,
648 LegacyMixLayer::Three => Role::Layer3,
649 }
650 }
651}
652
653impl From<LegacyMixLayer> for String {
654 fn from(layer: LegacyMixLayer) -> Self {
655 (layer as u8).to_string()
656 }
657}
658
659impl TryFrom<u8> for LegacyMixLayer {
660 type Error = MixnetContractError;
661
662 fn try_from(i: u8) -> Result<LegacyMixLayer, MixnetContractError> {
663 match i {
664 1 => Ok(LegacyMixLayer::One),
665 2 => Ok(LegacyMixLayer::Two),
666 3 => Ok(LegacyMixLayer::Three),
667 _ => Err(MixnetContractError::InvalidLayer(i)),
668 }
669 }
670}
671
672impl From<LegacyMixLayer> for u8 {
673 fn from(layer: LegacyMixLayer) -> u8 {
674 match layer {
675 LegacyMixLayer::One => 1,
676 LegacyMixLayer::Two => 2,
677 LegacyMixLayer::Three => 3,
678 }
679 }
680}
681
682#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
683#[cfg_attr(
684 feature = "generate-ts",
685 ts(
686 export,
687 export_to = "ts-packages/types/src/types/rust/PendingMixnodeChanges.ts"
688 )
689)]
690#[derive(
693 ::cosmwasm_schema::serde::Serialize,
694 ::cosmwasm_schema::serde::Deserialize,
695 ::std::clone::Clone,
696 ::std::fmt::Debug,
697 ::std::cmp::PartialEq,
698 ::cosmwasm_schema::schemars::JsonSchema,
699 Default,
700 Copy,
701)]
702#[schemars(crate = "::cosmwasm_schema::schemars")]
703#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
704pub struct PendingMixNodeChanges {
705 pub pledge_change: Option<EpochEventId>,
706
707 #[serde(default)]
708 pub cost_params_change: Option<IntervalEventId>,
709}
710
711#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
712#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
713pub struct LegacyPendingMixNodeChanges {
714 #[cfg_attr(feature = "utoipa", schema(value_type = Option<u32>))]
715 pub pledge_change: Option<EpochEventId>,
716}
717
718impl From<PendingMixNodeChanges> for LegacyPendingMixNodeChanges {
719 fn from(value: PendingMixNodeChanges) -> Self {
720 LegacyPendingMixNodeChanges {
721 pledge_change: value.pledge_change,
722 }
723 }
724}
725
726impl PendingMixNodeChanges {
727 pub fn new_empty() -> PendingMixNodeChanges {
728 PendingMixNodeChanges {
729 pledge_change: None,
730 cost_params_change: None,
731 }
732 }
733}
734
735#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
737#[cfg_attr(
738 feature = "generate-ts",
739 ts(
740 export,
741 export_to = "ts-packages/types/src/types/rust/UnbondedMixnode.ts"
742 )
743)]
744#[cw_serde]
745pub struct UnbondedMixnode {
746 pub identity_key: IdentityKey,
748
749 #[cfg_attr(feature = "generate-ts", ts(type = "string"))]
751 pub owner: Addr,
752
753 #[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
756 pub proxy: Option<Addr>,
757
758 #[cfg_attr(feature = "generate-ts", ts(type = "number"))]
760 pub unbonding_height: u64,
761}
762
763#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
764#[cfg_attr(
765 feature = "generate-ts",
766 ts(
767 export,
768 export_to = "ts-packages/types/src/types/rust/MixNodeConfigUpdate.ts"
769 )
770)]
771#[cw_serde]
772pub struct MixNodeConfigUpdate {
773 pub host: String,
774 pub mix_port: u16,
775 pub verloc_port: u16,
776 pub http_api_port: u16,
777 pub version: String,
778}
779
780impl MixNodeConfigUpdate {
781 pub fn to_inline_json(&self) -> String {
782 to_json_string(self).unwrap_or_else(|_| "serialisation failure".into())
783 }
784}
785
786#[cw_serde]
788pub struct PagedMixnodeBondsResponse {
789 pub nodes: Vec<MixNodeBond>,
791
792 pub per_page: usize,
795
796 pub start_next_after: Option<NodeId>,
798}
799
800impl PagedMixnodeBondsResponse {
801 pub fn new(nodes: Vec<MixNodeBond>, per_page: usize, start_next_after: Option<NodeId>) -> Self {
802 PagedMixnodeBondsResponse {
803 nodes,
804 per_page,
805 start_next_after,
806 }
807 }
808}
809
810#[cw_serde]
812pub struct PagedMixnodesDetailsResponse {
813 pub nodes: Vec<MixNodeDetails>,
817
818 pub per_page: usize,
821
822 pub start_next_after: Option<NodeId>,
824}
825
826impl PagedMixnodesDetailsResponse {
827 pub fn new(
828 nodes: Vec<MixNodeDetails>,
829 per_page: usize,
830 start_next_after: Option<NodeId>,
831 ) -> Self {
832 PagedMixnodesDetailsResponse {
833 nodes,
834 per_page,
835 start_next_after,
836 }
837 }
838}
839
840#[cw_serde]
842pub struct PagedUnbondedMixnodesResponse {
843 pub nodes: Vec<(NodeId, UnbondedMixnode)>,
845
846 pub per_page: usize,
849
850 pub start_next_after: Option<NodeId>,
852}
853
854impl PagedUnbondedMixnodesResponse {
855 pub fn new(
856 nodes: Vec<(NodeId, UnbondedMixnode)>,
857 per_page: usize,
858 start_next_after: Option<NodeId>,
859 ) -> Self {
860 PagedUnbondedMixnodesResponse {
861 nodes,
862 per_page,
863 start_next_after,
864 }
865 }
866}
867
868#[cw_serde]
870pub struct MixOwnershipResponse {
871 pub address: Addr,
873
874 pub mixnode_details: Option<MixNodeDetails>,
876}
877
878#[cw_serde]
880pub struct MixnodeDetailsResponse {
881 pub mix_id: NodeId,
883
884 pub mixnode_details: Option<MixNodeDetails>,
886}
887
888#[cw_serde]
890pub struct MixnodeDetailsByIdentityResponse {
891 pub identity_key: IdentityKey,
893
894 pub mixnode_details: Option<MixNodeDetails>,
896}
897
898#[cw_serde]
900pub struct MixnodeRewardingDetailsResponse {
901 pub mix_id: NodeId,
903
904 pub rewarding_details: Option<NodeRewarding>,
906}
907
908#[cw_serde]
910pub struct UnbondedMixnodeResponse {
911 pub mix_id: NodeId,
913
914 pub unbonded_info: Option<UnbondedMixnode>,
916}
917
918#[cw_serde]
920pub struct MixStakeSaturationResponse {
921 pub mix_id: NodeId,
923
924 pub current_saturation: Option<Decimal>,
927
928 pub uncapped_saturation: Option<Decimal>,
932}