diff --git a/src/App.tsx b/src/App.tsx
index 5964467..dbd38ca 100644
--- a/src/App.tsx
+++ b/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() {
- {parameterDescriptions.map(({ name, text }) => (
+ {[
+ parameterDescriptions.theta,
+ parameterDescriptions.p0,
+ parameterDescriptions.p1,
+ parameterDescriptions.wFee,
+ parameterDescriptions.d0
+ ].map(({ name, text }) => (
|
-
- {name}
-
+ {name}
|
- {text}
+
+ {text}
+
|
))}
@@ -398,13 +425,16 @@ export default function App() {
Preview
- 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.
-
+
+
+ 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.
+
+
}
/>
@@ -449,14 +479,40 @@ export default function App() {
Simulation
- 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.
-
+
+
+
+ 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.
+
+
+
+
+
+ {Object.values(
+ simulationParameterDescriptions
+ ).map(({ name, text }) => (
+
+ |
+ {name}
+ |
+
+
+ {text}
+
+ |
+
+ ))}
+
+
+
}
/>
@@ -465,6 +521,7 @@ export default function App() {
@@ -486,18 +543,18 @@ export default function App() {
- {resultParameterDescriptions.map(
+ {Object.values(resultParameterDescriptions).map(
({ name, text }) => (
|
-
- {name}
-
+ {name}
|
- {text}
+
+ {text}
+
|
)
diff --git a/src/CurveDesignInputParams.tsx b/src/CurveDesignInputParams.tsx
index d0d9134..e9f7298 100644
--- a/src/CurveDesignInputParams.tsx
+++ b/src/CurveDesignInputParams.tsx
@@ -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,
diff --git a/src/InputParams.tsx b/src/InputParams.tsx
index fd9f603..3d06aaa 100644
--- a/src/InputParams.tsx
+++ b/src/InputParams.tsx
@@ -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 (
-
- {label}
-
+
diff --git a/src/PriceSimulationChart.tsx b/src/PriceSimulationChart.tsx
index bcc0e40..df5ab1e 100644
--- a/src/PriceSimulationChart.tsx
+++ b/src/PriceSimulationChart.tsx
@@ -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 (
@@ -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 */}
@@ -166,7 +170,20 @@ function PriceSimulationChart({
dataKey={keyVerticalLeft}
stroke={theme.palette.primary.main}
fill={theme.palette.primary.main}
+ fillOpacity={0.3}
+ strokeWidth={2}
/>
+
+
{/*
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 (
- {resultFields.map(({ label, value }) => (
+ {resultFields.map(({ label, description, value }) => (
-
- {label}
-
+
diff --git a/src/SimulationInputParams.tsx b/src/SimulationInputParams.tsx
index e02f36e..93dcc55 100644
--- a/src/SimulationInputParams.tsx
+++ b/src/SimulationInputParams.tsx
@@ -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,
diff --git a/src/SupplyVsDemandChart.tsx b/src/SupplyVsDemandChart.tsx
index 54c76a7..85e3d1b 100644
--- a/src/SupplyVsDemandChart.tsx
+++ b/src/SupplyVsDemandChart.tsx
@@ -186,6 +186,8 @@ function SupplyVsDemandChart({
dataKey={keyVertical}
stroke={theme.palette.primary.main}
fill={theme.palette.primary.main}
+ fillOpacity={0.3}
+ strokeWidth={2}
/>
({
+ 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 (
+
+
+ {content}
+
+
+
+ {content}
+
+ {popoverText}
+
+
+
+
+ );
+}
diff --git a/src/math.ts b/src/math.ts
index 94050a4..4a054bd 100644
--- a/src/math.ts
+++ b/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);
+}
diff --git a/src/parametersDescriptions.ts b/src/parametersDescriptions.ts
new file mode 100644
index 0000000..0b7ebf7
--- /dev/null
+++ b/src/parametersDescriptions.ts
@@ -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"
+ }
+};
diff --git a/src/types.ts b/src/types.ts
index 2fcc5e2..e0f426c 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,5 +1,6 @@
export interface InputFieldInterface {
label: string;
+ description: string;
value: number;
setter(newValue: any): void;
min: number;