update validation for rewards playground

This commit is contained in:
fmtabbara
2022-09-21 09:49:10 +01:00
parent ecb0f11bbb
commit 480f8a0a53
6 changed files with 241 additions and 165 deletions
@@ -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}>