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

View File

@ -14,12 +14,14 @@ export default function CurveDesignInputParams({
const [p0, setP0] = useState(0.1); // Hatch sale Price p0 (DAI / token) const [p0, setP0] = useState(0.1); // Hatch sale Price p0 (DAI / token)
const [p1, setP1] = useState(0.3); // Return factor (.) const [p1, setP1] = useState(0.3); // Return factor (.)
const [wFee, setWFee] = useState(0.05); // friction coefficient (.) const [wFee, setWFee] = useState(0.05); // friction coefficient (.)
const [vHalflife, setVHalflife] = useState(52); // friction coefficient (.)
useEffect(() => { useEffect(() => {
setTheta(curveParams.theta); setTheta(curveParams.theta);
setP0(curveParams.p0); setP0(curveParams.p0);
setP1(curveParams.p1); setP1(curveParams.p1);
setWFee(curveParams.wFee); setWFee(curveParams.wFee);
setVHalflife(curveParams.vHalflife);
}, [curveParams]); }, [curveParams]);
function _setP0(newP0: number) { function _setP0(newP0: number) {
@ -34,7 +36,8 @@ export default function CurveDesignInputParams({
theta, theta,
p0, p0,
p1, p1,
wFee wFee,
vHalflife
})); }));
} }
@ -90,6 +93,19 @@ export default function CurveDesignInputParams({
format: (n: number) => `${+(100 * n).toFixed(1)}%`, format: (n: number) => `${+(100 * n).toFixed(1)}%`,
toText: (n: number) => String(+(n * 1e2).toFixed(1)), toText: (n: number) => String(+(n * 1e2).toFixed(1)),
toNum: (n: string) => parseFloat(n) * 1e-2 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 React from "react";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import Popover from "@material-ui/core/Popover"; import Popover from "@material-ui/core/Popover";
import Typography from "@material-ui/core/Typography";
import Box from "@material-ui/core/Box"; import Box from "@material-ui/core/Box";
import HelpIcon from "@material-ui/icons/HelpOutline"; 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)`]: { [`@media screen and (max-width: ${theme.breakpoints.values.md}px)`]: {
maxWidth: "90vw" 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 classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState(null); const [anchorEl, setAnchorEl] = React.useState(null);
@ -65,7 +98,48 @@ export default function SimplePopover({ text }: { text: any }) {
horizontal: "center" 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> </Popover>
</div> </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: { slider: {
color: theme.palette.primary.main color: theme.palette.primary.main
},
secondaryColor: {
color: theme.palette.secondary.light
} }
}) })
); );
@ -89,7 +86,6 @@ export default function InputParams({
step, step,
prefix, prefix,
suffix, suffix,
secondaryColor,
format, format,
toText, toText,
toNum toNum
@ -108,7 +104,6 @@ export default function InputParams({
</Grid> </Grid>
<Grid item xs={2} className={classes.centerContainer}> <Grid item xs={2} className={classes.centerContainer}>
{/* <Typography gutterBottom>{display(value)}</Typography> */}
<TextField <TextField
onChange={e => { onChange={e => {
sanitizeInput( sanitizeInput(
@ -130,9 +125,7 @@ export default function InputParams({
<Grid item xs={4}> <Grid item xs={4}>
<PrettoSlider <PrettoSlider
className={`${classes.slider} ${ className={classes.slider}
secondaryColor ? classes.secondaryColor : ""
}`}
valueLabelDisplay="auto" valueLabelDisplay="auto"
aria-label={label} aria-label={label}
defaultValue={value} defaultValue={value}

View File

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

View File

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

View File

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

View File

@ -22,6 +22,11 @@ export const parameterDescriptions: DescriptionObject = {
text: 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" "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: { d0: {
name: "Initial raise", name: "Initial raise",
text: "Amount of funds contributed during the hatch period" text: "Amount of funds contributed during the hatch period"

View File

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