Merge branch 'main' into main2
This commit is contained in:
commit
4285198f09
|
@ -147,7 +147,8 @@ function calculateAmountToBuyShares(
|
||||||
state: CpmmState,
|
state: CpmmState,
|
||||||
shares: number,
|
shares: number,
|
||||||
outcome: 'YES' | 'NO',
|
outcome: 'YES' | 'NO',
|
||||||
unfilledBets: LimitBet[]
|
unfilledBets: LimitBet[],
|
||||||
|
balanceByUserId: { [userId: string]: number }
|
||||||
) {
|
) {
|
||||||
// Search for amount between bounds (0, shares).
|
// Search for amount between bounds (0, shares).
|
||||||
// Min share price is M$0, and max is M$1 each.
|
// Min share price is M$0, and max is M$1 each.
|
||||||
|
@ -157,7 +158,8 @@ function calculateAmountToBuyShares(
|
||||||
amount,
|
amount,
|
||||||
state,
|
state,
|
||||||
undefined,
|
undefined,
|
||||||
unfilledBets
|
unfilledBets,
|
||||||
|
balanceByUserId
|
||||||
)
|
)
|
||||||
|
|
||||||
const totalShares = sumBy(takers, (taker) => taker.shares)
|
const totalShares = sumBy(takers, (taker) => taker.shares)
|
||||||
|
@ -169,7 +171,8 @@ export function calculateCpmmSale(
|
||||||
state: CpmmState,
|
state: CpmmState,
|
||||||
shares: number,
|
shares: number,
|
||||||
outcome: 'YES' | 'NO',
|
outcome: 'YES' | 'NO',
|
||||||
unfilledBets: LimitBet[]
|
unfilledBets: LimitBet[],
|
||||||
|
balanceByUserId: { [userId: string]: number }
|
||||||
) {
|
) {
|
||||||
if (Math.round(shares) < 0) {
|
if (Math.round(shares) < 0) {
|
||||||
throw new Error('Cannot sell non-positive shares')
|
throw new Error('Cannot sell non-positive shares')
|
||||||
|
@ -180,15 +183,17 @@ export function calculateCpmmSale(
|
||||||
state,
|
state,
|
||||||
shares,
|
shares,
|
||||||
oppositeOutcome,
|
oppositeOutcome,
|
||||||
unfilledBets
|
unfilledBets,
|
||||||
|
balanceByUserId
|
||||||
)
|
)
|
||||||
|
|
||||||
const { cpmmState, makers, takers, totalFees } = computeFills(
|
const { cpmmState, makers, takers, totalFees, ordersToCancel } = computeFills(
|
||||||
oppositeOutcome,
|
oppositeOutcome,
|
||||||
buyAmount,
|
buyAmount,
|
||||||
state,
|
state,
|
||||||
undefined,
|
undefined,
|
||||||
unfilledBets
|
unfilledBets,
|
||||||
|
balanceByUserId
|
||||||
)
|
)
|
||||||
|
|
||||||
// Transform buys of opposite outcome into sells.
|
// Transform buys of opposite outcome into sells.
|
||||||
|
@ -211,6 +216,7 @@ export function calculateCpmmSale(
|
||||||
fees: totalFees,
|
fees: totalFees,
|
||||||
makers,
|
makers,
|
||||||
takers: saleTakers,
|
takers: saleTakers,
|
||||||
|
ordersToCancel,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,9 +224,16 @@ export function getCpmmProbabilityAfterSale(
|
||||||
state: CpmmState,
|
state: CpmmState,
|
||||||
shares: number,
|
shares: number,
|
||||||
outcome: 'YES' | 'NO',
|
outcome: 'YES' | 'NO',
|
||||||
unfilledBets: LimitBet[]
|
unfilledBets: LimitBet[],
|
||||||
|
balanceByUserId: { [userId: string]: number }
|
||||||
) {
|
) {
|
||||||
const { cpmmState } = calculateCpmmSale(state, shares, outcome, unfilledBets)
|
const { cpmmState } = calculateCpmmSale(
|
||||||
|
state,
|
||||||
|
shares,
|
||||||
|
outcome,
|
||||||
|
unfilledBets,
|
||||||
|
balanceByUserId
|
||||||
|
)
|
||||||
return getCpmmProbability(cpmmState.pool, cpmmState.p)
|
return getCpmmProbability(cpmmState.pool, cpmmState.p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { last, sortBy, sum, sumBy } from 'lodash'
|
import { last, sortBy, sum, sumBy, uniq } from 'lodash'
|
||||||
import { calculatePayout } from './calculate'
|
import { calculatePayout } from './calculate'
|
||||||
import { Bet, LimitBet } from './bet'
|
import { Bet, LimitBet } from './bet'
|
||||||
import { Contract, CPMMContract, DPMContract } from './contract'
|
import { Contract, CPMMContract, DPMContract } from './contract'
|
||||||
|
@ -62,16 +62,28 @@ export const computeBinaryCpmmElasticity = (
|
||||||
const limitBets = bets
|
const limitBets = bets
|
||||||
.filter(
|
.filter(
|
||||||
(b) =>
|
(b) =>
|
||||||
!b.isFilled && !b.isSold && !b.isRedemption && !b.sale && !b.isCancelled
|
!b.isFilled &&
|
||||||
|
!b.isSold &&
|
||||||
|
!b.isRedemption &&
|
||||||
|
!b.sale &&
|
||||||
|
!b.isCancelled &&
|
||||||
|
b.limitProb !== undefined
|
||||||
)
|
)
|
||||||
.sort((a, b) => a.createdTime - b.createdTime)
|
.sort((a, b) => a.createdTime - b.createdTime) as LimitBet[]
|
||||||
|
|
||||||
|
const userIds = uniq(limitBets.map((b) => b.userId))
|
||||||
|
// Assume all limit orders are good.
|
||||||
|
const userBalances = Object.fromEntries(
|
||||||
|
userIds.map((id) => [id, Number.MAX_SAFE_INTEGER])
|
||||||
|
)
|
||||||
|
|
||||||
const { newPool: poolY, newP: pY } = getBinaryCpmmBetInfo(
|
const { newPool: poolY, newP: pY } = getBinaryCpmmBetInfo(
|
||||||
'YES',
|
'YES',
|
||||||
betAmount,
|
betAmount,
|
||||||
contract,
|
contract,
|
||||||
undefined,
|
undefined,
|
||||||
limitBets as LimitBet[]
|
limitBets,
|
||||||
|
userBalances
|
||||||
)
|
)
|
||||||
const resultYes = getCpmmProbability(poolY, pY)
|
const resultYes = getCpmmProbability(poolY, pY)
|
||||||
|
|
||||||
|
@ -80,7 +92,8 @@ export const computeBinaryCpmmElasticity = (
|
||||||
betAmount,
|
betAmount,
|
||||||
contract,
|
contract,
|
||||||
undefined,
|
undefined,
|
||||||
limitBets as LimitBet[]
|
limitBets,
|
||||||
|
userBalances
|
||||||
)
|
)
|
||||||
const resultNo = getCpmmProbability(poolN, pN)
|
const resultNo = getCpmmProbability(poolN, pN)
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,8 @@ export function calculateShares(
|
||||||
export function calculateSaleAmount(
|
export function calculateSaleAmount(
|
||||||
contract: Contract,
|
contract: Contract,
|
||||||
bet: Bet,
|
bet: Bet,
|
||||||
unfilledBets: LimitBet[]
|
unfilledBets: LimitBet[],
|
||||||
|
balanceByUserId: { [userId: string]: number }
|
||||||
) {
|
) {
|
||||||
return contract.mechanism === 'cpmm-1' &&
|
return contract.mechanism === 'cpmm-1' &&
|
||||||
(contract.outcomeType === 'BINARY' ||
|
(contract.outcomeType === 'BINARY' ||
|
||||||
|
@ -87,7 +88,8 @@ export function calculateSaleAmount(
|
||||||
contract,
|
contract,
|
||||||
Math.abs(bet.shares),
|
Math.abs(bet.shares),
|
||||||
bet.outcome as 'YES' | 'NO',
|
bet.outcome as 'YES' | 'NO',
|
||||||
unfilledBets
|
unfilledBets,
|
||||||
|
balanceByUserId
|
||||||
).saleValue
|
).saleValue
|
||||||
: calculateDpmSaleAmount(contract, bet)
|
: calculateDpmSaleAmount(contract, bet)
|
||||||
}
|
}
|
||||||
|
@ -102,14 +104,16 @@ export function getProbabilityAfterSale(
|
||||||
contract: Contract,
|
contract: Contract,
|
||||||
outcome: string,
|
outcome: string,
|
||||||
shares: number,
|
shares: number,
|
||||||
unfilledBets: LimitBet[]
|
unfilledBets: LimitBet[],
|
||||||
|
balanceByUserId: { [userId: string]: number }
|
||||||
) {
|
) {
|
||||||
return contract.mechanism === 'cpmm-1'
|
return contract.mechanism === 'cpmm-1'
|
||||||
? getCpmmProbabilityAfterSale(
|
? getCpmmProbabilityAfterSale(
|
||||||
contract,
|
contract,
|
||||||
shares,
|
shares,
|
||||||
outcome as 'YES' | 'NO',
|
outcome as 'YES' | 'NO',
|
||||||
unfilledBets
|
unfilledBets,
|
||||||
|
balanceByUserId
|
||||||
)
|
)
|
||||||
: getDpmProbabilityAfterSale(contract.totalShares, outcome, shares)
|
: getDpmProbabilityAfterSale(contract.totalShares, outcome, shares)
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,7 +143,8 @@ export const computeFills = (
|
||||||
betAmount: number,
|
betAmount: number,
|
||||||
state: CpmmState,
|
state: CpmmState,
|
||||||
limitProb: number | undefined,
|
limitProb: number | undefined,
|
||||||
unfilledBets: LimitBet[]
|
unfilledBets: LimitBet[],
|
||||||
|
balanceByUserId: { [userId: string]: number }
|
||||||
) => {
|
) => {
|
||||||
if (isNaN(betAmount)) {
|
if (isNaN(betAmount)) {
|
||||||
throw new Error('Invalid bet amount: ${betAmount}')
|
throw new Error('Invalid bet amount: ${betAmount}')
|
||||||
|
@ -165,10 +166,12 @@ export const computeFills = (
|
||||||
shares: number
|
shares: number
|
||||||
timestamp: number
|
timestamp: number
|
||||||
}[] = []
|
}[] = []
|
||||||
|
const ordersToCancel: LimitBet[] = []
|
||||||
|
|
||||||
let amount = betAmount
|
let amount = betAmount
|
||||||
let cpmmState = { pool: state.pool, p: state.p }
|
let cpmmState = { pool: state.pool, p: state.p }
|
||||||
let totalFees = noFees
|
let totalFees = noFees
|
||||||
|
const currentBalanceByUserId = { ...balanceByUserId }
|
||||||
|
|
||||||
let i = 0
|
let i = 0
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -185,9 +188,20 @@ export const computeFills = (
|
||||||
takers.push(taker)
|
takers.push(taker)
|
||||||
} else {
|
} else {
|
||||||
// Matched against bet.
|
// Matched against bet.
|
||||||
|
i++
|
||||||
|
const { userId } = maker.bet
|
||||||
|
const makerBalance = currentBalanceByUserId[userId]
|
||||||
|
|
||||||
|
if (floatingGreaterEqual(makerBalance, maker.amount)) {
|
||||||
|
currentBalanceByUserId[userId] = makerBalance - maker.amount
|
||||||
|
} else {
|
||||||
|
// Insufficient balance. Cancel maker bet.
|
||||||
|
ordersToCancel.push(maker.bet)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
takers.push(taker)
|
takers.push(taker)
|
||||||
makers.push(maker)
|
makers.push(maker)
|
||||||
i++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
amount -= taker.amount
|
amount -= taker.amount
|
||||||
|
@ -195,7 +209,7 @@ export const computeFills = (
|
||||||
if (floatingEqual(amount, 0)) break
|
if (floatingEqual(amount, 0)) break
|
||||||
}
|
}
|
||||||
|
|
||||||
return { takers, makers, totalFees, cpmmState }
|
return { takers, makers, totalFees, cpmmState, ordersToCancel }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getBinaryCpmmBetInfo = (
|
export const getBinaryCpmmBetInfo = (
|
||||||
|
@ -203,15 +217,17 @@ export const getBinaryCpmmBetInfo = (
|
||||||
betAmount: number,
|
betAmount: number,
|
||||||
contract: CPMMBinaryContract | PseudoNumericContract,
|
contract: CPMMBinaryContract | PseudoNumericContract,
|
||||||
limitProb: number | undefined,
|
limitProb: number | undefined,
|
||||||
unfilledBets: LimitBet[]
|
unfilledBets: LimitBet[],
|
||||||
|
balanceByUserId: { [userId: string]: number }
|
||||||
) => {
|
) => {
|
||||||
const { pool, p } = contract
|
const { pool, p } = contract
|
||||||
const { takers, makers, cpmmState, totalFees } = computeFills(
|
const { takers, makers, cpmmState, totalFees, ordersToCancel } = computeFills(
|
||||||
outcome,
|
outcome,
|
||||||
betAmount,
|
betAmount,
|
||||||
{ pool, p },
|
{ pool, p },
|
||||||
limitProb,
|
limitProb,
|
||||||
unfilledBets
|
unfilledBets,
|
||||||
|
balanceByUserId
|
||||||
)
|
)
|
||||||
const probBefore = getCpmmProbability(contract.pool, contract.p)
|
const probBefore = getCpmmProbability(contract.pool, contract.p)
|
||||||
const probAfter = getCpmmProbability(cpmmState.pool, cpmmState.p)
|
const probAfter = getCpmmProbability(cpmmState.pool, cpmmState.p)
|
||||||
|
@ -246,6 +262,7 @@ export const getBinaryCpmmBetInfo = (
|
||||||
newP: cpmmState.p,
|
newP: cpmmState.p,
|
||||||
newTotalLiquidity,
|
newTotalLiquidity,
|
||||||
makers,
|
makers,
|
||||||
|
ordersToCancel,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,14 +271,16 @@ export const getBinaryBetStats = (
|
||||||
betAmount: number,
|
betAmount: number,
|
||||||
contract: CPMMBinaryContract | PseudoNumericContract,
|
contract: CPMMBinaryContract | PseudoNumericContract,
|
||||||
limitProb: number,
|
limitProb: number,
|
||||||
unfilledBets: LimitBet[]
|
unfilledBets: LimitBet[],
|
||||||
|
balanceByUserId: { [userId: string]: number }
|
||||||
) => {
|
) => {
|
||||||
const { newBet } = getBinaryCpmmBetInfo(
|
const { newBet } = getBinaryCpmmBetInfo(
|
||||||
outcome,
|
outcome,
|
||||||
betAmount ?? 0,
|
betAmount ?? 0,
|
||||||
contract,
|
contract,
|
||||||
limitProb,
|
limitProb,
|
||||||
unfilledBets as LimitBet[]
|
unfilledBets,
|
||||||
|
balanceByUserId
|
||||||
)
|
)
|
||||||
const remainingMatched =
|
const remainingMatched =
|
||||||
((newBet.orderAmount ?? 0) - newBet.amount) /
|
((newBet.orderAmount ?? 0) - newBet.amount) /
|
||||||
|
|
|
@ -84,15 +84,17 @@ export const getCpmmSellBetInfo = (
|
||||||
outcome: 'YES' | 'NO',
|
outcome: 'YES' | 'NO',
|
||||||
contract: CPMMContract,
|
contract: CPMMContract,
|
||||||
unfilledBets: LimitBet[],
|
unfilledBets: LimitBet[],
|
||||||
|
balanceByUserId: { [userId: string]: number },
|
||||||
loanPaid: number
|
loanPaid: number
|
||||||
) => {
|
) => {
|
||||||
const { pool, p } = contract
|
const { pool, p } = contract
|
||||||
|
|
||||||
const { saleValue, cpmmState, fees, makers, takers } = calculateCpmmSale(
|
const { saleValue, cpmmState, fees, makers, takers, ordersToCancel } = calculateCpmmSale(
|
||||||
contract,
|
contract,
|
||||||
shares,
|
shares,
|
||||||
outcome,
|
outcome,
|
||||||
unfilledBets
|
unfilledBets,
|
||||||
|
balanceByUserId,
|
||||||
)
|
)
|
||||||
|
|
||||||
const probBefore = getCpmmProbability(pool, p)
|
const probBefore = getCpmmProbability(pool, p)
|
||||||
|
@ -134,5 +136,6 @@ export const getCpmmSellBetInfo = (
|
||||||
fees,
|
fees,
|
||||||
makers,
|
makers,
|
||||||
takers,
|
takers,
|
||||||
|
ordersToCancel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@ import { HOUSE_LIQUIDITY_PROVIDER_ID } from '../../common/antes'
|
||||||
import { createReferralNotification } from './create-notification'
|
import { createReferralNotification } from './create-notification'
|
||||||
import { ReferralTxn } from '../../common/txn'
|
import { ReferralTxn } from '../../common/txn'
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from '../../common/contract'
|
||||||
import { LimitBet } from '../../common/bet'
|
|
||||||
import { QuerySnapshot } from 'firebase-admin/firestore'
|
|
||||||
import { Group } from '../../common/group'
|
import { Group } from '../../common/group'
|
||||||
import { REFERRAL_AMOUNT } from '../../common/economy'
|
import { REFERRAL_AMOUNT } from '../../common/economy'
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
@ -21,10 +19,6 @@ export const onUpdateUser = functions.firestore
|
||||||
if (prevUser.referredByUserId !== user.referredByUserId) {
|
if (prevUser.referredByUserId !== user.referredByUserId) {
|
||||||
await handleUserUpdatedReferral(user, eventId)
|
await handleUserUpdatedReferral(user, eventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.balance <= 0) {
|
|
||||||
await cancelLimitOrders(user.id)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
async function handleUserUpdatedReferral(user: User, eventId: string) {
|
async function handleUserUpdatedReferral(user: User, eventId: string) {
|
||||||
|
@ -123,15 +117,3 @@ async function handleUserUpdatedReferral(user: User, eventId: string) {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cancelLimitOrders(userId: string) {
|
|
||||||
const snapshot = (await firestore
|
|
||||||
.collectionGroup('bets')
|
|
||||||
.where('userId', '==', userId)
|
|
||||||
.where('isFilled', '==', false)
|
|
||||||
.get()) as QuerySnapshot<LimitBet>
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
snapshot.docs.map((doc) => doc.ref.update({ isCancelled: true }))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import { floatingEqual } from '../../common/util/math'
|
||||||
import { redeemShares } from './redeem-shares'
|
import { redeemShares } from './redeem-shares'
|
||||||
import { log } from './utils'
|
import { log } from './utils'
|
||||||
import { addUserToContractFollowers } from './follow-market'
|
import { addUserToContractFollowers } from './follow-market'
|
||||||
|
import { filterDefined } from '../../common/util/array'
|
||||||
|
|
||||||
const bodySchema = z.object({
|
const bodySchema = z.object({
|
||||||
contractId: z.string(),
|
contractId: z.string(),
|
||||||
|
@ -73,9 +74,11 @@ export const placebet = newEndpoint({}, async (req, auth) => {
|
||||||
newTotalLiquidity,
|
newTotalLiquidity,
|
||||||
newP,
|
newP,
|
||||||
makers,
|
makers,
|
||||||
|
ordersToCancel,
|
||||||
} = await (async (): Promise<
|
} = await (async (): Promise<
|
||||||
BetInfo & {
|
BetInfo & {
|
||||||
makers?: maker[]
|
makers?: maker[]
|
||||||
|
ordersToCancel?: LimitBet[]
|
||||||
}
|
}
|
||||||
> => {
|
> => {
|
||||||
if (
|
if (
|
||||||
|
@ -99,17 +102,16 @@ export const placebet = newEndpoint({}, async (req, auth) => {
|
||||||
limitProb = Math.round(limitProb * 100) / 100
|
limitProb = Math.round(limitProb * 100) / 100
|
||||||
}
|
}
|
||||||
|
|
||||||
const unfilledBetsSnap = await trans.get(
|
const { unfilledBets, balanceByUserId } =
|
||||||
getUnfilledBetsQuery(contractDoc)
|
await getUnfilledBetsAndUserBalances(trans, contractDoc)
|
||||||
)
|
|
||||||
const unfilledBets = unfilledBetsSnap.docs.map((doc) => doc.data())
|
|
||||||
|
|
||||||
return getBinaryCpmmBetInfo(
|
return getBinaryCpmmBetInfo(
|
||||||
outcome,
|
outcome,
|
||||||
amount,
|
amount,
|
||||||
contract,
|
contract,
|
||||||
limitProb,
|
limitProb,
|
||||||
unfilledBets
|
unfilledBets,
|
||||||
|
balanceByUserId
|
||||||
)
|
)
|
||||||
} else if (
|
} else if (
|
||||||
(outcomeType == 'FREE_RESPONSE' || outcomeType === 'MULTIPLE_CHOICE') &&
|
(outcomeType == 'FREE_RESPONSE' || outcomeType === 'MULTIPLE_CHOICE') &&
|
||||||
|
@ -152,6 +154,13 @@ export const placebet = newEndpoint({}, async (req, auth) => {
|
||||||
if (makers) {
|
if (makers) {
|
||||||
updateMakers(makers, betDoc.id, contractDoc, trans)
|
updateMakers(makers, betDoc.id, contractDoc, trans)
|
||||||
}
|
}
|
||||||
|
if (ordersToCancel) {
|
||||||
|
for (const bet of ordersToCancel) {
|
||||||
|
trans.update(contractDoc.collection('bets').doc(bet.id), {
|
||||||
|
isCancelled: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (newBet.amount !== 0) {
|
if (newBet.amount !== 0) {
|
||||||
trans.update(userDoc, { balance: FieldValue.increment(-newBet.amount) })
|
trans.update(userDoc, { balance: FieldValue.increment(-newBet.amount) })
|
||||||
|
@ -193,13 +202,36 @@ export const placebet = newEndpoint({}, async (req, auth) => {
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
export const getUnfilledBetsQuery = (contractDoc: DocumentReference) => {
|
const getUnfilledBetsQuery = (contractDoc: DocumentReference) => {
|
||||||
return contractDoc
|
return contractDoc
|
||||||
.collection('bets')
|
.collection('bets')
|
||||||
.where('isFilled', '==', false)
|
.where('isFilled', '==', false)
|
||||||
.where('isCancelled', '==', false) as Query<LimitBet>
|
.where('isCancelled', '==', false) as Query<LimitBet>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getUnfilledBetsAndUserBalances = async (
|
||||||
|
trans: Transaction,
|
||||||
|
contractDoc: DocumentReference
|
||||||
|
) => {
|
||||||
|
const unfilledBetsSnap = await trans.get(getUnfilledBetsQuery(contractDoc))
|
||||||
|
const unfilledBets = unfilledBetsSnap.docs.map((doc) => doc.data())
|
||||||
|
|
||||||
|
// Get balance of all users with open limit orders.
|
||||||
|
const userIds = uniq(unfilledBets.map((bet) => bet.userId))
|
||||||
|
const userDocs =
|
||||||
|
userIds.length === 0
|
||||||
|
? []
|
||||||
|
: await trans.getAll(
|
||||||
|
...userIds.map((userId) => firestore.doc(`users/${userId}`))
|
||||||
|
)
|
||||||
|
const users = filterDefined(userDocs.map((doc) => doc.data() as User))
|
||||||
|
const balanceByUserId = Object.fromEntries(
|
||||||
|
users.map((user) => [user.id, user.balance])
|
||||||
|
)
|
||||||
|
|
||||||
|
return { unfilledBets, balanceByUserId }
|
||||||
|
}
|
||||||
|
|
||||||
type maker = {
|
type maker = {
|
||||||
bet: LimitBet
|
bet: LimitBet
|
||||||
amount: number
|
amount: number
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { mapValues, groupBy, sumBy, uniq } from 'lodash'
|
import { mapValues, groupBy, sumBy, uniq } from 'lodash'
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
import { FieldValue } from 'firebase-admin/firestore'
|
||||||
|
|
||||||
import { APIError, newEndpoint, validate } from './api'
|
import { APIError, newEndpoint, validate } from './api'
|
||||||
import { Contract, CPMM_MIN_POOL_QTY } from '../../common/contract'
|
import { Contract, CPMM_MIN_POOL_QTY } from '../../common/contract'
|
||||||
|
@ -10,8 +11,7 @@ import { addObjects, removeUndefinedProps } from '../../common/util/object'
|
||||||
import { log } from './utils'
|
import { log } from './utils'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from '../../common/bet'
|
||||||
import { floatingEqual, floatingLesserEqual } from '../../common/util/math'
|
import { floatingEqual, floatingLesserEqual } from '../../common/util/math'
|
||||||
import { getUnfilledBetsQuery, updateMakers } from './place-bet'
|
import { getUnfilledBetsAndUserBalances, updateMakers } from './place-bet'
|
||||||
import { FieldValue } from 'firebase-admin/firestore'
|
|
||||||
import { redeemShares } from './redeem-shares'
|
import { redeemShares } from './redeem-shares'
|
||||||
import { removeUserFromContractFollowers } from './follow-market'
|
import { removeUserFromContractFollowers } from './follow-market'
|
||||||
|
|
||||||
|
@ -29,16 +29,18 @@ export const sellshares = newEndpoint({}, async (req, auth) => {
|
||||||
const contractDoc = firestore.doc(`contracts/${contractId}`)
|
const contractDoc = firestore.doc(`contracts/${contractId}`)
|
||||||
const userDoc = firestore.doc(`users/${auth.uid}`)
|
const userDoc = firestore.doc(`users/${auth.uid}`)
|
||||||
const betsQ = contractDoc.collection('bets').where('userId', '==', auth.uid)
|
const betsQ = contractDoc.collection('bets').where('userId', '==', auth.uid)
|
||||||
const [[contractSnap, userSnap], userBetsSnap, unfilledBetsSnap] =
|
const [
|
||||||
await Promise.all([
|
[contractSnap, userSnap],
|
||||||
transaction.getAll(contractDoc, userDoc),
|
userBetsSnap,
|
||||||
transaction.get(betsQ),
|
{ unfilledBets, balanceByUserId },
|
||||||
transaction.get(getUnfilledBetsQuery(contractDoc)),
|
] = await Promise.all([
|
||||||
])
|
transaction.getAll(contractDoc, userDoc),
|
||||||
|
transaction.get(betsQ),
|
||||||
|
getUnfilledBetsAndUserBalances(transaction, contractDoc),
|
||||||
|
])
|
||||||
if (!contractSnap.exists) throw new APIError(400, 'Contract not found.')
|
if (!contractSnap.exists) throw new APIError(400, 'Contract not found.')
|
||||||
if (!userSnap.exists) throw new APIError(400, 'User not found.')
|
if (!userSnap.exists) throw new APIError(400, 'User not found.')
|
||||||
const userBets = userBetsSnap.docs.map((doc) => doc.data() as Bet)
|
const userBets = userBetsSnap.docs.map((doc) => doc.data() as Bet)
|
||||||
const unfilledBets = unfilledBetsSnap.docs.map((doc) => doc.data())
|
|
||||||
|
|
||||||
const contract = contractSnap.data() as Contract
|
const contract = contractSnap.data() as Contract
|
||||||
const user = userSnap.data() as User
|
const user = userSnap.data() as User
|
||||||
|
@ -86,13 +88,15 @@ export const sellshares = newEndpoint({}, async (req, auth) => {
|
||||||
let loanPaid = saleFrac * loanAmount
|
let loanPaid = saleFrac * loanAmount
|
||||||
if (!isFinite(loanPaid)) loanPaid = 0
|
if (!isFinite(loanPaid)) loanPaid = 0
|
||||||
|
|
||||||
const { newBet, newPool, newP, fees, makers } = getCpmmSellBetInfo(
|
const { newBet, newPool, newP, fees, makers, ordersToCancel } =
|
||||||
soldShares,
|
getCpmmSellBetInfo(
|
||||||
chosenOutcome,
|
soldShares,
|
||||||
contract,
|
chosenOutcome,
|
||||||
unfilledBets,
|
contract,
|
||||||
loanPaid
|
unfilledBets,
|
||||||
)
|
balanceByUserId,
|
||||||
|
loanPaid
|
||||||
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!newP ||
|
!newP ||
|
||||||
|
@ -127,6 +131,12 @@ export const sellshares = newEndpoint({}, async (req, auth) => {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for (const bet of ordersToCancel) {
|
||||||
|
transaction.update(contractDoc.collection('bets').doc(bet.id), {
|
||||||
|
isCancelled: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return { newBet, makers, maxShares, soldShares }
|
return { newBet, makers, maxShares, soldShares }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,12 @@ import clsx from 'clsx'
|
||||||
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'
|
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'
|
||||||
import { MenuIcon } from '@heroicons/react/solid'
|
import { MenuIcon } from '@heroicons/react/solid'
|
||||||
import { toast } from 'react-hot-toast'
|
import { toast } from 'react-hot-toast'
|
||||||
|
import { XCircleIcon } from '@heroicons/react/outline'
|
||||||
|
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Col } from 'web/components/layout/col'
|
||||||
import { Row } from 'web/components/layout/row'
|
import { Row } from 'web/components/layout/row'
|
||||||
import { Subtitle } from 'web/components/subtitle'
|
import { Subtitle } from 'web/components/subtitle'
|
||||||
import { keyBy } from 'lodash'
|
import { keyBy } from 'lodash'
|
||||||
import { XCircleIcon } from '@heroicons/react/outline'
|
|
||||||
import { Button } from './button'
|
import { Button } from './button'
|
||||||
import { updateUser } from 'web/lib/firebase/users'
|
import { updateUser } from 'web/lib/firebase/users'
|
||||||
import { leaveGroup } from 'web/lib/firebase/groups'
|
import { leaveGroup } from 'web/lib/firebase/groups'
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { Button } from 'web/components/button'
|
||||||
import { BetSignUpPrompt } from './sign-up-prompt'
|
import { BetSignUpPrompt } from './sign-up-prompt'
|
||||||
import { User } from 'web/lib/firebase/users'
|
import { User } from 'web/lib/firebase/users'
|
||||||
import { SellRow } from './sell-row'
|
import { SellRow } from './sell-row'
|
||||||
import { useUnfilledBets } from 'web/hooks/use-bets'
|
import { useUnfilledBetsAndBalanceByUserId } from 'web/hooks/use-bets'
|
||||||
import { PlayMoneyDisclaimer } from './play-money-disclaimer'
|
import { PlayMoneyDisclaimer } from './play-money-disclaimer'
|
||||||
|
|
||||||
/** Button that opens BetPanel in a new modal */
|
/** Button that opens BetPanel in a new modal */
|
||||||
|
@ -100,7 +100,9 @@ export function SignedInBinaryMobileBetting(props: {
|
||||||
user: User
|
user: User
|
||||||
}) {
|
}) {
|
||||||
const { contract, user } = props
|
const { contract, user } = props
|
||||||
const unfilledBets = useUnfilledBets(contract.id) ?? []
|
const { unfilledBets, balanceByUserId } = useUnfilledBetsAndBalanceByUserId(
|
||||||
|
contract.id
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -111,6 +113,7 @@ export function SignedInBinaryMobileBetting(props: {
|
||||||
contract={contract as CPMMBinaryContract}
|
contract={contract as CPMMBinaryContract}
|
||||||
user={user}
|
user={user}
|
||||||
unfilledBets={unfilledBets}
|
unfilledBets={unfilledBets}
|
||||||
|
balanceByUserId={balanceByUserId}
|
||||||
mobileView={true}
|
mobileView={true}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { BuyAmountInput } from './amount-input'
|
||||||
import { Button } from './button'
|
import { Button } from './button'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
import { YesNoSelector } from './yes-no-selector'
|
import { YesNoSelector } from './yes-no-selector'
|
||||||
import { useUnfilledBets } from 'web/hooks/use-bets'
|
import { useUnfilledBetsAndBalanceByUserId } from 'web/hooks/use-bets'
|
||||||
import { useUser } from 'web/hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { BetSignUpPrompt } from './sign-up-prompt'
|
import { BetSignUpPrompt } from './sign-up-prompt'
|
||||||
import { getCpmmProbability } from 'common/calculate-cpmm'
|
import { getCpmmProbability } from 'common/calculate-cpmm'
|
||||||
|
@ -34,14 +34,17 @@ export function BetInline(props: {
|
||||||
const [error, setError] = useState<string>()
|
const [error, setError] = useState<string>()
|
||||||
|
|
||||||
const isPseudoNumeric = contract.outcomeType === 'PSEUDO_NUMERIC'
|
const isPseudoNumeric = contract.outcomeType === 'PSEUDO_NUMERIC'
|
||||||
const unfilledBets = useUnfilledBets(contract.id) ?? []
|
const { unfilledBets, balanceByUserId } = useUnfilledBetsAndBalanceByUserId(
|
||||||
|
contract.id
|
||||||
|
)
|
||||||
|
|
||||||
const { newPool, newP } = getBinaryCpmmBetInfo(
|
const { newPool, newP } = getBinaryCpmmBetInfo(
|
||||||
outcome ?? 'YES',
|
outcome ?? 'YES',
|
||||||
amount ?? 0,
|
amount ?? 0,
|
||||||
contract,
|
contract,
|
||||||
undefined,
|
undefined,
|
||||||
unfilledBets
|
unfilledBets,
|
||||||
|
balanceByUserId
|
||||||
)
|
)
|
||||||
const resultProb = getCpmmProbability(newPool, newP)
|
const resultProb = getCpmmProbability(newPool, newP)
|
||||||
useEffect(() => setProbAfter(resultProb), [setProbAfter, resultProb])
|
useEffect(() => setProbAfter(resultProb), [setProbAfter, resultProb])
|
||||||
|
|
|
@ -35,7 +35,7 @@ import { useSaveBinaryShares } from './use-save-binary-shares'
|
||||||
import { BetSignUpPrompt } from './sign-up-prompt'
|
import { BetSignUpPrompt } from './sign-up-prompt'
|
||||||
import { ProbabilityOrNumericInput } from './probability-input'
|
import { ProbabilityOrNumericInput } from './probability-input'
|
||||||
import { track } from 'web/lib/service/analytics'
|
import { track } from 'web/lib/service/analytics'
|
||||||
import { useUnfilledBets } from 'web/hooks/use-bets'
|
import { useUnfilledBetsAndBalanceByUserId } from 'web/hooks/use-bets'
|
||||||
import { LimitBets } from './limit-bets'
|
import { LimitBets } from './limit-bets'
|
||||||
import { PillButton } from './buttons/pill-button'
|
import { PillButton } from './buttons/pill-button'
|
||||||
import { YesNoSelector } from './yes-no-selector'
|
import { YesNoSelector } from './yes-no-selector'
|
||||||
|
@ -55,7 +55,9 @@ export function BetPanel(props: {
|
||||||
const { contract, className } = props
|
const { contract, className } = props
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
const userBets = useUserContractBets(user?.id, contract.id)
|
const userBets = useUserContractBets(user?.id, contract.id)
|
||||||
const unfilledBets = useUnfilledBets(contract.id) ?? []
|
const { unfilledBets, balanceByUserId } = useUnfilledBetsAndBalanceByUserId(
|
||||||
|
contract.id
|
||||||
|
)
|
||||||
const { sharesOutcome } = useSaveBinaryShares(contract, userBets)
|
const { sharesOutcome } = useSaveBinaryShares(contract, userBets)
|
||||||
|
|
||||||
const [isLimitOrder, setIsLimitOrder] = useState(false)
|
const [isLimitOrder, setIsLimitOrder] = useState(false)
|
||||||
|
@ -86,12 +88,14 @@ export function BetPanel(props: {
|
||||||
contract={contract}
|
contract={contract}
|
||||||
user={user}
|
user={user}
|
||||||
unfilledBets={unfilledBets}
|
unfilledBets={unfilledBets}
|
||||||
|
balanceByUserId={balanceByUserId}
|
||||||
/>
|
/>
|
||||||
<LimitOrderPanel
|
<LimitOrderPanel
|
||||||
hidden={!isLimitOrder}
|
hidden={!isLimitOrder}
|
||||||
contract={contract}
|
contract={contract}
|
||||||
user={user}
|
user={user}
|
||||||
unfilledBets={unfilledBets}
|
unfilledBets={unfilledBets}
|
||||||
|
balanceByUserId={balanceByUserId}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
@ -117,7 +121,9 @@ export function SimpleBetPanel(props: {
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
const [isLimitOrder, setIsLimitOrder] = useState(false)
|
const [isLimitOrder, setIsLimitOrder] = useState(false)
|
||||||
|
|
||||||
const unfilledBets = useUnfilledBets(contract.id) ?? []
|
const { unfilledBets, balanceByUserId } = useUnfilledBetsAndBalanceByUserId(
|
||||||
|
contract.id
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col className={className}>
|
<Col className={className}>
|
||||||
|
@ -142,6 +148,7 @@ export function SimpleBetPanel(props: {
|
||||||
contract={contract}
|
contract={contract}
|
||||||
user={user}
|
user={user}
|
||||||
unfilledBets={unfilledBets}
|
unfilledBets={unfilledBets}
|
||||||
|
balanceByUserId={balanceByUserId}
|
||||||
onBuySuccess={onBetSuccess}
|
onBuySuccess={onBetSuccess}
|
||||||
/>
|
/>
|
||||||
<LimitOrderPanel
|
<LimitOrderPanel
|
||||||
|
@ -149,6 +156,7 @@ export function SimpleBetPanel(props: {
|
||||||
contract={contract}
|
contract={contract}
|
||||||
user={user}
|
user={user}
|
||||||
unfilledBets={unfilledBets}
|
unfilledBets={unfilledBets}
|
||||||
|
balanceByUserId={balanceByUserId}
|
||||||
onBuySuccess={onBetSuccess}
|
onBuySuccess={onBetSuccess}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -167,13 +175,21 @@ export function SimpleBetPanel(props: {
|
||||||
export function BuyPanel(props: {
|
export function BuyPanel(props: {
|
||||||
contract: CPMMBinaryContract | PseudoNumericContract
|
contract: CPMMBinaryContract | PseudoNumericContract
|
||||||
user: User | null | undefined
|
user: User | null | undefined
|
||||||
unfilledBets: Bet[]
|
unfilledBets: LimitBet[]
|
||||||
|
balanceByUserId: { [userId: string]: number }
|
||||||
hidden: boolean
|
hidden: boolean
|
||||||
onBuySuccess?: () => void
|
onBuySuccess?: () => void
|
||||||
mobileView?: boolean
|
mobileView?: boolean
|
||||||
}) {
|
}) {
|
||||||
const { contract, user, unfilledBets, hidden, onBuySuccess, mobileView } =
|
const {
|
||||||
props
|
contract,
|
||||||
|
user,
|
||||||
|
unfilledBets,
|
||||||
|
balanceByUserId,
|
||||||
|
hidden,
|
||||||
|
onBuySuccess,
|
||||||
|
mobileView,
|
||||||
|
} = props
|
||||||
|
|
||||||
const initialProb = getProbability(contract)
|
const initialProb = getProbability(contract)
|
||||||
const isPseudoNumeric = contract.outcomeType === 'PSEUDO_NUMERIC'
|
const isPseudoNumeric = contract.outcomeType === 'PSEUDO_NUMERIC'
|
||||||
|
@ -261,7 +277,8 @@ export function BuyPanel(props: {
|
||||||
betAmount ?? 0,
|
betAmount ?? 0,
|
||||||
contract,
|
contract,
|
||||||
undefined,
|
undefined,
|
||||||
unfilledBets as LimitBet[]
|
unfilledBets,
|
||||||
|
balanceByUserId
|
||||||
)
|
)
|
||||||
|
|
||||||
const [seeLimit, setSeeLimit] = useState(false)
|
const [seeLimit, setSeeLimit] = useState(false)
|
||||||
|
@ -416,6 +433,7 @@ export function BuyPanel(props: {
|
||||||
contract={contract}
|
contract={contract}
|
||||||
user={user}
|
user={user}
|
||||||
unfilledBets={unfilledBets}
|
unfilledBets={unfilledBets}
|
||||||
|
balanceByUserId={balanceByUserId}
|
||||||
/>
|
/>
|
||||||
<LimitBets
|
<LimitBets
|
||||||
contract={contract}
|
contract={contract}
|
||||||
|
@ -431,11 +449,19 @@ export function BuyPanel(props: {
|
||||||
function LimitOrderPanel(props: {
|
function LimitOrderPanel(props: {
|
||||||
contract: CPMMBinaryContract | PseudoNumericContract
|
contract: CPMMBinaryContract | PseudoNumericContract
|
||||||
user: User | null | undefined
|
user: User | null | undefined
|
||||||
unfilledBets: Bet[]
|
unfilledBets: LimitBet[]
|
||||||
|
balanceByUserId: { [userId: string]: number }
|
||||||
hidden: boolean
|
hidden: boolean
|
||||||
onBuySuccess?: () => void
|
onBuySuccess?: () => void
|
||||||
}) {
|
}) {
|
||||||
const { contract, user, unfilledBets, hidden, onBuySuccess } = props
|
const {
|
||||||
|
contract,
|
||||||
|
user,
|
||||||
|
unfilledBets,
|
||||||
|
balanceByUserId,
|
||||||
|
hidden,
|
||||||
|
onBuySuccess,
|
||||||
|
} = props
|
||||||
|
|
||||||
const initialProb = getProbability(contract)
|
const initialProb = getProbability(contract)
|
||||||
const isPseudoNumeric = contract.outcomeType === 'PSEUDO_NUMERIC'
|
const isPseudoNumeric = contract.outcomeType === 'PSEUDO_NUMERIC'
|
||||||
|
@ -581,7 +607,8 @@ function LimitOrderPanel(props: {
|
||||||
yesAmount,
|
yesAmount,
|
||||||
contract,
|
contract,
|
||||||
yesLimitProb ?? initialProb,
|
yesLimitProb ?? initialProb,
|
||||||
unfilledBets as LimitBet[]
|
unfilledBets,
|
||||||
|
balanceByUserId
|
||||||
)
|
)
|
||||||
const yesReturnPercent = formatPercent(yesReturn)
|
const yesReturnPercent = formatPercent(yesReturn)
|
||||||
|
|
||||||
|
@ -595,7 +622,8 @@ function LimitOrderPanel(props: {
|
||||||
noAmount,
|
noAmount,
|
||||||
contract,
|
contract,
|
||||||
noLimitProb ?? initialProb,
|
noLimitProb ?? initialProb,
|
||||||
unfilledBets as LimitBet[]
|
unfilledBets,
|
||||||
|
balanceByUserId
|
||||||
)
|
)
|
||||||
const noReturnPercent = formatPercent(noReturn)
|
const noReturnPercent = formatPercent(noReturn)
|
||||||
|
|
||||||
|
@ -830,7 +858,9 @@ export function SellPanel(props: {
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
const [wasSubmitted, setWasSubmitted] = useState(false)
|
const [wasSubmitted, setWasSubmitted] = useState(false)
|
||||||
|
|
||||||
const unfilledBets = useUnfilledBets(contract.id) ?? []
|
const { unfilledBets, balanceByUserId } = useUnfilledBetsAndBalanceByUserId(
|
||||||
|
contract.id
|
||||||
|
)
|
||||||
|
|
||||||
const betDisabled = isSubmitting || !amount || error !== undefined
|
const betDisabled = isSubmitting || !amount || error !== undefined
|
||||||
|
|
||||||
|
@ -889,7 +919,8 @@ export function SellPanel(props: {
|
||||||
contract,
|
contract,
|
||||||
sellQuantity ?? 0,
|
sellQuantity ?? 0,
|
||||||
sharesOutcome,
|
sharesOutcome,
|
||||||
unfilledBets
|
unfilledBets,
|
||||||
|
balanceByUserId
|
||||||
)
|
)
|
||||||
const netProceeds = saleValue - loanPaid
|
const netProceeds = saleValue - loanPaid
|
||||||
const profit = saleValue - costBasis
|
const profit = saleValue - costBasis
|
||||||
|
|
|
@ -37,7 +37,7 @@ import { NumericContract } from 'common/contract'
|
||||||
import { formatNumericProbability } from 'common/pseudo-numeric'
|
import { formatNumericProbability } from 'common/pseudo-numeric'
|
||||||
import { useUser } from 'web/hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { useUserBets } from 'web/hooks/use-user-bets'
|
import { useUserBets } from 'web/hooks/use-user-bets'
|
||||||
import { useUnfilledBets } from 'web/hooks/use-bets'
|
import { useUnfilledBetsAndBalanceByUserId } from 'web/hooks/use-bets'
|
||||||
import { LimitBet } from 'common/bet'
|
import { LimitBet } from 'common/bet'
|
||||||
import { Pagination } from './pagination'
|
import { Pagination } from './pagination'
|
||||||
import { LimitOrderTable } from './limit-bets'
|
import { LimitOrderTable } from './limit-bets'
|
||||||
|
@ -412,7 +412,9 @@ export function ContractBetsTable(props: {
|
||||||
const isNumeric = outcomeType === 'NUMERIC'
|
const isNumeric = outcomeType === 'NUMERIC'
|
||||||
const isPseudoNumeric = outcomeType === 'PSEUDO_NUMERIC'
|
const isPseudoNumeric = outcomeType === 'PSEUDO_NUMERIC'
|
||||||
|
|
||||||
const unfilledBets = useUnfilledBets(contract.id) ?? []
|
const { unfilledBets, balanceByUserId } = useUnfilledBetsAndBalanceByUserId(
|
||||||
|
contract.id
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
|
@ -461,6 +463,7 @@ export function ContractBetsTable(props: {
|
||||||
contract={contract}
|
contract={contract}
|
||||||
isYourBet={isYourBets}
|
isYourBet={isYourBets}
|
||||||
unfilledBets={unfilledBets}
|
unfilledBets={unfilledBets}
|
||||||
|
balanceByUserId={balanceByUserId}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -475,8 +478,10 @@ function BetRow(props: {
|
||||||
saleBet?: Bet
|
saleBet?: Bet
|
||||||
isYourBet: boolean
|
isYourBet: boolean
|
||||||
unfilledBets: LimitBet[]
|
unfilledBets: LimitBet[]
|
||||||
|
balanceByUserId: { [userId: string]: number }
|
||||||
}) {
|
}) {
|
||||||
const { bet, saleBet, contract, isYourBet, unfilledBets } = props
|
const { bet, saleBet, contract, isYourBet, unfilledBets, balanceByUserId } =
|
||||||
|
props
|
||||||
const {
|
const {
|
||||||
amount,
|
amount,
|
||||||
outcome,
|
outcome,
|
||||||
|
@ -504,9 +509,9 @@ function BetRow(props: {
|
||||||
} else if (contract.isResolved) {
|
} else if (contract.isResolved) {
|
||||||
return resolvedPayout(contract, bet)
|
return resolvedPayout(contract, bet)
|
||||||
} else {
|
} else {
|
||||||
return calculateSaleAmount(contract, bet, unfilledBets)
|
return calculateSaleAmount(contract, bet, unfilledBets, balanceByUserId)
|
||||||
}
|
}
|
||||||
}, [contract, bet, saleBet, unfilledBets])
|
}, [contract, bet, saleBet, unfilledBets, balanceByUserId])
|
||||||
|
|
||||||
const saleDisplay = isAnte ? (
|
const saleDisplay = isAnte ? (
|
||||||
'ANTE'
|
'ANTE'
|
||||||
|
@ -545,6 +550,7 @@ function BetRow(props: {
|
||||||
contract={contract}
|
contract={contract}
|
||||||
bet={bet}
|
bet={bet}
|
||||||
unfilledBets={unfilledBets}
|
unfilledBets={unfilledBets}
|
||||||
|
balanceByUserId={balanceByUserId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
|
@ -590,8 +596,9 @@ function SellButton(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
bet: Bet
|
bet: Bet
|
||||||
unfilledBets: LimitBet[]
|
unfilledBets: LimitBet[]
|
||||||
|
balanceByUserId: { [userId: string]: number }
|
||||||
}) {
|
}) {
|
||||||
const { contract, bet, unfilledBets } = props
|
const { contract, bet, unfilledBets, balanceByUserId } = props
|
||||||
const { outcome, shares, loanAmount } = bet
|
const { outcome, shares, loanAmount } = bet
|
||||||
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
|
@ -605,10 +612,16 @@ function SellButton(props: {
|
||||||
contract,
|
contract,
|
||||||
outcome,
|
outcome,
|
||||||
shares,
|
shares,
|
||||||
unfilledBets
|
unfilledBets,
|
||||||
|
balanceByUserId
|
||||||
)
|
)
|
||||||
|
|
||||||
const saleAmount = calculateSaleAmount(contract, bet, unfilledBets)
|
const saleAmount = calculateSaleAmount(
|
||||||
|
contract,
|
||||||
|
bet,
|
||||||
|
unfilledBets,
|
||||||
|
balanceByUserId
|
||||||
|
)
|
||||||
const profit = saleAmount - bet.amount
|
const profit = saleAmount - bet.amount
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -33,7 +33,7 @@ import { sellShares } from 'web/lib/firebase/api'
|
||||||
import { calculateCpmmSale, getCpmmProbability } from 'common/calculate-cpmm'
|
import { calculateCpmmSale, getCpmmProbability } from 'common/calculate-cpmm'
|
||||||
import { track } from 'web/lib/service/analytics'
|
import { track } from 'web/lib/service/analytics'
|
||||||
import { formatNumericProbability } from 'common/pseudo-numeric'
|
import { formatNumericProbability } from 'common/pseudo-numeric'
|
||||||
import { useUnfilledBets } from 'web/hooks/use-bets'
|
import { useUnfilledBetsAndBalanceByUserId } from 'web/hooks/use-bets'
|
||||||
import { getBinaryProb } from 'common/contract-details'
|
import { getBinaryProb } from 'common/contract-details'
|
||||||
|
|
||||||
const BET_SIZE = 10
|
const BET_SIZE = 10
|
||||||
|
@ -48,7 +48,10 @@ export function QuickBet(props: {
|
||||||
const isCpmm = mechanism === 'cpmm-1'
|
const isCpmm = mechanism === 'cpmm-1'
|
||||||
|
|
||||||
const userBets = useUserContractBets(user.id, contract.id)
|
const userBets = useUserContractBets(user.id, contract.id)
|
||||||
const unfilledBets = useUnfilledBets(contract.id) ?? []
|
// TODO: Below hook fetches a decent amount of data. Maybe not worth it to show prob change on hover?
|
||||||
|
const { unfilledBets, balanceByUserId } = useUnfilledBetsAndBalanceByUserId(
|
||||||
|
contract.id
|
||||||
|
)
|
||||||
|
|
||||||
const { hasYesShares, hasNoShares, yesShares, noShares } =
|
const { hasYesShares, hasNoShares, yesShares, noShares } =
|
||||||
useSaveBinaryShares(contract, userBets)
|
useSaveBinaryShares(contract, userBets)
|
||||||
|
@ -94,7 +97,8 @@ export function QuickBet(props: {
|
||||||
contract,
|
contract,
|
||||||
sharesSold,
|
sharesSold,
|
||||||
sellOutcome,
|
sellOutcome,
|
||||||
unfilledBets
|
unfilledBets,
|
||||||
|
balanceByUserId
|
||||||
)
|
)
|
||||||
saleAmount = saleValue
|
saleAmount = saleValue
|
||||||
previewProb = getCpmmProbability(cpmmState.pool, cpmmState.p)
|
previewProb = getCpmmProbability(cpmmState.pool, cpmmState.p)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
withoutAnteBets,
|
withoutAnteBets,
|
||||||
} from 'web/lib/firebase/bets'
|
} from 'web/lib/firebase/bets'
|
||||||
import { LimitBet } from 'common/bet'
|
import { LimitBet } from 'common/bet'
|
||||||
|
import { getUser } from 'web/lib/firebase/users'
|
||||||
|
|
||||||
export const useBets = (
|
export const useBets = (
|
||||||
contractId: string,
|
contractId: string,
|
||||||
|
@ -62,3 +63,31 @@ export const useUnfilledBets = (contractId: string) => {
|
||||||
)
|
)
|
||||||
return unfilledBets
|
return unfilledBets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useUnfilledBetsAndBalanceByUserId = (contractId: string) => {
|
||||||
|
const [data, setData] = useState<{
|
||||||
|
unfilledBets: LimitBet[]
|
||||||
|
balanceByUserId: { [userId: string]: number }
|
||||||
|
}>({ unfilledBets: [], balanceByUserId: {} })
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let requestCount = 0
|
||||||
|
|
||||||
|
return listenForUnfilledBets(contractId, (unfilledBets) => {
|
||||||
|
requestCount++
|
||||||
|
const count = requestCount
|
||||||
|
|
||||||
|
Promise.all(unfilledBets.map((bet) => getUser(bet.userId))).then(
|
||||||
|
(users) => {
|
||||||
|
if (count === requestCount) {
|
||||||
|
const balanceByUserId = Object.fromEntries(
|
||||||
|
users.map((user) => [user.id, user.balance])
|
||||||
|
)
|
||||||
|
setData({ unfilledBets, balanceByUserId })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}, [contractId])
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user