augmented-tbc-design/src/App.tsx

349 lines
10 KiB
TypeScript

import React, { useState, useEffect, useMemo } from "react";
// Material UI
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
import Container from "@material-ui/core/Container";
import Typography from "@material-ui/core/Typography";
import Box from "@material-ui/core/Box";
import Paper from "@material-ui/core/Paper";
import Grid from "@material-ui/core/Grid";
import Button from "@material-ui/core/Button";
import Fade from "@material-ui/core/Fade";
// Components
import Header from "./Header";
import InputParams from "./InputParams";
import SupplyVsDemandChart from "./SupplyVsDemandChart";
import ResultParams from "./ResultParams";
import PriceSimulationChart from "./PriceSimulationChart";
import HelpText from "./HelpText";
// Utils
import { getLast, getAvg, pause } from "./utils";
import {
getInitialParams,
getPriceR,
getRandomDeltas,
getSlippage
} from "./math";
import { throttle } from "lodash";
// General styles
import "./app.css";
const headerOffset = 10;
const useStyles = makeStyles((theme: Theme) =>
createStyles({
mainContainer: {
"& > div:not(:last-child)": {
paddingBottom: theme.spacing(3)
},
"& > div": {
"& > div": {
paddingTop: "0 !important"
}
},
paddingBottom: theme.spacing(9)
},
paper: {
width: "100%",
height: "100%",
minHeight: 310,
backgroundColor: "#293640"
},
box: {
padding: theme.spacing(3, 3),
minHeight: 310
},
boxButton: {
padding: theme.spacing(3, 3)
},
boxHeader: {
padding: theme.spacing(3, 3),
height: theme.spacing(headerOffset),
display: "flex",
alignItems: "center",
borderBottom: "1px solid #313d47"
},
boxChart: {
width: "100%",
height: "100%",
minHeight: 310,
maxHeight: 350,
padding: theme.spacing(3, 3),
// Correct the chart excessive margins
paddingRight: "5px",
paddingLeft: "5px"
},
header: {
backgroundColor: "#0b1216",
color: "#f8f8f8",
textAlign: "center",
padding: theme.spacing(3, 0, 6 + headerOffset),
marginBottom: -theme.spacing(headerOffset)
},
button: {
background: "linear-gradient(290deg, #2ad179, #4ab47c)",
color: "white"
}
})
);
export default function App() {
const [curveParams, setCurveParams] = useState({
d0: 1e6, // Initial raise, d0 (DAI)
theta: 0.35, // fraction allocated to reserve (.)
p0: 0.1, // Hatch sale price p0 (DAI / token)
returnF: 3, // Return factor (.)
wFee: 0.05 // friction coefficient (.)
});
const { d0, theta, p0, returnF, wFee } = curveParams;
/**
* Throttle the curve update to prevent the expensive chart
* to re-render too often
*/
const setCurveParamsThrottle = useMemo(
() => throttle(setCurveParams, 1000),
[]
);
// Simulation results
const {
k, // Invariant power kappa (.)
R0, // Initial reserve (DAI)
S0, // initial supply of tokens (token)
V0 // invariant coef
} = getInitialParams({
d0,
theta,
p0,
returnF
});
const [priceTimeseries, setPriceTimeseries] = useState([0]);
const [withdrawFeeTimeseries, setWithdrawFeeTimeseries] = useState([0]);
const [totalReserve, setTotalReserve] = useState(R0);
const [withdrawCount, setWithdrawCount] = useState(0);
const [avgSlippage, setAvgSlippage] = useState(0);
const [avgTxSize] = useState(10000);
// Simulation state variables
const [simulationActive, setSimulationActive] = useState(false);
const [simulationRunning, setSimulationRunning] = useState(false);
// #### TEST: Immediate simulation
async function startSimulation() {
// If there's a simulation already active, clear it
clearSimulation();
await pause(0);
// Start simulation by setting it to active
setSimulationActive(true);
}
function clearSimulation() {
// Stop simulation
setSimulationActive(false);
// Clear simulation variables
setWithdrawCount(0);
setPriceTimeseries([0]);
setWithdrawFeeTimeseries([0]);
setAvgSlippage(0);
}
useEffect(() => {
let canContinueSimulation = true;
async function simulateRandomDelta() {
const R_t: number[] = [R0];
const p_t: number[] = [getPriceR({ R: R0, V0, k })];
const wFee_t: number[] = [0];
const slippage_t: number[] = [];
// Random walk
const numSteps = 100;
const updateEveryNth = 1;
// Compute the random deltas
const deltaR_t = getRandomDeltas({ numSteps, avgTxSize });
setSimulationRunning(true);
for (let i = 0; i < numSteps; i++) {
const deltaR = deltaR_t[i];
const R = getLast(R_t);
const R_next = R + deltaR;
R_t.push(R_next);
p_t.push(getPriceR({ R: R_next, V0, k }));
slippage_t.push(getSlippage({ R, deltaR, V0, k }));
// Consider withdraw fees on sales only
if (deltaR < 0) {
wFee_t.push(getLast(wFee_t) - deltaR * wFee);
setWithdrawCount(c => c + 1);
} else {
wFee_t.push(getLast(wFee_t));
}
// Stop the simulation if it's no longer active
if (!simulationActive || !canContinueSimulation) break;
if (i % updateEveryNth === 0) {
setPriceTimeseries(p_t);
setWithdrawFeeTimeseries(wFee_t);
setAvgSlippage(getAvg(slippage_t));
setTotalReserve(R_next);
// Make this run non-UI blocking
await pause(5);
}
}
setSimulationRunning(false);
}
if (simulationActive) simulateRandomDelta();
// Return an "unsubscribe" function that halts the run
return () => {
canContinueSimulation = false;
};
}, [simulationActive]);
const resultFields = [
{
label: `Average slippage (avg tx size ${avgTxSize} DAI)`,
value: +(100 * avgSlippage).toFixed(3) + "%"
},
{
label: `Capital collected from withdraw fees (${withdrawCount} txs)`,
value:
(+getLast(withdrawFeeTimeseries).toPrecision(3)).toLocaleString() +
" DAI"
},
{
label: `Total reserve`,
value: (+totalReserve.toPrecision(3)).toLocaleString() + " DAI"
}
];
const classes = useStyles();
return (
<>
<header className={classes.header}>
<Container fixed>
<Header />
</Container>
</header>
<Container fixed className={classes.mainContainer}>
<Grid container spacing={3}>
<Grid item xs={12} sm={12} md={6} lg={4}>
<Paper className={classes.paper}>
<Box className={classes.boxHeader}>
<Typography variant="h6">Curve Design</Typography>
<HelpText
text={
<span>
Description of the different parameters <br />
Initial raise: Lorem ipsum <br />
Allocation to project: Lorem ipsum <br />
Initial token price: Lorem ipsum <br />
Return factor: Lorem ipsum <br />
Withdrawl fee: Lorem ipsum
</span>
}
/>
</Box>
<Box className={classes.box}>
<InputParams setCurveParams={setCurveParamsThrottle} />
</Box>
</Paper>
</Grid>
<Grid item xs={12} sm={12} md={6} lg={8}>
<Paper className={classes.paper}>
<Box className={classes.boxHeader}>
<Typography variant="h6">Preview</Typography>
<HelpText
text={<span>Preview of the token bonding curve</span>}
/>
</Box>
<Box className={classes.boxChart}>
<SupplyVsDemandChart
returnF={returnF}
theta={theta}
d0={d0}
p0={p0}
/>
</Box>
</Paper>
</Grid>
</Grid>
<Grid container spacing={3}>
<Grid item xs={12} md={12}>
<Paper>
<Box className={classes.boxHeader}>
<Grid
container
direction="row"
justify="center"
alignItems="center"
>
<Button
variant="contained"
className={classes.button}
onClick={startSimulation}
disabled={simulationRunning}
>
Run simulation
</Button>
</Grid>
</Box>
</Paper>
</Grid>
</Grid>
{simulationActive && (
<Grid container spacing={3}>
<Grid item xs={12} sm={12} md={6} lg={8}>
<Paper className={classes.paper}>
<Box className={classes.boxHeader}>
<Typography variant="h6">Simulation</Typography>
<HelpText
text={<span>Some context about this simulation</span>}
/>
</Box>
<Box className={classes.boxChart}>
<PriceSimulationChart
priceTimeseries={priceTimeseries}
withdrawFeeTimeseries={withdrawFeeTimeseries}
p0={p0}
/>
</Box>
</Paper>
</Grid>
<Grid item xs={12} sm={12} md={6} lg={4}>
<Paper className={classes.paper}>
<Box className={classes.boxHeader}>
<Typography variant="h6">Results</Typography>
<HelpText
text={<span>Explanation of what do this results mean</span>}
/>
</Box>
<Box className={classes.box}>
<ResultParams resultFields={resultFields} />
</Box>
</Paper>
</Grid>
</Grid>
)}
</Container>
</>
);
}