Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2cf672fac1 | ||
|
989fa1306b | ||
|
6ac29106f2 | ||
|
3898ec95ed | ||
|
4cd35c8742 | ||
|
7acd3aed93 | ||
|
06c49be05b | ||
|
17ae9d953d | ||
|
853c45d564 | ||
|
2fdb962a14 | ||
|
81064e67cf | ||
|
8734a14e6b | ||
|
ab3b88112f | ||
|
538ae323d7 | ||
|
4b09640e60 |
260
common/calculate-swap3.ts
Normal file
260
common/calculate-swap3.ts
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
type Swap3LiquidityPosition = {
|
||||||
|
// TODO: Record who added this stuff?
|
||||||
|
|
||||||
|
// Not sure if this is needed; maybe YES and NO left
|
||||||
|
// amount: number // M$ quantity
|
||||||
|
|
||||||
|
// For now, only support YES and NO outcome tokens
|
||||||
|
// TODO: replace with Outcome
|
||||||
|
// Hm, is this...
|
||||||
|
// 1. Number of shares left in this particular pool?
|
||||||
|
// 2. Fixed at injection time?
|
||||||
|
pool: { YES: number; NO: number }
|
||||||
|
|
||||||
|
// Uniswap uses 0.01, 0.003, 0.0005. Let's stick with 0.003 for now.
|
||||||
|
// fee: number
|
||||||
|
|
||||||
|
// Min/max is expressed as a odds ratio of cost of YES to cost of NO
|
||||||
|
// E.g. ratio of 1 = 1:1 = 50%; ratio of 3 = 3:1 = 75%
|
||||||
|
// minRatio: number
|
||||||
|
// maxRatio: number
|
||||||
|
minTick: number
|
||||||
|
// minTick = loq_sqrt_1.0001(sqrtRatio)
|
||||||
|
// sqrt(1.0001)^(minTick) = sqrtRatio
|
||||||
|
// minRatio = 1.0001^minTick
|
||||||
|
// e.g. minTick = 20k => 7.3883
|
||||||
|
maxTick: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type TickState = {
|
||||||
|
tick: number
|
||||||
|
|
||||||
|
// Amount of liquidity added when crossing this tick from left to right
|
||||||
|
// Negative if we should remove liquidity
|
||||||
|
liquidityNet: number
|
||||||
|
|
||||||
|
// Total liquidity referencing this pool
|
||||||
|
liquidityGross: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// From https://uniswap.org/whitepaper-v3.pdf
|
||||||
|
export type Swap3Pool = {
|
||||||
|
// id: string
|
||||||
|
// userId: string
|
||||||
|
// contractId: string
|
||||||
|
// createdTime: number
|
||||||
|
|
||||||
|
// 6.2 Global State
|
||||||
|
liquidity: number // = sqrt(NY)
|
||||||
|
// sqrtRatio: number // = sqrt(N / Y); N = # NO shares in pool
|
||||||
|
// So N = liquidity * sqrtRatio; Y = liquidity / sqrtRatio
|
||||||
|
|
||||||
|
// Current tick number.
|
||||||
|
// Stored as optimization. equal to floor(log_sqrt_1.0001(sqrtRatio))
|
||||||
|
tick: number
|
||||||
|
// TODO add fees?
|
||||||
|
|
||||||
|
// Mapping of tick indices to tick values.
|
||||||
|
tickStates: TickState[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function noShares(pool: Swap3Pool) {
|
||||||
|
return pool.liquidity * toRatio(pool.tick) ** 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
export function yesShares(pool: Swap3Pool) {
|
||||||
|
return pool.liquidity / toRatio(pool.tick) ** 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSwap3Probability(pool: Swap3Pool) {
|
||||||
|
// Probability is given by N / (N + Y)
|
||||||
|
// const N = pool.liquidity * pool.sqrtRatio
|
||||||
|
// const Y = pool.liquidity / pool.sqrtRatio
|
||||||
|
// return N / (N + Y)
|
||||||
|
|
||||||
|
// To check: this should be equal to toProb(pool.tick)?
|
||||||
|
return toProb(pool.tick)
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculatePurchase(
|
||||||
|
pool: Swap3Pool,
|
||||||
|
amount: number, // In M$
|
||||||
|
outcome: 'YES' | 'NO'
|
||||||
|
) {}
|
||||||
|
|
||||||
|
export function calculateLPCost(
|
||||||
|
curTick: number,
|
||||||
|
minTick: number,
|
||||||
|
maxTick: number,
|
||||||
|
deltaL: number
|
||||||
|
) {
|
||||||
|
// TODO: this is subtly wrong, because of rounding between curTick and sqrtPrice
|
||||||
|
// Also below in buyYES
|
||||||
|
const upperTick = Math.min(maxTick, Math.max(minTick, curTick))
|
||||||
|
const costN = toRatio(upperTick) ** 0.5 - toRatio(minTick) ** 0.5
|
||||||
|
|
||||||
|
const lowerTick = Math.max(minTick, Math.min(maxTick, curTick))
|
||||||
|
const costY = 1 / toRatio(lowerTick) ** 0.5 - 1 / toRatio(maxTick) ** 0.5
|
||||||
|
|
||||||
|
return {
|
||||||
|
requiredN: deltaL * costN,
|
||||||
|
requiredY: deltaL * costY,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a preview of the new pool + number of YES shares purchased.
|
||||||
|
// Does NOT modify the pool
|
||||||
|
// Hm, logic is pretty complicated. Let's see if we can simplify this.
|
||||||
|
export function buyYes(
|
||||||
|
pool: Swap3Pool,
|
||||||
|
amount: number // In M$
|
||||||
|
) {
|
||||||
|
const tickStates = sortedTickStates(pool)
|
||||||
|
let tick = pool.tick
|
||||||
|
let stateIndex = 0
|
||||||
|
let amountLeft = amount
|
||||||
|
let yesPurchased = 0
|
||||||
|
// Stop if there's epsilon M$ left, due to rounding issues
|
||||||
|
while (amountLeft > 1e-6) {
|
||||||
|
// Find the current & next states for this tick
|
||||||
|
while (tick >= tickStates[stateIndex + 1].tick) {
|
||||||
|
stateIndex++
|
||||||
|
if (stateIndex > tickStates.length - 2) {
|
||||||
|
// We've reached the end of the tick states...
|
||||||
|
throw new Error('Ran out of tick states')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const state = tickStates[stateIndex]
|
||||||
|
const nextState = tickStates[stateIndex + 1]
|
||||||
|
|
||||||
|
// nextState.tick purchases through the bucket; fullTick uses the remaining amountLeft
|
||||||
|
const fullCostN = amountLeft / state.liquidityGross
|
||||||
|
// Note: fullTick is NOT floored here; it's for the sqrtPrice to buy up to
|
||||||
|
const fullTick = fromRatioUnfloored((fullCostN + toRatio(tick) ** 0.5) ** 2)
|
||||||
|
const nextTick = Math.min(nextState.tick, fullTick)
|
||||||
|
|
||||||
|
// Copied from above; TODO extract to common function?
|
||||||
|
const noCost = toRatio(nextTick) ** 0.5 - toRatio(tick) ** 0.5
|
||||||
|
const yesCost = 1 / toRatio(tick) ** 0.5 - 1 / toRatio(nextTick) ** 0.5
|
||||||
|
|
||||||
|
amountLeft -= noCost * state.liquidityGross
|
||||||
|
yesPurchased += yesCost * state.liquidityGross
|
||||||
|
tick = Math.floor(nextTick)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Right now we eat the epsilon amounntLeft as a fee. Could return it, shrug.
|
||||||
|
return {
|
||||||
|
newPoolTick: tick,
|
||||||
|
yesPurchased,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Currently, this mutates the pool. Should it return a new object instead?
|
||||||
|
export function addPosition(
|
||||||
|
pool: Swap3Pool,
|
||||||
|
minTick: number,
|
||||||
|
maxTick: number,
|
||||||
|
deltaL: number
|
||||||
|
) {
|
||||||
|
const { requiredN, requiredY } = calculateLPCost(
|
||||||
|
pool.tick,
|
||||||
|
minTick,
|
||||||
|
maxTick,
|
||||||
|
deltaL
|
||||||
|
)
|
||||||
|
// console.log(`Deducting required N: ${requiredN} and required Y: ${requiredY}`)
|
||||||
|
|
||||||
|
// Add liquidity as we pass through the smaller tick
|
||||||
|
const minTickState = pool.tickStates[minTick] || {
|
||||||
|
tick: minTick,
|
||||||
|
liquidityNet: 0,
|
||||||
|
liquidityGross: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
minTickState.liquidityNet += deltaL
|
||||||
|
pool.tickStates[minTick] = minTickState
|
||||||
|
|
||||||
|
// And remove it as we pass through the larger one
|
||||||
|
const maxTickState = pool.tickStates[maxTick] || {
|
||||||
|
tick: maxTick,
|
||||||
|
liquidityNet: 0,
|
||||||
|
liquidityGross: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
maxTickState.liquidityNet -= deltaL
|
||||||
|
pool.tickStates[maxTick] = maxTickState
|
||||||
|
|
||||||
|
return pool
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addBalancer(pool: Swap3Pool, p: number, deltaL: number) {
|
||||||
|
// TODO: math is borked, shouldn't be returning infinity
|
||||||
|
function tickL(tick: number) {
|
||||||
|
const q = 1 - p
|
||||||
|
return deltaL * 2 * p ** q * q ** p * 1.0001 ** ((p - 0.5) * tick)
|
||||||
|
}
|
||||||
|
|
||||||
|
// See how much liquidity is provided at +/- 5pp around p
|
||||||
|
// const minTick = fromProb(p - 0.1)
|
||||||
|
// const maxTick = fromProb(p + 0.05)
|
||||||
|
const minTick = fromProb(0.000001)
|
||||||
|
const maxTick = fromProb(0.999999)
|
||||||
|
let totalN = 0
|
||||||
|
let totalY = 0
|
||||||
|
const stride = 300
|
||||||
|
for (let t = minTick; t <= maxTick; t += stride) {
|
||||||
|
// console.log('liquidity at tick ', t, toProb(t), tickL(t))
|
||||||
|
const { requiredN, requiredY } = calculateLPCost(
|
||||||
|
fromProb(p),
|
||||||
|
t,
|
||||||
|
t + stride,
|
||||||
|
tickL(t)
|
||||||
|
)
|
||||||
|
totalN += requiredN
|
||||||
|
totalY += requiredY
|
||||||
|
// Add liquidity
|
||||||
|
addPosition(pool, t, t + stride, tickL(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('rough number of ticks', (maxTick - minTick) / stride)
|
||||||
|
console.log(`Total N: ${totalN} and total Y: ${totalY}`)
|
||||||
|
grossLiquidity(pool)
|
||||||
|
return pool
|
||||||
|
}
|
||||||
|
|
||||||
|
// This also mutates the pool directly
|
||||||
|
export function grossLiquidity(pool: Swap3Pool) {
|
||||||
|
let liquidityGross = 0
|
||||||
|
for (const tickState of sortedTickStates(pool)) {
|
||||||
|
liquidityGross += tickState.liquidityNet
|
||||||
|
tickState.liquidityGross = liquidityGross
|
||||||
|
}
|
||||||
|
return pool
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sortedTickStates(pool: Swap3Pool) {
|
||||||
|
return Object.values(pool.tickStates).sort((a, b) => a.tick - b.tick)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toRatio(tick: number) {
|
||||||
|
return 1.0001 ** tick
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toProb(tick: number) {
|
||||||
|
const ratio = toRatio(tick)
|
||||||
|
return ratio / (ratio + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the tick for a given probability from 0 to 1
|
||||||
|
export function fromProb(prob: number) {
|
||||||
|
const ratio = prob / (1 - prob)
|
||||||
|
return fromRatio(ratio)
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromRatio(ratio: number) {
|
||||||
|
return Math.floor(Math.log(ratio) / Math.log(1.0001))
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromRatioUnfloored(ratio: number) {
|
||||||
|
return Math.log(ratio) / Math.log(1.0001)
|
||||||
|
}
|
121
web/components/contract/liquidity-graph.tsx
Normal file
121
web/components/contract/liquidity-graph.tsx
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import { CartesianMarkerProps, DatumValue } from '@nivo/core'
|
||||||
|
import { Point, ResponsiveLine } from '@nivo/line'
|
||||||
|
import { NUMERIC_GRAPH_COLOR } from 'common/numeric-constants'
|
||||||
|
import { memo } from 'react'
|
||||||
|
import { range } from 'lodash'
|
||||||
|
import { getDpmOutcomeProbabilities } from '../../../common/calculate-dpm'
|
||||||
|
import { NumericContract } from '../../../common/contract'
|
||||||
|
import { useWindowSize } from '../../hooks/use-window-size'
|
||||||
|
import { Col } from '../layout/col'
|
||||||
|
import { formatLargeNumber, formatPercent } from 'common/util/format'
|
||||||
|
|
||||||
|
export type GraphPoint = {
|
||||||
|
// A probability between 0 and 1
|
||||||
|
x: number
|
||||||
|
// Amount of liquidity
|
||||||
|
y: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LiquidityGraph = memo(function NumericGraph(props: {
|
||||||
|
min?: 0
|
||||||
|
max?: 1
|
||||||
|
points: GraphPoint[]
|
||||||
|
height?: number
|
||||||
|
marker?: number // Value between min and max to highlight on x-axis
|
||||||
|
previewMarker?: number
|
||||||
|
}) {
|
||||||
|
const { height, min, max, points, marker, previewMarker } = props
|
||||||
|
|
||||||
|
// Really maxLiquidity
|
||||||
|
const maxLiquidity = 500
|
||||||
|
const data = [{ id: 'Probability', data: points, color: NUMERIC_GRAPH_COLOR }]
|
||||||
|
|
||||||
|
const yTickValues = [
|
||||||
|
0,
|
||||||
|
0.25 * maxLiquidity,
|
||||||
|
0.5 * maxLiquidity,
|
||||||
|
0.75 * maxLiquidity,
|
||||||
|
maxLiquidity,
|
||||||
|
]
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
|
||||||
|
const numXTickValues = !width || width < 800 ? 2 : 5
|
||||||
|
|
||||||
|
const markers: CartesianMarkerProps<DatumValue>[] = []
|
||||||
|
if (marker) {
|
||||||
|
markers.push({
|
||||||
|
axis: 'x',
|
||||||
|
value: marker,
|
||||||
|
lineStyle: { stroke: '#000', strokeWidth: 2 },
|
||||||
|
legend: `Implied: ${formatPercent(marker)}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (previewMarker) {
|
||||||
|
markers.push({
|
||||||
|
axis: 'x',
|
||||||
|
value: previewMarker,
|
||||||
|
lineStyle: { stroke: '#8888', strokeWidth: 2 },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="w-full overflow-hidden"
|
||||||
|
style={{ height: height ?? (!width || width >= 800 ? 350 : 250) }}
|
||||||
|
>
|
||||||
|
<ResponsiveLine
|
||||||
|
data={data}
|
||||||
|
yScale={{ min: 0, max: maxLiquidity, type: 'linear' }}
|
||||||
|
yFormat={formatLiquidity}
|
||||||
|
axisLeft={{
|
||||||
|
tickValues: yTickValues,
|
||||||
|
format: formatLiquidity,
|
||||||
|
}}
|
||||||
|
xScale={{
|
||||||
|
type: 'linear',
|
||||||
|
min: min,
|
||||||
|
max: max,
|
||||||
|
}}
|
||||||
|
xFormat={(d) => `${formatLargeNumber(+d, 3)}`}
|
||||||
|
axisBottom={{
|
||||||
|
tickValues: numXTickValues,
|
||||||
|
format: (d) => `${formatLargeNumber(+d, 3)}`,
|
||||||
|
}}
|
||||||
|
colors={{ datum: 'color' }}
|
||||||
|
pointSize={0}
|
||||||
|
enableSlices="x"
|
||||||
|
sliceTooltip={({ slice }) => {
|
||||||
|
const point = slice.points[0]
|
||||||
|
return <Tooltip point={point} />
|
||||||
|
}}
|
||||||
|
enableGridX={!!width && width >= 800}
|
||||||
|
enableArea
|
||||||
|
margin={{ top: 20, right: 28, bottom: 22, left: 50 }}
|
||||||
|
markers={markers}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
function formatLiquidity(y: DatumValue) {
|
||||||
|
const p = Math.round(+y * 100) / 100
|
||||||
|
return `${p}L`
|
||||||
|
}
|
||||||
|
|
||||||
|
function Tooltip(props: { point: Point }) {
|
||||||
|
const { point } = props
|
||||||
|
return (
|
||||||
|
<Col className="border border-gray-300 bg-white py-2 px-3">
|
||||||
|
<div
|
||||||
|
className="pb-1"
|
||||||
|
style={{
|
||||||
|
color: point.serieColor,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<strong>{point.serieId}</strong> {point.data.yFormatted}
|
||||||
|
</div>
|
||||||
|
<div>{formatLargeNumber(+point.data.x)}</div>
|
||||||
|
</Col>
|
||||||
|
)
|
||||||
|
}
|
269
web/pages/swap.tsx
Normal file
269
web/pages/swap.tsx
Normal file
|
@ -0,0 +1,269 @@
|
||||||
|
import {
|
||||||
|
addBalancer,
|
||||||
|
addPosition,
|
||||||
|
buyYes,
|
||||||
|
calculateLPCost,
|
||||||
|
fromProb,
|
||||||
|
getSwap3Probability,
|
||||||
|
grossLiquidity,
|
||||||
|
noShares,
|
||||||
|
sortedTickStates,
|
||||||
|
Swap3Pool,
|
||||||
|
toProb,
|
||||||
|
yesShares,
|
||||||
|
} from 'common/calculate-swap3'
|
||||||
|
import { formatPercent } from 'common/util/format'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { LiquidityGraph } from 'web/components/contract/liquidity-graph'
|
||||||
|
import { Col } from 'web/components/layout/col'
|
||||||
|
import { Row } from 'web/components/layout/row'
|
||||||
|
import { addLiquidity } from 'web/lib/firebase/fn-call'
|
||||||
|
|
||||||
|
const users: Record<string, any> = {
|
||||||
|
alice: {
|
||||||
|
M: 100,
|
||||||
|
YES: 0,
|
||||||
|
NO: 0,
|
||||||
|
},
|
||||||
|
bob: {
|
||||||
|
M: 200,
|
||||||
|
YES: 0,
|
||||||
|
NO: 0,
|
||||||
|
},
|
||||||
|
kipply: {
|
||||||
|
M: 300,
|
||||||
|
YES: 0,
|
||||||
|
NO: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function BalanceTable() {
|
||||||
|
/* Display all users current M, YES, and NO in a table */
|
||||||
|
return (
|
||||||
|
<table className="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-2">User</th>
|
||||||
|
<th className="px-4 py-2">M</th>
|
||||||
|
<th className="px-4 py-2">YES</th>
|
||||||
|
<th className="px-4 py-2">NO</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{Object.keys(users).map((user) => (
|
||||||
|
<tr key={user}>
|
||||||
|
<td className="px-4 py-2">{user}</td>
|
||||||
|
<td className="px-4 py-2">{users[user].M}</td>
|
||||||
|
<td className="px-4 py-2">{users[user].YES}</td>
|
||||||
|
<td className="px-4 py-2">{users[user].NO}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show the values in pool */
|
||||||
|
function PoolTable(props: { pool: Swap3Pool }) {
|
||||||
|
const { pool } = props
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Row className="gap-4">
|
||||||
|
<div>
|
||||||
|
<label>Implied: </label>
|
||||||
|
{formatPercent(getSwap3Probability(pool))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label>Liquidity: </label>
|
||||||
|
{pool.liquidity}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Tick: </label>
|
||||||
|
{pool.tick}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Pool YES: </label>
|
||||||
|
{yesShares(pool).toFixed(2)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Pool NO: </label>
|
||||||
|
{noShares(pool).toFixed(2)}
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
{/* Render each tickState as another row in a table */}
|
||||||
|
<table className="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-2">Tick</th>
|
||||||
|
<th className="px-4 py-2">Prob</th>
|
||||||
|
<th className="px-4 py-2">Net Liquidity</th>
|
||||||
|
<th className="px-4 py-2">Gross Liquidity</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{sortedTickStates(pool).map((tickState, i) => (
|
||||||
|
<tr key={i}>
|
||||||
|
<td className="px-4 py-2">{tickState.tick}</td>
|
||||||
|
<td className="px-4 py-2">
|
||||||
|
{formatPercent(toProb(tickState.tick))}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2">{tickState.liquidityNet}</td>
|
||||||
|
<td className="px-4 py-2">{tickState.liquidityGross}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</table>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Graph(props: { pool: Swap3Pool; previewMarker?: number }) {
|
||||||
|
const { pool, previewMarker } = props
|
||||||
|
const points = []
|
||||||
|
let lastGross = 0
|
||||||
|
for (const tickState of sortedTickStates(pool)) {
|
||||||
|
const { tick, liquidityGross } = tickState
|
||||||
|
points.push({ x: toProb(tick), y: lastGross })
|
||||||
|
points.push({ x: toProb(tick), y: liquidityGross })
|
||||||
|
lastGross = liquidityGross
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<LiquidityGraph
|
||||||
|
points={points}
|
||||||
|
marker={toProb(pool.tick)}
|
||||||
|
previewMarker={previewMarker}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function LiquidityPanel(props: {
|
||||||
|
pool: Swap3Pool
|
||||||
|
setPool: (pool: Swap3Pool) => void
|
||||||
|
}) {
|
||||||
|
const { pool, setPool } = props
|
||||||
|
const [minTick, setMinTick] = useState(0)
|
||||||
|
const [maxTick, setMaxTick] = useState(0)
|
||||||
|
const [deltaL, setDeltaL] = useState(100)
|
||||||
|
|
||||||
|
const { requiredN, requiredY } = calculateLPCost(
|
||||||
|
pool.tick,
|
||||||
|
minTick,
|
||||||
|
maxTick,
|
||||||
|
deltaL
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col>
|
||||||
|
<h2 className="my-2 text-xl">Add liquidity</h2>
|
||||||
|
{/* <input className="input" placeholder="Amount" type="number" /> */}
|
||||||
|
<Row className="gap-2">
|
||||||
|
<input
|
||||||
|
className="input"
|
||||||
|
placeholder="Min%"
|
||||||
|
type="number"
|
||||||
|
onChange={(e) => setMinTick(inputPercentToTick(e))}
|
||||||
|
/>
|
||||||
|
{/* Min Tick: {minTick} */}
|
||||||
|
<input
|
||||||
|
className="input"
|
||||||
|
placeholder="Max%"
|
||||||
|
type="number"
|
||||||
|
onChange={(e) => setMaxTick(inputPercentToTick(e))}
|
||||||
|
/>
|
||||||
|
{/* Max Tick: {maxTick} */}
|
||||||
|
<input
|
||||||
|
className="input"
|
||||||
|
placeholder="delta Liquidity"
|
||||||
|
type="number"
|
||||||
|
value={deltaL}
|
||||||
|
onChange={(e) => setDeltaL(parseFloat(e.target.value))}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
<Row className="gap-2 py-2">
|
||||||
|
<div>Y required: {requiredY.toFixed(2)}</div>
|
||||||
|
<div>N required: {requiredN.toFixed(2)}</div>{' '}
|
||||||
|
</Row>
|
||||||
|
<button
|
||||||
|
className="btn"
|
||||||
|
onClick={() => {
|
||||||
|
addPosition(pool, minTick, maxTick, deltaL)
|
||||||
|
grossLiquidity(pool)
|
||||||
|
setPool({ ...pool })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create pool
|
||||||
|
</button>
|
||||||
|
</Col>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Swap() {
|
||||||
|
// Set up an initial pool with 100 liquidity from 0% to 100%
|
||||||
|
// TODO: Not sure why maxTick of 2**23 breaks it, but 2**20 is okay...
|
||||||
|
let INIT_POOL: Swap3Pool = {
|
||||||
|
liquidity: 0,
|
||||||
|
tick: fromProb(0.3),
|
||||||
|
tickStates: [],
|
||||||
|
}
|
||||||
|
INIT_POOL = addPosition(INIT_POOL, -(2 ** 23), 2 ** 20, 1)
|
||||||
|
// INIT_POOL = addPosition(INIT_POOL, fromProb(0.32), fromProb(0.35), 100)
|
||||||
|
INIT_POOL = addBalancer(INIT_POOL, 0.3, 100)
|
||||||
|
INIT_POOL = grossLiquidity(INIT_POOL)
|
||||||
|
|
||||||
|
const [pool, setPool] = useState(INIT_POOL)
|
||||||
|
const [buyAmount, setBuyAmount] = useState(0)
|
||||||
|
|
||||||
|
const { newPoolTick, yesPurchased } = buyYes(pool, buyAmount)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col className="mx-auto max-w-2xl gap-10 p-4">
|
||||||
|
{/* <BalanceTable /> */}
|
||||||
|
{/* <PoolTable pool={pool} /> */}
|
||||||
|
<Graph
|
||||||
|
pool={pool}
|
||||||
|
previewMarker={
|
||||||
|
newPoolTick === pool.tick ? undefined : toProb(newPoolTick)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
className="input"
|
||||||
|
placeholder="Current%"
|
||||||
|
type="number"
|
||||||
|
onChange={(e) => {
|
||||||
|
pool.tick = inputPercentToTick(e)
|
||||||
|
setPool({ ...pool })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<LiquidityPanel pool={pool} setPool={setPool} />
|
||||||
|
|
||||||
|
<Col>
|
||||||
|
<h2 className="my-2 text-xl">Limit Order</h2>
|
||||||
|
TODO
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col>
|
||||||
|
<h2 className="my-2 text-xl">Buy Shares</h2>
|
||||||
|
{/* <input className="input" placeholder="User" type="text" /> */}
|
||||||
|
<input
|
||||||
|
className="input"
|
||||||
|
placeholder="Amount"
|
||||||
|
type="number"
|
||||||
|
onChange={(e) => setBuyAmount(parseFloat(e.target.value))}
|
||||||
|
/>
|
||||||
|
<Row className="gap-2 py-2">
|
||||||
|
<div>Y shares purchaseable: {yesPurchased.toFixed(2)}</div>
|
||||||
|
<div>New Tick: {newPoolTick}</div>
|
||||||
|
<div>New prob: {formatPercent(toProb(newPoolTick))}</div>
|
||||||
|
</Row>
|
||||||
|
<Row className="gap-2">
|
||||||
|
<button className="btn">Buy YES</button>
|
||||||
|
{/* <button className="btn">Buy NO</button> */}
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
</Col>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function inputPercentToTick(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
return fromProb(parseFloat(event.target.value) / 100)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user