Compare commits
No commits in common. "master" and "gh-pages" have entirely different histories.
|
|
@ -1,27 +0,0 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
/.vscode
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
File diff suppressed because one or more lines are too long
15
.travis.yml
15
.travis.yml
|
|
@ -1,15 +0,0 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "10"
|
||||
install:
|
||||
- yarn
|
||||
script:
|
||||
- yarn run build
|
||||
deploy:
|
||||
provider: pages
|
||||
skip-cleanup: true
|
||||
github-token: $GITHUB_TOKEN # Set in the settings page of your repository, as a secure variable
|
||||
keep-history: true
|
||||
local-dir: build
|
||||
on:
|
||||
branch: master
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
# Augmented Token Bonding Curve Design
|
||||
|
||||
Experiment and test augmented token bonding curves, part of the Commons Stack
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"files": {
|
||||
"main.css": "/augmented-tbc-design/static/css/main.ed579467.chunk.css",
|
||||
"main.js": "/augmented-tbc-design/static/js/main.8197c9cc.chunk.js",
|
||||
"main.js.map": "/augmented-tbc-design/static/js/main.8197c9cc.chunk.js.map",
|
||||
"runtime~main.js": "/augmented-tbc-design/static/js/runtime~main.e0588710.js",
|
||||
"runtime~main.js.map": "/augmented-tbc-design/static/js/runtime~main.e0588710.js.map",
|
||||
"static/js/2.dc6af9b8.chunk.js": "/augmented-tbc-design/static/js/2.dc6af9b8.chunk.js",
|
||||
"static/js/2.dc6af9b8.chunk.js.map": "/augmented-tbc-design/static/js/2.dc6af9b8.chunk.js.map",
|
||||
"index.html": "/augmented-tbc-design/index.html",
|
||||
"precache-manifest.e205497e3cbeb3c5d1b36f132edd93ab.js": "/augmented-tbc-design/precache-manifest.e205497e3cbeb3c5d1b36f132edd93ab.js",
|
||||
"service-worker.js": "/augmented-tbc-design/service-worker.js",
|
||||
"static/css/main.ed579467.chunk.css.map": "/augmented-tbc-design/static/css/main.ed579467.chunk.css.map"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 91 KiB |
|
|
@ -0,0 +1 @@
|
|||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/augmented-tbc-design/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/augmented-tbc-design/manifest.json"/><title>Augmented Bonding Curve Design</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><link href="/augmented-tbc-design/static/css/main.ed579467.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(l){function e(e){for(var t,r,n=e[0],o=e[1],u=e[2],i=0,a=[];i<n.length;i++)r=n[i],Object.prototype.hasOwnProperty.call(f,r)&&f[r]&&a.push(f[r][0]),f[r]=0;for(t in o)Object.prototype.hasOwnProperty.call(o,t)&&(l[t]=o[t]);for(d&&d(e);a.length;)a.shift()();return p.push.apply(p,u||[]),c()}function c(){for(var e,t=0;t<p.length;t++){for(var r=p[t],n=!0,o=1;o<r.length;o++){var u=r[o];0!==f[u]&&(n=!1)}n&&(p.splice(t--,1),e=i(i.s=r[0]))}return e}var r={},f={1:0},p=[];function i(e){if(r[e])return r[e].exports;var t=r[e]={i:e,l:!1,exports:{}};return l[e].call(t.exports,t,t.exports,i),t.l=!0,t.exports}i.m=l,i.c=r,i.d=function(e,t,r){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(t,e){if(1&e&&(t=i(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(i.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var n in t)i.d(r,n,function(e){return t[e]}.bind(null,n));return r},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="/augmented-tbc-design/";var t=window["webpackJsonpaugmented-tbc-design"]=window["webpackJsonpaugmented-tbc-design"]||[],n=t.push.bind(t);t.push=e,t=t.slice();for(var o=0;o<t.length;o++)e(t[o]);var d=n;c()}([])</script><script src="/augmented-tbc-design/static/js/2.dc6af9b8.chunk.js"></script><script src="/augmented-tbc-design/static/js/main.8197c9cc.chunk.js"></script></body></html>
|
||||
File diff suppressed because one or more lines are too long
45
package.json
45
package.json
|
|
@ -1,45 +0,0 @@
|
|||
{
|
||||
"name": "augmented-tbc-design",
|
||||
"version": "0.1.0",
|
||||
"homepage": "https://commons-stack.github.io/augmented-tbc-design",
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.3.0",
|
||||
"@material-ui/icons": "^4.2.1",
|
||||
"@types/lodash": "^4.14.136",
|
||||
"@types/react": "^16.8.24",
|
||||
"@types/react-dom": "^16.8.5",
|
||||
"lodash": "^4.17.15",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-number-format": "^4.0.8",
|
||||
"react-scripts": "^3.0.1",
|
||||
"recharts": "^1.6.2",
|
||||
"typescript": "^3.5.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"predeploy": "npm run build",
|
||||
"deploy": "gh-pages -d build",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^12.7.1",
|
||||
"@types/recharts": "^1.1.20",
|
||||
"gh-pages": "^2.0.1"
|
||||
},
|
||||
"author": "dappLion <dapplion@dappnode.com> (https://github.com/dapplion/)"
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
self.__precacheManifest = (self.__precacheManifest || []).concat([
|
||||
{
|
||||
"revision": "3571fe45c368722da5e7f321b9c716ea",
|
||||
"url": "/augmented-tbc-design/index.html"
|
||||
},
|
||||
{
|
||||
"revision": "131b8f54a943c24b6048",
|
||||
"url": "/augmented-tbc-design/static/css/main.ed579467.chunk.css"
|
||||
},
|
||||
{
|
||||
"revision": "0fb397e8f6daa44f859d",
|
||||
"url": "/augmented-tbc-design/static/js/2.dc6af9b8.chunk.js"
|
||||
},
|
||||
{
|
||||
"revision": "131b8f54a943c24b6048",
|
||||
"url": "/augmented-tbc-design/static/js/main.8197c9cc.chunk.js"
|
||||
},
|
||||
{
|
||||
"revision": "bbaf5adc59b3ad16792b",
|
||||
"url": "/augmented-tbc-design/static/js/runtime~main.e0588710.js"
|
||||
}
|
||||
]);
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Augmented Bonding Curve Design</title>
|
||||
<!-- Fonts to support Material Design -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* Welcome to your Workbox-powered service worker!
|
||||
*
|
||||
* You'll need to register this file in your web app and you should
|
||||
* disable HTTP caching for this file too.
|
||||
* See https://goo.gl/nhQhGp
|
||||
*
|
||||
* The rest of the code is auto-generated. Please don't update this file
|
||||
* directly; instead, make changes to your Workbox build configuration
|
||||
* and re-run your build process.
|
||||
* See https://goo.gl/2aRDsh
|
||||
*/
|
||||
|
||||
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
|
||||
|
||||
importScripts(
|
||||
"/augmented-tbc-design/precache-manifest.e205497e3cbeb3c5d1b36f132edd93ab.js"
|
||||
);
|
||||
|
||||
self.addEventListener('message', (event) => {
|
||||
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||
self.skipWaiting();
|
||||
}
|
||||
});
|
||||
|
||||
workbox.core.clientsClaim();
|
||||
|
||||
/**
|
||||
* The workboxSW.precacheAndRoute() method efficiently caches and responds to
|
||||
* requests for URLs in the manifest.
|
||||
* See https://goo.gl/S9QRab
|
||||
*/
|
||||
self.__precacheManifest = [].concat(self.__precacheManifest || []);
|
||||
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
|
||||
|
||||
workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("/augmented-tbc-design/index.html"), {
|
||||
|
||||
blacklist: [/^\/_/,/\/[^\/?]+\.[^\/]+$/],
|
||||
});
|
||||
565
src/App.tsx
565
src/App.tsx
|
|
@ -1,565 +0,0 @@
|
|||
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";
|
||||
// Components
|
||||
import Header from "./Header";
|
||||
import CurveDesignInputParams from "./CurveDesignInputParams";
|
||||
import SimulationInputParams from "./SimulationInputParams";
|
||||
import SupplyVsDemandChart from "./SupplyVsDemandChart";
|
||||
import ResultParams from "./ResultParams";
|
||||
import PriceSimulationChart from "./PriceSimulationChart";
|
||||
import HelpText from "./HelpText";
|
||||
// Text content
|
||||
import {
|
||||
parameterDescriptions,
|
||||
simulationParameterDescriptions,
|
||||
resultParameterDescriptions,
|
||||
supplyVsDemandChartDescription,
|
||||
simulationChartDescription
|
||||
} from "./parametersDescriptions";
|
||||
// Utils
|
||||
import { getLast, getAvg, pause } from "./utils";
|
||||
import {
|
||||
getInitialParams,
|
||||
getPriceR,
|
||||
getMinPrice,
|
||||
getS,
|
||||
vest_tokens,
|
||||
getR,
|
||||
getSlippage,
|
||||
getTxDistribution,
|
||||
getDeltaR_priceGrowth,
|
||||
rv_U,
|
||||
getMedian,
|
||||
getSum
|
||||
} from "./math";
|
||||
import { throttle } from "lodash";
|
||||
// Data
|
||||
import { u_min_t, u_max_t } from "./u_values";
|
||||
// General styles
|
||||
import "./app.css";
|
||||
|
||||
const headerOffset = 10;
|
||||
const simulationDuration = 4000;
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
mainContainer: {
|
||||
"& > div:not(:last-child)": {
|
||||
paddingBottom: theme.spacing(3)
|
||||
},
|
||||
"& > div": {
|
||||
"& > div": {
|
||||
paddingTop: "0 !important"
|
||||
}
|
||||
},
|
||||
paddingBottom: theme.spacing(9)
|
||||
},
|
||||
simulationContainer: {
|
||||
minHeight: "442px"
|
||||
},
|
||||
paper: {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
minHeight: 310,
|
||||
backgroundColor: "#293640"
|
||||
},
|
||||
box: {
|
||||
padding: theme.spacing(3, 3)
|
||||
},
|
||||
boxButton: {
|
||||
padding: theme.spacing(3, 3)
|
||||
},
|
||||
boxHeader: {
|
||||
padding: theme.spacing(3, 3),
|
||||
height: theme.spacing(headerOffset),
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
borderBottom: "1px solid #313d47"
|
||||
},
|
||||
boxBorderBottom: {
|
||||
borderBottom: "1px solid #313d47"
|
||||
},
|
||||
initialRaise: {
|
||||
justifyContent: "space-between"
|
||||
},
|
||||
boxChart: {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
minHeight: 310,
|
||||
maxHeight: 350,
|
||||
padding: theme.spacing(3, 3),
|
||||
// Correct the chart excessive margins
|
||||
paddingRight: "5px",
|
||||
paddingLeft: "5px"
|
||||
},
|
||||
boxPlaceholder: {
|
||||
padding: theme.spacing(3, 3),
|
||||
display: "flex",
|
||||
height: "100%",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
color: theme.palette.text.secondary,
|
||||
opacity: 0.4
|
||||
},
|
||||
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)", // Green gradient
|
||||
background: "linear-gradient(290deg, #1aa059, #3d9567)", // Darker Green gradient
|
||||
// background: "linear-gradient(290deg, #1880e0, #3873d8)", // blue gradient
|
||||
color: "white"
|
||||
},
|
||||
// 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"
|
||||
},
|
||||
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"
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
export default function App() {
|
||||
const [curveParams, setCurveParams] = useState({
|
||||
theta: 0.35, // fraction allocated to reserve (.)
|
||||
p0: 0.1, // Hatch sale price p0 (DAI / token)
|
||||
p1: 0.3, // Return factor (.)
|
||||
wFee: 0.05, // friction coefficient (.)
|
||||
vHalflife: 17, // Vesting half life (weeks)
|
||||
d0: 3e6 // Initial raise, d0 (DAI)
|
||||
});
|
||||
|
||||
const { d0, theta, p0, p1, wFee, vHalflife } = curveParams;
|
||||
|
||||
/**
|
||||
* Throttle the curve update to prevent the expensive chart
|
||||
* to re-render too often
|
||||
*/
|
||||
const setCurveParamsThrottle = useMemo(
|
||||
() => throttle(setCurveParams, 250),
|
||||
[]
|
||||
);
|
||||
|
||||
// Simulation results
|
||||
const {
|
||||
k, // Invariant power kappa (.)
|
||||
R0, // Initial reserve (DAI)
|
||||
S0, // initial supply of tokens (token)
|
||||
V0 // invariant coef
|
||||
} = getInitialParams({
|
||||
d0,
|
||||
theta,
|
||||
p0,
|
||||
p1
|
||||
});
|
||||
|
||||
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);
|
||||
const [avgTxSize, setAvgTxSize] = useState(0);
|
||||
// Simulation state variables
|
||||
const [simulationActive, setSimulationActive] = useState(false);
|
||||
const [simulationRunning, setSimulationRunning] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setSimulationActive(false);
|
||||
}, [curveParams]);
|
||||
|
||||
// #### 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 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 = rv_U(100, 40 * t + 100);
|
||||
|
||||
const R = getLast(R_t);
|
||||
const S = getLast(S_t);
|
||||
const H = getLast(H_t);
|
||||
|
||||
let R_next: number = 0,
|
||||
S_next: number = 0,
|
||||
H_next: number = 0,
|
||||
price_next: number = 0,
|
||||
txsWithdraw: number[] = [0],
|
||||
floorprice_next: number = 1,
|
||||
slippage: number = 0;
|
||||
// Run the value compution again if the price goes below the floor price
|
||||
|
||||
/**
|
||||
* Since the values u_min and u_max are predefined, it's possible that
|
||||
* the price becomes less than the floor price. This cannot happen.
|
||||
* So the next loop has 10 opportunities to find a price greater than
|
||||
* the floor price. The for loop is used to prevent a possible infinite
|
||||
* loop if a `while` loop was used.
|
||||
*/
|
||||
for (let i = 0; i < 20 && price_next < floorprice_next * 1.05; i++) {
|
||||
// enforce the effects of the unvested tokens not being burnable
|
||||
let u_lower: number, u_upper: number;
|
||||
// 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);
|
||||
// }
|
||||
// let priceGrowth = rv_U(u_lower, u_max);
|
||||
|
||||
u_lower = u_min_t[t];
|
||||
u_upper = u_max_t[t];
|
||||
|
||||
if (i > 15) {
|
||||
u_lower = 1.02;
|
||||
u_upper = u_upper + 1.04;
|
||||
}
|
||||
|
||||
const priceGrowth = rv_U(u_lower, u_upper);
|
||||
|
||||
const deltaR = getDeltaR_priceGrowth({ R, k, priceGrowth });
|
||||
R_next = R + deltaR;
|
||||
|
||||
const txs = getTxDistribution({
|
||||
sum: deltaR,
|
||||
num: txsWeek,
|
||||
spread: tx_spread
|
||||
});
|
||||
// Compute slippage
|
||||
const slippage_txs = txs.map(txR =>
|
||||
getSlippage({ R, deltaR: txR, V0, k })
|
||||
);
|
||||
slippage = getMedian(slippage_txs);
|
||||
|
||||
txsWithdraw = txs.filter(tx => tx < 0);
|
||||
|
||||
// Vest
|
||||
const delta_H = vest_tokens({
|
||||
week: t,
|
||||
H,
|
||||
halflife: vHalflife,
|
||||
cliff
|
||||
});
|
||||
H_next = H - delta_H;
|
||||
|
||||
// find floor price
|
||||
S_next = getS({ R, V0, k });
|
||||
floorprice_next = getMinPrice({
|
||||
S: S_next,
|
||||
H: S_next - H_next,
|
||||
V0,
|
||||
k
|
||||
});
|
||||
|
||||
price_next = getPriceR({ R: R_next, V0, k });
|
||||
}
|
||||
|
||||
const _avgTxSize = getMedian(txsWithdraw);
|
||||
const wFees = -wFee * getSum(txsWithdraw);
|
||||
|
||||
R_t.push(R_next);
|
||||
S_t.push(S_next);
|
||||
H_t.push(H_next);
|
||||
p_t.push(price_next);
|
||||
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.filter(n => !isNaN(n))));
|
||||
setTotalReserve(getLast(R_t));
|
||||
|
||||
setSimulationRunning(false);
|
||||
}
|
||||
|
||||
if (simulationActive) simulateRandomDelta();
|
||||
// Return an "unsubscribe" function that halts the run
|
||||
return () => {
|
||||
canContinueSimulation = false;
|
||||
};
|
||||
}, [simulationActive]);
|
||||
|
||||
// End results computed for chart visualization
|
||||
const initialHatchFunds = d0 * theta;
|
||||
const totalFundsRaisedTimeseries = withdrawFeeTimeseries.map(
|
||||
x => x + initialHatchFunds
|
||||
);
|
||||
|
||||
const totalInitialHatchFunds = Math.round(d0 * theta);
|
||||
const totalExitTributes = Math.round(getLast(withdrawFeeTimeseries));
|
||||
const totalFunds = totalInitialHatchFunds + totalExitTributes;
|
||||
const formatFunds = (n: number) => (+n.toPrecision(3)).toLocaleString();
|
||||
|
||||
const resultFields = [
|
||||
{
|
||||
label: resultParameterDescriptions.totalReserve.name,
|
||||
description: resultParameterDescriptions.totalReserve.text,
|
||||
value: (+totalReserve.toPrecision(3)).toLocaleString() + " DAI"
|
||||
},
|
||||
{
|
||||
label: resultParameterDescriptions.slippage.name,
|
||||
description: resultParameterDescriptions.slippage.text,
|
||||
value: +(100 * avgSlippage).toFixed(3) + " %",
|
||||
valueFooter: `Avg tx size ${Math.round(avgTxSize).toLocaleString()} DAI`
|
||||
},
|
||||
{
|
||||
label: resultParameterDescriptions.initialHatchFunds.name,
|
||||
description: resultParameterDescriptions.initialHatchFunds.text,
|
||||
value: formatFunds(totalInitialHatchFunds) + " DAI"
|
||||
},
|
||||
{
|
||||
label: resultParameterDescriptions.exitTributes.name,
|
||||
description: resultParameterDescriptions.exitTributes.text,
|
||||
value: formatFunds(totalExitTributes) + " DAI",
|
||||
valueFooter: `From ${withdrawCount} exit txs`
|
||||
},
|
||||
{
|
||||
label: resultParameterDescriptions.totalRaised.name,
|
||||
description: resultParameterDescriptions.totalRaised.text,
|
||||
value: formatFunds(totalFunds) + " 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
|
||||
title={"Parameters description"}
|
||||
table={[
|
||||
parameterDescriptions.theta,
|
||||
parameterDescriptions.p0,
|
||||
parameterDescriptions.p1,
|
||||
parameterDescriptions.wFee,
|
||||
parameterDescriptions.vHalflife
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box className={`${classes.box} ${classes.boxBorderBottom}`}>
|
||||
<CurveDesignInputParams
|
||||
curveParams={curveParams}
|
||||
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 body={supplyVsDemandChartDescription} />
|
||||
</Box>
|
||||
|
||||
<Box className={classes.boxChart}>
|
||||
<SupplyVsDemandChart theta={theta} d0={d0} p0={p0} p1={p1} />
|
||||
</Box>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Paper>
|
||||
<Box className={`${classes.box} ${classes.boxBorderBottom}`}>
|
||||
<SimulationInputParams
|
||||
curveParams={curveParams}
|
||||
setCurveParams={setCurveParamsThrottle}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<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>
|
||||
|
||||
<Grid container spacing={3} className={classes.simulationContainer}>
|
||||
{simulationActive ? (
|
||||
<>
|
||||
<Grid item xs={12} sm={12} md={6} lg={8}>
|
||||
<Paper className={classes.paper}>
|
||||
<Box className={classes.boxHeader}>
|
||||
<Typography variant="h6">Simulation</Typography>
|
||||
<HelpText
|
||||
body={simulationChartDescription}
|
||||
table={Object.values(simulationParameterDescriptions)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box className={classes.boxChart}>
|
||||
<PriceSimulationChart
|
||||
priceTimeseries={priceTimeseries}
|
||||
floorpriceTimeseries={floorpriceTimeseries}
|
||||
totalFundsRaisedTimeseries={totalFundsRaisedTimeseries}
|
||||
simulationDuration={simulationDuration}
|
||||
p0={p0}
|
||||
p1={p1}
|
||||
/>
|
||||
</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
|
||||
title={"Result parameters description"}
|
||||
table={Object.values(resultParameterDescriptions)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box className={classes.box}>
|
||||
<ResultParams
|
||||
resultFields={resultFields}
|
||||
simulationDuration={simulationDuration}
|
||||
/>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</>
|
||||
) : (
|
||||
<Grid item xs={12}>
|
||||
<Paper className={classes.paper}>
|
||||
<Box className={classes.boxPlaceholder}>
|
||||
<Typography variant="h6">
|
||||
Run a simulation to see results
|
||||
</Typography>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { InputFieldInterface, CurveParamsInterface } from "./types";
|
||||
import InputParams from "./InputParams";
|
||||
import { parameterDescriptions } from "./parametersDescriptions";
|
||||
|
||||
export default function CurveDesignInputParams({
|
||||
curveParams,
|
||||
setCurveParams
|
||||
}: {
|
||||
curveParams: CurveParamsInterface;
|
||||
setCurveParams(newCurveParams: any): void;
|
||||
}) {
|
||||
const [theta, setTheta] = useState(curveParams.theta); // fraction allocated to reserve (.)
|
||||
const [p0, setP0] = useState(curveParams.p0); // Hatch sale Price p0 (DAI / token)
|
||||
const [p1, setP1] = useState(curveParams.p1); // Return factor (.)
|
||||
const [wFee, setWFee] = useState(curveParams.wFee); // friction coefficient (.)
|
||||
const [vHalflife, setVHalflife] = useState(curveParams.vHalflife); // friction coefficient (.)
|
||||
|
||||
useEffect(() => {
|
||||
setTheta(curveParams.theta);
|
||||
setP0(curveParams.p0);
|
||||
setP1(curveParams.p1);
|
||||
setWFee(curveParams.wFee);
|
||||
setVHalflife(curveParams.vHalflife);
|
||||
}, [curveParams]);
|
||||
|
||||
const maxReturnRate = 10;
|
||||
const minP1P0Rate = 1.5;
|
||||
|
||||
function _setP0(newP0: number) {
|
||||
setP0(newP0);
|
||||
if (p1 < newP0 * minP1P0Rate) setP1(newP0 * minP1P0Rate);
|
||||
else if (p1 > newP0 * maxReturnRate) setP1(newP0 * maxReturnRate);
|
||||
}
|
||||
|
||||
function setParentCurveParams() {
|
||||
setCurveParams((params: CurveParamsInterface) => ({
|
||||
...params,
|
||||
theta,
|
||||
p0,
|
||||
p1,
|
||||
wFee,
|
||||
vHalflife
|
||||
}));
|
||||
}
|
||||
|
||||
const inputFields: InputFieldInterface[] = [
|
||||
{
|
||||
label: parameterDescriptions.theta.name,
|
||||
description: parameterDescriptions.theta.text,
|
||||
value: theta,
|
||||
setter: setTheta,
|
||||
min: 0,
|
||||
max: 0.9,
|
||||
step: 0.01,
|
||||
suffix: "%",
|
||||
format: (n: number) => `${Math.round(100 * n)}%`,
|
||||
toText: (n: number) => String(+(n * 1e2).toFixed(0)),
|
||||
toNum: (n: string) => parseFloat(n) * 1e-2
|
||||
},
|
||||
{
|
||||
label: `${parameterDescriptions.p0.name} (DAI/token)`,
|
||||
description: parameterDescriptions.p0.text,
|
||||
value: p0,
|
||||
setter: _setP0,
|
||||
min: 0.01,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
toText: (n: number) => String(+n.toFixed(2)),
|
||||
toNum: (n: string) => parseFloat(n),
|
||||
format: (n: number) => `$${n}`
|
||||
},
|
||||
{
|
||||
label: `${parameterDescriptions.p1.name} (DAI/token)`,
|
||||
description: parameterDescriptions.p1.text,
|
||||
value: p1,
|
||||
setter: setP1,
|
||||
min: Number((minP1P0Rate * (p0 || 0.1)).toFixed(2)),
|
||||
max: Number((maxReturnRate * p0).toFixed(2)),
|
||||
step: 0.01,
|
||||
toText: (n: number) => String(+n.toFixed(2)),
|
||||
toNum: (n: string) => parseFloat(n),
|
||||
format: (n: number) => `$${n}`
|
||||
},
|
||||
{
|
||||
label: parameterDescriptions.wFee.name,
|
||||
description: parameterDescriptions.wFee.text,
|
||||
value: wFee,
|
||||
setter: setWFee,
|
||||
min: 0,
|
||||
max: 0.1,
|
||||
step: 0.001,
|
||||
suffix: "%",
|
||||
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: 1,
|
||||
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))
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<InputParams
|
||||
inputFields={inputFields}
|
||||
onChangeCommited={setParentCurveParams}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import React from "react";
|
||||
import "./dotsLoader.css";
|
||||
|
||||
export default function DotsLoader() {
|
||||
return (
|
||||
<div className="spinner">
|
||||
<div className="bounce1" />
|
||||
<div className="bounce2" />
|
||||
<div className="bounce3" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
import React from "react";
|
||||
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
|
||||
const cadCadLink =
|
||||
"https://medium.com/giveth/deep-dive-augmented-bonding-curves-3f1f7c1fa751";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
container: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center"
|
||||
},
|
||||
title: {
|
||||
// color: theme.palette.text.secondary,
|
||||
},
|
||||
subtitle: {
|
||||
color: theme.palette.text.secondary,
|
||||
margin: theme.spacing(3, 0, 0),
|
||||
maxWidth: theme.spacing(82)
|
||||
},
|
||||
subsubtitle: {
|
||||
color: theme.palette.text.secondary,
|
||||
opacity: 0.6,
|
||||
maxWidth: theme.spacing(74)
|
||||
},
|
||||
lightBulb: {
|
||||
verticalAlign: "middle",
|
||||
marginRight: theme.spacing(1)
|
||||
},
|
||||
link: {
|
||||
color: theme.palette.primary.main
|
||||
},
|
||||
logoContainer: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
marginBottom: theme.spacing(4)
|
||||
},
|
||||
logo: {
|
||||
width: "25px",
|
||||
marginRight: "4px"
|
||||
},
|
||||
logoText: {
|
||||
display: "inline",
|
||||
fontSize: "1.1rem",
|
||||
fontWeight: 500
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
export default function Header() {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<div className={classes.logoContainer}>
|
||||
<img src="./favicon.ico" className={classes.logo} alt="logo" />
|
||||
<Typography className={classes.logoText}>Commons Stack</Typography>
|
||||
</div>
|
||||
|
||||
<Typography className={classes.title} variant="h4">
|
||||
Augmented Bonding Curve Design
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.subtitle}>
|
||||
Experiment with the Commons Stack's Augmented Bonding Curve component.
|
||||
Change the Hatch variables to explore what continuous funding streams
|
||||
can do for your community.
|
||||
</Typography>
|
||||
<Typography className={classes.subsubtitle}>
|
||||
Read more about the Augmented Bonding Curve{" "}
|
||||
<Link href={cadCadLink}>here</Link>. Note that this is a demo for
|
||||
illustration purposes only, a narrative showcase of cadCAD's
|
||||
capabilities.
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
146
src/HelpText.tsx
146
src/HelpText.tsx
|
|
@ -1,146 +0,0 @@
|
|||
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";
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
container: {
|
||||
display: "flex",
|
||||
marginLeft: "6px",
|
||||
fontSize: "0.9rem",
|
||||
cursor: "pointer",
|
||||
transition: "opacity ease 150ms",
|
||||
opacity: 0.2,
|
||||
"&:hover": {
|
||||
opacity: 0.85
|
||||
}
|
||||
},
|
||||
popoverContainer: {
|
||||
padding: theme.spacing(2)
|
||||
},
|
||||
paper: {
|
||||
backgroundColor: "#384b59",
|
||||
maxWidth: theme.breakpoints.values.md * 0.9,
|
||||
[`@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,
|
||||
title,
|
||||
table,
|
||||
body
|
||||
}: {
|
||||
text?: any;
|
||||
title?: string;
|
||||
table?: { name: string; text: string }[];
|
||||
body?: string;
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
|
||||
function handleClick(e: any) {
|
||||
setAnchorEl(e.currentTarget);
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
setAnchorEl(null);
|
||||
}
|
||||
|
||||
const open = Boolean(anchorEl);
|
||||
const id = open ? "simple-popover" : undefined;
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<HelpIcon onClick={handleClick} />
|
||||
|
||||
<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}>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
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>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
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 NumberFormat from "react-number-format";
|
||||
import { InputFieldInterface } from "./types";
|
||||
import PrettoSlider from "./PrettoSlider";
|
||||
import TextWithPopover from "./TextWithPopover";
|
||||
|
||||
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 InputParams({
|
||||
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} className={classes.leftContainer}>
|
||||
<TextWithPopover content={label} popoverText={description} />
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={2} 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}>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
import { withStyles } from "@material-ui/core/styles";
|
||||
import Slider from "@material-ui/core/Slider";
|
||||
|
||||
export default withStyles({
|
||||
root: {
|
||||
height: 8
|
||||
},
|
||||
thumb: {
|
||||
height: 24,
|
||||
width: 24,
|
||||
backgroundColor: "#fff",
|
||||
border: "2px solid currentColor",
|
||||
marginTop: -8,
|
||||
marginLeft: -12,
|
||||
"&:focus,&:hover,&$active": {
|
||||
boxShadow: "inherit"
|
||||
}
|
||||
},
|
||||
active: {},
|
||||
valueLabel: {
|
||||
left: "calc(-50% + 4px)"
|
||||
},
|
||||
track: {
|
||||
height: 8,
|
||||
borderRadius: 4
|
||||
},
|
||||
rail: {
|
||||
height: 8,
|
||||
borderRadius: 4
|
||||
},
|
||||
markLabel: {
|
||||
top: 30
|
||||
}
|
||||
})(Slider);
|
||||
|
|
@ -1,273 +0,0 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
AreaChart,
|
||||
Area,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Legend,
|
||||
ReferenceLine,
|
||||
ResponsiveContainer,
|
||||
Tooltip
|
||||
} from "recharts";
|
||||
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
|
||||
import { useTheme } from "@material-ui/styles";
|
||||
import { linspace, getUnits } from "./utils";
|
||||
|
||||
const keyHorizontal = "x";
|
||||
const keyVerticalLeft = "Price (DAI/token)";
|
||||
const keyVerticalLeft2 = "Floor price (DAI/token)";
|
||||
const keyVerticalRight = "Total funds raised (DAI)";
|
||||
const p1LineText = "Post-Hatch price";
|
||||
const p0LineText = "Hatch price";
|
||||
|
||||
// Do to transparency and color merging issues
|
||||
// these colors are handpicked to look the closest to the theme colors
|
||||
const yLeftColor = "#53c388";
|
||||
const yRightColor = "#4090d9";
|
||||
const referenceLineColor = "#b7c1cb";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
tooltip: {
|
||||
border: "1px solid #313d47",
|
||||
backgroundColor: "#384b59",
|
||||
padding: theme.spacing(1),
|
||||
color: "#c7ccd2"
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
function PriceSimulationChart({
|
||||
priceTimeseries,
|
||||
totalFundsRaisedTimeseries,
|
||||
floorpriceTimeseries,
|
||||
simulationDuration,
|
||||
p0,
|
||||
p1
|
||||
}: {
|
||||
priceTimeseries: number[];
|
||||
totalFundsRaisedTimeseries: number[];
|
||||
floorpriceTimeseries: number[];
|
||||
simulationDuration: number;
|
||||
p0: number;
|
||||
p1: number;
|
||||
}) {
|
||||
// d0 - Initial raise, d0 (DAI)
|
||||
// theta - fraction allocated to reserve (.)
|
||||
// p0 - Hatch sale Price p0 (DAI / token)
|
||||
// returnF - Return factor (.)
|
||||
// wFee - friction coefficient (.)
|
||||
|
||||
const data = [];
|
||||
for (let t = 0; t < priceTimeseries.length; t++) {
|
||||
data.push({
|
||||
[keyHorizontal]: t,
|
||||
[keyVerticalLeft]: priceTimeseries[t] || 0,
|
||||
[keyVerticalLeft2]: floorpriceTimeseries[t] || 0,
|
||||
[keyVerticalRight]: totalFundsRaisedTimeseries[t] || 0
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* When resizing the window the chart animation looks very bad
|
||||
* Keep the animation active only during the initial animation time,
|
||||
* but afterwards, deactivate to prevent the re-size ugly effect
|
||||
*/
|
||||
const [isAnimationActive, setIsAnimationActive] = useState(
|
||||
process.env.NODE_ENV !== "development"
|
||||
);
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
setIsAnimationActive(false);
|
||||
}, simulationDuration + 100);
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
});
|
||||
|
||||
// Compute chart related math
|
||||
|
||||
const totalFundsMin = totalFundsRaisedTimeseries[0];
|
||||
const totalFundsMax = totalFundsRaisedTimeseries.slice(-1)[0];
|
||||
const totalFundsRange = totalFundsMax - totalFundsMin;
|
||||
|
||||
const daiFormatter = (n: number) => (+n.toFixed(2)).toLocaleString();
|
||||
const { scaling, unit } = getUnits(totalFundsMax);
|
||||
const fundsFormatter = (n: number) => (+n.toPrecision(3)).toLocaleString();
|
||||
const fundsFormatterShort = (n: number) =>
|
||||
(+(n / scaling).toPrecision(3)).toLocaleString();
|
||||
|
||||
// Load styles
|
||||
|
||||
const theme: any = useTheme();
|
||||
const classes = useStyles();
|
||||
|
||||
// Chart components
|
||||
|
||||
function renderColorfulLegendText(value: string) {
|
||||
return <span style={{ color: theme.palette.text.secondary }}>{value}</span>;
|
||||
}
|
||||
|
||||
function ReferenceLabel(props: any) {
|
||||
const { textAnchor, viewBox, text, fill } = props;
|
||||
return (
|
||||
<text
|
||||
x={viewBox.x + 4}
|
||||
y={viewBox.y + 17}
|
||||
fill={referenceLineColor}
|
||||
textAnchor={textAnchor}
|
||||
>
|
||||
{text}
|
||||
</text>
|
||||
);
|
||||
}
|
||||
|
||||
function CustomTooltip({ active, payload, label }: any) {
|
||||
if (active) {
|
||||
const price = payload[0].value;
|
||||
const floor = payload[1].value;
|
||||
const funds = payload[2].value;
|
||||
const weekNum = label;
|
||||
const toolTipData: string[][] = [
|
||||
["Price", daiFormatter(price), "DAI/tk"],
|
||||
["Floor P.", daiFormatter(floor), "DAI/tk"],
|
||||
["Funds R.", fundsFormatterShort(funds) + unit, "DAI"],
|
||||
["Week", weekNum, ""]
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={classes.tooltip}>
|
||||
<table>
|
||||
<tbody>
|
||||
{toolTipData.map(([name, value, unit]) => (
|
||||
<tr key={name}>
|
||||
<td>{name}</td>
|
||||
<td>{value}</td>
|
||||
<td>{unit}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
} else return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ResponsiveContainer debounce={1}>
|
||||
<AreaChart
|
||||
width={0}
|
||||
height={400}
|
||||
data={data}
|
||||
margin={{
|
||||
top: 10,
|
||||
right: 30,
|
||||
left: 0,
|
||||
bottom: 0
|
||||
}}
|
||||
>
|
||||
<CartesianGrid
|
||||
vertical={false}
|
||||
stroke={theme.palette.text.secondary}
|
||||
strokeOpacity={0.13}
|
||||
/>
|
||||
<XAxis
|
||||
dataKey={keyHorizontal}
|
||||
tick={{ fill: theme.palette.text.secondary }}
|
||||
stroke={theme.palette.text.secondary}
|
||||
ticks={[
|
||||
...linspace({
|
||||
to: priceTimeseries.length,
|
||||
steps: 4
|
||||
}).map(Math.floor),
|
||||
priceTimeseries.length - 1
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* Price time evolution */}
|
||||
<YAxis
|
||||
yAxisId="left"
|
||||
domain={[0, Math.max(...priceTimeseries, p1 * 1.25)]}
|
||||
tickFormatter={daiFormatter}
|
||||
tick={{ fill: yLeftColor }}
|
||||
stroke={yLeftColor}
|
||||
/>
|
||||
|
||||
{/* Capital collected from withdraw fees - AXIS */}
|
||||
<YAxis
|
||||
yAxisId="right"
|
||||
domain={[
|
||||
totalFundsMin.toPrecision(2),
|
||||
+(totalFundsMax + totalFundsRange).toPrecision(2)
|
||||
]}
|
||||
orientation="right"
|
||||
tickFormatter={fundsFormatter}
|
||||
tick={{ fill: yRightColor }}
|
||||
stroke={yRightColor}
|
||||
/>
|
||||
|
||||
<Tooltip content={<CustomTooltip />} />
|
||||
|
||||
<Area
|
||||
isAnimationActive={isAnimationActive}
|
||||
animationDuration={simulationDuration}
|
||||
yAxisId="left"
|
||||
type="monotone"
|
||||
dataKey={keyVerticalLeft}
|
||||
stroke={theme.palette.primary.main}
|
||||
fill={theme.palette.primary.main}
|
||||
fillOpacity={0.3}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
<Area
|
||||
isAnimationActive={isAnimationActive}
|
||||
animationDuration={simulationDuration}
|
||||
yAxisId="left"
|
||||
type="monotone"
|
||||
dataKey={keyVerticalLeft2}
|
||||
stroke={"#adcd2e"}
|
||||
fill={"#adcd2e"}
|
||||
fillOpacity={0.05}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
|
||||
<ReferenceLine
|
||||
y={p0}
|
||||
yAxisId="left"
|
||||
stroke={theme.palette.primary.main}
|
||||
label={<ReferenceLabel text={p0LineText} />}
|
||||
/>
|
||||
<ReferenceLine
|
||||
y={p1}
|
||||
yAxisId="left"
|
||||
stroke={theme.palette.primary.main}
|
||||
label={<ReferenceLabel text={p1LineText} />}
|
||||
/>
|
||||
|
||||
{/* Capital collected from withdraw fees - AREA */}
|
||||
<Area
|
||||
isAnimationActive={isAnimationActive}
|
||||
animationDuration={simulationDuration}
|
||||
yAxisId="right"
|
||||
type="monotone"
|
||||
dataKey={keyVerticalRight}
|
||||
stroke={"#0085ff"}
|
||||
fill={theme.palette.secondary.dark}
|
||||
fillOpacity={0.5}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
|
||||
{/* <ReferenceLine
|
||||
x={R0}
|
||||
stroke="#90a4ae"
|
||||
strokeDasharray="6 3"
|
||||
label={<ReferenceLabel />}
|
||||
/> */}
|
||||
<Legend formatter={renderColorfulLegendText} />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default PriceSimulationChart;
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
import React, { useState, useEffect } 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";
|
||||
import DotsLoader from "./DotsLoader";
|
||||
|
||||
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)": {
|
||||
marginBottom: "12px",
|
||||
borderBottom: "1px solid #313d47"
|
||||
}
|
||||
},
|
||||
listBox: {
|
||||
paddingBottom: "12px",
|
||||
"& > div": {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
"& p": {
|
||||
marginBottom: 0
|
||||
}
|
||||
},
|
||||
"& > div:not(:last-child)": {
|
||||
paddingRight: "12px"
|
||||
}
|
||||
},
|
||||
valueFooter: {
|
||||
color: theme.palette.text.secondary,
|
||||
fontSize: "80%"
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
export default function ResultParams({
|
||||
resultFields,
|
||||
simulationDuration
|
||||
}: {
|
||||
resultFields: {
|
||||
label: string;
|
||||
description: string;
|
||||
value: number | string;
|
||||
valueFooter?: string;
|
||||
}[];
|
||||
simulationDuration: number;
|
||||
}) {
|
||||
/**
|
||||
* When resizing the window the chart animation looks very bad
|
||||
* Keep the animation active only during the initial animation time,
|
||||
* but afterwards, deactivate to prevent the re-size ugly effect
|
||||
*/
|
||||
const [isAnimationActive, setIsAnimationActive] = useState(
|
||||
process.env.NODE_ENV !== "development"
|
||||
);
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
setIsAnimationActive(false);
|
||||
}, simulationDuration);
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
});
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<div className={classes.listBoxContainer}>
|
||||
{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} />
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={4} className={classes.centerContainer}>
|
||||
{isAnimationActive ? (
|
||||
<DotsLoader />
|
||||
) : (
|
||||
<div>
|
||||
<Typography>{value}</Typography>
|
||||
{valueFooter && (
|
||||
<Typography className={classes.valueFooter}>
|
||||
{valueFooter}
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { InputFieldInterface, CurveParamsInterface } from "./types";
|
||||
import InputParamBig from "./InputParamBig";
|
||||
import { parameterDescriptions } from "./parametersDescriptions";
|
||||
|
||||
export default function SimulationInputParams({
|
||||
curveParams,
|
||||
setCurveParams
|
||||
}: {
|
||||
curveParams: CurveParamsInterface;
|
||||
setCurveParams(newCurveParams: any): void;
|
||||
}) {
|
||||
const [d0, setD0] = useState(3e6); // Initial raise, d0 (DAI)
|
||||
|
||||
useEffect(() => {
|
||||
setD0(curveParams.d0);
|
||||
}, [curveParams]);
|
||||
|
||||
function setParentCurveParams() {
|
||||
setCurveParams((params: CurveParamsInterface) => ({
|
||||
...params,
|
||||
d0
|
||||
}));
|
||||
}
|
||||
|
||||
const inputFields: InputFieldInterface[] = [
|
||||
{
|
||||
label: `${parameterDescriptions.d0.name}`,
|
||||
description: parameterDescriptions.d0.text,
|
||||
value: d0,
|
||||
setter: setD0,
|
||||
min: 0.1e6,
|
||||
max: 10e6,
|
||||
step: 0.1e6,
|
||||
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)
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<InputParamBig
|
||||
inputFields={inputFields}
|
||||
onChangeCommited={setParentCurveParams}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,214 +0,0 @@
|
|||
import React from "react";
|
||||
import {
|
||||
AreaChart,
|
||||
Area,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Legend,
|
||||
ReferenceLine,
|
||||
ReferenceDot,
|
||||
ReferenceArea,
|
||||
ResponsiveContainer,
|
||||
Tooltip
|
||||
} from "recharts";
|
||||
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
|
||||
import { getLinspaceTicks, getUnits } from "./utils";
|
||||
import { getInitialParams, getPriceR } from "./math";
|
||||
import { useTheme } from "@material-ui/styles";
|
||||
|
||||
const isAnimationActive = false;
|
||||
const keyHorizontal = "x";
|
||||
const keyVertical = "Supply (tokens) / Collateral (DAI)";
|
||||
|
||||
// Do to transparency and color merging issues
|
||||
// these colors are handpicked to look the closest to the theme colors
|
||||
const referenceLineColor = "#b7c1cb";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
tooltip: {
|
||||
border: "1px solid #313d47",
|
||||
backgroundColor: "#384b59",
|
||||
padding: theme.spacing(1),
|
||||
color: "#c7ccd2"
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
function SupplyVsDemandChart({
|
||||
theta,
|
||||
d0,
|
||||
p0,
|
||||
p1
|
||||
}: {
|
||||
theta: number;
|
||||
d0: number;
|
||||
p0: number;
|
||||
p1: number;
|
||||
}) {
|
||||
// d0 - Initial raise, d0 (DAI)
|
||||
// theta - fraction allocated to reserve (.)
|
||||
// p0 - Hatch sale Price p0 (DAI / token)
|
||||
// returnF - Return factor (.)
|
||||
// wFee - friction coefficient (.)
|
||||
|
||||
// Hatch parameters
|
||||
const {
|
||||
k, // Invariant power kappa (.)
|
||||
R0, // Initial reserve (DAI)
|
||||
S0, // initial supply of tokens (token)
|
||||
V0 // invariant coef
|
||||
} = getInitialParams({
|
||||
d0,
|
||||
theta,
|
||||
p0,
|
||||
p1
|
||||
});
|
||||
const R0_round = Math.round(R0);
|
||||
const S_of_R = (R: number) => S0 * (R / R0_round) ** (1 / k);
|
||||
|
||||
// Function setup
|
||||
const f = S_of_R;
|
||||
const from = 0;
|
||||
const to = 4 * R0_round;
|
||||
const steps = 100 + 1; // Add 1 for the ticks to match
|
||||
const step = Math.round((to - from) / (steps - 1));
|
||||
|
||||
/**
|
||||
* Prettify the result converting 1000000 to 1M
|
||||
*/
|
||||
const biggest = Math.max(to, f(to));
|
||||
const { scaling, unit } = getUnits(biggest);
|
||||
|
||||
const data = [];
|
||||
for (let i = 0; i < steps; i++) {
|
||||
const x = Math.round(from + step * i);
|
||||
data.push({
|
||||
[keyHorizontal]: x,
|
||||
[keyVertical]: f(x)
|
||||
});
|
||||
}
|
||||
|
||||
// Chart components
|
||||
|
||||
const theme: any = useTheme();
|
||||
const classes = useStyles();
|
||||
|
||||
const formatter = (n: number) =>
|
||||
(+(n / scaling).toPrecision(2)).toLocaleString();
|
||||
|
||||
function renderColorfulLegendText(value: string) {
|
||||
return <span style={{ color: theme.palette.text.secondary }}>{value}</span>;
|
||||
}
|
||||
|
||||
function ReferenceLabel(props: any) {
|
||||
const { textAnchor, viewBox } = props;
|
||||
return (
|
||||
<text
|
||||
x={viewBox.x + viewBox.width / 4 + 10}
|
||||
y={viewBox.y + 20}
|
||||
fill={referenceLineColor}
|
||||
textAnchor={textAnchor}
|
||||
>
|
||||
Initial Token Supply
|
||||
</text>
|
||||
);
|
||||
}
|
||||
|
||||
function CustomTooltip({ active, payload, label }: any) {
|
||||
if (active) {
|
||||
const supply = payload[0].value;
|
||||
const reserve = label;
|
||||
const price = getPriceR({ R: reserve, V0, k });
|
||||
const toolTipData: string[][] = [
|
||||
["Supply", formatter(supply) + unit, "tokens"],
|
||||
["Collateral", formatter(reserve) + unit, "DAI"],
|
||||
["Price", price.toFixed(2), "DAI/token"]
|
||||
];
|
||||
return (
|
||||
<div className={classes.tooltip}>
|
||||
<table>
|
||||
<tbody>
|
||||
{toolTipData.map(([name, value, _unit]) => (
|
||||
<tr key={name}>
|
||||
<td>{name}</td>
|
||||
<td>{value}</td>
|
||||
<td>{_unit}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
} else return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ResponsiveContainer debounce={1}>
|
||||
<AreaChart
|
||||
width={0}
|
||||
height={400}
|
||||
data={data}
|
||||
margin={{
|
||||
top: 10,
|
||||
right: 30,
|
||||
left: 0,
|
||||
bottom: 0
|
||||
}}
|
||||
>
|
||||
<CartesianGrid
|
||||
vertical={false}
|
||||
stroke={theme.palette.text.secondary}
|
||||
strokeOpacity={0.13}
|
||||
/>
|
||||
<XAxis
|
||||
interval={24}
|
||||
dataKey={keyHorizontal}
|
||||
tickFormatter={formatter}
|
||||
unit={unit}
|
||||
tick={{ fill: theme.palette.text.secondary }}
|
||||
stroke={theme.palette.text.secondary}
|
||||
/>
|
||||
<YAxis
|
||||
interval={"preserveStartEnd"}
|
||||
ticks={getLinspaceTicks(data.map(d => d[keyVertical]), 3)}
|
||||
tickFormatter={formatter}
|
||||
unit={unit}
|
||||
tick={{ fill: theme.palette.text.secondary }}
|
||||
domain={[0, f(to)]}
|
||||
stroke={theme.palette.text.secondary}
|
||||
/>
|
||||
<Tooltip content={<CustomTooltip />} />
|
||||
<Area
|
||||
isAnimationActive={isAnimationActive}
|
||||
type="monotone"
|
||||
dataKey={keyVertical}
|
||||
stroke={theme.palette.primary.main}
|
||||
fill={theme.palette.primary.main}
|
||||
fillOpacity={0.3}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
{/* Necessary because ReferenceDot types do not allow "label" k */}
|
||||
<ReferenceLine
|
||||
x={R0_round}
|
||||
y={f(R0_round)}
|
||||
stroke={"transparent"}
|
||||
strokeDasharray="9 0"
|
||||
label={<ReferenceLabel />}
|
||||
/>
|
||||
<ReferenceDot
|
||||
x={R0_round}
|
||||
y={f(R0_round)}
|
||||
r={6}
|
||||
fill={theme.palette.primary.main}
|
||||
stroke={2}
|
||||
/>
|
||||
|
||||
<Legend formatter={renderColorfulLegendText} />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default SupplyVsDemandChart;
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
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",
|
||||
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>{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>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
#root,
|
||||
body {
|
||||
background-color: #152128;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
/*Huge thanks to @tobiasahlin at http://tobiasahlin.com/spinkit/ */
|
||||
.spinner {
|
||||
opacity: 0.5;
|
||||
width: 70px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.spinner > div {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin-right: 4px;
|
||||
background-color: #9aa3ad;
|
||||
border-radius: 100%;
|
||||
display: inline-block;
|
||||
-webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
||||
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
||||
}
|
||||
|
||||
.spinner .bounce1 {
|
||||
-webkit-animation-delay: -0.32s;
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
.spinner .bounce2 {
|
||||
-webkit-animation-delay: -0.16s;
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes sk-bouncedelay {
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
-webkit-transform: scale(0);
|
||||
}
|
||||
40% {
|
||||
-webkit-transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sk-bouncedelay {
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
}
|
||||
40% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||
import { ThemeProvider } from '@material-ui/styles';
|
||||
import App from './App';
|
||||
import theme from './theme';
|
||||
|
||||
ReactDOM.render(
|
||||
<ThemeProvider theme={theme}>
|
||||
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
|
||||
<CssBaseline />
|
||||
<App />
|
||||
</ThemeProvider>,
|
||||
document.querySelector('#root'),
|
||||
);
|
||||
186
src/math.ts
186
src/math.ts
|
|
@ -1,186 +0,0 @@
|
|||
/**
|
||||
* Computes the initial params given the "user friendly" params:
|
||||
* - Initial raise, `d0` (DAI)
|
||||
* - fraction allocated to reserve, `theta`
|
||||
* - Hatch sale price, `p0` (DAI / token)
|
||||
* - Return factor, `returnF`
|
||||
*/
|
||||
export function getInitialParams({
|
||||
d0,
|
||||
theta,
|
||||
p0,
|
||||
p1
|
||||
}: {
|
||||
d0: number;
|
||||
theta: number;
|
||||
p0: number;
|
||||
p1: number;
|
||||
}) {
|
||||
const k = p1 / p0 / (1 - theta); // Invariant power kappa (.)
|
||||
const R0 = (1 - theta) * d0; // Initial reserve (DAI)
|
||||
const S0 = d0 / p0; // initial supply of tokens (token)
|
||||
const V0 = S0 ** k / R0; // invariant coef
|
||||
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`
|
||||
*/
|
||||
export function getPriceR({ R, V0, k }: { R: number; V0: number; k: number }) {
|
||||
return (k * R ** ((k - 1) / k)) / V0 ** (1 / k);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute slippage at a point `R`, given a `deltaR`
|
||||
*/
|
||||
export function getSlippage({
|
||||
R,
|
||||
deltaR,
|
||||
V0,
|
||||
k
|
||||
}: {
|
||||
R: number;
|
||||
deltaR: number;
|
||||
V0: number;
|
||||
k: number;
|
||||
}) {
|
||||
const S = (V0 * R) ** (1 / k);
|
||||
const deltaS = (V0 * (R + deltaR)) ** (1 / k) - S;
|
||||
const realizedPrice = deltaR / deltaS;
|
||||
const spotPrice = getPriceR({ R, V0, k });
|
||||
return Math.abs(realizedPrice - spotPrice) / spotPrice;
|
||||
}
|
||||
|
||||
// Price walk utils
|
||||
|
||||
/**
|
||||
* Get deltaR for a given price growth factor
|
||||
*/
|
||||
export function getDeltaR_priceGrowth({
|
||||
R,
|
||||
k,
|
||||
priceGrowth
|
||||
}: {
|
||||
R: number;
|
||||
k: number;
|
||||
priceGrowth: number;
|
||||
}) {
|
||||
return -R + (priceGrowth * R ** (1 - 1 / k)) ** (k / (-1 + k));
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a tx distribution using a normal distribution,
|
||||
* Given a sum of tx value and a number of transactions
|
||||
*
|
||||
* Demo: https://codepen.io/anon/pen/mNqJjv?editors=0010#0
|
||||
* Very quick: < 10ms for 10000 txs
|
||||
*/
|
||||
export function getTxDistribution({
|
||||
sum,
|
||||
num,
|
||||
spread
|
||||
}: {
|
||||
sum: number;
|
||||
num: number;
|
||||
spread: number;
|
||||
}) {
|
||||
const mean = sum / num;
|
||||
const off = mean * spread;
|
||||
const x: number[] = [];
|
||||
for (let i = 0; i < num; i++) {
|
||||
x.push(randn_bm(mean - off, mean + off));
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
export function rv_U(min: number, max: number) {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard Normal variate using Box-Muller transform.
|
||||
* by https://stackoverflow.com/questions/25582882/javascript-math-random-normal-distribution-gaussian-bell-curve/36481059#36481059
|
||||
*/
|
||||
function randn_bm(min: number, max: number) {
|
||||
var u = 0,
|
||||
v = 0;
|
||||
while (u === 0) u = Math.random(); //Converting [0,1) to (0,1)
|
||||
while (v === 0) v = Math.random();
|
||||
let num = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
|
||||
|
||||
num = num / 10.0 + 0.5; // Translate to 0 -> 1
|
||||
if (num > 1 || num < 0) num = randn_bm(min, max); // resample between 0 and 1 if out of range
|
||||
num *= max - min; // Stretch to fill range
|
||||
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);
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
export interface DescriptionObject {
|
||||
[key: string]: { name: string; text: string };
|
||||
}
|
||||
|
||||
export const parameterDescriptions: DescriptionObject = {
|
||||
theta: {
|
||||
name: "Hatch Tribute",
|
||||
text:
|
||||
"The percentage of the funds raised during the Hatch that goes directly to Funding Pool to be used to support the Commons' mission, the rest goes to the collateral pool"
|
||||
},
|
||||
p0: {
|
||||
name: "Hatch price",
|
||||
text: "The price paid per token by when hatching the Commons"
|
||||
},
|
||||
p1: {
|
||||
name: "Post-Hatch price",
|
||||
text:
|
||||
"The price per token at the launch of the Commons when the curve is set and anyone can join with the Commons"
|
||||
},
|
||||
wFee: {
|
||||
name: "Exit Tribute",
|
||||
text:
|
||||
"The percentage that goes to the Funding Pool when token holders 'sell' by burning their token at the price determined by the ABC. If the Exit Tribute is 10% and the price is 1 DAI/token then, for every token burned, the exiting token holder would get 0.9 Dai and the Funding Pool would get 0.1 Dai"
|
||||
},
|
||||
vHalflife: {
|
||||
name: "Vesting Curve",
|
||||
text:
|
||||
"Tokens that are purchased during the Hatch are locked initially and then released slowly such that 50% of the tokens will be able to be sold after the locking period + this many weeks and 87.5% of the tokens after 3x this many weeks + the locking period. In this demo the locking period is 7 weeks"
|
||||
},
|
||||
d0: {
|
||||
name: "Hatch Raise",
|
||||
text: "Amount of funds initially contributed before the launch of the Commons, this variable helps shape the curve"
|
||||
}
|
||||
};
|
||||
|
||||
export const supplyVsDemandChartDescription =
|
||||
"Visualization of the bonding curve up to 4x the initial size of the Collateral Pool Post-Hatch. This result is deterministic given the parameters set. It will not change regardless of the campaign's performance, it simply shows how the price will react to changes in the Collateral Pool.";
|
||||
|
||||
export const simulationChartDescription =
|
||||
"This chart shows a 52 week simulation of discrete transactions interacting with the Augmented Bonding Curve. Each transaction adds to or subtracts collateral from 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 is a NOT a cadCAD simulation, but it showcases the intention behind cadCAD.";
|
||||
|
||||
export const simulationParameterDescriptions: DescriptionObject = {
|
||||
price: {
|
||||
name: "Price",
|
||||
text: "Price of the token over time."
|
||||
},
|
||||
floorPrice: {
|
||||
name: "Vesting Curve",
|
||||
text:
|
||||
"Lower bound of the price guaranteed by the Vesting Curve. It decreases over time as the tokens received by Hatchers are unlocked"
|
||||
},
|
||||
totalRaised: {
|
||||
name: "Total funds raised",
|
||||
text: "Cumulative sum of the funds sent to the Funding Pool"
|
||||
}
|
||||
};
|
||||
|
||||
export const resultParameterDescriptions: DescriptionObject = {
|
||||
totalReserve: {
|
||||
name: "Collateral Pool balance",
|
||||
text: "Total Dai in the collateral pool at the end of the simulated period"
|
||||
},
|
||||
slippage: {
|
||||
name: "Median slippage",
|
||||
text:
|
||||
"Median of change in price a user experiences from the current price of one token to the price actually received when minting or burning many tokens"
|
||||
},
|
||||
initialHatchFunds: {
|
||||
name: "Funds generated from Hatch Tribute",
|
||||
text: "Funds raised during the Hatch that go directly to the Funding Pool"
|
||||
},
|
||||
exitTributes: {
|
||||
name: "Funds generated from Exit Tributes",
|
||||
text:
|
||||
"Cumulative sum of Exit Tributes collected from tokens that are burned"
|
||||
},
|
||||
totalRaised: {
|
||||
name: "Total funds raised for your community",
|
||||
text: "Sum of funds from the Hatch Tribute + funds from Exit Tributes"
|
||||
}
|
||||
};
|
||||
|
|
@ -1 +0,0 @@
|
|||
/// <reference types="react-scripts" />
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import red from "@material-ui/core/colors/red";
|
||||
import { createMuiTheme } from "@material-ui/core/styles";
|
||||
|
||||
// A custom theme for this app
|
||||
const theme = createMuiTheme({
|
||||
palette: {
|
||||
type: "dark",
|
||||
primary: {
|
||||
main: "#2ecd79"
|
||||
},
|
||||
secondary: {
|
||||
main: "#116be0",
|
||||
light: "#0f8bff",
|
||||
dark: "#116be0"
|
||||
},
|
||||
error: {
|
||||
main: red.A400
|
||||
},
|
||||
background: {
|
||||
default: "#fff",
|
||||
paper: "#293640"
|
||||
},
|
||||
text: {
|
||||
primary: "#fff",
|
||||
secondary: "#9aa3ad"
|
||||
}
|
||||
},
|
||||
typography: {
|
||||
h6: {
|
||||
fontWeight: 400
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log(theme);
|
||||
|
||||
export default theme;
|
||||
24
src/types.ts
24
src/types.ts
|
|
@ -1,24 +0,0 @@
|
|||
export interface InputFieldInterface {
|
||||
label: string;
|
||||
description: string;
|
||||
value: number;
|
||||
setter(newValue: any): void;
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
unit?: string;
|
||||
prefix?: string;
|
||||
suffix?: string;
|
||||
toText?(value: number): string;
|
||||
toNum?(value: string): number;
|
||||
format(value: number): string;
|
||||
}
|
||||
|
||||
export interface CurveParamsInterface {
|
||||
d0: number;
|
||||
theta: number;
|
||||
p0: number;
|
||||
p1: number;
|
||||
wFee: number;
|
||||
vHalflife: number;
|
||||
}
|
||||
114
src/u_values.tsx
114
src/u_values.tsx
|
|
@ -1,114 +0,0 @@
|
|||
const low_u = 0.97;
|
||||
const high_u = 1.07;
|
||||
const dump = 0.75;
|
||||
|
||||
export const u_min_t = [
|
||||
1,
|
||||
0.8,
|
||||
0.8,
|
||||
0.9,
|
||||
0.9,
|
||||
0.8,
|
||||
0.6,
|
||||
0.5,
|
||||
0.8,
|
||||
0.9,
|
||||
0.9,
|
||||
low_u,
|
||||
low_u,
|
||||
low_u,
|
||||
0.8,
|
||||
low_u,
|
||||
low_u,
|
||||
low_u,
|
||||
dump,
|
||||
low_u,
|
||||
low_u,
|
||||
low_u,
|
||||
dump,
|
||||
low_u,
|
||||
low_u,
|
||||
low_u,
|
||||
dump,
|
||||
low_u,
|
||||
low_u,
|
||||
low_u,
|
||||
dump,
|
||||
low_u,
|
||||
low_u,
|
||||
low_u,
|
||||
dump,
|
||||
low_u,
|
||||
low_u,
|
||||
low_u,
|
||||
dump,
|
||||
low_u,
|
||||
low_u,
|
||||
low_u,
|
||||
low_u,
|
||||
low_u,
|
||||
low_u,
|
||||
dump,
|
||||
low_u,
|
||||
low_u,
|
||||
low_u,
|
||||
low_u,
|
||||
low_u,
|
||||
0.97
|
||||
];
|
||||
|
||||
export const u_max_t = [
|
||||
3,
|
||||
1.5,
|
||||
1.01,
|
||||
1.04,
|
||||
1.1,
|
||||
1.15,
|
||||
1.15,
|
||||
1.15,
|
||||
1.1,
|
||||
1.1,
|
||||
1.2,
|
||||
1.15,
|
||||
high_u,
|
||||
high_u,
|
||||
1.3,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
1.3,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
1.2,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
1.4,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
high_u,
|
||||
1.04
|
||||
];
|
||||
72
src/utils.ts
72
src/utils.ts
|
|
@ -1,72 +0,0 @@
|
|||
/**
|
||||
* Returns an equally spaced array of numbers `from`, `to` with `steps`.
|
||||
*/
|
||||
export function linspace({
|
||||
from = 0,
|
||||
to,
|
||||
steps
|
||||
}: {
|
||||
from?: number;
|
||||
to: number;
|
||||
steps: number;
|
||||
}) {
|
||||
const arr = [];
|
||||
for (let x = from; x <= to; x += (to - from) / steps) arr.push(x);
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a uniform distribution of `num` ticks
|
||||
*/
|
||||
export function getLinspaceTicks(data: number[], num: number) {
|
||||
const desiredPoints = [];
|
||||
const step = (data[data.length - 1] - data[0]) / num;
|
||||
for (let x = data[0]; x < data[data.length - 1]; x += step) {
|
||||
desiredPoints.push(x);
|
||||
}
|
||||
if (desiredPoints.length < num + 1) desiredPoints.push(data[data.length - 1]);
|
||||
|
||||
return desiredPoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last element of an array
|
||||
*/
|
||||
export function getLast(a: number[]) {
|
||||
return a[a.length - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the average of an array
|
||||
*/
|
||||
export function getAvg(a: number[]) {
|
||||
return a.reduce((t, c) => t + Math.abs(c), 0) / a.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits `ms` miliseconds and resolves
|
||||
*/
|
||||
export function pause(ms: number) {
|
||||
return new Promise(r => setTimeout(r, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the units of big numbers
|
||||
*/
|
||||
export function getUnits(
|
||||
biggestNum: number
|
||||
): { scaling: number; unit: string } {
|
||||
const [scaling, unit] =
|
||||
// Billion
|
||||
biggestNum > 0.5e9
|
||||
? [1e9, "B"]
|
||||
: // Million
|
||||
biggestNum > 0.5e6
|
||||
? [1e6, "M"]
|
||||
: // 1 thousand
|
||||
biggestNum > 0.5e3
|
||||
? [1e3, "K"]
|
||||
: // No scale
|
||||
[1, ""];
|
||||
return { scaling, unit };
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
.spinner{opacity:.5;width:70px;text-align:center}.spinner>div{width:10px;height:10px;margin-right:4px;background-color:#9aa3ad;border-radius:100%;display:inline-block;-webkit-animation:sk-bouncedelay 1.4s ease-in-out infinite both;animation:sk-bouncedelay 1.4s ease-in-out infinite both}.spinner .bounce1{-webkit-animation-delay:-.32s;animation-delay:-.32s}.spinner .bounce2{-webkit-animation-delay:-.16s;animation-delay:-.16s}@-webkit-keyframes sk-bouncedelay{0%,80%,to{-webkit-transform:scale(0)}40%{-webkit-transform:scale(1)}}@keyframes sk-bouncedelay{0%,80%,to{-webkit-transform:scale(0);transform:scale(0)}40%{-webkit-transform:scale(1);transform:scale(1)}}#root,body{background-color:#152128;min-height:100vh;margin:0}
|
||||
/*# sourceMappingURL=main.ed579467.chunk.css.map */
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"sources":["dotsLoader.css","app.css"],"names":[],"mappings":"AACA,SACE,UAAY,CACZ,UAAW,CACX,iBACF,CAEA,aACE,UAAW,CACX,WAAY,CACZ,gBAAiB,CACjB,wBAAyB,CACzB,kBAAmB,CACnB,oBAAqB,CACrB,+DAAgE,CAChE,uDACF,CAEA,kBACE,6BAA+B,CAC/B,qBACF,CAEA,kBACE,6BAA+B,CAC/B,qBACF,CAEA,kCACE,UAGE,0BACF,CACA,IACE,0BACF,CACF,CAEA,0BACE,UAGE,0BAA2B,CAC3B,kBACF,CACA,IACE,0BAA2B,CAC3B,kBACF,CACF,CClDA,WAEE,wBAAyB,CACzB,gBAAiB,CACjB,QACF","file":"main.ed579467.chunk.css","sourcesContent":["/*Huge thanks to @tobiasahlin at http://tobiasahlin.com/spinkit/ */\n.spinner {\n opacity: 0.5;\n width: 70px;\n text-align: center;\n}\n\n.spinner > div {\n width: 10px;\n height: 10px;\n margin-right: 4px;\n background-color: #9aa3ad;\n border-radius: 100%;\n display: inline-block;\n -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;\n animation: sk-bouncedelay 1.4s infinite ease-in-out both;\n}\n\n.spinner .bounce1 {\n -webkit-animation-delay: -0.32s;\n animation-delay: -0.32s;\n}\n\n.spinner .bounce2 {\n -webkit-animation-delay: -0.16s;\n animation-delay: -0.16s;\n}\n\n@-webkit-keyframes sk-bouncedelay {\n 0%,\n 80%,\n 100% {\n -webkit-transform: scale(0);\n }\n 40% {\n -webkit-transform: scale(1);\n }\n}\n\n@keyframes sk-bouncedelay {\n 0%,\n 80%,\n 100% {\n -webkit-transform: scale(0);\n transform: scale(0);\n }\n 40% {\n -webkit-transform: scale(1);\n transform: scale(1);\n }\n}\n","#root,\nbody {\n background-color: #152128;\n min-height: 100vh;\n margin: 0;\n}\n"]}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,2 @@
|
|||
!function(e){function t(t){for(var n,i,a=t[0],l=t[1],c=t[2],p=0,d=[];p<a.length;p++)i=a[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&d.push(o[i][0]),o[i]=0;for(n in l)Object.prototype.hasOwnProperty.call(l,n)&&(e[n]=l[n]);for(f&&f(t);d.length;)d.shift()();return u.push.apply(u,c||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,a=1;a<r.length;a++){var l=r[a];0!==o[l]&&(n=!1)}n&&(u.splice(t--,1),e=i(i.s=r[0]))}return e}var n={},o={1:0},u=[];function i(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,i),r.l=!0,r.exports}i.m=e,i.c=n,i.d=function(e,t,r){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},i.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(i.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)i.d(r,n,function(t){return e[t]}.bind(null,n));return r},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="/augmented-tbc-design/";var a=window["webpackJsonpaugmented-tbc-design"]=window["webpackJsonpaugmented-tbc-design"]||[],l=a.push.bind(a);a.push=t,a=a.slice();for(var c=0;c<a.length;c++)t(a[c]);var f=l;r()}([]);
|
||||
//# sourceMappingURL=runtime~main.e0588710.js.map
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Loading…
Reference in New Issue