diff --git a/explorer-api/src/mix_node/models.rs b/explorer-api/src/mix_node/models.rs
index ff94f921b4..6cbb295b6b 100644
--- a/explorer-api/src/mix_node/models.rs
+++ b/explorer-api/src/mix_node/models.rs
@@ -11,7 +11,7 @@ use serde::Serialize;
use std::sync::Arc;
use std::time::SystemTime;
use tokio::sync::RwLock;
-use validator_client::models::SelectionChance;
+use validator_client::models::{NodePerformance, SelectionChance};
#[derive(Clone, Debug, Serialize, JsonSchema, PartialEq)]
#[serde(rename_all = "snake_case")]
@@ -36,6 +36,7 @@ pub(crate) struct PrettyDetailedMixNodeBond {
pub stake_saturation: f32,
pub uncapped_saturation: f32,
pub avg_uptime: u8,
+ pub node_performance: NodePerformance,
pub estimated_operator_apy: f64,
pub estimated_delegators_apy: f64,
pub operating_cost: Coin,
diff --git a/explorer-api/src/mix_nodes/models.rs b/explorer-api/src/mix_nodes/models.rs
index c4483cf192..0cbc7b0167 100644
--- a/explorer-api/src/mix_nodes/models.rs
+++ b/explorer-api/src/mix_nodes/models.rs
@@ -153,6 +153,7 @@ impl ThreadsafeMixNodesCache {
layer: node.mixnode_details.bond_information.layer,
mix_node: node.mixnode_details.bond_information.mix_node.clone(),
avg_uptime: node.performance.round_to_integer(),
+ node_performance: node.node_performance.clone(),
stake_saturation: best_effort_small_dec_to_f64(node.stake_saturation) as f32,
uncapped_saturation: best_effort_small_dec_to_f64(node.uncapped_stake_saturation)
as f32,
diff --git a/explorer/src/api/index.ts b/explorer/src/api/index.ts
index 894dd3e179..f6a46f151d 100644
--- a/explorer/src/api/index.ts
+++ b/explorer/src/api/index.ts
@@ -62,6 +62,7 @@ export class Api {
if (cachedMixnodes) {
return cachedMixnodes;
}
+
const res = await fetch(MIXNODES_API);
const json = await res.json();
storeInCache('mixnodes', JSON.stringify(json));
diff --git a/explorer/src/components/DetailTable.tsx b/explorer/src/components/DetailTable.tsx
index 80e7ce969b..d333f0eb59 100644
--- a/explorer/src/components/DetailTable.tsx
+++ b/explorer/src/components/DetailTable.tsx
@@ -1,5 +1,15 @@
import * as React from 'react';
-import { Link, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material';
+import {
+ Link,
+ Paper,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ TableCellProps,
+} from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { Tooltip } from '@nymproject/react/tooltip/Tooltip';
import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard';
@@ -8,13 +18,13 @@ import { cellStyles } from './Universal-DataGrid';
import { unymToNym } from '../utils/currency';
import { GatewayEnrichedRowType } from './Gateways';
import { MixnodeRowType } from './MixNodes';
+import { StakeSaturationProgressBar } from './MixNodes/Economics/StakeSaturationProgressBar';
export type ColumnsType = {
field: string;
title: string;
- headerAlign: string;
- flex?: number;
- width?: number;
+ headerAlign?: TableCellProps['align'];
+ width?: string | number;
tooltipInfo?: string;
};
@@ -50,6 +60,10 @@ function formatCellValues(val: string | number, field: string) {
);
}
+ if (field === 'stake_saturation') {
+ return ;
+ }
+
return val;
}
@@ -61,11 +75,11 @@ export const DetailTable: FCWithChildren<{
const theme = useTheme();
return (
-
+
- {columnsData?.map(({ field, title, flex, tooltipInfo }) => (
-
+ {columnsData?.map(({ field, title, width, tooltipInfo }) => (
+
{tooltipInfo && (
diff --git a/explorer/src/components/MixNodes/Economics/Columns.ts b/explorer/src/components/MixNodes/Economics/Columns.ts
index 3c20e0eebd..c3ac85baaf 100644
--- a/explorer/src/components/MixNodes/Economics/Columns.ts
+++ b/explorer/src/components/MixNodes/Economics/Columns.ts
@@ -4,57 +4,48 @@ export const EconomicsInfoColumns: ColumnsType[] = [
{
field: 'estimatedTotalReward',
title: 'Estimated Total Reward',
- flex: 1,
- headerAlign: 'left',
+ width: '15%',
tooltipInfo:
'Estimated node reward (total for the operator and delegators) in the current epoch. There are roughly 24 epochs in a day.',
},
{
field: 'estimatedOperatorReward',
title: 'Estimated Operator Reward',
- flex: 1,
- headerAlign: 'left',
+ width: '15%',
tooltipInfo:
"Estimated operator's reward (including PM and Operating Cost) in the current epoch. There are roughly 24 epochs in a day.",
},
{
field: 'selectionChance',
title: 'Active Set Probability',
- flex: 1,
- headerAlign: 'left',
+ width: '12.5%',
tooltipInfo:
'Probability of getting selected in the reward set (active and standby nodes) in the next epoch. The more your stake, the higher the chances to be selected.',
},
- {
- field: 'stakeSaturation',
- title: 'Stake Saturation',
- flex: 1,
- headerAlign: 'left',
- tooltipInfo:
- 'Level of stake saturation for this node. Nodes receive more rewards the higher their saturation level, up to 100%. Beyond 100% no additional rewards are granted. The current stake saturation level is 730k NYM, computed as S/K where S is target amount of tokens staked in the network and K is the number of nodes in the reward set.',
- },
{
field: 'profitMargin',
title: 'Profit Margin',
- flex: 1,
- headerAlign: 'left',
+ width: '12.5%',
tooltipInfo:
'Percentage of the delegators rewards that the operator takes as fee before rewards are distributed to the delegators.',
},
{
field: 'operatingCost',
title: 'Operating Cost',
- flex: 1,
- headerAlign: 'left',
+ width: '10%',
tooltipInfo:
'Monthly operational cost of running this node. This cost is set by the operator and it influences how the rewards are split between the operator and delegators.',
},
{
field: 'avgUptime',
+ title: 'Avg. Score',
+ width: '10%',
+ tooltipInfo: "Mixnode's average routing score in the last 24 hour",
+ },
+ {
+ field: 'nodePerformance',
title: 'Routing Score',
- flex: 1,
- headerAlign: 'left',
tooltipInfo:
- 'Node’s routing score is relative to that of the network. Each time a node is tested, the test packets have to go through the full path of the network (a gateway + 3 nodes). If a node in the path drop packets it will affect the score of other nodes in the test.',
+ "Mixnode's most recent score (measured in the last 15 minutes). Routing score is relative to that of the network. Each time a gateway is tested, the test packets have to go through the full path of the network (gateway + 3 nodes). If a node in the path drop packets it will affect the score of the gateway and other nodes in the test.",
},
];
diff --git a/explorer/src/components/MixNodes/Economics/EconomicsProgress.tsx b/explorer/src/components/MixNodes/Economics/EconomicsProgress.tsx
index 9f8525a754..24db62d167 100644
--- a/explorer/src/components/MixNodes/Economics/EconomicsProgress.tsx
+++ b/explorer/src/components/MixNodes/Economics/EconomicsProgress.tsx
@@ -9,14 +9,14 @@ const parseToNumber = (value: number | undefined | string) =>
export const EconomicsProgress: FCWithChildren<
LinearProgressProps & {
threshold?: number;
+ color: string;
}
-> = ({ threshold, ...props }) => {
+> = ({ threshold, color, ...props }) => {
const theme = useTheme();
const { value } = props;
const valueNumber: number = parseToNumber(value);
const thresholdNumber: number = parseToNumber(threshold);
- const percentageColor = valueNumber > (threshold || 100) ? 'warning' : 'inherit';
const percentageToDisplay = Math.min(valueNumber, thresholdNumber);
return (
@@ -29,9 +29,9 @@ export const EconomicsProgress: FCWithChildren<
);
diff --git a/explorer/src/components/MixNodes/Economics/MixNodeEconomics.stories.tsx b/explorer/src/components/MixNodes/Economics/MixNodeEconomics.stories.tsx
index aa3156ccf1..c9c82c0438 100644
--- a/explorer/src/components/MixNodes/Economics/MixNodeEconomics.stories.tsx
+++ b/explorer/src/components/MixNodes/Economics/MixNodeEconomics.stories.tsx
@@ -14,9 +14,7 @@ const row: EconomicsInfoRowWithIndex = {
selectionChance: {
value: 'High',
},
- avgUptime: {
- value: '65 %',
- },
+
estimatedOperatorReward: {
value: '80000.123456 NYM',
},
@@ -29,9 +27,11 @@ const row: EconomicsInfoRowWithIndex = {
operatingCost: {
value: '11121 NYM',
},
- stakeSaturation: {
- value: '80 %',
- progressBarValue: 80,
+ avgUptime: {
+ value: '-',
+ },
+ nodePerformance: {
+ value: '-',
},
};
@@ -55,9 +55,7 @@ const emptyRow: EconomicsInfoRowWithIndex = {
value: '-',
progressBarValue: 0,
},
- avgUptime: {
- value: '-',
- },
+
estimatedOperatorReward: {
value: '-',
},
@@ -70,9 +68,11 @@ const emptyRow: EconomicsInfoRowWithIndex = {
operatingCost: {
value: '-',
},
- stakeSaturation: {
+ avgUptime: {
+ value: '-',
+ },
+ nodePerformance: {
value: '-',
- progressBarValue: 0,
},
};
diff --git a/explorer/src/components/MixNodes/Economics/Rows.ts b/explorer/src/components/MixNodes/Economics/Rows.ts
index 61049a5ca3..a1387a03de 100644
--- a/explorer/src/components/MixNodes/Economics/Rows.ts
+++ b/explorer/src/components/MixNodes/Economics/Rows.ts
@@ -14,11 +14,15 @@ export const EconomicsInfoRows = (): EconomicsInfoRowWithIndex => {
currencyToString((economicDynamicsStats?.data?.estimated_total_node_reward || '').toString()) || '-';
const estimatedOperatorRewards =
currencyToString((economicDynamicsStats?.data?.estimated_operator_reward || '').toString()) || '-';
- const stakeSaturation = economicDynamicsStats?.data?.uncapped_saturation || '-';
const profitMargin = mixNode?.data?.profit_margin_percent
? toPercentIntegerString(mixNode?.data?.profit_margin_percent)
: '-';
- const avgUptime = economicDynamicsStats?.data?.current_interval_uptime;
+ const avgUptime = mixNode?.data?.node_performance
+ ? toPercentIntegerString(mixNode?.data?.node_performance.last_24h)
+ : '-';
+ const nodePerformance = mixNode?.data?.node_performance
+ ? toPercentIntegerString(mixNode?.data?.node_performance.most_recent)
+ : '-';
const opCost = mixNode?.data?.operating_cost;
@@ -33,10 +37,6 @@ export const EconomicsInfoRows = (): EconomicsInfoRowWithIndex => {
selectionChance: {
value: selectionChance(economicDynamicsStats),
},
- stakeSaturation: {
- progressBarValue: typeof stakeSaturation === 'number' ? stakeSaturation * 100 : 0,
- value: typeof stakeSaturation === 'number' ? `${(stakeSaturation * 100).toFixed(2)} %` : '-',
- },
profitMargin: {
value: profitMargin ? `${profitMargin} %` : '-',
},
@@ -46,5 +46,8 @@ export const EconomicsInfoRows = (): EconomicsInfoRowWithIndex => {
avgUptime: {
value: avgUptime ? `${avgUptime} %` : '-',
},
+ nodePerformance: {
+ value: nodePerformance,
+ },
};
};
diff --git a/explorer/src/components/MixNodes/Economics/StakeSaturationProgressBar.tsx b/explorer/src/components/MixNodes/Economics/StakeSaturationProgressBar.tsx
new file mode 100644
index 0000000000..918d024643
--- /dev/null
+++ b/explorer/src/components/MixNodes/Economics/StakeSaturationProgressBar.tsx
@@ -0,0 +1,32 @@
+import React from 'react';
+import { Box, Typography } from '@mui/material';
+import { useIsMobile } from '../../../hooks/useIsMobile';
+import { EconomicsProgress } from './EconomicsProgress';
+
+export const StakeSaturationProgressBar = ({ value, threshold }: { value: number; threshold: number }) => {
+ const isTablet = useIsMobile('lg');
+ const percentageColor = value > (threshold || 100) ? 'warning' : 'inherit';
+ const textColor = percentageColor === 'warning' ? 'warning.main' : 'nym.wallet.fee';
+
+ return (
+
+
+ {value}%
+
+
+
+ );
+};
diff --git a/explorer/src/components/MixNodes/Economics/Table.tsx b/explorer/src/components/MixNodes/Economics/Table.tsx
index 929445254a..74bfa30251 100644
--- a/explorer/src/components/MixNodes/Economics/Table.tsx
+++ b/explorer/src/components/MixNodes/Economics/Table.tsx
@@ -1,70 +1,19 @@
import * as React from 'react';
import { Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from '@mui/material';
import { Box } from '@mui/system';
-import { useTheme, Theme } from '@mui/material/styles';
+import { useTheme } from '@mui/material/styles';
import { Tooltip } from '@nymproject/react/tooltip/Tooltip';
import { EconomicsRowsType, EconomicsInfoRowWithIndex } from './types';
-import { EconomicsProgress } from './EconomicsProgress';
-import { cellStyles } from '../../Universal-DataGrid';
import { UniversalTableProps } from '../../DetailTable';
-import { useIsMobile } from '../../../hooks/useIsMobile';
+import { textColour } from '../../../utils';
-const threshold = 100;
-
-const textColour = (value: EconomicsRowsType, field: string, theme: Theme) => {
- const progressBarValue = value?.progressBarValue || 0;
- const fieldValue = value.value;
-
- if (progressBarValue > 100) {
- return theme.palette.warning.main;
- }
- if (field === 'selectionChance') {
- // TODO: when v2 will be deployed, remove cases: VeryHigh, Moderate and VeryLow
- switch (fieldValue) {
- case 'High':
- case 'VeryHigh':
- return theme.palette.nym.networkExplorer.selectionChance.overModerate;
- case 'Good':
- case 'Moderate':
- return theme.palette.nym.networkExplorer.selectionChance.moderate;
- case 'Low':
- case 'VeryLow':
- return theme.palette.nym.networkExplorer.selectionChance.underModerate;
- default:
- return theme.palette.nym.wallet.fee;
- }
- }
- return theme.palette.nym.wallet.fee;
-};
-
-const formatCellValues = (value: EconomicsRowsType, field: string) => {
- const isTablet = useIsMobile('lg');
- if (value.progressBarValue) {
- return (
-
-
- {value.value}
-
-
-
- );
- }
- return (
-
-
- {value.value}
-
-
- );
-};
+const formatCellValues = (value: EconomicsRowsType, field: string) => (
+
+
+ {value.value}
+
+
+);
export const DelegatorsInfoTable: FCWithChildren> = ({
tableName,
@@ -78,21 +27,19 @@ export const DelegatorsInfoTable: FCWithChildren
- {columnsData?.map(({ field, title, flex, tooltipInfo }) => (
-
+ {columnsData?.map(({ field, title, tooltipInfo, width }) => (
+
{tooltipInfo && (
-
-
-
+
)}
{title}
@@ -106,19 +53,11 @@ export const DelegatorsInfoTable: FCWithChildren {
const { field } = columnsData[index];
const value: EconomicsRowsType = (eachRow as any)[field];
-
+ console.log(value);
return (
{
const { mixNode, mixNodeRow, description, stats, status, uptimeStory, uniqDelegations } = useMixnodeContext();
-
+ console.log(mixNodeRow);
return (
-
{mixNodeRow && description?.data && (
@@ -76,13 +75,11 @@ const PageMixnodeDetailWithState: FCWithChildren = () => {
)}
-
-
{
/>
-
@@ -100,7 +96,6 @@ const PageMixnodeDetailWithState: FCWithChildren = () => {
-
@@ -144,7 +139,6 @@ const PageMixnodeDetailWithState: FCWithChildren = () => {
)}
-
{status && (
diff --git a/explorer/src/pages/Mixnodes/index.tsx b/explorer/src/pages/Mixnodes/index.tsx
index 50d85583b1..d42b99efef 100644
--- a/explorer/src/pages/Mixnodes/index.tsx
+++ b/explorer/src/pages/Mixnodes/index.tsx
@@ -168,7 +168,7 @@ export const PageMixnodes: FCWithChildren = () => {
component={RRDLink}
to={`/network-components/mixnode/${params.row.mix_id}`}
>
- {`${params.value.toFixed(2)} %`}
+ {`${params.value} %`}
),
},
@@ -238,13 +238,13 @@ export const PageMixnodes: FCWithChildren = () => {
),
},
{
- field: 'avg_uptime',
+ field: 'node_performance',
headerName: 'Routing Score',
disableColumnMenu: true,
renderHeader: () => (
),
headerClassName: 'MuiDataGrid-header-override',
@@ -332,7 +332,6 @@ export const PageMixnodes: FCWithChildren = () => {
const handlePageSize = (event: SelectChangeEvent) => {
setPageSize(event.target.value);
};
-
return (
<>
diff --git a/explorer/src/typeDefs/explorer-api.ts b/explorer/src/typeDefs/explorer-api.ts
index e430427aff..4d5b44854f 100644
--- a/explorer/src/typeDefs/explorer-api.ts
+++ b/explorer/src/typeDefs/explorer-api.ts
@@ -84,6 +84,7 @@ export interface MixNodeResponseItem {
};
mix_node: MixNode;
avg_uptime: number;
+ node_performance: NodePerformance;
stake_saturation: number;
uncapped_saturation: number;
operating_cost: Amount;
diff --git a/explorer/src/utils/index.ts b/explorer/src/utils/index.ts
index 1065c000ae..4f922d870d 100644
--- a/explorer/src/utils/index.ts
+++ b/explorer/src/utils/index.ts
@@ -1,7 +1,9 @@
/* eslint-disable camelcase */
import { MutableRefObject } from 'react';
+import { Theme } from '@mui/material/styles';
import { registerLocale, getName } from 'i18n-iso-countries';
import { CountryData } from '../typeDefs/explorer-api';
+import { EconomicsRowsType } from '../components/MixNodes/Economics/types';
registerLocale(require('i18n-iso-countries/langs/en.json'));
@@ -53,3 +55,29 @@ export const splice = (start: number, deleteCount: number, address?: string): st
* @returns A stringified integer
*/
export const toPercentIntegerString = (value: string) => Math.round(Number(value) * 100).toString();
+
+export const textColour = (value: EconomicsRowsType, field: string, theme: Theme) => {
+ const progressBarValue = value?.progressBarValue || 0;
+ const fieldValue = value.value;
+
+ if (progressBarValue > 100) {
+ return theme.palette.warning.main;
+ }
+ if (field === 'selectionChance') {
+ // TODO: when v2 will be deployed, remove cases: VeryHigh, Moderate and VeryLow
+ switch (fieldValue) {
+ case 'High':
+ case 'VeryHigh':
+ return theme.palette.nym.networkExplorer.selectionChance.overModerate;
+ case 'Good':
+ case 'Moderate':
+ return theme.palette.nym.networkExplorer.selectionChance.moderate;
+ case 'Low':
+ case 'VeryLow':
+ return theme.palette.nym.networkExplorer.selectionChance.underModerate;
+ default:
+ return theme.palette.nym.wallet.fee;
+ }
+ }
+ return theme.palette.nym.wallet.fee;
+};