manifold/functions/src/withdraw-liquidity.ts
2022-07-09 13:53:50 -04:00

122 lines
4.0 KiB
TypeScript

import * as admin from 'firebase-admin'
import { z } from 'zod'
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, newEndpoint, validate } from './api'
import { redeemShares } from './redeem-shares'
const bodySchema = z.object({
contractId: z.string(),
})
export const withdrawliquidity = newEndpoint({}, async (req, auth) => {
const { contractId } = validate(bodySchema, req.body)
return await firestore
.runTransaction(async (trans) => {
const lpDoc = firestore.doc(`users/${auth.uid}`)
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(
auth.uid,
contract,
liquidities,
true
)
// 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 === auth.uid
)
.forEach((doc) => trans.update(doc.ref, { amount: 0 }))
const payout = Math.min(...Object.values(userShares))
if (payout <= 0) return {}
const newBalance = lp.balance + payout
const newTotalDeposits = lp.totalDeposits + payout
trans.update(lpDoc, {
balance: newBalance,
totalDeposits: newTotalDeposits,
} as Partial<User>)
const newPool = subtractObjects(contract.pool, userShares)
const minPoolShares = Math.min(...Object.values(newPool))
const adjustedTotal = contract.totalLiquidity - payout
// total liquidity is a bogus number; use minPoolShares to prevent from going negative
const newTotalLiquidity = Math.max(adjustedTotal, minPoolShares)
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: auth.uid,
contractId: contract.id,
amount:
(outcome === 'YES' ? prob : 1 - prob) * (shares - payout),
shares: shares - payout,
outcome,
probBefore: prob,
probAfter: prob,
createdTime: Date.now(),
isLiquidityProvision: true,
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
})
.then(async (result) => {
// redeem surplus bet with pre-existing bets
await redeemShares(auth.uid, contractId)
console.log('userid', auth.uid, 'withdraws', result)
return result
})
})
const firestore = admin.firestore()