Wallet - bonding, new node stats component (#1535)
* feature(wallet-bonding): add bond more skeleton * fix(wallet-bonding): post godzilla merge fix: post rebase feature(wallet-bonding): wip * feat(wallet-bonding): add node stats component feat(wallet-bonding): add node stats component * feat(wallet-bonding): fix tooltip icon size * fix(wallet-bonding): node stats fix responsiveness fix(wallet-bonding): post godzilla merge * feat(wallet): fetch active set probabilities * feat(wallet): operation mixnode avg uptime * fix: typo * fix(wallet): theme colors fix(wallet): theme colors * fix(wallet): destructuring * feat(wallet): request total reward data * refactor(wallet): clean code * fix(wallet): typo, better error logging * fix(wallet): some nym decimal values * fix(wallet): deal with unym nym values * fix(wallet): routing score chart * feat(wallet): new node stats design * feat(wallet): new node stats design * fix(wallet): increase component width * fix(wallet): some vertical alignements
This commit is contained in:
@@ -34,6 +34,7 @@
|
||||
"@storybook/react": "^6.5.8",
|
||||
"@tauri-apps/api": "^1.0.2",
|
||||
"@tauri-apps/tauri-forage": "^1.0.0-beta.2",
|
||||
"big.js": "^6.2.1",
|
||||
"bs58": "^4.0.1",
|
||||
"clsx": "^1.1.1",
|
||||
"date-fns": "^2.28.0",
|
||||
@@ -46,6 +47,7 @@
|
||||
"react-error-boundary": "^3.1.3",
|
||||
"react-hook-form": "^7.14.2",
|
||||
"react-router-dom": "6",
|
||||
"recharts": "^2.1.13",
|
||||
"semver": "^6.3.0",
|
||||
"string-to-color": "^2.2.2",
|
||||
"use-clipboard-copy": "^0.2.0",
|
||||
@@ -65,6 +67,7 @@
|
||||
"@tauri-apps/cli": "^1.0.5",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^12.0.0",
|
||||
"@types/big.js": "^6.1.6",
|
||||
"@types/bs58": "^4.0.1",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/node": "^16.7.13",
|
||||
|
||||
@@ -64,6 +64,7 @@ fn main() {
|
||||
mixnet::bond::update_mixnode_config,
|
||||
mixnet::bond::get_number_of_mixnode_delegators,
|
||||
mixnet::bond::get_mix_node_description,
|
||||
mixnet::bond::get_mixnode_avg_uptime,
|
||||
mixnet::delegate::delegate_to_mixnode,
|
||||
mixnet::delegate::get_pending_delegator_rewards,
|
||||
mixnet::delegate::get_pending_delegation_events,
|
||||
|
||||
@@ -170,6 +170,32 @@ pub async fn update_mixnode_config(
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_mixnode_avg_uptime(
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<Option<u8>, BackendError> {
|
||||
log::info!(">>> Get mixnode bond details");
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
let res = client.nymd.get_owned_mixnode(client.nymd.address()).await?;
|
||||
|
||||
match res.mixnode_details {
|
||||
Some(details) => {
|
||||
let id = details.mix_id();
|
||||
log::trace!(" >>> Get average uptime percentage: mix_id = {}", id);
|
||||
let avg_uptime_percent = client
|
||||
.validator_api
|
||||
.get_mixnode_avg_uptime(id)
|
||||
.await
|
||||
.ok()
|
||||
.map(|r| r.avg_uptime);
|
||||
log::trace!(" <<< {:?}", avg_uptime_percent);
|
||||
Ok(avg_uptime_percent)
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn mixnode_bond_details(
|
||||
state: tauri::State<'_, WalletState>,
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Box, Button, Stack, Typography } from '@mui/material';
|
||||
import { Box, Button, Stack, Tooltip, Typography } from '@mui/material';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { isMixnode } from 'src/types';
|
||||
import { isMixnode, Network } from 'src/types';
|
||||
import { TBondedMixnode, urls } from 'src/context';
|
||||
import { NymCard } from 'src/components';
|
||||
import { Network } from 'src/types';
|
||||
import { IdentityKey } from 'src/components/IdentityKey';
|
||||
import { NodeStatus } from 'src/components/NodeStatus';
|
||||
import { Node as NodeIcon } from '../../svg-icons/node';
|
||||
import { Cell, Header, NodeTable } from './NodeTable';
|
||||
import { BondedMixnodeActions, TBondedMixnodeActions } from './BondedMixnodeActions';
|
||||
import { NodeStats } from './NodeStats';
|
||||
|
||||
const headers: Header[] = [
|
||||
{
|
||||
@@ -74,6 +74,7 @@ export const BondedMixnode = ({
|
||||
delegators,
|
||||
status,
|
||||
identityKey,
|
||||
host,
|
||||
} = mixnode;
|
||||
const cells: Cell[] = [
|
||||
{
|
||||
@@ -93,7 +94,7 @@ export const BondedMixnode = ({
|
||||
id: 'pm-cell',
|
||||
},
|
||||
{
|
||||
cell: operatorCost ? `${operatorCost} USD` : '-',
|
||||
cell: operatorCost ? `${operatorCost} NYM` : '-',
|
||||
id: 'operator-cost-cell',
|
||||
},
|
||||
{
|
||||
@@ -109,6 +110,7 @@ export const BondedMixnode = ({
|
||||
<BondedMixnodeActions
|
||||
onActionSelect={onActionSelect}
|
||||
disabledRedeemAndCompound={(operatorRewards && Number(operatorRewards.amount) === 0) || false}
|
||||
disabledBondMore // TODO for now disable bond more feature until backend is ready
|
||||
/>
|
||||
),
|
||||
id: 'actions-cell',
|
||||
@@ -117,46 +119,51 @@ export const BondedMixnode = ({
|
||||
];
|
||||
|
||||
return (
|
||||
<NymCard
|
||||
borderless
|
||||
title={
|
||||
<Stack gap={3}>
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
<Typography variant="h5" fontWeight={600}>
|
||||
Mix node
|
||||
</Typography>
|
||||
<NodeStatus status={status} />
|
||||
</Box>
|
||||
{name && (
|
||||
<Typography fontWeight="regular" variant="h6">
|
||||
{name}
|
||||
</Typography>
|
||||
)}
|
||||
<IdentityKey identityKey={identityKey} />
|
||||
</Stack>
|
||||
}
|
||||
Action={
|
||||
isMixnode(mixnode) && (
|
||||
<Button
|
||||
variant="text"
|
||||
color="secondary"
|
||||
onClick={() => navigate('/bonding/node-settings')}
|
||||
startIcon={<NodeIcon />}
|
||||
>
|
||||
Settings
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
>
|
||||
<NodeTable headers={headers} cells={cells} />
|
||||
{network && (
|
||||
<Typography sx={{ mt: 2, fontSize: 'small' }}>
|
||||
Check more stats of your node on the{' '}
|
||||
<Link href={`${urls(network).networkExplorer}/network-components/mixnodes`} target="_blank">
|
||||
explorer
|
||||
</Link>
|
||||
</Typography>
|
||||
)}
|
||||
</NymCard>
|
||||
<Stack gap={2}>
|
||||
<NymCard
|
||||
borderless
|
||||
title={
|
||||
<Stack gap={3}>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Typography variant="h5" fontWeight={600}>
|
||||
Mix node
|
||||
</Typography>
|
||||
<NodeStatus status={status} />
|
||||
</Box>
|
||||
{name && (
|
||||
<Tooltip title={host} arrow>
|
||||
<Typography fontWeight="regular" variant="h6">
|
||||
{name}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
)}
|
||||
<IdentityKey identityKey={identityKey} />
|
||||
</Stack>
|
||||
}
|
||||
Action={
|
||||
isMixnode(mixnode) && (
|
||||
<Button
|
||||
variant="text"
|
||||
color="secondary"
|
||||
onClick={() => navigate('/bonding/node-settings')}
|
||||
startIcon={<NodeIcon />}
|
||||
>
|
||||
Node Settings
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
>
|
||||
<NodeTable headers={headers} cells={cells} />
|
||||
{network && (
|
||||
<Typography sx={{ mt: 2, fontSize: 'small' }}>
|
||||
Check more stats of your node on the{' '}
|
||||
<Link href={`${urls(network).networkExplorer}/network-components/mixnodes`} target="_blank">
|
||||
explorer
|
||||
</Link>
|
||||
</Typography>
|
||||
)}
|
||||
</NymCard>
|
||||
<NodeStats mixnode={mixnode} />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,9 +8,11 @@ export type TBondedMixnodeActions = 'nodeSettings' | 'bondMore' | 'unbond' | 're
|
||||
export const BondedMixnodeActions = ({
|
||||
onActionSelect,
|
||||
disabledRedeemAndCompound,
|
||||
disabledBondMore,
|
||||
}: {
|
||||
onActionSelect: (action: TBondedMixnodeActions) => void;
|
||||
disabledRedeemAndCompound: boolean;
|
||||
disabledBondMore?: boolean;
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
@@ -24,11 +26,13 @@ export const BondedMixnodeActions = ({
|
||||
|
||||
return (
|
||||
<ActionsMenu open={isOpen} onOpen={handleOpen} onClose={handleClose}>
|
||||
<ActionsMenuItem
|
||||
title="Bond More"
|
||||
Icon={<BondIcon fontSize="inherit" />}
|
||||
onClick={() => handleActionClick('bondMore')}
|
||||
/>
|
||||
{!disabledBondMore && (
|
||||
<ActionsMenuItem
|
||||
title="Bond More"
|
||||
Icon={<BondIcon fontSize="inherit" />}
|
||||
onClick={() => handleActionClick('bondMore')}
|
||||
/>
|
||||
)}
|
||||
<ActionsMenuItem
|
||||
title="Redeem rewards"
|
||||
Icon={<Typography sx={{ pl: 1, fontWeight: 700 }}>R</Typography>}
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
import React from 'react';
|
||||
import { Stack, Typography, Box, useTheme, Grid, LinearProgress, LinearProgressProps } from '@mui/material';
|
||||
import { TBondedMixnode } from 'src/context';
|
||||
import { Cell, Pie, PieChart, Legend, ResponsiveContainer } from 'recharts';
|
||||
import { SelectionChance } from '@nymproject/types';
|
||||
import { NymCard } from '../NymCard';
|
||||
import { InfoTooltip } from '../InfoToolTip';
|
||||
|
||||
const LinearProgressWithLabel = (props: LinearProgressProps & { value: number }) => {
|
||||
const { value } = props;
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box sx={{ minWidth: 30 }}>
|
||||
<Typography>{`${Math.round(value)}%`}</Typography>
|
||||
</Box>
|
||||
<Box sx={{ width: 40 }}>
|
||||
<LinearProgress sx={{ borderRadius: 4 }} color="success" variant="determinate" {...props} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const StatRow = ({
|
||||
label,
|
||||
tooltipText,
|
||||
value,
|
||||
textColor,
|
||||
progressValue,
|
||||
}: {
|
||||
label: string;
|
||||
tooltipText?: string;
|
||||
value: string | number;
|
||||
textColor?: string;
|
||||
progressValue?: number;
|
||||
}) => (
|
||||
<Stack direction="row" gap={1} justifyContent="space-between" alignItems="center" width="100%">
|
||||
<Stack direction="row" alignItems="center" gap={1} sx={{ color: (t) => t.palette.nym.text.muted }}>
|
||||
{tooltipText && <InfoTooltip title={tooltipText} />}
|
||||
<Typography>{label}</Typography>
|
||||
</Stack>
|
||||
{typeof progressValue === 'number' ? (
|
||||
<LinearProgressWithLabel value={progressValue} />
|
||||
) : (
|
||||
<Typography color={textColor}>{value}</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
export const NodeStats = ({ mixnode }: { mixnode: TBondedMixnode }) => {
|
||||
const { activeSetProbability, routingScore } = mixnode;
|
||||
const theme = useTheme();
|
||||
|
||||
// clamp routing score to [0-100]
|
||||
const score = Math.min(Math.max(routingScore, 0), 100);
|
||||
|
||||
const data = [
|
||||
{ key: 'routingScore', value: score },
|
||||
{ key: 'rest', value: 100 - score },
|
||||
];
|
||||
const colors = [theme.palette.success.main, theme.palette.nym.nymWallet.chart.grey];
|
||||
|
||||
const getSetProbabilityLabel = (chance?: SelectionChance): { value: string; color?: string } => {
|
||||
switch (chance) {
|
||||
case 'High':
|
||||
return { value: 'High', color: theme.palette.nym.success };
|
||||
case 'Good':
|
||||
return { value: 'Good' };
|
||||
case 'Low':
|
||||
return { value: 'Low', color: theme.palette.nym.red };
|
||||
default:
|
||||
return { value: 'Unknown' };
|
||||
}
|
||||
};
|
||||
|
||||
const renderLegend = () => (
|
||||
<Stack
|
||||
alignItems="center"
|
||||
maxWidth={200}
|
||||
width={200}
|
||||
sx={{
|
||||
borderBottom: `1px solid ${theme.palette.nym.nymWallet.chart.grey}`,
|
||||
}}
|
||||
>
|
||||
<Typography fontWeight={600} fontSize={24} mb={1}>
|
||||
{routingScore}%
|
||||
</Typography>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
const activeSetProb = getSetProbabilityLabel(activeSetProbability);
|
||||
|
||||
return (
|
||||
<Grid container spacing={4} direction="row" justifyContent="space-between" alignItems="flex-end">
|
||||
<Grid item xs={12} sm={8} md={7} lg={5}>
|
||||
<NymCard
|
||||
borderless
|
||||
title={
|
||||
<Typography variant="h5" fontWeight={600}>
|
||||
Node stats
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Stack justifyContent="center" alignItems="center" mb={2}>
|
||||
<ResponsiveContainer width="100%" height={100}>
|
||||
<PieChart width={200} height={100}>
|
||||
<Pie
|
||||
cy={90}
|
||||
data={data}
|
||||
startAngle={180}
|
||||
endAngle={0}
|
||||
innerRadius={58}
|
||||
outerRadius={78}
|
||||
dataKey="value"
|
||||
stroke="none"
|
||||
>
|
||||
{data.map(({ key }, index) => (
|
||||
<Cell key={`cell-${key}`} fill={colors[index]} />
|
||||
))}
|
||||
</Pie>
|
||||
<Legend
|
||||
verticalAlign="bottom"
|
||||
content={renderLegend}
|
||||
wrapperStyle={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
/>
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
<Typography color="nym.text.muted">Routing score</Typography>
|
||||
</Stack>
|
||||
<StatRow
|
||||
label="Chance of being in the active set"
|
||||
value={activeSetProb.value}
|
||||
textColor={activeSetProb.color}
|
||||
/>
|
||||
</NymCard>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4} md={5} lg={7} />
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -28,7 +28,7 @@ export const NodeTable = ({ headers, cells }: TableProps) => (
|
||||
<TableRow>
|
||||
{headers.map(({ header, id, tooltipText }) => (
|
||||
<TableCell key={id}>
|
||||
<Stack direction="row" gap={1}>
|
||||
<Stack direction="row" alignItems="center" gap={1}>
|
||||
{tooltipText && <InfoTooltip title={tooltipText} />}
|
||||
<Typography>{header}</Typography>
|
||||
</Stack>
|
||||
|
||||
@@ -6,12 +6,12 @@ export const InfoTooltip = ({
|
||||
title,
|
||||
tooltipPlacement = 'bottom',
|
||||
light,
|
||||
size = 'small',
|
||||
size = 'inherit',
|
||||
}: {
|
||||
title: string;
|
||||
tooltipPlacement?: TooltipProps['placement'];
|
||||
light?: boolean;
|
||||
size?: 'small' | 'medium' | 'large';
|
||||
size?: 'small' | 'medium' | 'large' | 'inherit';
|
||||
}) => (
|
||||
<Tooltip title={title} arrow placement={tooltipPlacement}>
|
||||
<InfoOutlined fontSize={size} sx={{ color: light ? 'grey.500' : undefined }} />
|
||||
|
||||
@@ -3,10 +3,11 @@ import {
|
||||
DecCoin,
|
||||
MixnodeStatus,
|
||||
TransactionExecuteResult,
|
||||
decimalToFloatApproximation,
|
||||
decimalToPercentage,
|
||||
SelectionChance,
|
||||
} from '@nymproject/types';
|
||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import Big from 'big.js';
|
||||
import { isGateway, isMixnode, TBondGatewayArgs, TBondMixNodeArgs } from 'src/types';
|
||||
import { Console } from 'src/utils/console';
|
||||
import {
|
||||
@@ -24,17 +25,24 @@ import {
|
||||
vestingUnbondMixnode,
|
||||
updateMixnodeCostParams as updateMixnodeCostParamsRequest,
|
||||
vestingUpdateMixnodeCostParams as updateMixnodeVestingCostParamsRequest,
|
||||
getNodeDescription as getNodeDescriptioRequest,
|
||||
getNodeDescription as getNodeDescriptionRequest,
|
||||
getMixnodeStatus,
|
||||
getPendingOperatorRewards,
|
||||
getMixnodeStakeSaturation,
|
||||
vestingClaimOperatorReward,
|
||||
getInclusionProbability,
|
||||
getMixnodeAvgUptime,
|
||||
} from '../requests';
|
||||
import { useCheckOwnership } from '../hooks/useCheckOwnership';
|
||||
import { AppContext } from './main';
|
||||
import { attachDefaultOperatingCost, toPercentFloatString, toPercentIntegerString } from '../utils';
|
||||
import {
|
||||
attachDefaultOperatingCost,
|
||||
toDisplay,
|
||||
toPercentFloatString,
|
||||
toPercentIntegerString,
|
||||
unymToNym,
|
||||
} from '../utils';
|
||||
|
||||
// TODO add relevant data
|
||||
export type TBondedMixnode = {
|
||||
name?: string;
|
||||
identityKey: string;
|
||||
@@ -46,12 +54,16 @@ export type TBondedMixnode = {
|
||||
delegators: number;
|
||||
status: MixnodeStatus;
|
||||
proxy?: string;
|
||||
operatorCost?: string;
|
||||
host: string;
|
||||
estimatedRewards?: DecCoin;
|
||||
activeSetProbability?: SelectionChance;
|
||||
standbySetProbability?: SelectionChance;
|
||||
routingScore: number;
|
||||
httpApiPort: number;
|
||||
mixPort: number;
|
||||
verlocPort: number;
|
||||
version: string;
|
||||
operatorCost?: string;
|
||||
};
|
||||
|
||||
export interface TBondedGateway {
|
||||
@@ -84,9 +96,6 @@ export type TBondingContext = {
|
||||
checkOwnership: () => Promise<void>;
|
||||
};
|
||||
|
||||
const calculateStake = (pledge: string, delegations: string): number =>
|
||||
decimalToFloatApproximation(pledge) + decimalToFloatApproximation(delegations);
|
||||
|
||||
export const BondingContext = createContext<TBondingContext>({
|
||||
isLoading: true,
|
||||
refresh: async () => undefined,
|
||||
@@ -129,7 +138,12 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
|
||||
};
|
||||
|
||||
const getAdditionalMixnodeDetails = async (mixId: number) => {
|
||||
const additionalDetails: { status: MixnodeStatus; stakeSaturation: string; operatorCost?: string } = {
|
||||
const additionalDetails: {
|
||||
status: MixnodeStatus;
|
||||
stakeSaturation: string;
|
||||
operatorCost?: string;
|
||||
estimatedRewards?: DecCoin;
|
||||
} = {
|
||||
status: 'not_found',
|
||||
stakeSaturation: '0',
|
||||
};
|
||||
@@ -138,31 +152,69 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
|
||||
const statusResponse = await getMixnodeStatus(mixId);
|
||||
additionalDetails.status = statusResponse.status;
|
||||
} catch (e) {
|
||||
Console.log(e);
|
||||
Console.log('getMixnodeStatus fails', e);
|
||||
}
|
||||
|
||||
try {
|
||||
const stakeSaturationResponse = await getMixnodeStakeSaturation(mixId);
|
||||
additionalDetails.stakeSaturation = decimalToPercentage(stakeSaturationResponse.saturation);
|
||||
} catch (e) {
|
||||
Console.log(e);
|
||||
Console.log('getMixnodeStakeSaturation fails', e);
|
||||
}
|
||||
try {
|
||||
const rewardEstimation = await getMixnodeRewardEstimation(mixId);
|
||||
additionalDetails.operatorCost = rewardEstimation.estimation.operating_cost;
|
||||
additionalDetails.operatorCost = unymToNym(rewardEstimation.estimation.operating_cost);
|
||||
const estimatedRewards = unymToNym(rewardEstimation.estimation.total_node_reward);
|
||||
if (estimatedRewards) {
|
||||
additionalDetails.estimatedRewards = {
|
||||
amount: estimatedRewards,
|
||||
denom: 'nym',
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
Console.log(e);
|
||||
Console.log('getMixnodeRewardEstimation fails', e);
|
||||
}
|
||||
return additionalDetails;
|
||||
};
|
||||
|
||||
const getNodeDescription = async (host: string, port: number) => {
|
||||
let result;
|
||||
try {
|
||||
return await getNodeDescriptioRequest(host, port);
|
||||
result = await getNodeDescriptionRequest(host, port);
|
||||
} catch (e) {
|
||||
Console.log(e);
|
||||
Console.log('getNodeDescriptionRequest fails', e);
|
||||
}
|
||||
return undefined;
|
||||
return result;
|
||||
};
|
||||
|
||||
const getSetProbabilities = async (mixId: number) => {
|
||||
let result;
|
||||
try {
|
||||
result = await getInclusionProbability(mixId);
|
||||
} catch (e: any) {
|
||||
Console.log('getInclusionProbability fails', e);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const getAvgUptime = async () => {
|
||||
let result;
|
||||
try {
|
||||
result = await getMixnodeAvgUptime();
|
||||
} catch (e: any) {
|
||||
Console.log('getMixnodeAvgUptime fails', e);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const calculateStake = (pledge: string, delegations: string) => {
|
||||
let stake;
|
||||
try {
|
||||
stake = unymToNym(Big(pledge).plus(delegations));
|
||||
} catch (e: any) {
|
||||
Console.warn(`not a valid decimal number: ${e}`);
|
||||
}
|
||||
return stake;
|
||||
};
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
@@ -174,22 +226,33 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
|
||||
let operatorRewards;
|
||||
try {
|
||||
operatorRewards = await getPendingOperatorRewards(clientDetails?.client_address);
|
||||
const opRewards = toDisplay(operatorRewards.amount);
|
||||
if (opRewards) {
|
||||
operatorRewards.amount = opRewards;
|
||||
}
|
||||
} catch (e) {
|
||||
Console.warn(`get_operator_rewards request failed: ${e}`);
|
||||
}
|
||||
if (data) {
|
||||
const { bond_information, rewarding_details } = data;
|
||||
const { status, stakeSaturation, operatorCost } = await getAdditionalMixnodeDetails(data.bond_information.id);
|
||||
const {
|
||||
bond_information,
|
||||
rewarding_details,
|
||||
bond_information: { id },
|
||||
} = data;
|
||||
|
||||
const { status, stakeSaturation, operatorCost, estimatedRewards } = await getAdditionalMixnodeDetails(id);
|
||||
const setProbabilities = await getSetProbabilities(id);
|
||||
const nodeDescription = await getNodeDescription(
|
||||
bond_information.mix_node.host,
|
||||
bond_information.mix_node.http_api_port,
|
||||
);
|
||||
const routingScore = await getAvgUptime();
|
||||
setBondedNode({
|
||||
name: nodeDescription?.name,
|
||||
identityKey: bond_information.mix_node.identity_key,
|
||||
ip: bond_information.id,
|
||||
stake: {
|
||||
amount: calculateStake(rewarding_details.operator, data.rewarding_details.delegates).toString(),
|
||||
amount: calculateStake(rewarding_details.operator, rewarding_details.delegates),
|
||||
denom: bond_information.original_pledge.denom,
|
||||
},
|
||||
bond: bond_information.original_pledge,
|
||||
@@ -199,12 +262,16 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
|
||||
operatorRewards,
|
||||
status,
|
||||
stakeSaturation,
|
||||
operatorCost,
|
||||
host: bond_information.mix_node.host.replace(/\s/g, ''),
|
||||
routingScore,
|
||||
activeSetProbability: setProbabilities?.in_active,
|
||||
standbySetProbability: setProbabilities?.in_reserve,
|
||||
estimatedRewards,
|
||||
httpApiPort: bond_information.mix_node.http_api_port,
|
||||
mixPort: bond_information.mix_node.mix_port,
|
||||
verlocPort: bond_information.mix_node.verloc_port,
|
||||
version: bond_information.mix_node.version,
|
||||
operatorCost,
|
||||
} as TBondedMixnode);
|
||||
}
|
||||
} catch (e: any) {
|
||||
|
||||
@@ -16,8 +16,12 @@ const bondedMixnodeMock: TBondedMixnode = {
|
||||
operatorRewards: { denom: 'nym', amount: '1234' },
|
||||
delegators: 5423,
|
||||
status: 'active',
|
||||
operatorCost: '2',
|
||||
operatorCost: '0.22',
|
||||
host: '1.2.3.4',
|
||||
routingScore: 75,
|
||||
activeSetProbability: 'High',
|
||||
standbySetProbability: 'Low',
|
||||
estimatedRewards: { denom: 'nym', amount: '2' },
|
||||
httpApiPort: 8000,
|
||||
mixPort: 1789,
|
||||
verlocPort: 1790,
|
||||
|
||||
@@ -16,6 +16,7 @@ export const getAllPendingDelegations = async () =>
|
||||
|
||||
export const getMixnodeBondDetails = async () => invokeWrapper<MixNodeDetails | null>('mixnode_bond_details');
|
||||
export const getGatewayBondDetails = async () => invokeWrapper<GatewayBond | null>('gateway_bond_details');
|
||||
export const getMixnodeAvgUptime = async () => invokeWrapper<number | null>('get_mixnode_avg_uptime');
|
||||
|
||||
export const getPendingOperatorRewards = async (address: string) =>
|
||||
invokeWrapper<DecCoin>('get_pending_operator_rewards', { address });
|
||||
|
||||
Vendored
+8
-5
@@ -73,6 +73,9 @@ declare module '@mui/material/styles' {
|
||||
modal: {
|
||||
border: string;
|
||||
};
|
||||
chart: {
|
||||
grey: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,7 +94,7 @@ declare module '@mui/material/styles' {
|
||||
/**
|
||||
* Add anything not palette related to the theme here
|
||||
*/
|
||||
interface NymTheme { }
|
||||
interface NymTheme {}
|
||||
|
||||
/**
|
||||
* This augments the definitions of the MUI Theme with the Nym theme, as well as
|
||||
@@ -99,8 +102,8 @@ declare module '@mui/material/styles' {
|
||||
*
|
||||
* IMPORTANT: only add extensions to the interfaces above, do not modify the lines below
|
||||
*/
|
||||
interface Theme extends NymTheme { }
|
||||
interface ThemeOptions extends Partial<NymTheme> { }
|
||||
interface Palette extends NymPaletteAndNymWalletPalette { }
|
||||
interface PaletteOptions extends NymPaletteAndNymWalletPaletteOptions { }
|
||||
interface Theme extends NymTheme {}
|
||||
interface ThemeOptions extends Partial<NymTheme> {}
|
||||
interface Palette extends NymPaletteAndNymWalletPalette {}
|
||||
interface PaletteOptions extends NymPaletteAndNymWalletPaletteOptions {}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ const darkMode: NymPaletteVariant = {
|
||||
modal: {
|
||||
border: '#484d53',
|
||||
},
|
||||
chart: { grey: '#3D4249' },
|
||||
};
|
||||
|
||||
const lightMode: NymPaletteVariant = {
|
||||
@@ -98,6 +99,7 @@ const lightMode: NymPaletteVariant = {
|
||||
modal: {
|
||||
border: 'transparent',
|
||||
},
|
||||
chart: { grey: '#E6E6E6' },
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { appWindow } from '@tauri-apps/api/window';
|
||||
import bs58 from 'bs58';
|
||||
import Big from 'big.js';
|
||||
import { valid } from 'semver';
|
||||
import { isValidRawCoin, DecCoin, MixNodeCostParams } from '@nymproject/types';
|
||||
import { TPoolOption } from 'src/components';
|
||||
@@ -142,3 +143,38 @@ export const toPercentFloatString = (value: string) => (Number(value) / 100).toS
|
||||
* @returns A stringified integer
|
||||
*/
|
||||
export const toPercentIntegerString = (value: string) => Math.round(Number(value) * 100).toString();
|
||||
|
||||
/**
|
||||
* Converts a decimal number of NYM to a pretty representation
|
||||
* with fixed decimal places.
|
||||
*
|
||||
* @param nym - a decimal number of NYM
|
||||
* @param dp - number of decimal places (4 by default ie. 0.0000)
|
||||
* @returns A prettyfied decimal number
|
||||
*/
|
||||
export const toDisplay = (nym: string | number | Big, dp = 4) => {
|
||||
let displayValue;
|
||||
try {
|
||||
displayValue = Big(nym).toFixed(dp);
|
||||
} catch (e: any) {
|
||||
Console.warn(`${displayValue} not a valid decimal number: ${e}`);
|
||||
}
|
||||
return displayValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a decimal number of μNYM (micro NYM) to NYM.
|
||||
*
|
||||
* @param unym - string representation of a decimal number of μNYM
|
||||
* @param dp - number of decimal places (4 by default ie. 0.0000)
|
||||
* @returns The corresponding decimal number in NYM
|
||||
*/
|
||||
export const unymToNym = (unym: string | Big, dp = 4) => {
|
||||
let nym;
|
||||
try {
|
||||
nym = Big(unym).div(1_000_000).toFixed(dp);
|
||||
} catch (e: any) {
|
||||
Console.warn(`${unym} not a valid decimal number: ${e}`);
|
||||
}
|
||||
return nym;
|
||||
};
|
||||
|
||||
@@ -5054,6 +5054,11 @@
|
||||
dependencies:
|
||||
"@babel/types" "^7.3.0"
|
||||
|
||||
"@types/big.js@^6.1.6":
|
||||
version "6.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/big.js/-/big.js-6.1.6.tgz#3d417e758483d55345a03a087f7e0c87137ca444"
|
||||
integrity sha512-0r9J+Zz9rYm2hOTwiMAVkm3XFQ4u5uTK37xrQMhc9bysn/sf/okzovWMYYIBMFTn/yrEZ11pusgLEaoarTlQbA==
|
||||
|
||||
"@types/body-parser@*":
|
||||
version "1.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0"
|
||||
@@ -6975,6 +6980,11 @@ big.js@^5.2.2:
|
||||
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
|
||||
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
|
||||
|
||||
big.js@^6.2.1:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/big.js/-/big.js-6.2.1.tgz#7205ce763efb17c2e41f26f121c420c6a7c2744f"
|
||||
integrity sha512-bCtHMwL9LeDIozFn+oNhhFoq+yQ3BNdnsLSASUxLciOb1vgvpHsIO1dsENiGMgbb4SkP5TrzWzRiLddn8ahVOQ==
|
||||
|
||||
bignumber.js@^2.1.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-2.4.0.tgz#838a992da9f9d737e0f4b2db0be62bb09dd0c5e8"
|
||||
@@ -7667,6 +7677,11 @@ class-utils@^0.3.5:
|
||||
isobject "^3.0.0"
|
||||
static-extend "^0.1.1"
|
||||
|
||||
classnames@^2.2.5:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
|
||||
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
|
||||
|
||||
clean-css@^4.2.3:
|
||||
version "4.2.4"
|
||||
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.4.tgz#733bf46eba4e607c6891ea57c24a989356831178"
|
||||
@@ -8406,6 +8421,11 @@ css-tree@^1.1.2, css-tree@^1.1.3:
|
||||
mdn-data "2.0.14"
|
||||
source-map "^0.6.1"
|
||||
|
||||
css-unit-converter@^1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.2.tgz#4c77f5a1954e6dbff60695ecb214e3270436ab21"
|
||||
integrity sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==
|
||||
|
||||
css-vendor@^2.0.8:
|
||||
version "2.0.8"
|
||||
resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-2.0.8.tgz#e47f91d3bd3117d49180a3c935e62e3d9f7f449d"
|
||||
@@ -8599,14 +8619,19 @@ d3-geo@^2.0.1:
|
||||
dependencies:
|
||||
d3-color "1 - 2"
|
||||
|
||||
"d3-interpolate@1.2.0 - 3":
|
||||
"d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
|
||||
integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
|
||||
dependencies:
|
||||
d3-color "1 - 3"
|
||||
|
||||
d3-scale@^4.0.0:
|
||||
"d3-path@1 - 3":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.0.1.tgz#f09dec0aaffd770b7995f1a399152bf93052321e"
|
||||
integrity sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==
|
||||
|
||||
d3-scale@^4.0.0, d3-scale@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396"
|
||||
integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==
|
||||
@@ -8622,6 +8647,13 @@ d3-selection@2, d3-selection@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-2.0.0.tgz#94a11638ea2141b7565f883780dabc7ef6a61066"
|
||||
integrity sha512-XoGGqhLUN/W14NmaqcO/bb1nqjDAw5WtSYb2X8wiuQWvSZUsUVYsOSkOybUrNvcBjaywBdYPy03eXHMXjk9nZA==
|
||||
|
||||
d3-shape@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.1.0.tgz#c8a495652d83ea6f524e482fca57aa3f8bc32556"
|
||||
integrity sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==
|
||||
dependencies:
|
||||
d3-path "1 - 3"
|
||||
|
||||
"d3-time-format@2 - 4":
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a"
|
||||
@@ -8750,6 +8782,11 @@ decamelize@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837"
|
||||
integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==
|
||||
|
||||
decimal.js-light@^2.4.1:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934"
|
||||
integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==
|
||||
|
||||
decimal.js@^10.2.1:
|
||||
version "10.3.1"
|
||||
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783"
|
||||
@@ -9082,6 +9119,13 @@ dom-converter@^0.2.0:
|
||||
dependencies:
|
||||
utila "~0.4"
|
||||
|
||||
dom-helpers@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8"
|
||||
integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
|
||||
dom-helpers@^5.0.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
|
||||
@@ -9834,7 +9878,7 @@ etag@~1.8.1:
|
||||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
||||
|
||||
eventemitter3@^4.0.0, eventemitter3@^4.0.4:
|
||||
eventemitter3@^4.0.0, eventemitter3@^4.0.1, eventemitter3@^4.0.4:
|
||||
version "4.0.7"
|
||||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
|
||||
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
|
||||
@@ -10033,6 +10077,11 @@ fast-diff@^1.1.2:
|
||||
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
|
||||
integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
|
||||
|
||||
fast-equals@^2.0.0:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-2.0.4.tgz#3add9410585e2d7364c2deeb6a707beadb24b927"
|
||||
integrity sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w==
|
||||
|
||||
fast-glob@^2.2.6:
|
||||
version "2.2.7"
|
||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d"
|
||||
@@ -15521,6 +15570,11 @@ postcss-unique-selectors@^5.1.0:
|
||||
dependencies:
|
||||
postcss-selector-parser "^6.0.5"
|
||||
|
||||
postcss-value-parser@^3.3.0:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
|
||||
integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
|
||||
|
||||
postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
|
||||
@@ -16129,7 +16183,7 @@ react-is@17.0.2, react-is@^17.0.1, react-is@^17.0.2:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||
|
||||
react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0:
|
||||
react-is@^16.10.2, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
@@ -16139,6 +16193,11 @@ react-is@^18.0.0:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
|
||||
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
|
||||
|
||||
react-lifecycles-compat@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
||||
|
||||
react-load-script@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/react-load-script/-/react-load-script-0.0.6.tgz#db6851236aaa25bb622677a2eb51dad4f8d2c258"
|
||||
@@ -16164,6 +16223,13 @@ react-refresh@^0.11.0:
|
||||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"
|
||||
integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==
|
||||
|
||||
react-resize-detector@^7.1.2:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-7.1.2.tgz#8ef975dd8c3d56f9a5160ac382ef7136dcd2d86c"
|
||||
integrity sha512-zXnPJ2m8+6oq9Nn8zsep/orts9vQv3elrpA+R8XTcW7DVVUJ9vwDwMXaBtykAYjMnkCIaOoK9vObyR7ZgFNlOw==
|
||||
dependencies:
|
||||
lodash "^4.17.21"
|
||||
|
||||
react-router-dom@6:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.3.0.tgz#a0216da813454e521905b5fa55e0e5176123f43d"
|
||||
@@ -16233,6 +16299,14 @@ react-simple-maps@^2.3.0:
|
||||
d3-zoom "^2.0.0"
|
||||
topojson-client "^3.1.0"
|
||||
|
||||
react-smooth@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-2.0.1.tgz#74c7309916d6ccca182c4b30c8992f179e6c5a05"
|
||||
integrity sha512-Own9TA0GPPf3as4vSwFhDouVfXP15ie/wIHklhyKBH5AN6NFtdk0UpHBnonV11BtqDkAWlt40MOUc+5srmW7NA==
|
||||
dependencies:
|
||||
fast-equals "^2.0.0"
|
||||
react-transition-group "2.9.0"
|
||||
|
||||
react-syntax-highlighter@^15.4.5:
|
||||
version "15.5.0"
|
||||
resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz#4b3eccc2325fa2ec8eff1e2d6c18fa4a9e07ab20"
|
||||
@@ -16252,6 +16326,16 @@ react-tooltip@*, react-tooltip@^4.2.21:
|
||||
prop-types "^15.7.2"
|
||||
uuid "^7.0.3"
|
||||
|
||||
react-transition-group@2.9.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d"
|
||||
integrity sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==
|
||||
dependencies:
|
||||
dom-helpers "^3.4.0"
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.6.2"
|
||||
react-lifecycles-compat "^3.0.4"
|
||||
|
||||
react-transition-group@^4.4.2:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470"
|
||||
@@ -16440,6 +16524,30 @@ readonly-date@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/readonly-date/-/readonly-date-1.0.0.tgz#5af785464d8c7d7c40b9d738cbde8c646f97dcd9"
|
||||
integrity sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ==
|
||||
|
||||
recharts-scale@^0.4.4:
|
||||
version "0.4.5"
|
||||
resolved "https://registry.yarnpkg.com/recharts-scale/-/recharts-scale-0.4.5.tgz#0969271f14e732e642fcc5bd4ab270d6e87dd1d9"
|
||||
integrity sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==
|
||||
dependencies:
|
||||
decimal.js-light "^2.4.1"
|
||||
|
||||
recharts@^2.1.13:
|
||||
version "2.1.13"
|
||||
resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.1.13.tgz#61801acf3e13896b07dc6a8b38cbdd648480d0b7"
|
||||
integrity sha512-9VWu2nzExmfiMFDHKqRFhYlJVmjzQGVKH5rBetXR4EuyEXuu3Y6cVxQuNEdusHhbm4SoPPrVDCwlBdREL3sQPA==
|
||||
dependencies:
|
||||
classnames "^2.2.5"
|
||||
d3-interpolate "^3.0.1"
|
||||
d3-scale "^4.0.2"
|
||||
d3-shape "^3.1.0"
|
||||
eventemitter3 "^4.0.1"
|
||||
lodash "^4.17.19"
|
||||
react-is "^16.10.2"
|
||||
react-resize-detector "^7.1.2"
|
||||
react-smooth "^2.0.1"
|
||||
recharts-scale "^0.4.4"
|
||||
reduce-css-calc "^2.1.8"
|
||||
|
||||
rechoir@^0.7.0:
|
||||
version "0.7.1"
|
||||
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.1.tgz#9478a96a1ca135b5e88fc027f03ee92d6c645686"
|
||||
@@ -16463,6 +16571,14 @@ redent@^3.0.0:
|
||||
indent-string "^4.0.0"
|
||||
strip-indent "^3.0.0"
|
||||
|
||||
reduce-css-calc@^2.1.8:
|
||||
version "2.1.8"
|
||||
resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz#7ef8761a28d614980dc0c982f772c93f7a99de03"
|
||||
integrity sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==
|
||||
dependencies:
|
||||
css-unit-converter "^1.1.1"
|
||||
postcss-value-parser "^3.3.0"
|
||||
|
||||
refractor@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.6.0.tgz#ac318f5a0715ead790fcfb0c71f4dd83d977935a"
|
||||
|
||||
Reference in New Issue
Block a user