Liquidity withdrawal (#457)
* withdrawLiquidity cloud function * update rules * exclude antes from getCpmmLiquidityPoolWeights * update correct lp shares * liquidity panel * don't create bet if less than 1 surplus share * withdrawLiquidity return type * static analysis fix * hook dependency * prettier * renaming * typo * getCpmmLiquidityPoolWeights: always exclude antes * delete unused function * casting
This commit is contained in:
parent
45eb5a3e63
commit
0cd9943e0d
|
@ -1,4 +1,4 @@
|
||||||
import { sum, groupBy, mapValues, sumBy } from 'lodash'
|
import { sum, groupBy, mapValues, sumBy, partition } from 'lodash'
|
||||||
|
|
||||||
import { CPMMContract } from './contract'
|
import { CPMMContract } from './contract'
|
||||||
import { CREATOR_FEE, Fees, LIQUIDITY_FEE, noFees, PLATFORM_FEE } from './fees'
|
import { CREATOR_FEE, Fees, LIQUIDITY_FEE, noFees, PLATFORM_FEE } from './fees'
|
||||||
|
@ -260,13 +260,7 @@ export function addCpmmLiquidity(
|
||||||
return { newPool, liquidity, newP }
|
return { newPool, liquidity, newP }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCpmmLiquidityPoolWeights(
|
const calculateLiquidityDelta = (p: number) => (l: LiquidityProvision) => {
|
||||||
contract: CPMMContract,
|
|
||||||
liquidities: LiquidityProvision[]
|
|
||||||
) {
|
|
||||||
const { p } = contract
|
|
||||||
|
|
||||||
const liquidityShares = liquidities.map((l) => {
|
|
||||||
const oldLiquidity = getCpmmLiquidity(l.pool, p)
|
const oldLiquidity = getCpmmLiquidity(l.pool, p)
|
||||||
|
|
||||||
const newPool = addObjects(l.pool, { YES: l.amount, NO: l.amount })
|
const newPool = addObjects(l.pool, { YES: l.amount, NO: l.amount })
|
||||||
|
@ -274,13 +268,22 @@ export function getCpmmLiquidityPoolWeights(
|
||||||
|
|
||||||
const liquidity = newLiquidity - oldLiquidity
|
const liquidity = newLiquidity - oldLiquidity
|
||||||
return liquidity
|
return liquidity
|
||||||
})
|
}
|
||||||
|
|
||||||
const shareSum = sum(liquidityShares)
|
export function getCpmmLiquidityPoolWeights(
|
||||||
|
contract: CPMMContract,
|
||||||
|
liquidities: LiquidityProvision[]
|
||||||
|
) {
|
||||||
|
const [antes, nonAntes] = partition(liquidities, (l) => !!l.isAnte)
|
||||||
|
|
||||||
|
const calcLiqudity = calculateLiquidityDelta(contract.p)
|
||||||
|
const liquidityShares = nonAntes.map(calcLiqudity)
|
||||||
|
|
||||||
|
const shareSum = sum(liquidityShares) + sum(antes.map(calcLiqudity))
|
||||||
|
|
||||||
const weights = liquidityShares.map((s, i) => ({
|
const weights = liquidityShares.map((s, i) => ({
|
||||||
weight: s / shareSum,
|
weight: s / shareSum,
|
||||||
providerId: liquidities[i].userId,
|
providerId: nonAntes[i].userId,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const userWeights = groupBy(weights, (w) => w.providerId)
|
const userWeights = groupBy(weights, (w) => w.providerId)
|
||||||
|
@ -290,22 +293,13 @@ export function getCpmmLiquidityPoolWeights(
|
||||||
return totalUserWeights
|
return totalUserWeights
|
||||||
}
|
}
|
||||||
|
|
||||||
// export function removeCpmmLiquidity(
|
export function getUserLiquidityShares(
|
||||||
// contract: CPMMContract,
|
userId: string,
|
||||||
// liquidity: number
|
contract: CPMMContract,
|
||||||
// ) {
|
liquidities: LiquidityProvision[]
|
||||||
// const { YES, NO } = contract.pool
|
) {
|
||||||
// const poolLiquidity = getCpmmLiquidity({ YES, NO })
|
const weights = getCpmmLiquidityPoolWeights(contract, liquidities)
|
||||||
// const p = getCpmmProbability({ YES, NO }, contract.p)
|
const userWeight = weights[userId] ?? 0
|
||||||
|
|
||||||
// const f = liquidity / poolLiquidity
|
return mapValues(contract.pool, (shares) => userWeight * shares)
|
||||||
// const [payoutYes, payoutNo] = [f * YES, f * NO]
|
}
|
||||||
|
|
||||||
// const betAmount = Math.abs(payoutYes - payoutNo)
|
|
||||||
// const betOutcome = p >= 0.5 ? 'NO' : 'YES' // opposite side as adding liquidity
|
|
||||||
// const payout = Math.min(payoutYes, payoutNo)
|
|
||||||
|
|
||||||
// const newPool = { YES: YES - payoutYes, NO: NO - payoutNo }
|
|
||||||
|
|
||||||
// return { newPool, payout, betAmount, betOutcome }
|
|
||||||
// }
|
|
||||||
|
|
|
@ -23,3 +23,18 @@ export const addObjects = <T extends { [key: string]: number }>(
|
||||||
|
|
||||||
return newObj as T
|
return newObj as T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const subtractObjects = <T extends { [key: string]: number }>(
|
||||||
|
obj1: T,
|
||||||
|
obj2: T
|
||||||
|
) => {
|
||||||
|
const keys = union(Object.keys(obj1), Object.keys(obj2))
|
||||||
|
const newObj = {} as any
|
||||||
|
|
||||||
|
for (const key of keys) {
|
||||||
|
newObj[key] = (obj1[key] ?? 0) - (obj2[key] ?? 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newObj as T
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,10 @@ service cloud.firestore {
|
||||||
allow read;
|
allow read;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match /{somePath=**}/liquidity/{liquidityId} {
|
||||||
|
allow read;
|
||||||
|
}
|
||||||
|
|
||||||
function commentMatchesUser(userId, comment) {
|
function commentMatchesUser(userId, comment) {
|
||||||
// it's a bad look if someone can impersonate other ids/names/avatars so check everything
|
// it's a bad look if someone can impersonate other ids/names/avatars so check everything
|
||||||
let user = get(/databases/$(database)/documents/users/$(userId));
|
let user = get(/databases/$(database)/documents/users/$(userId));
|
||||||
|
|
|
@ -35,3 +35,4 @@ export * from './place-bet'
|
||||||
export * from './sell-bet'
|
export * from './sell-bet'
|
||||||
export * from './sell-shares'
|
export * from './sell-shares'
|
||||||
export * from './create-contract'
|
export * from './create-contract'
|
||||||
|
export * from './withdraw-liquidity'
|
111
functions/src/withdraw-liquidity.ts
Normal file
111
functions/src/withdraw-liquidity.ts
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
import * as functions from 'firebase-functions'
|
||||||
|
import * as admin from 'firebase-admin'
|
||||||
|
|
||||||
|
import { CPMMContract } from '../../common/contract'
|
||||||
|
import { User } from '../../common/user'
|
||||||
|
import { subtractObjects } from '../../common/util/object'
|
||||||
|
import { LiquidityProvision } from '../../common/liquidity-provision'
|
||||||
|
import { getUserLiquidityShares } from '../../common/calculate-cpmm'
|
||||||
|
import { Bet } from '../../common/bet'
|
||||||
|
import { getProbability } from '../../common/calculate'
|
||||||
|
import { noFees } from '../../common/fees'
|
||||||
|
|
||||||
|
import { APIError } from './api'
|
||||||
|
|
||||||
|
export const withdrawLiquidity = functions
|
||||||
|
.runWith({ minInstances: 1 })
|
||||||
|
.https.onCall(
|
||||||
|
async (
|
||||||
|
data: {
|
||||||
|
contractId: string
|
||||||
|
},
|
||||||
|
context
|
||||||
|
) => {
|
||||||
|
const userId = context?.auth?.uid
|
||||||
|
if (!userId) return { status: 'error', message: 'Not authorized' }
|
||||||
|
|
||||||
|
const { contractId } = data
|
||||||
|
if (!contractId)
|
||||||
|
return { status: 'error', message: 'Missing contract id' }
|
||||||
|
|
||||||
|
const result = await firestore.runTransaction(async (trans) => {
|
||||||
|
const lpDoc = firestore.doc(`users/${userId}`)
|
||||||
|
const lpSnap = await trans.get(lpDoc)
|
||||||
|
if (!lpSnap.exists) throw new APIError(400, 'User not found.')
|
||||||
|
const lp = lpSnap.data() as User
|
||||||
|
|
||||||
|
const contractDoc = firestore.doc(`contracts/${contractId}`)
|
||||||
|
const contractSnap = await trans.get(contractDoc)
|
||||||
|
if (!contractSnap.exists) throw new APIError(400, 'Contract not found.')
|
||||||
|
const contract = contractSnap.data() as CPMMContract
|
||||||
|
|
||||||
|
const liquidityCollection = firestore.collection(
|
||||||
|
`contracts/${contractId}/liquidity`
|
||||||
|
)
|
||||||
|
|
||||||
|
const liquiditiesSnap = await trans.get(liquidityCollection)
|
||||||
|
|
||||||
|
const liquidities = liquiditiesSnap.docs.map(
|
||||||
|
(doc) => doc.data() as LiquidityProvision
|
||||||
|
)
|
||||||
|
|
||||||
|
const userShares = getUserLiquidityShares(userId, contract, liquidities)
|
||||||
|
|
||||||
|
// zero all added amounts for now
|
||||||
|
// can add support for partial withdrawals in the future
|
||||||
|
liquiditiesSnap.docs
|
||||||
|
.filter(
|
||||||
|
(_, i) => !liquidities[i].isAnte && liquidities[i].userId === userId
|
||||||
|
)
|
||||||
|
.forEach((doc) => trans.update(doc.ref, { amount: 0 }))
|
||||||
|
|
||||||
|
const payout = Math.min(...Object.values(userShares))
|
||||||
|
if (payout <= 0) return {}
|
||||||
|
|
||||||
|
const newBalance = lp.balance + payout
|
||||||
|
trans.update(lpDoc, { balance: newBalance })
|
||||||
|
|
||||||
|
const newPool = subtractObjects(contract.pool, userShares)
|
||||||
|
const newTotalLiquidity = contract.totalLiquidity - payout
|
||||||
|
trans.update(contractDoc, {
|
||||||
|
pool: newPool,
|
||||||
|
totalLiquidity: newTotalLiquidity,
|
||||||
|
})
|
||||||
|
|
||||||
|
const prob = getProbability(contract)
|
||||||
|
|
||||||
|
// surplus shares become user's bets
|
||||||
|
const bets = Object.entries(userShares)
|
||||||
|
.map(([outcome, shares]) =>
|
||||||
|
shares - payout < 1 // don't create bet if less than 1 share
|
||||||
|
? undefined
|
||||||
|
: ({
|
||||||
|
userId: userId,
|
||||||
|
contractId: contract.id,
|
||||||
|
amount: shares - payout,
|
||||||
|
shares: shares - payout,
|
||||||
|
outcome,
|
||||||
|
probBefore: prob,
|
||||||
|
probAfter: prob,
|
||||||
|
createdTime: Date.now(),
|
||||||
|
fees: noFees,
|
||||||
|
} as Omit<Bet, 'id'>)
|
||||||
|
)
|
||||||
|
.filter((x) => x !== undefined)
|
||||||
|
|
||||||
|
for (const bet of bets) {
|
||||||
|
const doc = firestore
|
||||||
|
.collection(`contracts/${contract.id}/bets`)
|
||||||
|
.doc()
|
||||||
|
trans.create(doc, { id: doc.id, ...bet })
|
||||||
|
}
|
||||||
|
|
||||||
|
return userShares
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('userid', userId, 'withdraws', result)
|
||||||
|
return { status: 'success', userShares: result }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const firestore = admin.firestore()
|
|
@ -1,87 +0,0 @@
|
||||||
import clsx from 'clsx'
|
|
||||||
import { useState } from 'react'
|
|
||||||
|
|
||||||
import { Contract } from 'common/contract'
|
|
||||||
import { formatMoney } from 'common/util/format'
|
|
||||||
import { useUser } from 'web/hooks/use-user'
|
|
||||||
import { addLiquidity } from 'web/lib/firebase/fn-call'
|
|
||||||
import { AmountInput } from './amount-input'
|
|
||||||
import { Row } from './layout/row'
|
|
||||||
|
|
||||||
export function AddLiquidityPanel(props: { contract: Contract }) {
|
|
||||||
const { contract } = props
|
|
||||||
const { id: contractId } = contract
|
|
||||||
|
|
||||||
const user = useUser()
|
|
||||||
|
|
||||||
const [amount, setAmount] = useState<number | undefined>(undefined)
|
|
||||||
const [error, setError] = useState<string | undefined>(undefined)
|
|
||||||
const [isSuccess, setIsSuccess] = useState(false)
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
|
||||||
|
|
||||||
const onAmountChange = (amount: number | undefined) => {
|
|
||||||
setIsSuccess(false)
|
|
||||||
setAmount(amount)
|
|
||||||
|
|
||||||
// Check for errors.
|
|
||||||
if (amount !== undefined) {
|
|
||||||
if (user && user.balance < amount) {
|
|
||||||
setError('Insufficient balance')
|
|
||||||
} else if (amount < 1) {
|
|
||||||
setError('Minimum amount: ' + formatMoney(1))
|
|
||||||
} else {
|
|
||||||
setError(undefined)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const submit = () => {
|
|
||||||
if (!amount) return
|
|
||||||
|
|
||||||
setIsLoading(true)
|
|
||||||
setIsSuccess(false)
|
|
||||||
|
|
||||||
addLiquidity({ amount, contractId })
|
|
||||||
.then((r) => {
|
|
||||||
if (r.status === 'success') {
|
|
||||||
setIsSuccess(true)
|
|
||||||
setError(undefined)
|
|
||||||
setIsLoading(false)
|
|
||||||
} else {
|
|
||||||
setError('Server error')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((e) => setError('Server error'))
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="text-gray-500">
|
|
||||||
Subsidize this market by adding liquidity for traders.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Row>
|
|
||||||
<AmountInput
|
|
||||||
amount={amount}
|
|
||||||
onChange={onAmountChange}
|
|
||||||
label="M$"
|
|
||||||
error={error}
|
|
||||||
disabled={isLoading}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className={clsx('btn btn-primary ml-2', isLoading && 'btn-disabled')}
|
|
||||||
onClick={submit}
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</button>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
{isSuccess && amount && (
|
|
||||||
<div>Success! Added {formatMoney(amount)} in liquidity.</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isLoading && <div>Processing...</div>}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
contractPool,
|
contractPool,
|
||||||
getBinaryProbPercent,
|
getBinaryProbPercent,
|
||||||
} from 'web/lib/firebase/contracts'
|
} from 'web/lib/firebase/contracts'
|
||||||
import { AddLiquidityPanel } from '../add-liquidity-panel'
|
import { LiquidityPanel } from '../liquidity-panel'
|
||||||
import { CopyLinkButton } from '../copy-link-button'
|
import { CopyLinkButton } from '../copy-link-button'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { Modal } from '../layout/modal'
|
import { Modal } from '../layout/modal'
|
||||||
|
@ -113,13 +113,8 @@ export function ContractInfoDialog(props: { contract: Contract; bets: Bet[] }) {
|
||||||
<TagsInput contract={contract} />
|
<TagsInput contract={contract} />
|
||||||
<div />
|
<div />
|
||||||
|
|
||||||
{contract.mechanism === 'cpmm-1' &&
|
{contract.mechanism === 'cpmm-1' && !contract.resolution && (
|
||||||
!contract.resolution &&
|
<LiquidityPanel contract={contract} />
|
||||||
(!closeTime || closeTime > Date.now()) && (
|
|
||||||
<>
|
|
||||||
<div className="">Add liquidity</div>
|
|
||||||
<AddLiquidityPanel contract={contract} />
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
199
web/components/liquidity-panel.tsx
Normal file
199
web/components/liquidity-panel.tsx
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
import { CPMMContract } from 'common/contract'
|
||||||
|
import { formatMoney } from 'common/util/format'
|
||||||
|
import { useUser } from 'web/hooks/use-user'
|
||||||
|
import { addLiquidity, withdrawLiquidity } from 'web/lib/firebase/fn-call'
|
||||||
|
import { AmountInput } from './amount-input'
|
||||||
|
import { Row } from './layout/row'
|
||||||
|
import { useUserLiquidity } from 'web/hooks/use-liquidity'
|
||||||
|
import { Tabs } from './layout/tabs'
|
||||||
|
import { NoLabel, YesLabel } from './outcome-label'
|
||||||
|
import { Col } from './layout/col'
|
||||||
|
|
||||||
|
export function LiquidityPanel(props: { contract: CPMMContract }) {
|
||||||
|
const { contract } = props
|
||||||
|
|
||||||
|
const user = useUser()
|
||||||
|
const lpShares = useUserLiquidity(contract, user?.id ?? '')
|
||||||
|
|
||||||
|
const [showWithdrawal, setShowWithdrawal] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!showWithdrawal && lpShares && lpShares.YES && lpShares.NO)
|
||||||
|
setShowWithdrawal(true)
|
||||||
|
}, [showWithdrawal, lpShares])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tabs
|
||||||
|
tabs={[
|
||||||
|
{
|
||||||
|
title: 'Add liquidity',
|
||||||
|
content: <AddLiquidityPanel contract={contract} />,
|
||||||
|
},
|
||||||
|
...(showWithdrawal
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
title: 'Withdraw liquidity',
|
||||||
|
content: (
|
||||||
|
<WithdrawLiquidityPanel
|
||||||
|
contract={contract}
|
||||||
|
lpShares={lpShares as { YES: number; NO: number }}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AddLiquidityPanel(props: { contract: CPMMContract }) {
|
||||||
|
const { contract } = props
|
||||||
|
const { id: contractId } = contract
|
||||||
|
|
||||||
|
const user = useUser()
|
||||||
|
|
||||||
|
const [amount, setAmount] = useState<number | undefined>(undefined)
|
||||||
|
const [error, setError] = useState<string | undefined>(undefined)
|
||||||
|
const [isSuccess, setIsSuccess] = useState(false)
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
|
const onAmountChange = (amount: number | undefined) => {
|
||||||
|
setIsSuccess(false)
|
||||||
|
setAmount(amount)
|
||||||
|
|
||||||
|
// Check for errors.
|
||||||
|
if (amount !== undefined) {
|
||||||
|
if (user && user.balance < amount) {
|
||||||
|
setError('Insufficient balance')
|
||||||
|
} else if (amount < 1) {
|
||||||
|
setError('Minimum amount: ' + formatMoney(1))
|
||||||
|
} else {
|
||||||
|
setError(undefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
if (!amount) return
|
||||||
|
|
||||||
|
setIsLoading(true)
|
||||||
|
setIsSuccess(false)
|
||||||
|
|
||||||
|
addLiquidity({ amount, contractId })
|
||||||
|
.then((r) => {
|
||||||
|
if (r.status === 'success') {
|
||||||
|
setIsSuccess(true)
|
||||||
|
setError(undefined)
|
||||||
|
setIsLoading(false)
|
||||||
|
} else {
|
||||||
|
setError('Server error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => setError('Server error'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="mb-2 text-gray-500">
|
||||||
|
Subsidize this market by adding liquidity for traders.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<AmountInput
|
||||||
|
amount={amount}
|
||||||
|
onChange={onAmountChange}
|
||||||
|
label="M$"
|
||||||
|
error={error}
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className={clsx('btn btn-primary ml-2', isLoading && 'btn-disabled')}
|
||||||
|
onClick={submit}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{isSuccess && amount && (
|
||||||
|
<div>Success! Added {formatMoney(amount)} in liquidity.</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isLoading && <div>Processing...</div>}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function WithdrawLiquidityPanel(props: {
|
||||||
|
contract: CPMMContract
|
||||||
|
lpShares: { YES: number; NO: number }
|
||||||
|
}) {
|
||||||
|
const { contract, lpShares } = props
|
||||||
|
const { YES: yesShares, NO: noShares } = lpShares
|
||||||
|
|
||||||
|
const [error, setError] = useState<string | undefined>(undefined)
|
||||||
|
const [isSuccess, setIsSuccess] = useState(false)
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
setIsLoading(true)
|
||||||
|
setIsSuccess(false)
|
||||||
|
|
||||||
|
withdrawLiquidity({ contractId: contract.id })
|
||||||
|
.then((r) => {
|
||||||
|
setIsSuccess(true)
|
||||||
|
setError(undefined)
|
||||||
|
setIsLoading(false)
|
||||||
|
})
|
||||||
|
.catch((e) => setError('Server error'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSuccess)
|
||||||
|
return (
|
||||||
|
<div className="text-gray-500">
|
||||||
|
Success! Your liquidity was withdrawn.
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!yesShares && !noShares)
|
||||||
|
return (
|
||||||
|
<div className="text-gray-500">
|
||||||
|
You do not have any liquidity positions to withdraw.
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col>
|
||||||
|
<div className="mb-4 text-gray-500">
|
||||||
|
Your liquidity position is currently:
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
{yesShares.toFixed(2)} <YesLabel /> shares
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
{noShares.toFixed(2)} <NoLabel /> shares
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<Row className="mt-4 mb-2">
|
||||||
|
<button
|
||||||
|
className={clsx(
|
||||||
|
'btn btn-outline btn-sm ml-2',
|
||||||
|
isLoading && 'btn-disabled'
|
||||||
|
)}
|
||||||
|
onClick={submit}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
Withdraw
|
||||||
|
</button>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{isLoading && <div>Processing...</div>}
|
||||||
|
</Col>
|
||||||
|
)
|
||||||
|
}
|
26
web/hooks/use-liquidity.ts
Normal file
26
web/hooks/use-liquidity.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
import { CPMMContract } from 'common/contract'
|
||||||
|
import { LiquidityProvision } from 'common/liquidity-provision'
|
||||||
|
import { getUserLiquidityShares } from 'common/calculate-cpmm'
|
||||||
|
|
||||||
|
import { listenForLiquidity } from 'web/lib/firebase/liquidity'
|
||||||
|
|
||||||
|
export const useLiquidity = (contractId: string) => {
|
||||||
|
const [liquidities, setLiquidities] = useState<
|
||||||
|
LiquidityProvision[] | undefined
|
||||||
|
>(undefined)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return listenForLiquidity(contractId, setLiquidities)
|
||||||
|
}, [contractId])
|
||||||
|
|
||||||
|
return liquidities
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUserLiquidity = (contract: CPMMContract, userId: string) => {
|
||||||
|
const liquidities = useLiquidity(contract.id)
|
||||||
|
|
||||||
|
const userShares = getUserLiquidityShares(userId, contract, liquidities ?? [])
|
||||||
|
return userShares
|
||||||
|
}
|
|
@ -10,6 +10,11 @@ import { safeLocalStorage } from '../util/local'
|
||||||
export const cloudFunction = <RequestData, ResponseData>(name: string) =>
|
export const cloudFunction = <RequestData, ResponseData>(name: string) =>
|
||||||
httpsCallable<RequestData, ResponseData>(functions, name)
|
httpsCallable<RequestData, ResponseData>(functions, name)
|
||||||
|
|
||||||
|
export const withdrawLiquidity = cloudFunction<
|
||||||
|
{ contractId: string },
|
||||||
|
{ status: 'error' | 'success'; userShares: { [outcome: string]: number } }
|
||||||
|
>('withdrawLiquidity')
|
||||||
|
|
||||||
export const createFold = cloudFunction<
|
export const createFold = cloudFunction<
|
||||||
{ name: string; about: string; tags: string[] },
|
{ name: string; about: string; tags: string[] },
|
||||||
{ status: 'error' | 'success'; message?: string; fold?: Fold }
|
{ status: 'error' | 'success'; message?: string; fold?: Fold }
|
||||||
|
|
17
web/lib/firebase/liquidity.ts
Normal file
17
web/lib/firebase/liquidity.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { collection, query } from 'firebase/firestore'
|
||||||
|
|
||||||
|
import { db } from './init'
|
||||||
|
import { listenForValues } from './utils'
|
||||||
|
import { LiquidityProvision } from 'common/liquidity-provision'
|
||||||
|
|
||||||
|
export function listenForLiquidity(
|
||||||
|
contractId: string,
|
||||||
|
setLiquidity: (lps: LiquidityProvision[]) => void
|
||||||
|
) {
|
||||||
|
const lpQuery = query(collection(db, 'contracts', contractId, 'liquidity'))
|
||||||
|
|
||||||
|
return listenForValues<LiquidityProvision>(lpQuery, (lps) => {
|
||||||
|
lps.sort((lp1, lp2) => lp1.createdTime - lp2.createdTime)
|
||||||
|
setLiquidity(lps)
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user