update validation for rewards playground
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { Typography } from '@mui/material';
|
||||
import { SelectionChance } from '@nymproject/types';
|
||||
|
||||
const colorMap: { [key in SelectionChance]: string } = {
|
||||
VeryLow: 'error.main',
|
||||
Low: 'error.main',
|
||||
Moderate: 'warning.main',
|
||||
High: 'success.main',
|
||||
VeryHigh: 'success.main',
|
||||
};
|
||||
|
||||
const textMap: { [key in SelectionChance]: string } = {
|
||||
VeryLow: 'VeryLow',
|
||||
Low: 'Low',
|
||||
Moderate: 'Moderate',
|
||||
High: 'High',
|
||||
VeryHigh: 'Very high',
|
||||
};
|
||||
|
||||
export const InclusionProbability = ({ probability }: { probability: SelectionChance }) => (
|
||||
<Typography sx={{ color: colorMap[probability] }}>{textMap[probability]}</Typography>
|
||||
);
|
||||
@@ -0,0 +1,60 @@
|
||||
import React from 'react';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { Button, Grid, TextField, Typography } from '@mui/material';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { inputValidationSchema } from './inputsValidationSchema';
|
||||
|
||||
export type InputValues = {
|
||||
label: string;
|
||||
name: 'profitMargin' | 'uptime' | 'bond' | 'delegations' | 'operatorCost';
|
||||
isPercentage?: boolean;
|
||||
}[];
|
||||
|
||||
export const Inputs = ({
|
||||
inputValues,
|
||||
onCalculate,
|
||||
}: {
|
||||
inputValues: InputValues;
|
||||
onCalculate: () => Promise<void>;
|
||||
}) => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: yupResolver(inputValidationSchema),
|
||||
defaultValues: { profitMargin: '', uptime: '', bond: '', delegations: '', operatorCost: '' },
|
||||
});
|
||||
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
{inputValues.map((input) => (
|
||||
<Grid item xs={12} lg={2}>
|
||||
<TextField
|
||||
{...register(input.name)}
|
||||
fullWidth
|
||||
label={input.label}
|
||||
name={input.name}
|
||||
error={Boolean(errors[input.name])}
|
||||
helperText={errors[input.name]?.message}
|
||||
InputProps={{
|
||||
endAdornment: <Typography sx={{ color: 'grey.600' }}>{input.isPercentage ? '%' : 'NYM'}</Typography>,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
))}{' '}
|
||||
<Grid item xs={12} lg={2}>
|
||||
<Button
|
||||
variant="contained"
|
||||
disableElevation
|
||||
onClick={handleSubmit(onCalculate)}
|
||||
size="large"
|
||||
fullWidth
|
||||
disabled={Boolean(Object.keys(errors).length)}
|
||||
>
|
||||
Calculate
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { Card, CardContent, Divider, Stack, Typography } from '@mui/material';
|
||||
import { SelectionChance } from '@nymproject/types';
|
||||
import { InclusionProbability } from './InclusionProbability';
|
||||
|
||||
export const NodeDetails = ({
|
||||
saturation,
|
||||
selectionProbability,
|
||||
}: {
|
||||
saturation: number;
|
||||
selectionProbability: SelectionChance;
|
||||
}) => (
|
||||
<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={selectionProbability} />
|
||||
</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,39 @@
|
||||
import * as Yup from 'yup';
|
||||
import { isGreaterThan, isLessThan } from 'src/utils';
|
||||
|
||||
export const inputValidationSchema = Yup.object().shape({
|
||||
profitMargin: Yup.string()
|
||||
.required()
|
||||
.test('Profit margin must be a number between 0 and 100', (value) => {
|
||||
const stringValueToNumber = Math.round(Number(value));
|
||||
console.log(isLessThan(stringValueToNumber, 101));
|
||||
if (stringValueToNumber && isGreaterThan(stringValueToNumber, -1) && isLessThan(stringValueToNumber, 101))
|
||||
return true;
|
||||
return false;
|
||||
}),
|
||||
uptime: Yup.string()
|
||||
.required()
|
||||
.test('Uptime must be a number between 0 and 100', (value) => {
|
||||
const stringValueToNumber = Math.round(Number(value));
|
||||
if (stringValueToNumber && isGreaterThan(stringValueToNumber, 0)) return true;
|
||||
return false;
|
||||
}),
|
||||
bond: Yup.string()
|
||||
.required()
|
||||
.test('Bond must be a valid number', (value) => {
|
||||
if (Number(value)) return true;
|
||||
return false;
|
||||
}),
|
||||
delegations: Yup.string()
|
||||
.required()
|
||||
.test('Delegations must be a valid number', (value) => {
|
||||
if (Number(value)) return true;
|
||||
return false;
|
||||
}),
|
||||
operatorCost: Yup.string()
|
||||
.required()
|
||||
.test('Operator cost must be a valid number', (value) => {
|
||||
if (Number(value)) return true;
|
||||
return false;
|
||||
}),
|
||||
});
|
||||
@@ -1,169 +1,44 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
Divider,
|
||||
Grid,
|
||||
Stack,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TextField,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { Box, Button, Card, CardContent, CardHeader, Grid, TextField, Typography } from '@mui/material';
|
||||
import { ResultsTable } from 'src/components/RewardsPlayground/ResultsTable';
|
||||
import { computeMixnodeRewardEstimation } from 'src/requests';
|
||||
import { SelectionChance } from '@nymproject/types';
|
||||
import { NodeDetails } from 'src/components/RewardsPlayground/NodeDetail';
|
||||
import { Inputs, InputValues } from 'src/components/RewardsPlayground/Inputs';
|
||||
|
||||
const MAJOR_AMOUNT_FOR_CALCS = 1000;
|
||||
|
||||
const tableHeader = [
|
||||
{ title: 'Estimated rewards', bold: true },
|
||||
{ title: 'Per day' },
|
||||
{ title: 'Per month' },
|
||||
{ title: 'Per year' },
|
||||
];
|
||||
|
||||
const colorMap: { [key in SelectionChance]: string } = {
|
||||
VeryLow: 'error.main',
|
||||
Low: 'error.main',
|
||||
Moderate: 'warning.main',
|
||||
High: 'success.main',
|
||||
VeryHigh: 'success.main',
|
||||
};
|
||||
|
||||
const textMap: { [key in SelectionChance]: string } = {
|
||||
VeryLow: 'VeryLow',
|
||||
Low: 'Low',
|
||||
Moderate: 'Moderate',
|
||||
High: 'High',
|
||||
VeryHigh: 'Very high',
|
||||
};
|
||||
|
||||
type Results = {
|
||||
operator: {
|
||||
daily: string;
|
||||
monthly: string;
|
||||
yearly: string;
|
||||
};
|
||||
delegator: {
|
||||
daily: string;
|
||||
monthly: string;
|
||||
yearly: string;
|
||||
};
|
||||
total: {
|
||||
daily: string;
|
||||
monthly: string;
|
||||
yearly: string;
|
||||
};
|
||||
};
|
||||
|
||||
const InclusionProbability = ({ probability }: { probability: SelectionChance }) => (
|
||||
<Typography sx={{ color: colorMap[probability] }}>{textMap[probability]}</Typography>
|
||||
);
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
const NodeDetails = ({
|
||||
saturation,
|
||||
selectionProbability,
|
||||
}: {
|
||||
saturation: number;
|
||||
selectionProbability: SelectionChance;
|
||||
}) => (
|
||||
<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={selectionProbability} />
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
export const ApyPlayground = () => {
|
||||
const [inputValues, setInputValues] = useState([
|
||||
{ label: 'Profit margin', isPercentage: true, value: '0' },
|
||||
{ label: 'Operator cost', value: '0' },
|
||||
{ label: 'Bond', value: '0' },
|
||||
{ label: 'Delegations', value: '0' },
|
||||
{ label: 'Uptime', isPercentage: true, value: '0' },
|
||||
const [inputValues, setInputValues] = useState<InputValues>([
|
||||
{ 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 },
|
||||
]);
|
||||
|
||||
const [results, setResults] = useState({
|
||||
total: { daily: '-', monthly: '-', yearly: '-' },
|
||||
operator: { daily: '-', monthly: '-', yearly: '-' },
|
||||
delegator: { daily: '-', monthly: '-', yearly: '-' },
|
||||
});
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setInputValues((current) => [
|
||||
...current.map((input) => (input.label === e.target.name ? { ...input, value: e.target.value } : input)),
|
||||
]);
|
||||
};
|
||||
|
||||
const getInputValue = (inputName: string) => inputValues.find((input) => input.label === inputName);
|
||||
const getInputValue = (inputName: string) => inputValues.find((input) => input.name === inputName);
|
||||
|
||||
const handleCalculate = async () => {
|
||||
try {
|
||||
const res = await computeMixnodeRewardEstimation({
|
||||
identity: 'DLdMKLPywEy1vnu3yPrtXvzY7fw1puiiHpA9n9UQatiQ',
|
||||
uptime: +getInputValue('Uptime')!,
|
||||
uptime: 0,
|
||||
isActive: true,
|
||||
pledgeAmount: Math.floor(+getInputValue('Bond')! * 1_000_000),
|
||||
totalDelegation: Math.floor(+getInputValue('Delegations')! * 1_000_000),
|
||||
pledgeAmount: Math.floor(0 * 1_000_000),
|
||||
totalDelegation: Math.floor(0 * 1_000_000),
|
||||
});
|
||||
|
||||
const operatorReward = (res.estimated_operator_reward / 1_000_000) * 24; // epoch_reward * 1 epoch_per_hour * 24 hours
|
||||
const delegatorsReward = (res.estimated_delegators_reward / 1_000_000) * 24;
|
||||
|
||||
const operatorRewardScaled = MAJOR_AMOUNT_FOR_CALCS * (operatorReward / +getInputValue('Bond')!.value);
|
||||
const delegatorReward = MAJOR_AMOUNT_FOR_CALCS * (delegatorsReward / +getInputValue('Delegations')!.value!);
|
||||
const operatorRewardScaled = MAJOR_AMOUNT_FOR_CALCS * (operatorReward / 0);
|
||||
const delegatorReward = MAJOR_AMOUNT_FOR_CALCS * (delegatorsReward / 0);
|
||||
|
||||
setResults({
|
||||
total: {
|
||||
@@ -205,29 +80,7 @@ export const ApyPlayground = () => {
|
||||
}
|
||||
/>
|
||||
<CardContent>
|
||||
<Grid container spacing={3} alignItems="center">
|
||||
{inputValues.map((input) => (
|
||||
<Grid item xs={12} lg={2}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={input.label}
|
||||
name={input.label}
|
||||
value={input.value}
|
||||
onChange={handleInputChange}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<Typography sx={{ color: 'grey.600' }}>{input.isPercentage ? '%' : 'NYM'}</Typography>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
<Grid item xs={12} lg={2}>
|
||||
<Button variant="contained" disableElevation onClick={handleCalculate} size="large" fullWidth>
|
||||
Calculate
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Inputs inputValues={inputValues} onCalculate={handleCalculate} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Grid container spacing={3}>
|
||||
|
||||
Reference in New Issue
Block a user