Compare commits

...

1 Commits

Author SHA1 Message Date
Fouad eff1f383c3 Feature/node settings apy playground (#1677) (#2738)
* initial ui for test my node

use svg for node path

add stories for test my node

* add initial rewards calculation

* update validation for rewards playground

* init playground with default values

* get node uptime

* get mixnode reward estimation

* calculate saturation

calculate stake saturation

* Make ComputeRewardEstParam derive Debug

* set active set to be always true

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
2022-12-21 12:56:40 +00:00
68 changed files with 672 additions and 41 deletions
Generated
+9 -9
View File
@@ -586,7 +586,7 @@ dependencies = [
[[package]] [[package]]
name = "client-core" name = "client-core"
version = "1.1.2" version = "1.1.4"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"client-connections", "client-connections",
@@ -3106,7 +3106,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-cli" name = "nym-cli"
version = "1.1.2" version = "1.1.4"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
@@ -3160,7 +3160,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-client" name = "nym-client"
version = "1.1.2" version = "1.1.4"
dependencies = [ dependencies = [
"clap 3.2.8", "clap 3.2.8",
"client-connections", "client-connections",
@@ -3199,7 +3199,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-gateway" name = "nym-gateway"
version = "1.1.2" version = "1.1.4"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -3246,7 +3246,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-mixnode" name = "nym-mixnode"
version = "1.1.2" version = "1.1.4"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bs58", "bs58",
@@ -3288,7 +3288,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-network-requester" name = "nym-network-requester"
version = "1.1.2" version = "1.1.4"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"clap 3.2.8", "clap 3.2.8",
@@ -3320,7 +3320,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-network-statistics" name = "nym-network-statistics"
version = "1.1.2" version = "1.1.4"
dependencies = [ dependencies = [
"dirs", "dirs",
"log", "log",
@@ -3336,7 +3336,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-socks5-client" name = "nym-socks5-client"
version = "1.1.2" version = "1.1.4"
dependencies = [ dependencies = [
"clap 3.2.8", "clap 3.2.8",
"client-connections", "client-connections",
@@ -3403,7 +3403,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-validator-api" name = "nym-validator-api"
version = "1.1.2" version = "1.1.4"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -12,10 +12,11 @@ use validator_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
}; };
use validator_api_requests::models::{ use validator_api_requests::models::{
GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse, ComputeRewardEstParam, GatewayCoreStatusResponse, GatewayStatusReportResponse,
InclusionProbabilityResponse, MixNodeBondAnnotated, MixnodeCoreStatusResponse, GatewayUptimeHistoryResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
MixnodeStatusReportResponse, MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RequestError, MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse,
RewardEstimationResponse, StakeSaturationResponse, UptimeResponse, MixnodeUptimeHistoryResponse, RequestError, RewardEstimationResponse, StakeSaturationResponse,
UptimeResponse,
}; };
pub mod error; pub mod error;
@@ -365,6 +366,25 @@ impl Client {
.await .await
} }
pub async fn compute_mixnode_reward_estimation(
&self,
mix_id: MixId,
request_body: &ComputeRewardEstParam,
) -> Result<RewardEstimationResponse, ValidatorAPIError> {
self.post_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
&mix_id.to_string(),
routes::COMPUTE_REWARD_ESTIMATION,
],
NO_PARAMS,
request_body,
)
.await
}
pub async fn get_mixnode_stake_saturation( pub async fn get_mixnode_stake_saturation(
&self, &self,
mix_id: MixId, mix_id: MixId,
@@ -28,6 +28,7 @@ pub const STATUS: &str = "status";
pub const REPORT: &str = "report"; pub const REPORT: &str = "report";
pub const HISTORY: &str = "history"; pub const HISTORY: &str = "history";
pub const REWARD_ESTIMATION: &str = "reward-estimation"; pub const REWARD_ESTIMATION: &str = "reward-estimation";
pub const COMPUTE_REWARD_ESTIMATION: &str = "compute-reward-estimation";
pub const AVG_UPTIME: &str = "avg_uptime"; pub const AVG_UPTIME: &str = "avg_uptime";
pub const STAKE_SATURATION: &str = "stake-saturation"; pub const STAKE_SATURATION: &str = "stake-saturation";
pub const INCLUSION_CHANCE: &str = "inclusion-probability"; pub const INCLUSION_CHANCE: &str = "inclusion-probability";
+1 -2
View File
@@ -16,8 +16,7 @@ impl AddDefaultSubmenus for Menu {
fn add_default_app_submenus(self) -> Self { fn add_default_app_submenus(self) -> Self {
let submenu = Submenu::new( let submenu = Submenu::new(
"Help", "Help",
Menu::new() Menu::new().add_item(CustomMenuItem::new(SHOW_LOG_WINDOW, "Show logs")),
.add_item(CustomMenuItem::new(SHOW_LOG_WINDOW, "Show logs"))
); );
self.add_submenu(submenu) self.add_submenu(submenu)
} }
+1 -1
View File
@@ -2862,7 +2862,7 @@ dependencies = [
[[package]] [[package]]
name = "nym_wallet" name = "nym_wallet"
version = "1.1.2" version = "1.1.4"
dependencies = [ dependencies = [
"aes-gcm", "aes-gcm",
"argon2 0.3.4", "argon2 0.3.4",
+3
View File
@@ -83,7 +83,9 @@ fn main() {
mixnet::rewards::claim_delegator_reward, mixnet::rewards::claim_delegator_reward,
mixnet::rewards::claim_operator_reward, mixnet::rewards::claim_operator_reward,
mixnet::rewards::claim_locked_and_unlocked_delegator_reward, mixnet::rewards::claim_locked_and_unlocked_delegator_reward,
mixnet::rewards::get_current_rewarding_parameters,
mixnet::send::send, mixnet::send::send,
mixnet::bond::get_mixnode_uptime,
network_config::add_validator, network_config::add_validator,
network_config::get_validator_api_urls, network_config::get_validator_api_urls,
network_config::get_validator_nymd_urls, network_config::get_validator_nymd_urls,
@@ -99,6 +101,7 @@ fn main() {
utils::get_old_and_incorrect_hardcoded_fee, utils::get_old_and_incorrect_hardcoded_fee,
utils::try_convert_pubkey_to_mix_id, utils::try_convert_pubkey_to_mix_id,
utils::default_mixnode_cost_params, utils::default_mixnode_cost_params,
validator_api::status::compute_mixnode_reward_estimation,
validator_api::status::gateway_core_node_status, validator_api::status::gateway_core_node_status,
validator_api::status::mixnode_core_node_status, validator_api::status::mixnode_core_node_status,
validator_api::status::mixnode_inclusion_probability, validator_api::status::mixnode_inclusion_probability,
@@ -341,3 +341,18 @@ pub async fn get_mix_node_description(
.json() .json()
.await?) .await?)
} }
#[tauri::command]
pub async fn get_mixnode_uptime(
mix_id: MixId,
state: tauri::State<'_, WalletState>,
) -> Result<u8, BackendError> {
log::info!(">>> Get mixnode uptime");
let guard = state.read().await;
let client = guard.current_client()?;
let uptime = client.validator_api.get_mixnode_avg_uptime(mix_id).await?;
log::info!(">>> Uptime response: {}", uptime.avg_uptime);
Ok(uptime.avg_uptime)
}
@@ -1,7 +1,7 @@
use crate::error::BackendError; use crate::error::BackendError;
use crate::state::WalletState; use crate::state::WalletState;
use crate::vesting::rewards::vesting_claim_delegator_reward; use crate::vesting::rewards::vesting_claim_delegator_reward;
use mixnet_contract_common::MixId; use mixnet_contract_common::{MixId, RewardingParams};
use nym_types::transaction::TransactionExecuteResult; use nym_types::transaction::TransactionExecuteResult;
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient}; use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
use validator_client::nymd::Fee; use validator_client::nymd::Fee;
@@ -95,3 +95,17 @@ pub async fn claim_locked_and_unlocked_delegator_reward(
log::trace!("<<< {:?}", res); log::trace!("<<< {:?}", res);
Ok(res) Ok(res)
} }
#[tauri::command]
pub async fn get_current_rewarding_parameters(
state: tauri::State<'_, WalletState>,
) -> Result<RewardingParams, BackendError> {
log::info!(">>> Get current rewarding params",);
let guard = state.read().await;
let client = guard.current_client()?;
let reward_params = client.nymd.get_rewarding_parameters().await?;
Ok(reward_params)
}
@@ -4,11 +4,11 @@
use crate::api_client; use crate::api_client;
use crate::error::BackendError; use crate::error::BackendError;
use crate::state::WalletState; use crate::state::WalletState;
use mixnet_contract_common::{IdentityKeyRef, MixId}; use mixnet_contract_common::{reward_params::Performance, Coin, IdentityKeyRef, MixId, Percent};
use validator_client::models::{ use validator_client::models::{
GatewayCoreStatusResponse, GatewayStatusReportResponse, InclusionProbabilityResponse, ComputeRewardEstParam, GatewayCoreStatusResponse, GatewayStatusReportResponse,
MixnodeCoreStatusResponse, MixnodeStatusResponse, RewardEstimationResponse, InclusionProbabilityResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
StakeSaturationResponse, RewardEstimationResponse, StakeSaturationResponse,
}; };
#[tauri::command] #[tauri::command]
@@ -59,6 +59,29 @@ pub async fn mixnode_reward_estimation(
.await?) .await?)
} }
#[tauri::command]
pub async fn compute_mixnode_reward_estimation(
mix_id: u32,
performance: Option<Performance>,
pledge_amount: Option<u64>,
total_delegation: Option<u64>,
interval_operating_cost: Option<Coin>,
profit_margin_percent: Option<Percent>,
state: tauri::State<'_, WalletState>,
) -> Result<RewardEstimationResponse, BackendError> {
let request_body = ComputeRewardEstParam {
performance,
active_in_rewarded_set: Some(true),
pledge_amount,
total_delegation,
interval_operating_cost,
profit_margin_percent,
};
Ok(api_client!(state)
.compute_mixnode_reward_estimation(mix_id, &request_body)
.await?)
}
#[tauri::command] #[tauri::command]
pub async fn mixnode_stake_saturation( pub async fn mixnode_stake_saturation(
mix_id: MixId, mix_id: MixId,
+1 -1
View File
@@ -15,7 +15,7 @@ export const AppBar = () => {
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<MuiAppBar position="sticky" sx={{ boxShadow: 'none', bgcolor: 'transparent', backgroundImage: 'none', pt: 3 }}> <MuiAppBar position="sticky" sx={{ boxShadow: 'none', bgcolor: 'transparent', backgroundImage: 'none', mt: 3 }}>
<Toolbar disableGutters> <Toolbar disableGutters>
<Grid container justifyContent="space-between" alignItems="center" flexWrap="nowrap"> <Grid container justifyContent="space-between" alignItems="center" flexWrap="nowrap">
<Grid item container alignItems="center" spacing={1}> <Grid item container alignItems="center" spacing={1}>
@@ -0,0 +1,19 @@
import React from 'react';
import { Typography } from '@mui/material';
import { SelectionChance } from '@nymproject/types';
const colorMap: { [key in SelectionChance]: string } = {
Low: 'error.main',
Good: 'warning.main',
High: 'success.main',
};
const textMap: { [key in SelectionChance]: string } = {
Low: 'Low',
Good: 'Good',
High: 'High',
};
export const InclusionProbability = ({ probability }: { probability: SelectionChance }) => (
<Typography sx={{ color: colorMap[probability] }}>{textMap[probability]}</Typography>
);
@@ -0,0 +1,91 @@
import React, { useCallback } from 'react';
import { yupResolver } from '@hookform/resolvers/yup';
import { Button, Grid, TextField, Typography } from '@mui/material';
import { useForm } from 'react-hook-form';
import { DefaultInputValues } from 'src/pages/bonding/node-settings/apy-playground';
import { inputValidationSchema } from './inputsValidationSchema';
export type InputFields = {
label: string;
name: 'profitMargin' | 'uptime' | 'bond' | 'delegations' | 'operatorCost' | 'uptime';
isPercentage?: boolean;
}[];
export type CalculateArgs = {
bond: string;
delegations: string;
uptime: string;
profitMargin: string;
operatorCost: string;
};
const inputFields: InputFields = [
{ label: 'Profit margin', name: 'profitMargin', isPercentage: true },
{ label: 'Operator cost', name: 'operatorCost' },
{ label: 'Bond', name: 'bond' },
{ label: 'Delegations', name: 'delegations' },
{ label: 'Uptime', name: 'uptime', isPercentage: true },
];
export const Inputs = ({
onCalculate,
defaultValues,
}: {
onCalculate: (args: CalculateArgs) => Promise<void>;
defaultValues: DefaultInputValues;
}) => {
const handleCalculate = useCallback(
async (args: CalculateArgs) => {
onCalculate({
bond: args.bond,
delegations: args.delegations,
uptime: args.uptime,
profitMargin: args.profitMargin,
operatorCost: args.operatorCost,
});
},
[onCalculate],
);
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: yupResolver(inputValidationSchema),
defaultValues,
});
return (
<Grid container spacing={3}>
{inputFields.map((field) => (
<Grid item xs={12} lg={2} key={field.name}>
<TextField
{...register(field.name)}
fullWidth
label={field.label}
name={field.name}
error={Boolean(errors[field.name])}
helperText={errors[field.name]?.message}
InputProps={{
endAdornment: <Typography sx={{ color: 'grey.600' }}>{field.isPercentage ? '%' : 'NYM'}</Typography>,
}}
InputLabelProps={{ shrink: true }}
/>
</Grid>
))}{' '}
<Grid item xs={12} lg={2}>
<Button
variant="contained"
disableElevation
onClick={handleSubmit(handleCalculate)}
size="large"
fullWidth
disabled={Boolean(Object.keys(errors).length)}
>
Calculate
</Button>
</Grid>
</Grid>
);
};
@@ -0,0 +1,32 @@
import React from 'react';
import { Card, CardContent, Divider, Stack, Typography } from '@mui/material';
import { SelectionChance } from '@nymproject/types';
import { InclusionProbability } from './InclusionProbability';
const computeSelectionProbability = (saturation: number): SelectionChance => {
if (saturation < 5) return 'Low';
if (saturation > 5 && saturation < 15) return 'Good';
return 'High';
};
export const NodeDetails = ({ saturation }: { saturation?: string }) => {
if (!saturation) return null;
return (
<Card variant="outlined" sx={{ p: 1 }}>
<CardContent>
<Stack direction="row" justifyContent="space-between">
<Typography fontWeight="medium">Stake saturation</Typography>
<Typography>{saturation || '- '}%</Typography>
</Stack>
<Divider sx={{ my: 1 }} />
<Stack direction="row" justifyContent="space-between">
<Typography fontWeight="medium">Selection probability</Typography>
<InclusionProbability probability={computeSelectionProbability(parseInt(saturation))} />
</Stack>
</CardContent>
</Card>
);
};
@@ -0,0 +1,75 @@
import React from 'react';
import {
Card,
CardContent,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography,
} from '@mui/material';
export type Results = {
operator: {
daily: string;
monthly: string;
yearly: string;
};
delegator: {
daily: string;
monthly: string;
yearly: string;
};
total: {
daily: string;
monthly: string;
yearly: string;
};
};
const tableHeader = [
{ title: 'Estimated rewards', bold: true },
{ title: 'Per day' },
{ title: 'Per month' },
{ title: 'Per year' },
];
export const ResultsTable = ({ results }: { results: Results }) => {
const tableRows = [
{ title: 'Total node reward', ...results.total },
{ title: 'Operator rewards', ...results.operator },
{ title: 'Delegator rewards', ...results.delegator },
];
return (
<Card variant="outlined" sx={{ p: 1 }}>
<CardContent>
<TableContainer>
<Table>
<TableHead>
<TableRow>
{tableHeader.map((header) => (
<TableCell>
<Typography fontWeight={header.bold ? 'bold' : 'regular'}>{header.title}</Typography>
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{tableRows.map((row) => (
<TableRow>
<TableCell>{row.title}</TableCell>
<TableCell>{row.daily}</TableCell>
<TableCell>{row.monthly}</TableCell>
<TableCell>{row.yearly}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
);
};
@@ -0,0 +1,41 @@
import * as Yup from 'yup';
import { isGreaterThan, isLessThan } from 'src/utils';
export const inputValidationSchema = Yup.object().shape({
profitMargin: Yup.string()
.required('profit margin is a required field')
.test('Is valid profit margin value', (value, ctx) => {
const stringValueToNumber = Math.round(Number(value));
if (isGreaterThan(stringValueToNumber, -1) && isLessThan(stringValueToNumber, 101)) return true;
return ctx.createError({ message: 'Profit margin must be a number from 0 and 100' });
}),
uptime: Yup.string()
.required()
.test('Is valid uptime value', (value, ctx) => {
const stringValueToNumber = Math.round(Number(value));
if (stringValueToNumber && isGreaterThan(stringValueToNumber, 0) && isLessThan(stringValueToNumber, 101))
return true;
return ctx.createError({ message: 'Uptime must be a number between 0 and 100' });
}),
bond: Yup.string()
.required()
.test('Is valid bond value', (value, ctx) => {
if (Number(value)) return true;
return ctx.createError({ message: 'Bond must be a valid number' });
}),
delegations: Yup.string()
.required()
.test('Is valid delegation value', (value, ctx) => {
if (Number(value)) return true;
return ctx.createError({ message: 'Delegations must be a valid number' });
}),
operatorCost: Yup.string()
.required('operator cost is a required field')
.test('Is valid operator cost value', (value, ctx) => {
const stringValueToNumber = Math.round(Number(value));
if (isGreaterThan(stringValueToNumber, -1) && isLessThan(stringValueToNumber, 101)) return true;
return ctx.createError({ message: 'Operator cost must be a valid number' });
}),
});
+12 -3
View File
@@ -24,7 +24,6 @@ import {
vestingBondMixNode, vestingBondMixNode,
vestingUnbondGateway, vestingUnbondGateway,
vestingUnbondMixnode, vestingUnbondMixnode,
getPendingEpochEvents,
updateMixnodeCostParams as updateMixnodeCostParamsRequest, updateMixnodeCostParams as updateMixnodeCostParamsRequest,
vestingUpdateMixnodeCostParams as updateMixnodeVestingCostParamsRequest, vestingUpdateMixnodeCostParams as updateMixnodeVestingCostParamsRequest,
getNodeDescription as getNodeDescriptionRequest, getNodeDescription as getNodeDescriptionRequest,
@@ -36,6 +35,7 @@ import {
getMixnodeAvgUptime, getMixnodeAvgUptime,
getMixnodeRewardEstimation, getMixnodeRewardEstimation,
getGatewayReport, getGatewayReport,
getMixnodeUptime,
} from '../requests'; } from '../requests';
import { useCheckOwnership } from '../hooks/useCheckOwnership'; import { useCheckOwnership } from '../hooks/useCheckOwnership';
import { AppContext } from './main'; import { AppContext } from './main';
@@ -71,10 +71,12 @@ export type TBondedMixnode = {
verlocPort: number; verlocPort: number;
version: string; version: string;
isUnbonding: boolean; isUnbonding: boolean;
uptime: number;
}; };
export interface TBondedGateway { export interface TBondedGateway {
name: string; name?: string;
id: number;
identityKey: string; identityKey: string;
ip: string; ip: string;
bond: DecCoin; bond: DecCoin;
@@ -154,14 +156,18 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
status: MixnodeStatus; status: MixnodeStatus;
stakeSaturation: string; stakeSaturation: string;
estimatedRewards?: DecCoin; estimatedRewards?: DecCoin;
uptime: number;
} = { } = {
status: 'not_found', status: 'not_found',
stakeSaturation: '0', stakeSaturation: '0',
uptime: 0,
}; };
try { try {
const statusResponse = await getMixnodeStatus(mixId); const statusResponse = await getMixnodeStatus(mixId);
const uptime = await getMixnodeUptime(mixId);
additionalDetails.status = statusResponse.status; additionalDetails.status = statusResponse.status;
additionalDetails.uptime = uptime;
} catch (e) { } catch (e) {
Console.log('getMixnodeStatus fails', e); Console.log('getMixnodeStatus fails', e);
} }
@@ -259,7 +265,8 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
rewarding_details, rewarding_details,
bond_information: { mix_id }, bond_information: { mix_id },
} = data; } = data;
const { status, stakeSaturation, estimatedRewards } = await getAdditionalMixnodeDetails(mix_id);
const { status, stakeSaturation, estimatedRewards, uptime } = await getAdditionalMixnodeDetails(mix_id);
const setProbabilities = await getSetProbabilities(mix_id); const setProbabilities = await getSetProbabilities(mix_id);
const nodeDescription = await getNodeDescription( const nodeDescription = await getNodeDescription(
bond_information.mix_node.host, bond_information.mix_node.host,
@@ -267,6 +274,7 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
); );
const routingScore = await getAvgUptime(); const routingScore = await getAvgUptime();
setBondedNode({ setBondedNode({
id: data.bond_information.mix_id,
name: nodeDescription?.name, name: nodeDescription?.name,
mixId: mix_id, mixId: mix_id,
identityKey: bond_information.mix_node.identity_key, identityKey: bond_information.mix_node.identity_key,
@@ -279,6 +287,7 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
delegators: rewarding_details.unique_delegations, delegators: rewarding_details.unique_delegations,
proxy: bond_information.proxy, proxy: bond_information.proxy,
operatorRewards, operatorRewards,
uptime,
status, status,
stakeSaturation, stakeSaturation,
operatorCost: decCoinToDisplay(rewarding_details.cost_params.interval_operating_cost), operatorCost: decCoinToDisplay(rewarding_details.cost_params.interval_operating_cost),
+3 -1
View File
@@ -7,8 +7,8 @@ import { mockSleep } from './utils';
const SLEEP_MS = 1000; const SLEEP_MS = 1000;
const bondedMixnodeMock: TBondedMixnode = { const bondedMixnodeMock: TBondedMixnode = {
name: 'Monster node',
mixId: 1, mixId: 1,
name: 'Monster node',
identityKey: '7mjM2fYbtN6kxMwp1TrmQ4VwPks3URR5pBgWPWhzT98F', identityKey: '7mjM2fYbtN6kxMwp1TrmQ4VwPks3URR5pBgWPWhzT98F',
stake: { denom: 'nym', amount: '1234' }, stake: { denom: 'nym', amount: '1234' },
bond: { denom: 'nym', amount: '1234' }, bond: { denom: 'nym', amount: '1234' },
@@ -28,9 +28,11 @@ const bondedMixnodeMock: TBondedMixnode = {
verlocPort: 1790, verlocPort: 1790,
version: '1.0.2', version: '1.0.2',
isUnbonding: false, isUnbonding: false,
uptime: 1,
}; };
const bondedGatewayMock: TBondedGateway = { const bondedGatewayMock: TBondedGateway = {
id: 1,
name: 'Monster node', name: 'Monster node',
identityKey: 'WayM2fYbtN6kxMwp1TrmQ4VwPks3URR5pBgWPWhzT98F', identityKey: 'WayM2fYbtN6kxMwp1TrmQ4VwPks3URR5pBgWPWhzT98F',
ip: '112.43.234.57', ip: '112.43.234.57',
@@ -10,13 +10,14 @@ import { LoadingModal } from 'src/components/Modals/LoadingModal';
import { NymCard } from 'src/components'; import { NymCard } from 'src/components';
import { PageLayout } from 'src/layouts'; import { PageLayout } from 'src/layouts';
import { Tabs } from 'src/components/Tabs'; import { Tabs } from 'src/components/Tabs';
import { useBondingContext, BondingContextProvider } from 'src/context'; import { useBondingContext, BondingContextProvider, TBondedMixnode } from 'src/context';
import { AppContext, urls } from 'src/context/main'; import { AppContext, urls } from 'src/context/main';
import { NodeGeneralSettings } from './settings-pages/general-settings'; import { NodeGeneralSettings } from './settings-pages/general-settings';
import { NodeUnbondPage } from './settings-pages/NodeUnbondPage'; import { NodeUnbondPage } from './settings-pages/NodeUnbondPage';
import { createNavItems } from './node-settings.constant'; import { createNavItems } from './node-settings.constant';
import { isMixnode } from 'src/types'; import { isMixnode } from 'src/types';
import { ApyPlayground } from './apy-playground';
export const NodeSettings = () => { export const NodeSettings = () => {
const theme = useTheme(); const theme = useTheme();
@@ -123,6 +124,7 @@ export const NodeSettings = () => {
{value === 'Unbond' && bondedNode && ( {value === 'Unbond' && bondedNode && (
<NodeUnbondPage bondedNode={bondedNode} onConfirm={handleUnbond} onError={handleError} /> <NodeUnbondPage bondedNode={bondedNode} onConfirm={handleUnbond} onError={handleError} />
)} )}
{value === 'Playground' && bondedNode && <ApyPlayground bondedNode={bondedNode as TBondedMixnode} />}
{confirmationDetails && confirmationDetails.status === 'success' && ( {confirmationDetails && confirmationDetails.status === 'success' && (
<ConfirmationDetailsModal <ConfirmationDetailsModal
title={confirmationDetails.title} title={confirmationDetails.title}
@@ -0,0 +1,139 @@
import React, { useState, useEffect, useContext } from 'react';
import { Box, Card, CardContent, CardHeader, Grid, Typography } from '@mui/material';
import { decimalToPercentage, percentToDecimal } from '@nymproject/types';
import { ResultsTable } from 'src/components/RewardsPlayground/ResultsTable';
import { getDelegationSummary } from 'src/requests';
import { NodeDetails } from 'src/components/RewardsPlayground/NodeDetail';
import { Inputs, CalculateArgs } from 'src/components/RewardsPlayground/Inputs';
import { AppContext, TBondedMixnode } from 'src/context';
import { computeEstimate, computeStakeSaturation, handleCalculatePeriodRewards } from './utils';
import { useSnackbar } from 'notistack';
import { LoadingModal } from 'src/components/Modals/LoadingModal';
export type DefaultInputValues = {
profitMargin: string;
uptime: string;
bond: string;
delegations: string;
operatorCost: string;
};
export const ApyPlayground = ({ bondedNode }: { bondedNode: TBondedMixnode }) => {
const { enqueueSnackbar } = useSnackbar();
const [results, setResults] = useState({
total: { daily: '-', monthly: '-', yearly: '-' },
operator: { daily: '-', monthly: '-', yearly: '-' },
delegator: { daily: '-', monthly: '-', yearly: '-' },
});
const [defaultInputValues, setDefaultInputValues] = useState<DefaultInputValues>();
const [stakeSaturation, setStakeSaturation] = useState<string>();
const [isLoading, setIsLoading] = useState(true);
const initialise = async (node: TBondedMixnode) => {
try {
const delegations = await getDelegationSummary();
const { estimation } = await computeEstimate({
mixId: node.mixId,
uptime: node.uptime.toString(),
profitMargin: node.profitMargin,
operatorCost: node.operatorCost.amount,
totalDelegation: delegations.total_delegations.amount,
pledgeAmount: node.bond.amount,
});
setResults(
handleCalculatePeriodRewards({
estimatedOperatorReward: estimation.operator,
estimatedDelegatorsReward: estimation.delegates,
}),
);
setStakeSaturation(node.stakeSaturation);
setDefaultInputValues({
profitMargin: node.profitMargin,
uptime: (node.uptime || 0).toString(),
bond: node.bond.amount || '',
delegations: delegations.total_delegations.amount,
operatorCost: node.operatorCost.amount,
});
setIsLoading(false);
} catch (e) {
enqueueSnackbar(e as string, { variant: 'error' });
}
};
useEffect(() => {
if (bondedNode) {
initialise(bondedNode);
}
}, []);
if (isLoading) return <LoadingModal />;
const handleCalculateEstimate = async ({ bond, delegations, uptime, profitMargin, operatorCost }: CalculateArgs) => {
try {
const { estimation, reward_params } = await computeEstimate({
mixId: bondedNode.mixId,
uptime: uptime,
profitMargin: profitMargin,
operatorCost: operatorCost,
totalDelegation: delegations,
pledgeAmount: bond,
});
const estimationResult = handleCalculatePeriodRewards({
estimatedOperatorReward: estimation.operator,
estimatedDelegatorsReward: estimation.delegates,
});
const computedStakeSaturation = computeStakeSaturation(
bond,
delegations,
reward_params.interval.stake_saturation_point,
);
setStakeSaturation(computedStakeSaturation);
setResults(estimationResult);
} catch (e) {
console.log(e);
}
};
return (
<Box sx={{ p: 3 }}>
<Typography fontWeight="medium" sx={{ mb: 1 }}>
Playground
</Typography>
<Typography variant="body2" sx={{ color: 'grey.600', mb: 2 }}>
This is your parameters playground - change the parameters below to see the node specific estimations in the
table
</Typography>
{defaultInputValues && (
<Card variant="outlined" sx={{ p: 1, mb: 3 }}>
<CardHeader
title={
<Typography variant="body2" fontWeight="medium">
Estimation calculator
</Typography>
}
/>
<CardContent>
<Inputs onCalculate={handleCalculateEstimate} defaultValues={defaultInputValues} />
</CardContent>
</Card>
)}
<Grid container spacing={3}>
<Grid item xs={12} md={8}>
<ResultsTable results={results} />
</Grid>
<Grid item xs={12} md={4}>
<NodeDetails saturation={stakeSaturation} />
</Grid>
</Grid>
</Box>
);
};
@@ -0,0 +1,66 @@
import { decimalToPercentage, percentToDecimal } from '@nymproject/types';
import { computeMixnodeRewardEstimation } from 'src/requests';
const SCALE_FACTOR = 1_000_000;
export const computeStakeSaturation = (bond: string, delegations: string, stakeSaturationPoint: string) => {
const res = ((+bond + +delegations) * SCALE_FACTOR) / +stakeSaturationPoint;
return decimalToPercentage(res.toFixed(18).toString());
};
export const computeEstimate = async ({
mixId,
uptime,
pledgeAmount,
totalDelegation,
profitMargin,
operatorCost,
}: {
mixId: number;
uptime: string;
pledgeAmount: string;
totalDelegation: string;
profitMargin: string;
operatorCost: string;
}) => {
const computedEstimate = await computeMixnodeRewardEstimation({
mixId: mixId,
performance: percentToDecimal(uptime),
pledgeAmount: Math.round(+pledgeAmount * SCALE_FACTOR),
totalDelegation: Math.round(+totalDelegation * SCALE_FACTOR),
profitMarginPercent: percentToDecimal(profitMargin),
intervalOperatingCost: { denom: 'unym', amount: Math.round(+operatorCost * SCALE_FACTOR).toString() },
});
return computedEstimate;
};
export const handleCalculatePeriodRewards = ({
estimatedOperatorReward,
estimatedDelegatorsReward,
}: {
estimatedOperatorReward: string;
estimatedDelegatorsReward: string;
}) => {
const dailyOperatorReward = (+estimatedOperatorReward / SCALE_FACTOR) * 24; // epoch_reward * 1 epoch_per_hour * 24 hours
const dailyDelegatorReward = (+estimatedDelegatorsReward / SCALE_FACTOR) * 24;
const dailyTotal = dailyOperatorReward + dailyDelegatorReward;
return {
total: {
daily: dailyTotal.toFixed(3).toString(),
monthly: (dailyTotal * 30).toFixed(3).toString(),
yearly: (dailyTotal * 365).toFixed(3).toString(),
},
operator: {
daily: dailyOperatorReward.toFixed(3).toString(),
monthly: (dailyOperatorReward * 30).toFixed(3).toString(),
yearly: (dailyOperatorReward * 365).toFixed(3).toString(),
},
delegator: {
daily: dailyDelegatorReward.toFixed(3).toString(),
monthly: (dailyDelegatorReward * 30).toFixed(3).toString(),
yearly: (dailyDelegatorReward * 365).toFixed(3).toString(),
},
};
};
@@ -1,5 +1,5 @@
export const createNavItems = (isMixnode: boolean) => { export const createNavItems = (isMixnode: boolean) => {
const navItems = ['Unbond']; const navItems = ['Unbond'];
if (isMixnode) return ['General', ...navItems]; if (isMixnode) return ['General', 'Playground', ...navItems];
return navItems; return navItems;
}; };
+15
View File
@@ -8,6 +8,7 @@ import {
RewardEstimationResponse, RewardEstimationResponse,
WrappedDelegationEvent, WrappedDelegationEvent,
PendingIntervalEvent, PendingIntervalEvent,
Coin,
} from '@nymproject/types'; } from '@nymproject/types';
import { Interval, TGatewayReport, TNodeDescription } from 'src/types'; import { Interval, TGatewayReport, TNodeDescription } from 'src/types';
import { invokeWrapper } from './wrapper'; import { invokeWrapper } from './wrapper';
@@ -51,3 +52,17 @@ export const getPendingIntervalEvents = async () =>
export const getGatewayReport = async (identity: string) => export const getGatewayReport = async (identity: string) =>
invokeWrapper<TGatewayReport>('gateway_report', { identity }); invokeWrapper<TGatewayReport>('gateway_report', { identity });
export const computeMixnodeRewardEstimation = async (args: {
mixId: number;
performance: string;
pledgeAmount: number;
totalDelegation: number;
profitMarginPercent: string;
intervalOperatingCost: { denom: 'unym'; amount: string };
}) => {
console.log(args);
return invokeWrapper<RewardEstimationResponse>('compute_mixnode_reward_estimation', args);
};
export const getMixnodeUptime = async (mixId: number) => invokeWrapper<number>('get_mixnode_uptime', { mixId });
+4 -1
View File
@@ -1,4 +1,4 @@
import { Fee, FeeDetails, TransactionExecuteResult } from '@nymproject/types'; import { Fee, FeeDetails, RewardingParams, TransactionExecuteResult } from '@nymproject/types';
import { invokeWrapper } from './wrapper'; import { invokeWrapper } from './wrapper';
export const claimOperatorReward = async (fee?: Fee) => export const claimOperatorReward = async (fee?: Fee) =>
@@ -9,3 +9,6 @@ export const claimDelegatorRewards = async (mixId: number, fee?: FeeDetails) =>
mixId, mixId,
fee: fee?.fee, fee: fee?.fee,
}); });
export const getCurrentRewardingParameter = async () =>
invokeWrapper<RewardingParams>('get_current_rewarding_parameters', {});
+5 -2
View File
@@ -1,2 +1,5 @@
export interface AppEnv {
export interface AppEnv { ADMIN_ADDRESS: string | null, SHOW_TERMINAL: string | null, ENABLE_QA_MODE: string | null, } ADMIN_ADDRESS: string | null;
SHOW_TERMINAL: string | null;
ENABLE_QA_MODE: string | null;
}
+1 -2
View File
@@ -1,2 +1 @@
export type Network = 'QA' | 'SANDBOX' | 'MAINNET';
export type Network = "QA" | "SANDBOX" | "MAINNET";
+6 -2
View File
@@ -1,3 +1,7 @@
import type { DecCoin } from "../../../../ts-packages/types/src/types/rust/DecCoin"; import type { DecCoin } from '../../../../ts-packages/types/src/types/rust/DecCoin';
export interface TauriContractStateParams { minimum_mixnode_pledge: DecCoin, minimum_gateway_pledge: DecCoin, minimum_mixnode_delegation: DecCoin | null, } export interface TauriContractStateParams {
minimum_mixnode_pledge: DecCoin;
minimum_gateway_pledge: DecCoin;
minimum_mixnode_delegation: DecCoin | null;
}
+4 -2
View File
@@ -1,2 +1,4 @@
export interface ValidatorUrl {
export interface ValidatorUrl { url: string, name: string | null, } url: string;
name: string | null;
}
+4 -2
View File
@@ -1,3 +1,5 @@
import type { ValidatorUrl } from "./ValidatorUrl"; import type { ValidatorUrl } from './ValidatorUrl';
export interface ValidatorUrls { urls: Array<ValidatorUrl>, } export interface ValidatorUrls {
urls: Array<ValidatorUrl>;
}
+1
View File
@@ -84,6 +84,7 @@ export const getDesignTokens = (mode: PaletteMode): ThemeOptions => {
].join(','), ].join(','),
fontSize: 14, fontSize: 14,
fontWeightRegular: 500, fontWeightRegular: 500,
fontWeightMedium: 600,
button: { button: {
textTransform: 'none', textTransform: 'none',
fontWeight: '600', fontWeight: '600',
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { CurrencyDenom } from './CurrencyDenom'; import type { CurrencyDenom } from './CurrencyDenom';
export interface Account { export interface Account {
@@ -1,3 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export interface AccountEntry { export interface AccountEntry {
id: string; id: string;
address: string; address: string;
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Account } from './Account'; import type { Account } from './Account';
export interface AccountWithMnemonic { export interface AccountWithMnemonic {
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DecCoin } from './DecCoin'; import type { DecCoin } from './DecCoin';
export interface Balance { export interface Balance {
+2
View File
@@ -1,3 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export interface Coin { export interface Coin {
denom: string; denom: string;
amount: bigint; amount: bigint;
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Coin } from './Coin'; import type { Coin } from './Coin';
export interface CosmosFee { export interface CosmosFee {
@@ -1 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type CurrencyDenom = 'unknown' | 'nym' | 'nymt' | 'nyx' | 'nyxt'; export type CurrencyDenom = 'unknown' | 'nym' | 'nymt' | 'nyx' | 'nyxt';
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { CurrencyDenom } from './CurrencyDenom'; import type { CurrencyDenom } from './CurrencyDenom';
export type DecCoin = { denom: CurrencyDenom; amount: string }; export type DecCoin = { denom: CurrencyDenom; amount: string };
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DecCoin } from './DecCoin'; import type { DecCoin } from './DecCoin';
export interface Delegation { export interface Delegation {
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DecCoin } from './DecCoin'; import type { DecCoin } from './DecCoin';
import type { DelegationEventKind } from './DelegationEventKind'; import type { DelegationEventKind } from './DelegationEventKind';
@@ -1 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type DelegationEventKind = 'Delegate' | 'Undelegate'; export type DelegationEventKind = 'Delegate' | 'Undelegate';
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DecCoin } from './DecCoin'; import type { DecCoin } from './DecCoin';
export interface DelegationResult { export interface DelegationResult {
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DecCoin } from './DecCoin'; import type { DecCoin } from './DecCoin';
import type { DelegationWithEverything } from './DelegationWithEverything'; import type { DelegationWithEverything } from './DelegationWithEverything';
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DecCoin } from './DecCoin'; import type { DecCoin } from './DecCoin';
import type { DelegationEvent } from './DelegationEvent'; import type { DelegationEvent } from './DelegationEvent';
import type { MixNodeCostParams } from './MixNodeCostParams'; import type { MixNodeCostParams } from './MixNodeCostParams';
+1
View File
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { CosmosFee } from './CosmosFee'; import type { CosmosFee } from './CosmosFee';
export type Fee = { Manual: CosmosFee } | { Auto: number | null }; export type Fee = { Manual: CosmosFee } | { Auto: number | null };
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DecCoin } from './DecCoin'; import type { DecCoin } from './DecCoin';
import type { Fee } from './Fee'; import type { Fee } from './Fee';
+2
View File
@@ -1,3 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export interface Gas { export interface Gas {
gas_units: bigint; gas_units: bigint;
} }
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Gas } from './Gas'; import type { Gas } from './Gas';
export interface GasInfo { export interface GasInfo {
@@ -1,3 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export interface Gateway { export interface Gateway {
host: string; host: string;
mix_port: number; mix_port: number;
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DecCoin } from './DecCoin'; import type { DecCoin } from './DecCoin';
import type { Gateway } from './Gateway'; import type { Gateway } from './Gateway';
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { SelectionChance } from './SelectionChance'; import type { SelectionChance } from './SelectionChance';
export interface InclusionProbabilityResponse { export interface InclusionProbabilityResponse {
+1 -1
View File
@@ -3,6 +3,6 @@ export interface Interval {
epochs_in_interval: number; epochs_in_interval: number;
current_epoch_start: string; current_epoch_start: string;
current_epoch_id: number; current_epoch_id: number;
epoch_length: string; epoch_length: { secs: number; nanos: number };
total_elapsed_epochs: number; total_elapsed_epochs: number;
} }
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DecCoin } from './DecCoin'; import type { DecCoin } from './DecCoin';
import type { MixNode } from './Mixnode'; import type { MixNode } from './Mixnode';
@@ -1,3 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export interface MixNode { export interface MixNode {
host: string; host: string;
mix_port: number; mix_port: number;
@@ -1 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type MixnodeStatus = 'active' | 'standby' | 'inactive' | 'not_found'; export type MixnodeStatus = 'active' | 'standby' | 'inactive' | 'not_found';
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { MixnodeStatus } from './MixnodeStatus'; import type { MixnodeStatus } from './MixnodeStatus';
export interface MixnodeStatusResponse { export interface MixnodeStatusResponse {
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DecCoin } from './DecCoin'; import type { DecCoin } from './DecCoin';
export interface OriginalVestingResponse { export interface OriginalVestingResponse {
@@ -1 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type Period = 'Before' | { In: number } | 'After'; export type Period = 'Before' | { In: number } | 'After';
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DecCoin } from './DecCoin'; import type { DecCoin } from './DecCoin';
export interface PledgeData { export interface PledgeData {
@@ -1 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type RewardedSetNodeStatus = 'Active' | 'Standby'; export type RewardedSetNodeStatus = 'Active' | 'Standby';
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DecCoin } from './DecCoin'; import type { DecCoin } from './DecCoin';
import type { Gas } from './Gas'; import type { Gas } from './Gas';
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DecCoin } from './DecCoin'; import type { DecCoin } from './DecCoin';
import type { Gas } from './Gas'; import type { Gas } from './Gas';
import type { TransactionDetails } from './TransactionDetails'; import type { TransactionDetails } from './TransactionDetails';
@@ -1,3 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export interface StakeSaturationResponse { export interface StakeSaturationResponse {
saturation: string; saturation: string;
uncapped_saturation: string; uncapped_saturation: string;
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DecCoin } from './DecCoin'; import type { DecCoin } from './DecCoin';
export interface TransactionDetails { export interface TransactionDetails {
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DecCoin } from './DecCoin'; import type { DecCoin } from './DecCoin';
import type { GasInfo } from './GasInfo'; import type { GasInfo } from './GasInfo';
@@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DecCoin } from './DecCoin'; import type { DecCoin } from './DecCoin';
import type { VestingPeriod } from './VestingPeriod'; import type { VestingPeriod } from './VestingPeriod';
@@ -1,3 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export interface VestingPeriod { export interface VestingPeriod {
start_time: bigint; start_time: bigint;
period_seconds: bigint; period_seconds: bigint;
@@ -10,5 +10,8 @@ export const stringToDecimal = (raw: string): Decimal => Decimal.fromUserInput(r
export const decimalToPercentage = (raw: string) => export const decimalToPercentage = (raw: string) =>
Math.round(Decimal.fromUserInput(raw, 18).toFloatApproximation() * 100).toString(); Math.round(Decimal.fromUserInput(raw, 18).toFloatApproximation() * 100).toString();
export const percentToDecimal = (raw: string) =>
(Decimal.fromUserInput(raw, 18).toFloatApproximation() / 100).toString();
export const decimalToFloatApproximation = (raw: string): number => export const decimalToFloatApproximation = (raw: string): number =>
Decimal.fromUserInput(raw, 18).toFloatApproximation(); Decimal.fromUserInput(raw, 18).toFloatApproximation();
@@ -114,7 +114,7 @@ impl MixNodeBondAnnotated {
} }
} }
#[derive(Deserialize, JsonSchema)] #[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ComputeRewardEstParam { pub struct ComputeRewardEstParam {
pub performance: Option<Performance>, pub performance: Option<Performance>,
pub active_in_rewarded_set: Option<bool>, pub active_in_rewarded_set: Option<bool>,