import React, { useEffect, useMemo } from "react"; import { CurveV0, fromCurve, ICurveConfig } from "@strata-foundation/spl-token-bonding"; import { useQueryString } from "@strata-foundation/react"; import { Box, SimpleGrid, VStack, Input } from "@chakra-ui/react"; import { CartesianGrid, Legend, Line, LineChart, ReferenceLine, ResponsiveContainer, Tooltip, XAxis, YAxis, } from "recharts"; import { useVariables } from "../../theme/Root/variables"; const NUM_DATAPOINTS = 40; export const PriceVsSupplyDisplay = ({ curveConfig, timeOffset, setTimeOffset, reserves, setReserves, startSupply, setStartSupply, endSupply, setEndSupply, setSupply, supply, supplyOffset, }: { curveConfig: CurveV0; timeOffset: string; setTimeOffset(args: string): void; reserves: string; setReserves(args: string): void; supply: string; setSupply(args: string): void; startSupply: string; setStartSupply(args: string): void; endSupply: string; setEndSupply(args: string): void; supplyOffset: number; }) => { const startSupplyNum = Number(startSupply) + supplyOffset; const endSupplyNum = Number(endSupply) + supplyOffset; const supplyNum = Number(supply); const data = useMemo(() => { const beforeCurrPoint: { supply: number; price: number; total: number }[] = []; const step = (endSupplyNum - startSupplyNum) / NUM_DATAPOINTS; if (step <= 0) { return []; } let tempReserves = Number(reserves); for (let i = supplyNum; i > startSupplyNum; i -= step) { const currPricing = fromCurve(curveConfig, tempReserves, i, 0); const decr = currPricing.sellTargetAmount(step, 0, 0, Number(timeOffset)); tempReserves -= decr; const price = fromCurve( curveConfig, tempReserves, i - step, 0 ).buyTargetAmount(1, 0, 0, Number(timeOffset)); if (i - step >= 0 && price > 0) { beforeCurrPoint.push({ supply: i - step, price, total: tempReserves, }); } } const afterCurrPoint: { supply: number; price: number; total: number }[] = []; let tempReserves2 = Number(reserves); if (startSupplyNum > supplyNum) { tempReserves2 = tempReserves2 + fromCurve( curveConfig, tempReserves2, Number(supply), 0 ).buyTargetAmount(startSupplyNum - supplyNum, 0, 0, Number(timeOffset)); } for ( let i = Math.max(supplyNum, startSupplyNum); i <= endSupplyNum; i += step ) { const currPricing = fromCurve(curveConfig, tempReserves2, i, 0); const price = currPricing.buyTargetAmount(1, 0, 0, Number(timeOffset)); const incr = currPricing.buyTargetAmount(step, 0, 0, Number(timeOffset)); afterCurrPoint.push({ supply: i, price, total: tempReserves2, }); tempReserves2 += incr; } return [...beforeCurrPoint.reverse(), ...afterCurrPoint]; }, [startSupply, curveConfig, reserves, supply, timeOffset, endSupply]); return ( {/* @ts-ignore */} {/* @ts-ignore */} Math.floor(amount - supplyOffset).toString() } tickCount={10} type="number" dataKey="supply" label={{ value: "Supply", dy: 10 }} domain={[startSupplyNum, endSupplyNum]} /> {/* @ts-ignore */} {/* @ts-ignore */} {/* @ts-ignore */} { //@ts-ignore setSupply(payload.payload.supply); //@ts-ignore setReserves(payload.payload.total); }, }} type="monotone" dataKey="price" stroke="#8884d8" /> ); }; export const RateVsTimeDisplay = ({ curveConfig, reserves, supply, setTimeOffset, maxTime, timeOffset, setMaxTime, }: { curveConfig: CurveV0; maxTime: string; timeOffset: string; setMaxTime(args: string): void; setTimeOffset(args: string): void; reserves: string; supply: string; }) => { const data = useMemo(() => { const step = Number(maxTime) / NUM_DATAPOINTS; const ret: { timeOffset: number; rate: number }[] = []; for (let i = 0; i < Number(maxTime); i += step) { const currPricing = fromCurve( curveConfig, Number(reserves), Number(supply), 0 ); const incAmt = 1 const plusOnePricing = fromCurve( curveConfig, Number(reserves) + currPricing.buyTargetAmount(incAmt, 0, 0, Number(i)), Number(supply + incAmt), 0 ); const priceIncrease = plusOnePricing.buyTargetAmount(incAmt, 0, 0, Number(i)) - currPricing.buyTargetAmount(incAmt, 0, 0, Number(i)); const priceFall = currPricing.buyTargetAmount(incAmt, 0, 0, Number(i)) - currPricing.buyTargetAmount(incAmt, 0, 0, Number(i + 1)); ret.push({ rate: priceFall / priceIncrease, timeOffset: i, }); } return ret; }, [curveConfig, reserves, supply, maxTime]); return ( {/* @ts-ignore */} {/* @ts-ignore */} Math.floor(formatTime(seconds, Number(maxTime))).toString()} dataKey="timeOffset" label={{ value: `Time (${getUnit(Number(maxTime))})`, dy: 10 }} domain={[0, Number(maxTime)]} /> {/* @ts-ignore */} {/* @ts-ignore */} {/* @ts-ignore */} { //@ts-ignore setTimeOffset(payload.payload.timeOffset); }, }} type="monotone" dataKey="rate" stroke="#8884d8" /> ); }; export const EstimatedSalesVsTime = ({ curveConfig, reserves, supply, setTimeOffset, maxTime, timeOffset, setMaxTime, endSupply, }: { curveConfig: CurveV0; maxTime: string; timeOffset: string; setMaxTime(args: string): void; setTimeOffset(args: string): void; reserves: string; supply: string; endSupply: string; }) => { const data = useMemo(() => { const step = Number(maxTime) / NUM_DATAPOINTS; const ret: { timeOffset: number; total: number }[] = []; for (let i = 0; i < Number(maxTime); i += step) { const currPricing = fromCurve( curveConfig, Number(reserves), Number(supply), 0 ); const incAmt = 1; const plusOnePricing = fromCurve( curveConfig, Number(reserves) + currPricing.buyTargetAmount(incAmt, 0, 0, Number(i)), Number(supply + incAmt), 0 ); const priceIncrease = plusOnePricing.buyTargetAmount(incAmt, 0, 0, Number(i)) - currPricing.buyTargetAmount(incAmt, 0, 0, Number(i)); const priceFall = currPricing.buyTargetAmount(incAmt, 0, 0, Number(i)) - currPricing.buyTargetAmount(incAmt, 0, 0, Number(i + 1)); const prev = ret[ret.length - 1] ? ret[ret.length - 1].total : 0; let total = (priceFall / priceIncrease) * step + prev; ret.push({ total: total > Number(endSupply) ? 0 : total, timeOffset: i, }); } return ret; }, [curveConfig, reserves, supply, maxTime]); return ( {/* @ts-ignore */} {/* @ts-ignore */} Math.floor(formatTime(seconds, Number(maxTime))).toString() } dataKey="timeOffset" label={{ value: `Time (${getUnit(Number(maxTime))})`, dy: 10 }} domain={[0, Number(maxTime)]} /> {/* @ts-ignore */} {/* @ts-ignore */} {/* @ts-ignore */} { //@ts-ignore setTimeOffset(payload.payload.timeOffset); }, }} type="monotone" dataKey="total" stroke="#8884d8" /> ); }; export const PriceVsTimeDisplay = ({ curveConfig, reserves, supply, setTimeOffset, maxTime, timeOffset, setMaxTime, }: { curveConfig: CurveV0; maxTime: string; timeOffset: string; setMaxTime(args: string): void; setTimeOffset(args: string): void; reserves: string; supply: string; }) => { const data = useMemo(() => { const step = Number(maxTime) / NUM_DATAPOINTS; const ret: { timeOffset: number; price: number }[] = []; for (let i = 0; i < Number(maxTime); i += step) { const currPricing = fromCurve( curveConfig, Number(reserves), Number(supply), 0 ); const price = currPricing.buyTargetAmount(1, 0, 0, Number(i)); ret.push({ price, timeOffset: i, }); } return ret; }, [curveConfig, reserves, supply, maxTime]); return ( {/* @ts-ignore */} {/* @ts-ignore */} Math.floor(formatTime(seconds, Number(maxTime))).toString() } dataKey="timeOffset" label={{ value: `Time (${getUnit(Number(maxTime))})`, dy: 10 }} domain={[0, Number(maxTime)]} /> {/* @ts-ignore */} {/* @ts-ignore */} {/* @ts-ignore */} { //@ts-ignore setTimeOffset(payload.payload.timeOffset); }, }} type="monotone" dataKey="price" stroke="#8884d8" /> ); }; function getUnit(maxTime: number): "seconds" | "hours" | "minutes" | "days" { if (maxTime < 60) { return "seconds" } else if (maxTime < 60 * 60) { return "minutes" } else if (maxTime < 60 * 60 * 72) { return "hours" } else { return "days" } } /** * Seconds from 0 to 60 * Minutes from 0 to 60 minutes * Hours from 0 to 72 hours * Days onward */ function formatTime(time: number, maxTime: number): number { const unit = getUnit(maxTime); if (unit === "seconds") { return time } else if (unit === "minutes") { return time / 60 } else if (unit === "hours") { return time / (60 * 60) } else { return time / (60 * 60 * 24) } } export type CurveConfiguratorFromVariablesProps = { priceVsSupply: boolean; priceVsTime: boolean; rateVsTime: boolean; salesVsTime: boolean; }; export const CurveConfiguratorFromVariables = ({ priceVsSupply = true, priceVsTime = true, rateVsTime = false, salesVsTime = false }: CurveConfiguratorFromVariablesProps) => { const { curveConfig, startSupply: passedStartSupply, endSupply: passedEndSupply, reserves: passedReserves, supply: passedSupply, maxTime: passedMaxTime, supplyOffset, } = useVariables(); const [timeOffset, setTimeOffset] = useQueryString("timeOffset", "0"); const [reserves, setReserves] = useQueryString("reserves", "0"); const [supply, setSupply] = useQueryString("supply", "0"); const [startSupply, setStartSupply] = useQueryString("startSupply", passedStartSupply || "0"); const [endSupply, setEndSupply] = useQueryString("endSupply", passedEndSupply || "100"); const [maxTime, setMaxTime] = useQueryString("maxTime", passedMaxTime || "10"); const args = { timeOffset, setTimeOffset, reserves, setReserves, supply, setSupply, startSupply, setStartSupply, endSupply, setEndSupply, maxTime, setMaxTime, } const numCharts = +priceVsSupply + +priceVsTime + +rateVsTime; useEffect(() => { passedReserves && setReserves(passedReserves); }, [passedReserves]); useEffect(() => { passedSupply && setSupply(passedSupply); }, [passedSupply]); useEffect(() => { passedEndSupply && setEndSupply(passedEndSupply); }, [passedEndSupply]); useEffect(() => { passedStartSupply && setStartSupply(passedStartSupply); }, [passedStartSupply]); useEffect(() => { passedMaxTime && setMaxTime(passedMaxTime); }, [passedMaxTime]); const rawCurve = curveConfig?.toRawConfig(); if (!curveConfig) { return Run the above code block to show curve } return ( setReserves(e.target.value)} value={reserves} /> setSupply(e.target.value)} value={supply} /> setTimeOffset(e.target.value)} value={timeOffset} /> setStartSupply(e.target.value)} value={startSupply} /> setEndSupply(e.target.value)} value={endSupply} /> setMaxTime(e.target.value)} value={maxTime} /> {priceVsSupply && ( )} {priceVsTime && } {rateVsTime && } {salesVsTime && ( )} ); }