manifold/functions/src/place-bet.ts

119 lines
3.2 KiB
TypeScript
Raw Normal View History

2021-12-11 00:06:17 +00:00
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import { Contract } from './types/contract'
import { User } from './types/user'
import { Bet } from './types/bet'
export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
async (
data: {
amount: number
outcome: string
contractId: string
},
context
) => {
2021-12-11 03:45:05 +00:00
const userId = context?.auth?.uid
if (!userId) return { status: 'error', message: 'Not authorized' }
2021-12-11 03:45:05 +00:00
const { amount, outcome, contractId } = data
if (outcome !== 'YES' && outcome !== 'NO')
return { status: 'error', message: 'Invalid outcome' }
// run as transaction to prevent race conditions
return await firestore.runTransaction(async (transaction) => {
2021-12-11 03:45:05 +00:00
const userDoc = firestore.doc(`users/${userId}`)
const userSnap = await transaction.get(userDoc)
if (!userSnap.exists)
return { status: 'error', message: 'User not found' }
2021-12-11 03:45:05 +00:00
const user = userSnap.data() as User
2021-12-13 17:58:47 +00:00
if (user.balance < amount)
2021-12-11 03:45:05 +00:00
return { status: 'error', message: 'Insufficient balance' }
const contractDoc = firestore.doc(`contracts/${contractId}`)
const contractSnap = await transaction.get(contractDoc)
if (!contractSnap.exists)
return { status: 'error', message: 'Invalid contract' }
2021-12-11 03:45:05 +00:00
const contract = contractSnap.data() as Contract
const newBetDoc = firestore
.collection(`contracts/${contractId}/bets`)
.doc()
2021-12-11 03:45:05 +00:00
2021-12-15 22:44:22 +00:00
const { newBet, newPot, newDpmWeights, newBalance } = getNewBetInfo(
user,
outcome,
amount,
contract,
newBetDoc.id
)
2021-12-11 03:45:05 +00:00
transaction.create(newBetDoc, newBet)
2021-12-15 22:44:22 +00:00
transaction.update(contractDoc, { pot: newPot, dpmWeights: newDpmWeights })
2021-12-13 17:58:47 +00:00
transaction.update(userDoc, { balance: newBalance })
2021-12-11 03:45:05 +00:00
return { status: 'success' }
})
}
)
2021-12-11 00:06:17 +00:00
const firestore = admin.firestore()
const getNewBetInfo = (
user: User,
outcome: 'YES' | 'NO',
amount: number,
contract: Contract,
newBetId: string
) => {
2021-12-11 00:06:17 +00:00
const { YES: yesPot, NO: noPot } = contract.pot
2021-12-15 22:44:22 +00:00
const newPot =
outcome === 'YES'
? { YES: yesPot + amount, NO: noPot }
: { YES: yesPot, NO: noPot + amount }
const dpmWeight =
outcome === 'YES'
? (amount * noPot ** 2) / (yesPot ** 2 + amount * yesPot)
: (amount * yesPot ** 2) / (noPot ** 2 + amount * noPot)
2021-12-15 22:44:22 +00:00
const { YES: yesWeight, NO: noWeight } = contract.dpmWeights
2021-12-15 22:57:16 +00:00
|| { YES: 0, NO: 0 } // only nesc for old contracts
2021-12-15 22:44:22 +00:00
const newDpmWeights =
outcome === 'YES'
2021-12-15 22:44:22 +00:00
? { YES: yesWeight + dpmWeight, NO: noWeight }
: { YES: yesWeight, NO: noWeight + dpmWeight }
const probBefore = yesPot ** 2 / (yesPot ** 2 + noPot ** 2)
const probAverage =
(amount +
noPot * Math.atan(yesPot / noPot) -
noPot * Math.atan((amount + yesPot) / noPot)) /
amount
const probAfter = newPot.YES ** 2 / (newPot.YES ** 2 + newPot.NO ** 2)
2021-12-11 00:06:17 +00:00
const newBet: Bet = {
id: newBetId,
userId: user.id,
contractId: contract.id,
amount,
dpmWeight,
outcome,
probBefore,
probAverage,
probAfter,
createdTime: Date.now(),
2021-12-11 00:06:17 +00:00
}
2021-12-13 17:58:47 +00:00
const newBalance = user.balance - amount
2021-12-11 00:06:17 +00:00
2021-12-15 22:44:22 +00:00
return { newBet, newPot, newDpmWeights, newBalance }
}