Add vesting life slider + make initial raise slider bigger

This commit is contained in:
dapplion 2019-08-11 18:12:07 +02:00
parent 0696188191
commit 5ce3706232
10 changed files with 331 additions and 142 deletions

View File

@ -115,7 +115,8 @@ const useStyles = makeStyles((theme: Theme) =>
},
button: {
// background: "linear-gradient(290deg, #2ad179, #4ab47c)", // Green gradient
background: "linear-gradient(290deg, #1880e0, #3873d8)", // blue gradient
background: "linear-gradient(290deg, #1aa059, #3d9567)", // Darker Green gradient
// background: "linear-gradient(290deg, #1880e0, #3873d8)", // blue gradient
color: "white"
},
// Descriptions
@ -138,6 +139,23 @@ const useStyles = makeStyles((theme: Theme) =>
},
descriptionPadding: {
padding: theme.spacing(0.5)
},
d0Container: {
"& > div": {
padding: "0 12px 0 0 !important",
display: "flex",
alignItems: "center"
}
},
d0Number: {
padding: "0 !important",
display: "flex",
alignItems: "center"
},
d0Slidder: {
padding: "0 12px 0 0 !important",
display: "flex",
alignItems: "center"
}
})
);
@ -148,10 +166,11 @@ export default function App() {
p0: 0.1, // Hatch sale price p0 (DAI / token)
p1: 0.3, // Return factor (.)
wFee: 0.05, // friction coefficient (.)
vHalflife: 52, // Vesting half life (weeks)
d0: 3e6 // Initial raise, d0 (DAI)
});
const { d0, theta, p0, p1, wFee } = curveParams;
const { d0, theta, p0, p1, wFee, vHalflife } = curveParams;
/**
* Throttle the curve update to prevent the expensive chart
@ -233,7 +252,7 @@ export default function App() {
const tx_spread = 10;
// vesting(should this be exposed in the app ?)
const cliff = 8; // weeks before vesting starts ~2 months
const halflife = 52; // 26 weeks, half life is ~6 months
// const halflife = 52; // 26 weeks, half life is ~6 months
// percentage of the hatch tokens which vest per week(since that is our timescale in the sim)
// numSteps = 52 take 8ms to run
@ -275,7 +294,7 @@ export default function App() {
// txsWithdraw.reduce((t, c) => t + c, 0);
// Vest
const delta_H = vest_tokens({ week: t, H, halflife, cliff });
const delta_H = vest_tokens({ week: t, H, halflife: vHalflife, cliff });
const H_next = H - delta_H;
// find floor price
@ -346,14 +365,14 @@ export default function App() {
description: resultParameterDescriptions.exitTributes.text,
value:
(+getLast(withdrawFeeTimeseries).toPrecision(3)).toLocaleString() +
` DAI (${withdrawCount} txs)`
" DAI",
valueFooter: `From ${withdrawCount} exit txs`
},
{
label: resultParameterDescriptions.slippage.name,
description: resultParameterDescriptions.slippage.text,
value:
+(100 * avgSlippage).toFixed(3) +
` % (avg tx size ${Math.round(avgTxSize).toLocaleString()} DAI)`
value: +(100 * avgSlippage).toFixed(3) + " %",
valueFooter: `Avg tx size ${Math.round(avgTxSize).toLocaleString()} DAI`
}
];
@ -374,37 +393,14 @@ export default function App() {
<Box className={classes.boxHeader}>
<Typography variant="h6">Curve Design</Typography>
<HelpText
text={
<div className={classes.descriptionContainer}>
<div>
<Typography className={classes.descriptionTitle}>
Parameters description:
</Typography>
</div>
<table>
<tbody>
{[
parameterDescriptions.theta,
parameterDescriptions.p0,
parameterDescriptions.p1,
parameterDescriptions.wFee,
parameterDescriptions.d0
].map(({ name, text }) => (
<tr key={name}>
<td>
<Typography>{name}</Typography>
</td>
<td>
<Typography className={classes.descriptionBody}>
{text}
</Typography>
</td>
</tr>
))}
</tbody>
</table>
</div>
}
title={"Parameters description"}
table={[
parameterDescriptions.theta,
parameterDescriptions.p0,
parameterDescriptions.p1,
parameterDescriptions.wFee,
parameterDescriptions.vHalflife
]}
/>
</Box>
@ -414,17 +410,6 @@ export default function App() {
setCurveParams={setCurveParamsThrottle}
/>
</Box>
<Box className={`${classes.boxHeader} ${classes.initialRaise}`}>
<Typography variant="h6">Run parameters</Typography>
</Box>
<Box className={classes.box}>
<SimulationInputParams
curveParams={curveParams}
setCurveParams={setCurveParamsThrottle}
/>
</Box>
</Paper>
</Grid>
@ -432,15 +417,7 @@ export default function App() {
<Paper className={classes.paper}>
<Box className={classes.boxHeader}>
<Typography variant="h6">Preview</Typography>
<HelpText
text={
<div className={classes.descriptionPadding}>
<Typography className={classes.descriptionBody}>
{supplyVsDemandChartDescription}
</Typography>
</div>
}
/>
<HelpText body={supplyVsDemandChartDescription} />
</Box>
<Box className={classes.boxChart}>
@ -451,8 +428,15 @@ export default function App() {
</Grid>
<Grid container spacing={3}>
<Grid item xs={12} md={12}>
<Grid item xs={12}>
<Paper>
<Box className={`${classes.box} ${classes.boxBorderBottom}`}>
<SimulationInputParams
curveParams={curveParams}
setCurveParams={setCurveParamsThrottle}
/>
</Box>
<Box className={classes.boxHeader}>
<Grid
container
@ -482,36 +466,8 @@ export default function App() {
<Box className={classes.boxHeader}>
<Typography variant="h6">Simulation</Typography>
<HelpText
text={
<div className={classes.descriptionContainer}>
<div className={classes.descriptionPadding}>
<Typography className={classes.descriptionBody}>
{simulationChartDescription}
</Typography>
</div>
<table>
<tbody>
{Object.values(
simulationParameterDescriptions
).map(({ name, text }) => (
<tr key={name}>
<td>
<Typography>{name}</Typography>
</td>
<td>
<Typography
className={classes.descriptionBody}
>
{text}
</Typography>
</td>
</tr>
))}
</tbody>
</table>
</div>
}
body={simulationChartDescription}
table={Object.values(simulationParameterDescriptions)}
/>
</Box>
@ -533,35 +489,8 @@ export default function App() {
<Box className={classes.boxHeader}>
<Typography variant="h6">Results</Typography>
<HelpText
text={
<div className={classes.descriptionContainer}>
<div>
<Typography className={classes.descriptionTitle}>
Result parameters description:
</Typography>
</div>
<table>
<tbody>
{Object.values(resultParameterDescriptions).map(
({ name, text }) => (
<tr key={name}>
<td>
<Typography>{name}</Typography>
</td>
<td>
<Typography
className={classes.descriptionBody}
>
{text}
</Typography>
</td>
</tr>
)
)}
</tbody>
</table>
</div>
}
title={"Result parameters description"}
table={Object.values(resultParameterDescriptions)}
/>
</Box>

View File

@ -14,12 +14,14 @@ export default function CurveDesignInputParams({
const [p0, setP0] = useState(0.1); // Hatch sale Price p0 (DAI / token)
const [p1, setP1] = useState(0.3); // Return factor (.)
const [wFee, setWFee] = useState(0.05); // friction coefficient (.)
const [vHalflife, setVHalflife] = useState(52); // friction coefficient (.)
useEffect(() => {
setTheta(curveParams.theta);
setP0(curveParams.p0);
setP1(curveParams.p1);
setWFee(curveParams.wFee);
setVHalflife(curveParams.vHalflife);
}, [curveParams]);
function _setP0(newP0: number) {
@ -34,7 +36,8 @@ export default function CurveDesignInputParams({
theta,
p0,
p1,
wFee
wFee,
vHalflife
}));
}
@ -90,6 +93,19 @@ export default function CurveDesignInputParams({
format: (n: number) => `${+(100 * n).toFixed(1)}%`,
toText: (n: number) => String(+(n * 1e2).toFixed(1)),
toNum: (n: string) => parseFloat(n) * 1e-2
},
{
label: `${parameterDescriptions.vHalflife.name} (weeks)`,
description: parameterDescriptions.vHalflife.text,
value: vHalflife,
setter: setVHalflife,
min: 52 / 2,
max: 52 * 2,
step: 1,
suffix: "",
format: (n: number) => String(Math.round(n)),
toText: (n: number) => String(Math.round(n)),
toNum: (n: string) => Math.round(parseInt(n))
}
];

View File

@ -1,6 +1,7 @@
import React from "react";
import { makeStyles } from "@material-ui/core/styles";
import Popover from "@material-ui/core/Popover";
import Typography from "@material-ui/core/Typography";
import Box from "@material-ui/core/Box";
import HelpIcon from "@material-ui/icons/HelpOutline";
@ -25,10 +26,42 @@ const useStyles = makeStyles(theme => ({
[`@media screen and (max-width: ${theme.breakpoints.values.md}px)`]: {
maxWidth: "90vw"
}
},
// Descriptions
descriptionContainer: {
"& > div:not(:last-child)": {
paddingBottom: theme.spacing(1),
marginBottom: theme.spacing(1),
borderBottom: "1px solid #3f5463"
},
"& td": {
verticalAlign: "top",
padding: theme.spacing(0.5)
}
},
descriptionTitle: {
padding: theme.spacing(0.5)
},
descriptionBody: {
color: "#dbdfe4",
padding: theme.spacing(0.5)
},
descriptionPadding: {
padding: theme.spacing(0.5)
}
}));
export default function SimplePopover({ text }: { text: any }) {
export default function SimplePopover({
text,
title,
table,
body
}: {
text?: any;
title?: string;
table?: { name: string; text: string }[];
body?: string;
}) {
const classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState(null);
@ -65,7 +98,48 @@ export default function SimplePopover({ text }: { text: any }) {
horizontal: "center"
}}
>
<Box className={classes.popoverContainer}>{text}</Box>
<Box className={classes.popoverContainer}>
<div className={classes.descriptionContainer}>
{title && (
<div>
<Typography className={classes.descriptionTitle}>
{title}
</Typography>
</div>
)}
{body && (
<div>
<Typography className={classes.descriptionBody}>
{body}
</Typography>
</div>
)}
{table && (
<div>
<table>
<tbody>
{table.map(({ name, text }) => (
<tr key={name}>
<td>
<Typography>{name}</Typography>
</td>
<td>
<Typography className={classes.descriptionBody}>
{text}
</Typography>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{text}
</div>
</Box>
</Popover>
</div>
);

162
src/InputParamBig.tsx Normal file
View File

@ -0,0 +1,162 @@
import React from "react";
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid";
import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography";
import NumberFormat from "react-number-format";
import { InputFieldInterface } from "./types";
import PrettoSlider from "./PrettoSlider";
import HelpText from "./HelpText";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
margin: theme.spacing(6, 0, 3)
},
lightBulb: {
verticalAlign: "middle",
marginRight: theme.spacing(1)
},
leftContainer: {
// color: theme.palette.text.secondary
},
centerContainer: {
// color: blackColor
},
listBoxContainer: {
"& > div:not(:last-child)": {
paddingBottom: "12px",
marginBottom: "12px",
borderBottom: "1px solid #313d47"
}
},
listBox: {
"& > div": {
display: "flex",
alignItems: "center",
"& p": {
marginBottom: 0
}
},
"& > div:not(:last-child)": {
paddingRight: "12px"
}
},
slider: {
color: theme.palette.primary.main
}
})
);
function NumberFormatCustom(props: any) {
const { inputRef, onChange, prefix, suffix, ...other } = props;
return (
<NumberFormat
{...other}
getInputRef={inputRef}
onValueChange={values => {
onChange({ target: { value: values.value } });
}}
thousandSeparator
prefix={prefix}
suffix={suffix}
/>
);
}
export default function InputParamBig({
inputFields,
onChangeCommited
}: {
inputFields: InputFieldInterface[];
onChangeCommited(): void;
}) {
const classes = useStyles();
return (
<div className={classes.listBoxContainer}>
{inputFields.map(
({
label,
description,
value,
setter,
min,
max,
step,
prefix,
suffix,
format,
toText,
toNum
}) => {
function sanitizeInput(num: number = 0) {
if (isNaN(num)) num = 0;
if (num > max) num = max;
else if (num < min) num = min;
setter(num);
}
return (
<Grid key={label} container spacing={0} className={classes.listBox}>
<Grid
item
xs={6}
sm={6}
md={3}
lg={2}
className={classes.leftContainer}
>
<Typography variant="h6">{label}</Typography>
<HelpText title={label} body={description} />
</Grid>
<Grid
item
xs={2}
sm={2}
md={1}
className={classes.centerContainer}
>
<TextField
onChange={e => {
sanitizeInput(
toNum ? toNum(e.target.value) : parseFloat(e.target.value)
);
onChangeCommited();
}}
InputProps={{
inputComponent: NumberFormatCustom,
disableUnderline: true,
inputProps: {
prefix,
suffix
}
}}
value={toText ? toText(value) : value}
/>
</Grid>
<Grid item xs={4} sm={4} md={8} lg={9}>
<PrettoSlider
className={classes.slider}
valueLabelDisplay="auto"
aria-label={label}
defaultValue={value}
onChange={(_, newValue) => sanitizeInput(Number(newValue))}
onChangeCommitted={onChangeCommited}
value={value}
min={min}
max={max}
step={step}
valueLabelFormat={value => format(value).replace("$", "")}
/>
</Grid>
</Grid>
);
}
)}
</div>
);
}

View File

@ -43,9 +43,6 @@ const useStyles = makeStyles((theme: Theme) =>
},
slider: {
color: theme.palette.primary.main
},
secondaryColor: {
color: theme.palette.secondary.light
}
})
);
@ -89,7 +86,6 @@ export default function InputParams({
step,
prefix,
suffix,
secondaryColor,
format,
toText,
toNum
@ -108,7 +104,6 @@ export default function InputParams({
</Grid>
<Grid item xs={2} className={classes.centerContainer}>
{/* <Typography gutterBottom>{display(value)}</Typography> */}
<TextField
onChange={e => {
sanitizeInput(
@ -130,9 +125,7 @@ export default function InputParams({
<Grid item xs={4}>
<PrettoSlider
className={`${classes.slider} ${
secondaryColor ? classes.secondaryColor : ""
}`}
className={classes.slider}
valueLabelDisplay="auto"
aria-label={label}
defaultValue={value}

View File

@ -38,6 +38,10 @@ const useStyles = makeStyles((theme: Theme) =>
"& > div:not(:last-child)": {
paddingRight: "12px"
}
},
valueFooter: {
color: theme.palette.text.secondary,
fontSize: "80%"
}
})
);
@ -50,6 +54,7 @@ export default function ResultParams({
label: string;
description: string;
value: number | string;
valueFooter?: string;
}[];
simulationDuration: number;
}) {
@ -58,7 +63,7 @@ export default function ResultParams({
* Keep the animation active only during the initial animation time,
* but afterwards, deactivate to prevent the re-size ugly effect
*/
const [isAnimationActive, setIsAnimationActive] = useState(true);
const [isAnimationActive, setIsAnimationActive] = useState(false);
useEffect(() => {
const timeout = setTimeout(() => {
setIsAnimationActive(false);
@ -72,7 +77,7 @@ export default function ResultParams({
return (
<div className={classes.listBoxContainer}>
{resultFields.map(({ label, description, value }) => (
{resultFields.map(({ label, description, value, valueFooter }) => (
<Grid key={label} container spacing={0} className={classes.listBox}>
<Grid item xs={8} className={classes.leftContainer}>
<TextWithPopover content={label} popoverText={description} />
@ -82,7 +87,14 @@ export default function ResultParams({
{isAnimationActive ? (
<DotsLoader />
) : (
<Typography gutterBottom>{value}</Typography>
<div>
<Typography>{value}</Typography>
{valueFooter && (
<Typography className={classes.valueFooter}>
{valueFooter}
</Typography>
)}
</div>
)}
</Grid>
</Grid>

View File

@ -1,9 +1,9 @@
import React, { useState, useEffect } from "react";
import { InputFieldInterface, CurveParamsInterface } from "./types";
import InputParams from "./InputParams";
import InputParamBig from "./InputParamBig";
import { parameterDescriptions } from "./parametersDescriptions";
export default function CurveDesignInputParams({
export default function SimulationInputParams({
curveParams,
setCurveParams
}: {
@ -25,7 +25,7 @@ export default function CurveDesignInputParams({
const inputFields: InputFieldInterface[] = [
{
label: `${parameterDescriptions.d0.name} (DAI)`,
label: `${parameterDescriptions.d0.name}`,
description: parameterDescriptions.d0.text,
value: d0,
setter: setD0,
@ -35,13 +35,12 @@ export default function CurveDesignInputParams({
suffix: "M",
format: (n: number) => `$${+(n * 1e-6).toFixed(1)}M`,
toText: (n: number) => String(+(n * 1e-6).toFixed(1)),
toNum: (n: string) => Math.floor(parseFloat(n) * 1e6),
secondaryColor: true
toNum: (n: string) => Math.floor(parseFloat(n) * 1e6)
}
];
return (
<InputParams
<InputParamBig
inputFields={inputFields}
onChangeCommited={setParentCurveParams}
/>

View File

@ -8,7 +8,6 @@ const useStyles = makeStyles(theme => ({
container: {
color: theme.palette.text.secondary,
display: "flex",
marginLeft: "6px",
fontSize: "0.9rem",
cursor: "pointer",
transition: "color ease 150ms",
@ -61,7 +60,7 @@ export default function TextWithPopover({
return (
<div className={classes.container}>
<div aria-describedby={id} onClick={handleClick}>
<Typography gutterBottom>{content}</Typography>
<Typography>{content}</Typography>
</div>
<Popover
PaperProps={{

View File

@ -22,6 +22,11 @@ export const parameterDescriptions: DescriptionObject = {
text:
"The percentage that goes to the funding pool when token holders 'sell' by burning their token at the price determined by the bonding curve"
},
vHalflife: {
name: "Vesting half life",
text:
"Tokens that are purchased during the Hatch are locked for 9 weeks and then released slowly such that 50% of the tokens will be able to be sold after this many weeks and 87.5% of the tokens after 3x this many weeks"
},
d0: {
name: "Initial raise",
text: "Amount of funds contributed during the hatch period"

View File

@ -9,7 +9,6 @@ export interface InputFieldInterface {
unit?: string;
prefix?: string;
suffix?: string;
secondaryColor?: boolean;
toText?(value: number): string;
toNum?(value: string): number;
format(value: number): string;
@ -21,4 +20,5 @@ export interface CurveParamsInterface {
p0: number;
p1: number;
wFee: number;
vHalflife: number;
}