Compare commits

...

12 Commits

Author SHA1 Message Date
fmtabbara 43d6908d22 display loading modal for initial loading of values
run cargo fmt

fix clippy error

minor fixes
2022-12-19 09:38:00 +00:00
fmtabbara e10f291ee9 set active set to be always true 2022-12-19 09:38:00 +00:00
Jon Häggblad ecc89ced08 Make ComputeRewardEstParam derive Debug 2022-12-19 09:38:00 +00:00
fmtabbara 1e10c247bc calculate saturation
tidy

Use NodeId in compute_mixnode_reward_estimation

only call handleCalculation on button click

fix validation tests

tweak calculations

calculate stake saturation

pick up and display errors

pass profit margin and operator cost as args

rebase develop

rebase develop

fix profit margin validation

tidy up

refactor requests for rewards playground

wip
2022-12-19 09:38:00 +00:00
fmtabbara 4584f35a2a get mixnode reward estimation
separarte handleCalculate function into own file

add mix-id to bondedNode state

remove unused imports
2022-12-19 09:38:00 +00:00
fmtabbara 41fa03862e get node uptime 2022-12-19 09:38:00 +00:00
fmtabbara 66303d7ca4 init playground with default values
add more default values

env updates
2022-12-19 09:38:00 +00:00
fmtabbara 480f8a0a53 update validation for rewards playground 2022-12-19 09:38:00 +00:00
fmtabbara ecb0f11bbb add initial rewards calculation
run make file
2022-12-19 09:38:00 +00:00
fmtabbara fe223b5a60 apy playground ui
update calc button style

validator-api-client and wallet: compute mixnode reward estimation
2022-12-19 09:37:45 +00:00
fmtabbara b9e52d22d1 add print to pdf package 2022-12-19 09:36:56 +00:00
fmtabbara ecdf192b47 initial ui for test my node
use svg for node path

adjust layout for overiew page

add stories for test my node

remove placeholder nav item

add top margin to app bar

add print to pdf functionality for node test results
2022-12-19 09:36:56 +00:00
82 changed files with 1230 additions and 29 deletions
@@ -9,10 +9,11 @@ use nym_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
};
use nym_api_requests::models::{
GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse,
InclusionProbabilityResponse, MixNodeBondAnnotated, MixnodeCoreStatusResponse,
MixnodeStatusReportResponse, MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RequestError,
RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
ComputeRewardEstParam, GatewayCoreStatusResponse, GatewayStatusReportResponse,
GatewayUptimeHistoryResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse,
MixnodeUptimeHistoryResponse, RequestError, RewardEstimationResponse, StakeSaturationResponse,
UptimeResponse,
};
use reqwest::Response;
use serde::{Deserialize, Serialize};
@@ -361,6 +362,25 @@ impl Client {
.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(
&self,
mix_id: MixId,
@@ -28,6 +28,7 @@ pub const STATUS: &str = "status";
pub const REPORT: &str = "report";
pub const HISTORY: &str = "history";
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 STAKE_SATURATION: &str = "stake-saturation";
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
+1 -1
View File
@@ -114,7 +114,7 @@ impl MixNodeBondAnnotated {
}
}
#[derive(Deserialize, JsonSchema)]
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ComputeRewardEstParam {
pub performance: Option<Performance>,
pub active_in_rewarded_set: Option<bool>,
+1
View File
@@ -48,6 +48,7 @@
"react-hook-form": "^7.14.2",
"react-router-dom": "6",
"recharts": "^2.1.13",
"react-to-print": "^2.14.7",
"semver": "^6.3.0",
"string-to-color": "^2.2.2",
"use-clipboard-copy": "^0.2.0",
+3
View File
@@ -83,7 +83,9 @@ fn main() {
mixnet::rewards::claim_delegator_reward,
mixnet::rewards::claim_operator_reward,
mixnet::rewards::claim_locked_and_unlocked_delegator_reward,
mixnet::rewards::get_current_rewarding_parameters,
mixnet::send::send,
mixnet::bond::get_mixnode_uptime,
network_config::add_validator,
network_config::get_nym_api_urls,
network_config::get_validator_nymd_urls,
@@ -99,6 +101,7 @@ fn main() {
utils::get_old_and_incorrect_hardcoded_fee,
utils::try_convert_pubkey_to_mix_id,
utils::default_mixnode_cost_params,
nym_api::status::compute_mixnode_reward_estimation,
nym_api::status::gateway_core_node_status,
nym_api::status::mixnode_core_node_status,
nym_api::status::mixnode_inclusion_probability,
@@ -341,3 +341,18 @@ pub async fn get_mix_node_description(
.json()
.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::state::WalletState;
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 validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
use validator_client::nymd::Fee;
@@ -95,3 +95,17 @@ pub async fn claim_locked_and_unlocked_delegator_reward(
log::trace!("<<< {:?}", 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::error::BackendError;
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::{
GatewayCoreStatusResponse, GatewayStatusReportResponse, InclusionProbabilityResponse,
MixnodeCoreStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
StakeSaturationResponse,
ComputeRewardEstParam, GatewayCoreStatusResponse, GatewayStatusReportResponse,
InclusionProbabilityResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
RewardEstimationResponse, StakeSaturationResponse,
};
#[tauri::command]
@@ -59,6 +59,29 @@ pub async fn mixnode_reward_estimation(
.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]
pub async fn mixnode_stake_saturation(
mix_id: MixId,
Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

+1 -1
View File
@@ -15,7 +15,7 @@ export const AppBar = () => {
const navigate = useNavigate();
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>
<Grid container justifyContent="space-between" alignItems="center" flexWrap="nowrap">
<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' });
}),
});
@@ -0,0 +1,40 @@
import React from 'react';
import { Button, Grid, Stack } from '@mui/material';
import testNode from 'src/assets/test-node-illustration.jpg';
import { OverviewDescription } from '../components/OverviewDescription';
const content = [
{
title: 'How is works',
description:
'This is your APY playground - play with the parameters on left to see estimated rewards on the right side',
},
{
title: 'Test path',
description:
'This is your APY playground - play with the parameters on left to see estimated rewards on the right side',
},
{
title: 'Results',
description:
'This is your APY playground - play with the parameters on left to see estimated rewards on the right side',
},
];
export const Overview = ({ onStartTest }: { onStartTest: () => void }) => (
<Grid container spacing={2} justifyContent="center">
<Grid item xs={12} md={6}>
<img src={testNode} style={{ borderRadius: 8 }} />
</Grid>
<Grid item container direction="column" xs={12} xl={6}>
<Grid item>
<Stack>{content.map(OverviewDescription)}</Stack>
</Grid>
<Grid item>
<Button variant="contained" fullWidth disableElevation onClick={onStartTest}>
Start test
</Button>
</Grid>
</Grid>
</Grid>
);
@@ -0,0 +1,27 @@
import * as React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { Box } from '@mui/material';
import { Packets } from '.';
export default {
title: 'Test my node / Packets',
component: Packets,
} as ComponentMeta<typeof Packets>;
const Transfer: ComponentStory<typeof Packets> = (args) => (
<Box width="500px">
<Packets {...args} />
</Box>
);
export const HighTransfer = Transfer.bind({});
HighTransfer.args = {
sent: '100',
received: '80',
};
export const LowTransfer = Transfer.bind({});
LowTransfer.args = {
sent: '100',
received: '50',
};
@@ -0,0 +1,35 @@
import * as React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { Box } from '@mui/material';
import { Path } from '.';
export default {
title: 'Test my node / Node path',
component: Path,
} as ComponentMeta<typeof Path>;
const Template: ComponentStory<typeof Path> = (args) => (
<Box display="flex">
<Path {...args} />
</Box>
);
export const Gateway = Template.bind({});
Gateway.args = {
layer: 'gateway',
};
export const LayerOne = Template.bind({});
LayerOne.args = {
layer: '1',
};
export const LayerTwo = Template.bind({});
LayerTwo.args = {
layer: '2',
};
export const LayerThree = Template.bind({});
LayerThree.args = {
layer: '3',
};
@@ -0,0 +1,18 @@
import * as React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { Box } from '@mui/material';
import { NodeSpeed, Results } from '.';
export default {
title: 'Test my node / Results',
component: Results,
} as ComponentMeta<typeof Results>;
const Template: ComponentStory<typeof Results> = (args) => <Results {...args} />;
export const Default = Template.bind({});
Default.args = {
layer: '1',
packetsSent: '1000',
packetsReceived: '5000',
};
@@ -0,0 +1,33 @@
import * as React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { Box } from '@mui/material';
import { NodeSpeed } from '.';
export default {
title: 'Test my node / Node speed',
component: NodeSpeed,
} as ComponentMeta<typeof NodeSpeed>;
const Template: ComponentStory<typeof NodeSpeed> = (args) => (
<Box display="flex" alignContent="center">
<NodeSpeed {...args} />
</Box>
);
export const FastNode = Template.bind({});
FastNode.args = {
Mbps: 500,
performance: 'good',
};
export const FairNode = Template.bind({});
FairNode.args = {
Mbps: 100,
performance: 'fair',
};
export const SlowNode = Template.bind({});
SlowNode.args = {
Mbps: 10,
performance: 'poor',
};
@@ -0,0 +1,109 @@
import React, { useRef } from 'react';
import { ArrowForward, CheckCircleOutline, Description, Download } from '@mui/icons-material';
import { Box, Button, Card, Chip, CircularProgress, Divider, Grid, Stack, Typography } from '@mui/material';
import format from 'date-fns/format';
import { NodePath } from 'src/svg-icons/node-path';
import { useReactToPrint } from 'react-to-print';
import { ResultsCard } from '../components/ResultsCard';
import { ResultsCardDetail } from '../components/ResultsCardDetail';
export type Layer = '1' | '2' | '3' | 'gateway';
const getLayerDescription = (layer: Layer) => {
if (layer === 'gateway') return 'Your node was in the Gateway layer';
return `Your node was in layer ${layer}`;
};
export const NodeSpeed = ({ Mbps, performance }: { Mbps: number; performance: 'poor' | 'fair' | 'good' }) => (
<ResultsCard
label="Node speed"
detail={`${performance === 'good' ? 'Fast' : performance === 'poor' ? 'Slow' : 'Fair'} node`}
isOk={performance === 'good'}
>
<Box
sx={{
display: 'flex',
position: 'relative',
width: 250,
height: 250,
justifyContent: 'center',
alignItems: 'center',
mx: 'auto',
mt: 4,
}}
>
<CircularProgress
variant="determinate"
value={performance === 'poor' ? 12.5 : performance === 'good' ? 85 : 65}
size={250}
sx={{ position: 'absolute', top: 0, left: 0 }}
color={performance === 'poor' ? 'error' : performance === 'good' ? 'success' : 'warning'}
/>
<Stack alignItems="center" gap={1}>
<Typography fontWeight="bold" variant="h4">
{Mbps}
</Typography>
<Typography>Mbps</Typography>
</Stack>
</Box>
</ResultsCard>
);
export const Packets = ({ sent, received }: { sent: string; received: string }) => {
const percentage = Math.round((+received / +sent) * 100);
return (
<ResultsCard label="Packets" detail={`${percentage}% packets`} isOk={percentage > 75}>
<Divider sx={{ my: 2 }} />
<ResultsCardDetail label="Packets sent" detail={sent} />
<Divider sx={{ my: 2 }} />
<ResultsCardDetail label="Packets received" detail={received} />
</ResultsCard>
);
};
export const Path = ({ layer }: { layer: Layer }) => (
<ResultsCard label="Path" detail={getLayerDescription(layer)} isOk>
<Box sx={{ mt: 3 }}>
<NodePath layer={layer} />
</Box>
</ResultsCard>
);
export const Results = ({
packetsSent,
packetsReceived,
layer,
}: {
packetsSent: string;
packetsReceived: string;
layer: '1' | '2' | '3' | 'gateway';
}) => {
const ref = useRef(null);
const handleSaveToPdf = useReactToPrint({ documentTitle: 'Test results', content: () => ref.current });
return (
<>
<Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ mb: 1 }}>
<Box display="flex" gap={1}>
<Typography fontWeight="bold" component="span">
Test date
</Typography>
<Typography>{format(new Date(), 'dd/MM/yyyy HH:mm')}</Typography>
</Box>
<Button onClick={handleSaveToPdf} startIcon={<Download />}>
Save to PDF
</Button>
</Stack>
<Grid container spacing={2} ref={ref}>
<Grid item md={5}>
<NodeSpeed Mbps={150.01} performance="good" />
</Grid>
<Grid item container direction="column" md={7}>
<Stack spacing={2}>
<Packets sent={packetsSent} received={packetsReceived} />
<Path layer={layer} />
</Stack>
</Grid>
</Grid>
</>
);
};
@@ -0,0 +1,38 @@
import * as React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { Box } from '@mui/material';
import { TestProgress } from '.';
export default {
title: 'Test my node / Test progress',
component: TestProgress,
} as ComponentMeta<typeof TestProgress>;
const Template: ComponentStory<typeof TestProgress> = ({ packetsSent, totalPackets }) => {
const [sent, setSent] = React.useState(packetsSent);
const mockPacketTransfer = (sent: number) => {
if (sent - 1 < totalPackets) {
setSent(sent);
setTimeout(() => {
mockPacketTransfer(sent + 1);
}, 25);
}
};
React.useEffect(() => {
mockPacketTransfer(0);
}, []);
return (
<Box display="flex" alignContent="center">
<TestProgress packetsSent={sent} totalPackets={totalPackets} />
</Box>
);
};
export const Default = Template.bind({});
Default.args = {
packetsSent: 0,
totalPackets: 100,
};
@@ -0,0 +1,34 @@
import React from 'react';
import { Box, CircularProgress, Stack, Typography } from '@mui/material';
export const TestProgress = ({ totalPackets, packetsSent }: { totalPackets: number; packetsSent: number }) => {
const percentage = Math.round((packetsSent / totalPackets) * 100);
return (
<Stack alignItems="center" gap={3}>
<Typography sx={{ textTransform: 'uppercase' }}>Test in progress</Typography>
<Box
sx={{
display: 'flex',
position: 'relative',
width: 250,
height: 250,
justifyContent: 'center',
alignItems: 'center',
}}
>
<CircularProgress
variant="determinate"
value={percentage}
size={250}
sx={{ position: 'absolute', top: 0, left: 0 }}
/>
<Typography fontWeight="bold" variant="h4">
{percentage}%
</Typography>
</Box>
<Typography>Sending packets...</Typography>
<Typography>{`${packetsSent} / ${totalPackets}`}</Typography>
</Stack>
);
};
@@ -0,0 +1,13 @@
import React from 'react';
import { Box, Typography } from '@mui/material';
export const OverviewDescription = ({ title, description }: { title: string; description: string }) => (
<Box>
<Typography fontWeight="bold" sx={{ mb: 1 }}>
{title}
</Typography>
<Typography fontSize="small" sx={{ color: 'grey.700', mb: 2 }}>
{description}
</Typography>
</Box>
);
@@ -0,0 +1,6 @@
import React from 'react';
import { Chip } from '@mui/material';
export const PathChip = ({ label, highlight }: { label: string; highlight: boolean }) => (
<Chip label={label} size="medium" color={highlight ? 'primary' : 'default'} />
);
@@ -0,0 +1,21 @@
import React from 'react';
import { CheckCircleOutline } from '@mui/icons-material';
import { Card } from '@mui/material';
import { ResultsCardDetail } from './ResultsCardDetail';
export const ResultsCard: React.FC<{ label: string; detail: string; isOk: boolean }> = ({
label,
detail,
isOk,
children,
}) => (
<Card variant="outlined" sx={{ p: 3 }}>
<ResultsCardDetail
label={label}
detail={detail}
boldLabel
DescriptionIcon={isOk && <CheckCircleOutline sx={{ color: 'success.light' }} />}
/>
{children}
</Card>
);
@@ -0,0 +1,22 @@
import React from 'react';
import { Box, Stack, Typography } from '@mui/material';
export const ResultsCardDetail = ({
label,
detail,
DescriptionIcon,
boldLabel,
}: {
label: string;
detail: string;
DescriptionIcon?: React.ReactNode;
boldLabel?: boolean;
}) => (
<Stack direction="row" justifyContent="space-between">
<Typography fontWeight={boldLabel ? 'bold' : 'regular'}>{label}</Typography>
<Box display="flex" gap={1} alignItems="center">
<Typography>{detail}</Typography>
{DescriptionIcon}
</Box>
</Stack>
);
+12 -3
View File
@@ -24,7 +24,6 @@ import {
vestingBondMixNode,
vestingUnbondGateway,
vestingUnbondMixnode,
getPendingEpochEvents,
updateMixnodeCostParams as updateMixnodeCostParamsRequest,
vestingUpdateMixnodeCostParams as updateMixnodeVestingCostParamsRequest,
getNodeDescription as getNodeDescriptionRequest,
@@ -36,6 +35,7 @@ import {
getMixnodeAvgUptime,
getMixnodeRewardEstimation,
getGatewayReport,
getMixnodeUptime,
} from '../requests';
import { useCheckOwnership } from '../hooks/useCheckOwnership';
import { AppContext } from './main';
@@ -71,10 +71,12 @@ export type TBondedMixnode = {
verlocPort: number;
version: string;
isUnbonding: boolean;
uptime: number;
};
export interface TBondedGateway {
name: string;
name?: string;
id: number;
identityKey: string;
ip: string;
bond: DecCoin;
@@ -154,14 +156,18 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
status: MixnodeStatus;
stakeSaturation: string;
estimatedRewards?: DecCoin;
uptime: number;
} = {
status: 'not_found',
stakeSaturation: '0',
uptime: 0,
};
try {
const statusResponse = await getMixnodeStatus(mixId);
const uptime = await getMixnodeUptime(mixId);
additionalDetails.status = statusResponse.status;
additionalDetails.uptime = uptime;
} catch (e) {
Console.log('getMixnodeStatus fails', e);
}
@@ -259,7 +265,8 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
rewarding_details,
bond_information: { mix_id },
} = 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 nodeDescription = await getNodeDescription(
bond_information.mix_node.host,
@@ -267,6 +274,7 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
);
const routingScore = await getAvgUptime();
setBondedNode({
id: data.bond_information.mix_id,
name: nodeDescription?.name,
mixId: mix_id,
identityKey: bond_information.mix_node.identity_key,
@@ -279,6 +287,7 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
delegators: rewarding_details.unique_delegations,
proxy: bond_information.proxy,
operatorRewards,
uptime,
status,
stakeSaturation,
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 bondedMixnodeMock: TBondedMixnode = {
name: 'Monster node',
mixId: 1,
name: 'Monster node',
identityKey: '7mjM2fYbtN6kxMwp1TrmQ4VwPks3URR5pBgWPWhzT98F',
stake: { denom: 'nym', amount: '1234' },
bond: { denom: 'nym', amount: '1234' },
@@ -28,9 +28,11 @@ const bondedMixnodeMock: TBondedMixnode = {
verlocPort: 1790,
version: '1.0.2',
isUnbonding: false,
uptime: 1,
};
const bondedGatewayMock: TBondedGateway = {
id: 1,
name: 'Monster node',
identityKey: 'WayM2fYbtN6kxMwp1TrmQ4VwPks3URR5pBgWPWhzT98F',
ip: '112.43.234.57',
@@ -5,18 +5,20 @@ import { Box, Typography, Stack, IconButton, Divider } from '@mui/material';
import { Close } from '@mui/icons-material';
import { useTheme } from '@mui/material/styles';
import { ConfirmationDetailProps, ConfirmationDetailsModal } from 'src/components/Bonding/modals/ConfirmationModal';
import { TestNode } from 'src/pages/bonding/node-settings/test-my-node';
import { Node as NodeIcon } from 'src/svg-icons/node';
import { LoadingModal } from 'src/components/Modals/LoadingModal';
import { NymCard } from 'src/components';
import { PageLayout } from 'src/layouts';
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 { NodeGeneralSettings } from './settings-pages/general-settings';
import { NodeUnbondPage } from './settings-pages/NodeUnbondPage';
import { createNavItems } from './node-settings.constant';
import { isMixnode } from 'src/types';
import { ApyPlayground } from './apy-playground';
export const NodeSettings = () => {
const theme = useTheme();
@@ -123,6 +125,7 @@ export const NodeSettings = () => {
{value === 'Unbond' && bondedNode && (
<NodeUnbondPage bondedNode={bondedNode} onConfirm={handleUnbond} onError={handleError} />
)}
{value === 'Playground' && bondedNode && <ApyPlayground bondedNode={bondedNode as TBondedMixnode} />}
{confirmationDetails && confirmationDetails.status === 'success' && (
<ConfirmationDetailsModal
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) => {
const navItems = ['Unbond'];
if (isMixnode) return ['General', ...navItems];
if (isMixnode) return ['General', 'Playground', ...navItems];
return navItems;
};
@@ -0,0 +1,36 @@
import React, { useState } from 'react';
import { NymCard } from 'src/components';
import { Overview } from 'src/components/TestMyNode/Overview';
import { Results } from 'src/components/TestMyNode/Results';
import { TestProgress } from 'src/components/TestMyNode/TestProgress';
export const TestNode = () => {
const [view, setView] = useState('overview');
const [packetsSent, setPacketsSent] = useState(0);
const totalPackets = 500;
const mockPacketTransfer = (sent: number) => {
if (sent - 1 < totalPackets) {
setPacketsSent(sent);
setTimeout(() => {
mockPacketTransfer(sent + 1);
}, 12.5);
}
if (sent === totalPackets) {
setView('results');
}
};
const startTest = () => {
setView('start-test');
mockPacketTransfer(0);
};
return (
<NymCard title="Test Node">
{view === 'overview' && <Overview onStartTest={startTest} />}
{view === 'start-test' && <TestProgress totalPackets={totalPackets} packetsSent={packetsSent} />}
{view === 'results' && <Results layer="1" packetsSent="5000" packetsReceived="1000" />}
</NymCard>
);
};
+15
View File
@@ -8,6 +8,7 @@ import {
RewardEstimationResponse,
WrappedDelegationEvent,
PendingIntervalEvent,
Coin,
} from '@nymproject/types';
import { Interval, TGatewayReport, TNodeDescription } from 'src/types';
import { invokeWrapper } from './wrapper';
@@ -51,3 +52,17 @@ export const getPendingIntervalEvents = async () =>
export const getGatewayReport = async (identity: string) =>
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';
export const claimOperatorReward = async (fee?: Fee) =>
@@ -9,3 +9,6 @@ export const claimDelegatorRewards = async (mixId: number, fee?: FeeDetails) =>
mixId,
fee: fee?.fee,
});
export const getCurrentRewardingParameter = async () =>
invokeWrapper<RewardingParams>('get_current_rewarding_parameters', {});
File diff suppressed because one or more lines are too long
+5 -2
View File
@@ -1,2 +1,5 @@
export interface AppEnv { ADMIN_ADDRESS: string | null, SHOW_TERMINAL: string | null, ENABLE_QA_MODE: string | null, }
export interface AppEnv {
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 { url: string, name: string | null, }
export interface ValidatorUrl {
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(','),
fontSize: 14,
fontWeightRegular: 500,
fontWeightMedium: 600,
button: {
textTransform: 'none',
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';
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 {
id: 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';
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';
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 {
denom: string;
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';
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';
@@ -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';
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';
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 { 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';
@@ -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';
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 { 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 { DelegationEvent } from './DelegationEvent';
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';
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 { 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 {
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';
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 {
host: string;
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 { 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';
export interface InclusionProbabilityResponse {
+1 -1
View File
@@ -3,6 +3,6 @@ export interface Interval {
epochs_in_interval: number;
current_epoch_start: string;
current_epoch_id: number;
epoch_length: string;
epoch_length: { secs: number; nanos: 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 { 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 {
host: string;
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';
@@ -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';
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';
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';
@@ -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';
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';
@@ -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 { 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 { Gas } from './Gas';
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 {
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';
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 { 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 { 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 {
start_time: bigint;
period_seconds: bigint;
@@ -10,5 +10,8 @@ export const stringToDecimal = (raw: string): Decimal => Decimal.fromUserInput(r
export const decimalToPercentage = (raw: string) =>
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 =>
Decimal.fromUserInput(raw, 18).toFloatApproximation();
+7
View File
@@ -15817,6 +15817,13 @@ react-syntax-highlighter@^15.4.5:
prismjs "^1.27.0"
refractor "^3.6.0"
react-to-print@^2.14.7:
version "2.14.7"
resolved "https://registry.yarnpkg.com/react-to-print/-/react-to-print-2.14.7.tgz#d288de0e573e6e63e203a48d4bad7a1759d18688"
integrity sha512-lWVVAs9Co25uyE0toxcWeFsmaZObwUozXrJD9WMpDPclpBgk+WIzxlt3Q3omL/BCBG/cpf0XNvhayUWa+99YGw==
dependencies:
prop-types "^15.8.1"
react-tooltip@*, react-tooltip@^4.2.21:
version "4.2.21"
resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.21.tgz#840123ed86cf33d50ddde8ec8813b2960bfded7f"