Verify balance of limit order "makers" (#1007)
* Fetch balance of users with open limit orders & cancel orders with insufficient balance
* Fix imports
* Fix bugs
* Fix a bug
* Remove redundant cast
* buttons overlaying content fix (#1005)
* buttons overlaying content fix
* stats: round DAU number
* made set width for portfolio/profit fields (#1006)
* tournaments: included resolved markets
* made delete red, moved button for regular posts (#1008)
* Fix localstorage saved user being overwritten on every page load
* Market page: Show no right panel while user loading
* Don't flash sign in button if user is loading
* election map coloring
* market group modal scroll fix (#1009)
* midterms: posititoning, make mobile friendly
* Un-daisy share buttons (#1010)
* Make embed and challenge buttons non-daisyui
* Allow link Buttons. Change tweet, dupe buttons.
* lint
* don't insert extra lines when upload photos
* Map fixes (#1011)
* usa map: fix sizing
* useSetIframeBackbroundColor
* preload contracts
* seo
* remove hook
* turn off sprig on dev
* Render timestamp only on client to prevent error of server not matching client
* Make sized container have default height so graph doesn't jump
* midterms: use null in static props
* Create common card component (#1012)
* Create common card component
* lint
* add key prop to pills
* redirect to /home after login
* create market: use transaction
* card: reduce border size
* Update groupContracts in db trigger
* Default sort to best
* Save comment sort per user rather than per contract
* Refactor Pinned Items into a reusable component
* Revert "create market: use transaction"
This reverts commit e1f24f24a9.
* Mark @v with a (Bot) label
* fix padding on daily movers
* fix type errors
* Wrap sprig init in check for window
* unindex date-docs from search engines
* Auto-prettification
* compute elasticity
* change dpm elasticity
* Fix google lighthouse issues (#1013)
* don't hide free response panel on open resolve
* liquidity sort
* Limit order trade log: '/' to 'of'. Remove 'of' in 'of YES'.
* Date doc: Toggle to disable creating a prediction market
* Listen for date doc changes
* Fix merge error
* Don't cancel all a users limit orders if they go negative
Co-authored-by: ingawei <46611122+ingawei@users.noreply.github.com>
Co-authored-by: mantikoros <sgrugett@gmail.com>
Co-authored-by: Sinclair Chen <abc.sinclair@gmail.com>
Co-authored-by: mantikoros <95266179+mantikoros@users.noreply.github.com>
Co-authored-by: Ian Philips <iansphilips@gmail.com>
Co-authored-by: Pico2x <pico2x@gmail.com>
Co-authored-by: Austin Chen <akrolsmir@gmail.com>
Co-authored-by: sipec <sipec@users.noreply.github.com>
			
			
This commit is contained in:
		
							parent
							
								
									42a7d04b4d
								
							
						
					
					
						commit
						f533d9bfcb
					
				|  | @ -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