Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 43d6908d22 | |||
| e10f291ee9 | |||
| ecc89ced08 | |||
| 1e10c247bc | |||
| 4584f35a2a | |||
| 41fa03862e | |||
| 66303d7ca4 | |||
| 480f8a0a53 | |||
| ecb0f11bbb | |||
| fe223b5a60 | |||
| b9e52d22d1 | |||
| ecdf192b47 |
@@ -9,10 +9,11 @@ use nym_api_requests::coconut::{
|
|||||||
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
|
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
|
||||||
};
|
};
|
||||||
use nym_api_requests::models::{
|
use nym_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,
|
||||||
};
|
};
|
||||||
use reqwest::Response;
|
use reqwest::Response;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -361,6 +362,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";
|
||||||
|
|||||||
@@ -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>,
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"react-hook-form": "^7.14.2",
|
"react-hook-form": "^7.14.2",
|
||||||
"react-router-dom": "6",
|
"react-router-dom": "6",
|
||||||
"recharts": "^2.1.13",
|
"recharts": "^2.1.13",
|
||||||
|
"react-to-print": "^2.14.7",
|
||||||
"semver": "^6.3.0",
|
"semver": "^6.3.0",
|
||||||
"string-to-color": "^2.2.2",
|
"string-to-color": "^2.2.2",
|
||||||
"use-clipboard-copy": "^0.2.0",
|
"use-clipboard-copy": "^0.2.0",
|
||||||
|
|||||||
@@ -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_nym_api_urls,
|
network_config::get_nym_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,
|
||||||
|
nym_api::status::compute_mixnode_reward_estimation,
|
||||||
nym_api::status::gateway_core_node_status,
|
nym_api::status::gateway_core_node_status,
|
||||||
nym_api::status::mixnode_core_node_status,
|
nym_api::status::mixnode_core_node_status,
|
||||||
nym_api::status::mixnode_inclusion_probability,
|
nym_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,
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
@@ -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' });
|
||||||
|
}),
|
||||||
|
});
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
@@ -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),
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -5,18 +5,20 @@ import { Box, Typography, Stack, IconButton, Divider } from '@mui/material';
|
|||||||
import { Close } from '@mui/icons-material';
|
import { Close } from '@mui/icons-material';
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import { ConfirmationDetailProps, ConfirmationDetailsModal } from 'src/components/Bonding/modals/ConfirmationModal';
|
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 { Node as NodeIcon } from 'src/svg-icons/node';
|
||||||
import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
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 +125,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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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 });
|
||||||
|
|||||||
@@ -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', {});
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -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 +1 @@
|
|||||||
|
export type Network = 'QA' | 'SANDBOX' | 'MAINNET';
|
||||||
export type Network = "QA" | "SANDBOX" | "MAINNET";
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
|
export interface ValidatorUrl {
|
||||||
export interface ValidatorUrl { url: string, name: string | null, }
|
url: string;
|
||||||
|
name: string | null;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,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';
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -15817,6 +15817,13 @@ react-syntax-highlighter@^15.4.5:
|
|||||||
prismjs "^1.27.0"
|
prismjs "^1.27.0"
|
||||||
refractor "^3.6.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:
|
react-tooltip@*, react-tooltip@^4.2.21:
|
||||||
version "4.2.21"
|
version "4.2.21"
|
||||||
resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.21.tgz#840123ed86cf33d50ddde8ec8813b2960bfded7f"
|
resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.21.tgz#840123ed86cf33d50ddde8ec8813b2960bfded7f"
|
||||||
|
|||||||
Reference in New Issue
Block a user