Merge branch 'master' of https://github.com/commons-stack/augmented-tbc-design
This commit is contained in:
commit
a0d6bf03af
239
src/App.tsx
239
src/App.tsx
|
|
@ -15,15 +15,27 @@ import SupplyVsDemandChart from "./SupplyVsDemandChart";
|
|||
import ResultParams from "./ResultParams";
|
||||
import PriceSimulationChart from "./PriceSimulationChart";
|
||||
import HelpText from "./HelpText";
|
||||
// Text content
|
||||
import {
|
||||
parameterDescriptions,
|
||||
simulationParameterDescriptions,
|
||||
resultParameterDescriptions
|
||||
} from "./parametersDescriptions";
|
||||
// Utils
|
||||
import { getLast, getAvg, pause } from "./utils";
|
||||
import {
|
||||
getInitialParams,
|
||||
getPriceR,
|
||||
getMinPrice,
|
||||
getS,
|
||||
vest_tokens,
|
||||
getR,
|
||||
getSlippage,
|
||||
getTxDistribution,
|
||||
getDeltaR_priceGrowth,
|
||||
rv_U
|
||||
rv_U,
|
||||
getMedian,
|
||||
getSum
|
||||
} from "./math";
|
||||
import { throttle } from "lodash";
|
||||
// General styles
|
||||
|
|
@ -116,65 +128,17 @@ const useStyles = makeStyles((theme: Theme) =>
|
|||
}
|
||||
},
|
||||
descriptionTitle: {
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
padding: theme.spacing(0.5)
|
||||
},
|
||||
descriptionName: {
|
||||
fontWeight: theme.typography.fontWeightBold
|
||||
descriptionBody: {
|
||||
color: "#dbdfe4"
|
||||
},
|
||||
descriptionPadding: {
|
||||
padding: theme.spacing(0.5)
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const parameterDescriptions = [
|
||||
{
|
||||
name: "Initial raise",
|
||||
text: "Total funds raised in the hatch period of the ABC launch"
|
||||
},
|
||||
{
|
||||
name: "Allocation to funding pool",
|
||||
text:
|
||||
"The percentage of the funds raised in the Hatch sale that go directly into the project funding pool to compensate future work done in the project"
|
||||
},
|
||||
{
|
||||
name: "Hatch price",
|
||||
text:
|
||||
"The price paid per 'ABC token' by community members involved in hatching the project"
|
||||
},
|
||||
{
|
||||
name: "Post-hatch price",
|
||||
text:
|
||||
"The price of the 'ABC token' when the curve enters the open phase and is live for public participation"
|
||||
},
|
||||
{
|
||||
name: "Exit tribute",
|
||||
text:
|
||||
"The percentage of funds that are diverted to the project funding pool from community members who exit funds from the project by burning 'ABC tokens' in exchange for collateral"
|
||||
}
|
||||
];
|
||||
|
||||
const resultParameterDescriptions = [
|
||||
{
|
||||
name: "Total reserve",
|
||||
text:
|
||||
"Total DAI in the smart contract reserve at the end of the simulated period"
|
||||
},
|
||||
{
|
||||
name: "Funds generated from initial hatch",
|
||||
text:
|
||||
"Fraction of the funds (theta) raised during the hatch that go directly to the cause"
|
||||
},
|
||||
{
|
||||
name: "Funds generated from exit tributes",
|
||||
text:
|
||||
"Cumulative amount of exit tributes collected from only exit /sell transactions"
|
||||
},
|
||||
{
|
||||
name: "Average slippage",
|
||||
text:
|
||||
"Average of the slippage of each transaction occured during the simulation period"
|
||||
}
|
||||
];
|
||||
|
||||
export default function App() {
|
||||
const [curveParams, setCurveParams] = useState({
|
||||
theta: 0.35, // fraction allocated to reserve (.)
|
||||
|
|
@ -199,7 +163,7 @@ export default function App() {
|
|||
const {
|
||||
k, // Invariant power kappa (.)
|
||||
R0, // Initial reserve (DAI)
|
||||
// S0, // initial supply of tokens (token)
|
||||
S0, // initial supply of tokens (token)
|
||||
V0 // invariant coef
|
||||
} = getInitialParams({
|
||||
d0,
|
||||
|
|
@ -210,6 +174,7 @@ export default function App() {
|
|||
|
||||
const [priceTimeseries, setPriceTimeseries] = useState([0]);
|
||||
const [withdrawFeeTimeseries, setWithdrawFeeTimeseries] = useState([0]);
|
||||
const [floorpriceTimeseries, setFloorpriceTimeseries] = useState([0]);
|
||||
const [totalReserve, setTotalReserve] = useState(R0);
|
||||
const [withdrawCount, setWithdrawCount] = useState(0);
|
||||
const [avgSlippage, setAvgSlippage] = useState(0);
|
||||
|
|
@ -248,48 +213,100 @@ export default function App() {
|
|||
|
||||
async function simulateRandomDelta() {
|
||||
const R_t: number[] = [R0];
|
||||
const S_t: number[] = [S0];
|
||||
const p_t: number[] = [getPriceR({ R: R0, V0, k })];
|
||||
const wFee_t: number[] = [0];
|
||||
const slippage_t: number[] = [];
|
||||
const avgTxSize_t: number[] = [];
|
||||
|
||||
// hatchers tokens = S0[section added by Z]
|
||||
const H_t: number[] = [S0]; // total hatcher tokens not vested
|
||||
const floorprice_t: number[] = []; // initially the price is the floor as all tokens are hatcher tokens
|
||||
|
||||
// Random walk
|
||||
const numSteps = 52;
|
||||
const u_min = 0.97;
|
||||
const u_max = 1.04;
|
||||
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
|
||||
// percentage of the hatch tokens which vest per week(since that is our timescale in the sim)
|
||||
|
||||
// numSteps = 52 take 8ms to run
|
||||
setSimulationRunning(true);
|
||||
for (let t = 0; t < numSteps; t++) {
|
||||
const txsWeek = Math.ceil(t < 5 ? rv_U(0, 5) : rv_U(5, 2 * t));
|
||||
const priceGrowth = rv_U(0.99, 1.03);
|
||||
const txsWeek = rv_U(5, 2 * t + 5);
|
||||
|
||||
const R = getLast(R_t);
|
||||
const deltaR = getDeltaR_priceGrowth({ R, k, priceGrowth });
|
||||
const S = getLast(S_t);
|
||||
const H = getLast(H_t);
|
||||
|
||||
// enforce the effects of the unvested tokens not being burnable
|
||||
let u_lower;
|
||||
if (H > S) {
|
||||
u_lower = 1;
|
||||
} else {
|
||||
// compute the reserve if all that supply is burned
|
||||
const R_ratio = getR({ S: S - H, V0, k }) / R;
|
||||
u_lower = Math.max(1 - R_ratio, u_min);
|
||||
}
|
||||
const priceGrowth = rv_U(u_lower, u_max);
|
||||
|
||||
const deltaR = getDeltaR_priceGrowth({ R, k, priceGrowth });
|
||||
const R_next = R + deltaR;
|
||||
|
||||
const txs = getTxDistribution({ sum: deltaR, num: txsWeek });
|
||||
const txs = getTxDistribution({
|
||||
sum: deltaR,
|
||||
num: txsWeek,
|
||||
spread: tx_spread
|
||||
});
|
||||
// Compute slippage
|
||||
const slippage = getAvg(
|
||||
txs.map(txR => getSlippage({ R, deltaR: txR, V0, k }))
|
||||
const slippage_txs = txs.map(txR =>
|
||||
getSlippage({ R, deltaR: txR, V0, k })
|
||||
);
|
||||
const slippage = getMedian(slippage_txs);
|
||||
|
||||
const txsWithdraw = txs.filter(tx => tx < 0);
|
||||
const wFees = -wFee * txsWithdraw.reduce((t, c) => t + c, 0);
|
||||
const _avgTxSize =
|
||||
txs.reduce((t, c) => t + Math.abs(c), 0) / txs.length;
|
||||
const wFees = -wFee * getSum(txsWithdraw);
|
||||
// txsWithdraw.reduce((t, c) => t + c, 0);
|
||||
|
||||
// Vest
|
||||
const delta_H = vest_tokens({ week: t, H, halflife, cliff });
|
||||
const H_next = H - delta_H;
|
||||
|
||||
// find floor price
|
||||
const S_next = getS({ R, V0, k });
|
||||
const floorprice_next = getMinPrice({
|
||||
S: S_next,
|
||||
H: S0 - H_next,
|
||||
V0,
|
||||
k
|
||||
});
|
||||
|
||||
const _avgTxSize = getMedian(txsWithdraw);
|
||||
|
||||
R_t.push(R_next);
|
||||
S_t.push(S_next);
|
||||
H_t.push(H_next);
|
||||
p_t.push(getPriceR({ R: R_next, V0, k }));
|
||||
slippage_t.push(slippage);
|
||||
avgTxSize_t.push(_avgTxSize);
|
||||
wFee_t.push(getLast(wFee_t) + wFees);
|
||||
|
||||
floorprice_t.push(floorprice_next);
|
||||
setWithdrawCount(c => c + txsWithdraw.length);
|
||||
|
||||
// Stop the simulation if it's no longer active
|
||||
if (!simulationActive || !canContinueSimulation) break;
|
||||
}
|
||||
|
||||
// floorprice_t is missing one data point
|
||||
floorprice_t[floorprice_t.length] = floorprice_t[floorprice_t.length - 1];
|
||||
|
||||
setPriceTimeseries(p_t);
|
||||
setWithdrawFeeTimeseries(wFee_t);
|
||||
setFloorpriceTimeseries(floorprice_t);
|
||||
setAvgSlippage(getAvg(slippage_t));
|
||||
setAvgTxSize(getAvg(avgTxSize_t));
|
||||
setTotalReserve(getLast(R_t));
|
||||
|
|
@ -307,14 +324,17 @@ export default function App() {
|
|||
const resultFields = [
|
||||
{
|
||||
label: `Total reserve`,
|
||||
description: resultParameterDescriptions.totalReserve.text,
|
||||
value: (+totalReserve.toPrecision(3)).toLocaleString() + " DAI"
|
||||
},
|
||||
{
|
||||
label: `Funds generated from initial hatch`,
|
||||
description: resultParameterDescriptions.initialHatchFunds.text,
|
||||
value: Math.round(d0 * theta).toLocaleString() + " DAI"
|
||||
},
|
||||
{
|
||||
label: `Funds generated from exit tributes (${withdrawCount} txs)`,
|
||||
description: resultParameterDescriptions.exitTributes.text,
|
||||
value:
|
||||
(+getLast(withdrawFeeTimeseries).toPrecision(3)).toLocaleString() +
|
||||
" DAI"
|
||||
|
|
@ -323,6 +343,7 @@ export default function App() {
|
|||
label: `Average slippage (avg tx size ${Math.round(
|
||||
avgTxSize
|
||||
).toLocaleString()} DAI)`,
|
||||
description: resultParameterDescriptions.slippage.text,
|
||||
value: +(100 * avgSlippage).toFixed(3) + "%"
|
||||
}
|
||||
];
|
||||
|
|
@ -353,15 +374,21 @@ export default function App() {
|
|||
</div>
|
||||
<table>
|
||||
<tbody>
|
||||
{parameterDescriptions.map(({ name, text }) => (
|
||||
{[
|
||||
parameterDescriptions.theta,
|
||||
parameterDescriptions.p0,
|
||||
parameterDescriptions.p1,
|
||||
parameterDescriptions.wFee,
|
||||
parameterDescriptions.d0
|
||||
].map(({ name, text }) => (
|
||||
<tr key={name}>
|
||||
<td>
|
||||
<Typography className={classes.descriptionName}>
|
||||
{name}
|
||||
</Typography>
|
||||
<Typography>{name}</Typography>
|
||||
</td>
|
||||
<td>
|
||||
<Typography>{text}</Typography>
|
||||
<Typography className={classes.descriptionBody}>
|
||||
{text}
|
||||
</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
|
@ -398,13 +425,16 @@ export default function App() {
|
|||
<Typography variant="h6">Preview</Typography>
|
||||
<HelpText
|
||||
text={
|
||||
<Typography>
|
||||
Visualization of the token bonding curve analytic function
|
||||
on a specific range of reserve [0, 4 * R0]. This result is
|
||||
deterministic given the current set of parameters and will
|
||||
never change regardes of the campaign performance, it only
|
||||
shows how the price will react to reserve changes.
|
||||
</Typography>
|
||||
<div className={classes.descriptionPadding}>
|
||||
<Typography className={classes.descriptionBody}>
|
||||
Visualization of the token bonding curve analytic
|
||||
function on a specific range of reserve [0, 4 * R0].
|
||||
This result is deterministic given the current set of
|
||||
parameters and will never change regardes of the
|
||||
campaign performance, it only shows how the price will
|
||||
react to reserve changes.
|
||||
</Typography>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
|
|
@ -449,14 +479,40 @@ export default function App() {
|
|||
<Typography variant="h6">Simulation</Typography>
|
||||
<HelpText
|
||||
text={
|
||||
<Typography>
|
||||
This chart shows a 52 week simulation of discrete
|
||||
transactions interacting with the token bonding curve.
|
||||
Each transaction adds or substract reserve to the
|
||||
system, modifying the price over time. The frequency,
|
||||
size and direction of each transaction is computed
|
||||
from a set of bounded random functions.
|
||||
</Typography>
|
||||
<div className={classes.descriptionContainer}>
|
||||
<div className={classes.descriptionPadding}>
|
||||
<Typography className={classes.descriptionBody}>
|
||||
This chart shows a 52 week simulation of discrete
|
||||
transactions interacting with the token bonding
|
||||
curve. Each transaction adds or substract reserve
|
||||
to the system, modifying the price over time. The
|
||||
frequency, size and direction of each transaction
|
||||
is computed from a set of bounded random
|
||||
functions.
|
||||
</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>
|
||||
|
|
@ -465,6 +521,7 @@ export default function App() {
|
|||
<PriceSimulationChart
|
||||
priceTimeseries={priceTimeseries}
|
||||
withdrawFeeTimeseries={withdrawFeeTimeseries}
|
||||
floorpriceTimeseries={floorpriceTimeseries}
|
||||
p0={p0}
|
||||
p1={p1}
|
||||
/>
|
||||
|
|
@ -486,18 +543,18 @@ export default function App() {
|
|||
</div>
|
||||
<table>
|
||||
<tbody>
|
||||
{resultParameterDescriptions.map(
|
||||
{Object.values(resultParameterDescriptions).map(
|
||||
({ name, text }) => (
|
||||
<tr key={name}>
|
||||
<td>
|
||||
<Typography
|
||||
className={classes.descriptionName}
|
||||
>
|
||||
{name}
|
||||
</Typography>
|
||||
<Typography>{name}</Typography>
|
||||
</td>
|
||||
<td>
|
||||
<Typography>{text}</Typography>
|
||||
<Typography
|
||||
className={classes.descriptionBody}
|
||||
>
|
||||
{text}
|
||||
</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { InputFieldInterface, CurveParamsInterface } from "./types";
|
||||
import InputParams from "./InputParams";
|
||||
import { parameterDescriptions } from "./parametersDescriptions";
|
||||
|
||||
export default function CurveDesignInputParams({
|
||||
curveParams,
|
||||
|
|
@ -42,6 +43,7 @@ export default function CurveDesignInputParams({
|
|||
const inputFields: InputFieldInterface[] = [
|
||||
{
|
||||
label: "Allocation to funding pool",
|
||||
description: parameterDescriptions.theta.text,
|
||||
value: theta,
|
||||
setter: setTheta,
|
||||
min: 0,
|
||||
|
|
@ -54,6 +56,7 @@ export default function CurveDesignInputParams({
|
|||
},
|
||||
{
|
||||
label: "Hatch price (DAI/token)",
|
||||
description: parameterDescriptions.p0.text,
|
||||
value: p0,
|
||||
setter: _setP0,
|
||||
min: 0.01,
|
||||
|
|
@ -65,6 +68,7 @@ export default function CurveDesignInputParams({
|
|||
},
|
||||
{
|
||||
label: "Post-hatch price (DAI/token)",
|
||||
description: parameterDescriptions.p1.text,
|
||||
value: p1,
|
||||
setter: setP1,
|
||||
min: p0 || 0.1,
|
||||
|
|
@ -76,6 +80,7 @@ export default function CurveDesignInputParams({
|
|||
},
|
||||
{
|
||||
label: "Exit tribute",
|
||||
description: parameterDescriptions.wFee.text,
|
||||
value: wFee,
|
||||
setter: setWFee,
|
||||
min: 0,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import React from "react";
|
||||
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import NumberFormat from "react-number-format";
|
||||
import { InputFieldInterface } from "./types";
|
||||
import PrettoSlider from "./PrettoSlider";
|
||||
import TextWithPopover from "./TextWithPopover";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
|
|
@ -81,6 +81,7 @@ export default function InputParams({
|
|||
{inputFields.map(
|
||||
({
|
||||
label,
|
||||
description,
|
||||
value,
|
||||
setter,
|
||||
min,
|
||||
|
|
@ -103,9 +104,7 @@ export default function InputParams({
|
|||
return (
|
||||
<Grid key={label} container spacing={0} className={classes.listBox}>
|
||||
<Grid item xs={6} className={classes.leftContainer}>
|
||||
<Typography id={label} gutterBottom>
|
||||
{label}
|
||||
</Typography>
|
||||
<TextWithPopover content={label} popoverText={description} />
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={2} className={classes.centerContainer}>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { linspace } from "./utils";
|
|||
const keyHorizontal = "x";
|
||||
const keyVerticalLeft = "Price (DAI/token)";
|
||||
const keyVerticalRight = "Total exit tributes (DAI)";
|
||||
const keyVerticalLeft2 = "Floor price (DAI/token)";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
|
|
@ -32,11 +33,13 @@ const useStyles = makeStyles((theme: Theme) =>
|
|||
function PriceSimulationChart({
|
||||
priceTimeseries,
|
||||
withdrawFeeTimeseries,
|
||||
floorpriceTimeseries,
|
||||
p0,
|
||||
p1
|
||||
}: {
|
||||
priceTimeseries: number[];
|
||||
withdrawFeeTimeseries: number[];
|
||||
floorpriceTimeseries: number[];
|
||||
p0: number;
|
||||
p1: number;
|
||||
}) {
|
||||
|
|
@ -51,6 +54,7 @@ function PriceSimulationChart({
|
|||
data.push({
|
||||
[keyHorizontal]: t,
|
||||
[keyVerticalLeft]: priceTimeseries[t] || 0,
|
||||
[keyVerticalLeft2]: floorpriceTimeseries[t] || 0,
|
||||
[keyVerticalRight]: withdrawFeeTimeseries[t] || 0
|
||||
});
|
||||
}
|
||||
|
|
@ -70,8 +74,8 @@ function PriceSimulationChart({
|
|||
const { textAnchor, viewBox, text } = props;
|
||||
return (
|
||||
<text
|
||||
x={viewBox.x + 10}
|
||||
y={viewBox.y - 10}
|
||||
x={viewBox.x + 8}
|
||||
y={viewBox.y + 17}
|
||||
fill={theme.palette.text.secondary}
|
||||
textAnchor={textAnchor}
|
||||
>
|
||||
|
|
@ -83,10 +87,12 @@ function PriceSimulationChart({
|
|||
function CustomTooltip({ active, payload, label }: any) {
|
||||
if (active) {
|
||||
const price = payload[0].value;
|
||||
const exit = payload[1].value;
|
||||
const floor = payload[1].value;
|
||||
const exit = payload[2].value;
|
||||
const weekNum = label;
|
||||
const toolTipData: string[][] = [
|
||||
["Price", price.toFixed(2), "DAI/tk"],
|
||||
["Floor", floor.toFixed(2), "DAI/tk"],
|
||||
["Exit t.", formatter(exit), "DAI"],
|
||||
["Week", weekNum, ""]
|
||||
];
|
||||
|
|
@ -148,12 +154,10 @@ function PriceSimulationChart({
|
|||
{/* Capital collected from withdraw fees - AXIS */}
|
||||
<YAxis
|
||||
yAxisId="right"
|
||||
// domain={[
|
||||
// Math.floor(Math.min(...withdrawFeeTimeseries)),
|
||||
// Math.ceil(Math.max(...withdrawFeeTimeseries))
|
||||
// ]}
|
||||
domain={[0, +(2 * withdrawFeeTimeseries.slice(-1)[0]).toPrecision(1)]}
|
||||
orientation="right"
|
||||
tick={{ fill: theme.palette.text.secondary }}
|
||||
tickFormatter={formatter}
|
||||
stroke={theme.palette.text.secondary}
|
||||
/>
|
||||
|
||||
|
|
@ -166,7 +170,20 @@ function PriceSimulationChart({
|
|||
dataKey={keyVerticalLeft}
|
||||
stroke={theme.palette.primary.main}
|
||||
fill={theme.palette.primary.main}
|
||||
fillOpacity={0.3}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
<Area
|
||||
isAnimationActive={false}
|
||||
yAxisId="left"
|
||||
type="monotone"
|
||||
dataKey={keyVerticalLeft2}
|
||||
stroke={"#adcd2e"}
|
||||
fill={"#adcd2e"}
|
||||
fillOpacity={0.05}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
|
||||
<ReferenceLine
|
||||
y={p0}
|
||||
yAxisId="left"
|
||||
|
|
@ -188,9 +205,10 @@ function PriceSimulationChart({
|
|||
yAxisId="right"
|
||||
type="monotone"
|
||||
dataKey={keyVerticalRight}
|
||||
stroke={theme.palette.secondary.dark}
|
||||
stroke={"#0085ff"}
|
||||
fill={theme.palette.secondary.dark}
|
||||
fillOpacity="0.8"
|
||||
fillOpacity={0.5}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
|
||||
{/* <ReferenceLine
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React from "react";
|
|||
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import TextWithPopover from "./TextWithPopover";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
|
|
@ -45,6 +46,7 @@ export default function ResultParams({
|
|||
}: {
|
||||
resultFields: {
|
||||
label: string;
|
||||
description: string;
|
||||
value: number | string;
|
||||
}[];
|
||||
}) {
|
||||
|
|
@ -52,12 +54,10 @@ export default function ResultParams({
|
|||
|
||||
return (
|
||||
<div className={classes.listBoxContainer}>
|
||||
{resultFields.map(({ label, value }) => (
|
||||
{resultFields.map(({ label, description, value }) => (
|
||||
<Grid key={label} container spacing={0} className={classes.listBox}>
|
||||
<Grid item xs={8} className={classes.leftContainer}>
|
||||
<Typography id={label} gutterBottom>
|
||||
{label}
|
||||
</Typography>
|
||||
<TextWithPopover content={label} popoverText={description} />
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={4} className={classes.centerContainer}>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { InputFieldInterface, CurveParamsInterface } from "./types";
|
||||
import InputParams from "./InputParams";
|
||||
import { parameterDescriptions } from "./parametersDescriptions";
|
||||
|
||||
export default function CurveDesignInputParams({
|
||||
curveParams,
|
||||
|
|
@ -25,6 +26,7 @@ export default function CurveDesignInputParams({
|
|||
const inputFields: InputFieldInterface[] = [
|
||||
{
|
||||
label: "Initial raise (DAI)",
|
||||
description: parameterDescriptions.d0.text,
|
||||
value: d0,
|
||||
setter: setD0,
|
||||
min: 0.1e6,
|
||||
|
|
|
|||
|
|
@ -186,6 +186,8 @@ function SupplyVsDemandChart({
|
|||
dataKey={keyVertical}
|
||||
stroke={theme.palette.primary.main}
|
||||
fill={theme.palette.primary.main}
|
||||
fillOpacity={0.3}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
<ReferenceLine
|
||||
x={R0_round}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
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";
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
container: {
|
||||
color: theme.palette.text.secondary,
|
||||
display: "flex",
|
||||
marginLeft: "6px",
|
||||
fontSize: "0.9rem",
|
||||
cursor: "pointer",
|
||||
transition: "color ease 150ms",
|
||||
"&:hover": {
|
||||
color: "#c3c9d0"
|
||||
}
|
||||
},
|
||||
popoverContainer: {
|
||||
padding: theme.spacing(2),
|
||||
"& > p:not(:last-child)": {
|
||||
paddingBottom: theme.spacing(1),
|
||||
marginBottom: theme.spacing(1),
|
||||
borderBottom: "1px solid #3f5463"
|
||||
}
|
||||
},
|
||||
paper: {
|
||||
backgroundColor: "#384b59",
|
||||
maxWidth: theme.breakpoints.values.md * 0.9,
|
||||
[`@media screen and (max-width: ${theme.breakpoints.values.md}px)`]: {
|
||||
maxWidth: "90vw"
|
||||
},
|
||||
padding: theme.spacing(0.5)
|
||||
},
|
||||
descriptionBody: {
|
||||
color: "#dbdfe4"
|
||||
}
|
||||
}));
|
||||
|
||||
export default function TextWithPopover({
|
||||
content,
|
||||
popoverText
|
||||
}: {
|
||||
content: string;
|
||||
popoverText: string;
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
|
||||
function handleClick(event: any) {
|
||||
setAnchorEl(event.currentTarget);
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
setAnchorEl(null);
|
||||
}
|
||||
|
||||
const open = Boolean(anchorEl);
|
||||
const id = open ? "simple-popover" : undefined;
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<div aria-describedby={id} onClick={handleClick}>
|
||||
<Typography gutterBottom>{content}</Typography>
|
||||
</div>
|
||||
<Popover
|
||||
PaperProps={{
|
||||
className: classes.paper
|
||||
}}
|
||||
id={id}
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleClose}
|
||||
onClick={handleClose}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "center"
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "center"
|
||||
}}
|
||||
>
|
||||
<Box className={classes.popoverContainer}>
|
||||
<Typography>{content}</Typography>
|
||||
<Typography className={classes.descriptionBody}>
|
||||
{popoverText}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
81
src/math.ts
81
src/math.ts
|
|
@ -23,6 +23,37 @@ export function getInitialParams({
|
|||
return { k, R0, S0, V0 };
|
||||
}
|
||||
|
||||
export function getR({ S, V0, k }: { S: number; V0: number; k: number }) {
|
||||
return S ** k / V0;
|
||||
}
|
||||
|
||||
export function getS({ R, V0, k }: { R: number; V0: number; k: number }) {
|
||||
return (V0 * R) ** (1 / k);
|
||||
}
|
||||
|
||||
// compute the price if all that supply is burned
|
||||
export function getMinPrice({
|
||||
S,
|
||||
H,
|
||||
V0,
|
||||
k
|
||||
}: {
|
||||
S: number;
|
||||
H: number;
|
||||
V0: number;
|
||||
k: number;
|
||||
}) {
|
||||
if (S === H) {
|
||||
const myR = getR({ S, V0, k });
|
||||
const myP = getPriceR({ R: myR, V0, k }); // numerical precision make complex numbers just suppress it
|
||||
return Math.abs(myP);
|
||||
} else {
|
||||
// compute the reserve if all that supply is burned
|
||||
const minR = getR({ S: S - H, V0, k });
|
||||
return getPriceR({ R: minR, V0, k });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the price at a specific reserve `R`
|
||||
*/
|
||||
|
|
@ -75,17 +106,47 @@ export function getDeltaR_priceGrowth({
|
|||
* Demo: https://codepen.io/anon/pen/mNqJjv?editors=0010#0
|
||||
* Very quick: < 10ms for 10000 txs
|
||||
*/
|
||||
export function getTxDistribution({ sum, num }: { sum: number; num: number }) {
|
||||
export function getTxDistribution({
|
||||
sum,
|
||||
num,
|
||||
spread
|
||||
}: {
|
||||
sum: number;
|
||||
num: number;
|
||||
spread: number;
|
||||
}) {
|
||||
const mean = sum / num;
|
||||
const off = mean * 4;
|
||||
const off = mean * spread;
|
||||
const x: number[] = [];
|
||||
for (let i = 0; i < num; i++) {
|
||||
x[i] = randn_bm(mean - off, mean + off);
|
||||
x.push(randn_bm(mean - off, mean + off));
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
// Minor utils
|
||||
export function vest_tokens({
|
||||
week,
|
||||
H, // unvested_hatch_tokens
|
||||
halflife,
|
||||
cliff
|
||||
}: {
|
||||
week: number;
|
||||
H: number;
|
||||
halflife: number;
|
||||
cliff: number;
|
||||
}) {
|
||||
// check cliff
|
||||
if (week < cliff) {
|
||||
return 0;
|
||||
} else {
|
||||
// rate of release given half - life
|
||||
const vest_fraction = 0.5 ** (1 / halflife);
|
||||
// number of tokens that vest in this week
|
||||
return H * (1 - vest_fraction);
|
||||
}
|
||||
}
|
||||
|
||||
// Statistics utils
|
||||
|
||||
/**
|
||||
* Random variable uniformly distributed
|
||||
|
|
@ -111,3 +172,15 @@ function randn_bm(min: number, max: number) {
|
|||
num += min; // offset to min
|
||||
return num;
|
||||
}
|
||||
|
||||
// Array utils
|
||||
|
||||
export function getMedian(arr: number[]) {
|
||||
const mid = Math.floor(arr.length / 2);
|
||||
const nums = [...arr].sort((a, b) => a - b);
|
||||
return arr.length % 2 !== 0 ? nums[mid] : (nums[mid - 1] + nums[mid]) / 2;
|
||||
}
|
||||
|
||||
export function getSum(arr: number[]) {
|
||||
return arr.reduce((a, b) => a + b, 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
export interface DescriptionObject {
|
||||
[key: string]: { name: string; text: string };
|
||||
}
|
||||
|
||||
export const parameterDescriptions: DescriptionObject = {
|
||||
theta: {
|
||||
name: "Allocation to funding pool",
|
||||
text:
|
||||
"The percentage of the funds raised in the Hatch sale that go directly into the project funding pool to compensate future work done in the project"
|
||||
},
|
||||
p0: {
|
||||
name: "Hatch price",
|
||||
text:
|
||||
"The price paid per 'ABC token' by community members involved in hatching the project"
|
||||
},
|
||||
p1: {
|
||||
name: "Post-hatch price",
|
||||
text:
|
||||
"The price of the 'ABC token' when the curve enters the open phase and is live for public participation"
|
||||
},
|
||||
wFee: {
|
||||
name: "Exit tribute",
|
||||
text:
|
||||
"The percentage of funds that are diverted to the project funding pool from community members who exit funds from the project by burning 'ABC tokens' in exchange for collateral"
|
||||
},
|
||||
d0: {
|
||||
name: "Initial raise",
|
||||
text: "Total funds raised in the hatch period of the ABC launch"
|
||||
}
|
||||
};
|
||||
|
||||
export const simulationParameterDescriptions: DescriptionObject = {
|
||||
price: {
|
||||
name: "Price",
|
||||
text: "Price of the token over time."
|
||||
},
|
||||
floorPrice: {
|
||||
name: "Floor price",
|
||||
text:
|
||||
"Lower bound of the price guaranteed by the vesting of hatch tokens. It decreases over time as more hatch tokens are allowed to be traded"
|
||||
},
|
||||
exitTributes: {
|
||||
name: "Total exit tributes",
|
||||
text:
|
||||
"Cumulative sum of exit tributes collected from only exit /sell transactions"
|
||||
}
|
||||
};
|
||||
|
||||
export const resultParameterDescriptions: DescriptionObject = {
|
||||
totalReserve: {
|
||||
name: "Total reserve",
|
||||
text:
|
||||
"Total DAI in the smart contract reserve at the end of the simulated period"
|
||||
},
|
||||
initialHatchFunds: {
|
||||
name: "Funds generated from initial hatch",
|
||||
text:
|
||||
"Fraction of the funds (theta) raised during the hatch that go directly to the cause (analytic result)"
|
||||
},
|
||||
exitTributes: {
|
||||
name: "Funds generated from exit tributes",
|
||||
text: simulationParameterDescriptions.exitTributes.text
|
||||
},
|
||||
slippage: {
|
||||
name: "Average slippage",
|
||||
text:
|
||||
"Average of the slippage of each transaction occured during the simulation period"
|
||||
}
|
||||
};
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
export interface InputFieldInterface {
|
||||
label: string;
|
||||
description: string;
|
||||
value: number;
|
||||
setter(newValue: any): void;
|
||||
min: number;
|
||||
|
|
|
|||
Loading…
Reference in New Issue