Sell shares cloud function.

This commit is contained in:
James Grugett 2022-03-27 23:31:56 -05:00
parent 618bdf76a6
commit a6d2e3594b
8 changed files with 116 additions and 35 deletions

View File

@ -131,7 +131,7 @@ export function calculateCpmmShareValue(
export function calculateCpmmSale(
contract: FullContract<CPMM, Binary>,
bet: Bet
bet: { shares: number; outcome: string }
) {
const { shares, outcome } = bet

View File

@ -85,21 +85,24 @@ export const getSellBetInfo = (
export const getCpmmSellBetInfo = (
user: User,
bet: Bet,
shares: number,
outcome: 'YES' | 'NO',
contract: FullContract<CPMM, Binary>,
newBetId: string
) => {
const { pool, p } = contract
const { id: betId, amount, shares, outcome } = bet
const { saleValue, newPool, fees } = calculateCpmmSale(contract, bet)
const { saleValue, newPool, fees } = calculateCpmmSale(contract, {
shares,
outcome,
})
const probBefore = getCpmmProbability(pool, p)
const probAfter = getCpmmProbability(newPool, p)
console.log(
'SELL M$',
amount,
shares,
outcome,
'for M$',
saleValue,
@ -117,10 +120,6 @@ export const getCpmmSellBetInfo = (
probBefore,
probAfter,
createdTime: Date.now(),
sale: {
amount: saleValue,
betId,
},
fees,
}

View File

@ -7,6 +7,7 @@ export * from './place-bet'
export * from './resolve-market'
export * from './stripe'
export * from './sell-bet'
export * from './sell-shares'
export * from './create-contract'
export * from './create-user'
export * from './create-fold'

View File

@ -4,7 +4,7 @@ import * as functions from 'firebase-functions'
import { Contract } from '../../common/contract'
import { User } from '../../common/user'
import { Bet } from '../../common/bet'
import { getCpmmSellBetInfo, getSellBetInfo } from '../../common/sell-bet'
import { getSellBetInfo } from '../../common/sell-bet'
import { addObjects, removeUndefinedProps } from '../../common/util/object'
import { Fees } from '../../common/fees'
@ -34,8 +34,14 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall(
if (!contractSnap.exists)
return { status: 'error', message: 'Invalid contract' }
const contract = contractSnap.data() as Contract
const { closeTime, mechanism, collectedFees, volume } = contract
if (mechanism !== 'dpm-2')
return {
status: 'error',
message: 'Sell shares only works with mechanism dpm-2',
}
if (closeTime && Date.now() > closeTime)
return { status: 'error', message: 'Trading is closed' }
@ -57,15 +63,7 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall(
newTotalBets,
newBalance,
fees,
} =
mechanism === 'dpm-2'
? getSellBetInfo(user, bet, contract, newBetDoc.id)
: (getCpmmSellBetInfo(
user,
bet,
contract as any,
newBetDoc.id
) as any)
} = getSellBetInfo(user, bet, contract, newBetDoc.id)
if (!isFinite(newBalance)) {
throw new Error('Invalid user balance for ' + user.username)
@ -81,7 +79,7 @@ export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall(
totalShares: newTotalShares,
totalBets: newTotalBets,
collectedFees: addObjects<Fees>(fees ?? {}, collectedFees ?? {}),
volume: volume + bet.amount,
volume: volume + Math.abs(newBet.amount),
})
)

View File

@ -0,0 +1,78 @@
import * as admin from 'firebase-admin'
import * as functions from 'firebase-functions'
import { Binary, CPMM, FullContract } from '../../common/contract'
import { User } from '../../common/user'
import { getCpmmSellBetInfo } from '../../common/sell-bet'
import { addObjects, removeUndefinedProps } from '../../common/util/object'
export const sellShares = functions.runWith({ minInstances: 1 }).https.onCall(
async (
data: {
contractId: string
shares: number
outcome: 'YES' | 'NO'
},
context
) => {
const userId = context?.auth?.uid
if (!userId) return { status: 'error', message: 'Not authorized' }
const { contractId, shares, outcome } = data
// Run as transaction to prevent race conditions.
return await firestore.runTransaction(async (transaction) => {
const userDoc = firestore.doc(`users/${userId}`)
const userSnap = await transaction.get(userDoc)
if (!userSnap.exists)
return { status: 'error', message: 'User not found' }
const user = userSnap.data() as User
const contractDoc = firestore.doc(`contracts/${contractId}`)
const contractSnap = await transaction.get(contractDoc)
if (!contractSnap.exists)
return { status: 'error', message: 'Invalid contract' }
const contract = contractSnap.data() as FullContract<CPMM, Binary>
const { closeTime, mechanism, collectedFees, volume } = contract
if (mechanism !== 'cpmm-1')
return {
status: 'error',
message: 'Sell shares only works with mechanism cpmm-1',
}
if (closeTime && Date.now() > closeTime)
return { status: 'error', message: 'Trading is closed' }
const newBetDoc = firestore
.collection(`contracts/${contractId}/bets`)
.doc()
const { newBet, newPool, newBalance, fees } = getCpmmSellBetInfo(
user,
shares,
outcome,
contract,
newBetDoc.id
)
if (!isFinite(newBalance)) {
throw new Error('Invalid user balance for ' + user.username)
}
transaction.update(userDoc, { balance: newBalance })
transaction.create(newBetDoc, newBet)
transaction.update(
contractDoc,
removeUndefinedProps({
pool: newPool,
collectedFees: addObjects(fees ?? {}, collectedFees ?? {}),
volume: volume + Math.abs(newBet.amount),
})
)
return { status: 'success' }
})
}
)
const firestore = admin.firestore()

View File

@ -212,12 +212,12 @@ export function SellAmountInput(props: {
const prevLoanAmount = _.sumBy(openUserBets, (bet) => bet.loanAmount ?? 0)
const sharesSold = Math.min(amount ?? 0, yesShares || noShares)
const sellAmount = calculateCpmmSale(contract, {
const { saleValue } = calculateCpmmSale(contract, {
shares: sharesSold,
outcome: sellOutcome,
} as Bet).saleValue
outcome: sellOutcome as 'YES' | 'NO',
})
const loanRepaid = Math.min(prevLoanAmount, sellAmount)
const loanRepaid = Math.min(prevLoanAmount, saleValue)
const onAmountChange = (amount: number | undefined) => {
onChange(amount)
@ -248,7 +248,7 @@ export function SellAmountInput(props: {
<Col className="gap-3 text-sm">
<Row className="items-center justify-between gap-2 text-gray-500">
Sale proceeds{' '}
<span className="text-neutral">{formatMoney(sellAmount)}</span>
<span className="text-neutral">{formatMoney(saleValue)}</span>
</Row>
{prevLoanAmount && (
<Row className="items-center justify-between gap-2 text-gray-500">

View File

@ -16,7 +16,7 @@ import {
import { Title } from './title'
import { firebaseLogin, User } from '../lib/firebase/users'
import { Bet } from '../../common/bet'
import { placeBet } from '../lib/firebase/api-call'
import { placeBet, sellShares } from '../lib/firebase/api-call'
import { BuyAmountInput, SellAmountInput } from './amount-input'
import { InfoTooltip } from './info-tooltip'
import { OutcomeLabel } from './outcome-label'
@ -196,7 +196,7 @@ function BuyPanel(props: {
setBetAmount(undefined)
if (onBuySuccess) onBuySuccess()
} else {
setError(result?.error || 'Error placing bet')
setError(result?.message || 'Error placing bet')
setIsSubmitting(false)
}
}
@ -339,13 +339,13 @@ function SellPanel(props: {
setError(undefined)
setIsSubmitting(true)
const result = await placeBet({
const result = await sellShares({
shares: amount,
outcome: sharesOutcome,
contractId: contract.id,
}).then((r) => r.data as any)
}).then((r) => r.data)
console.log('placed bet. Result:', result)
console.log('Sold shares. Result:', result)
if (result?.status === 'success') {
setIsSubmitting(false)
@ -353,7 +353,7 @@ function SellPanel(props: {
setAmount(undefined)
if (onSellSuccess) onSellSuccess()
} else {
setError(result?.error || 'Error selling')
setError(result?.message || 'Error selling')
setIsSubmitting(false)
}
}
@ -362,7 +362,7 @@ function SellPanel(props: {
const { newPool } = calculateCpmmSale(contract, {
shares: amount ?? 0,
outcome: sharesOutcome,
} as Bet)
})
const resultProb = getCpmmProbability(newPool, contract.p)
return (

View File

@ -18,6 +18,13 @@ export const createFold = cloudFunction<
export const placeBet = cloudFunction('placeBet')
export const sellBet = cloudFunction('sellBet')
export const sellShares = cloudFunction<
{ contractId: string; shares: number; outcome: 'YES' | 'NO' },
{ status: 'error' | 'success'; message?: string }
>('sellShares')
export const createAnswer = cloudFunction<
{ contractId: string; text: string; amount: number },
{
@ -38,8 +45,6 @@ export const resolveMarket = cloudFunction<
{ status: 'error' | 'success'; message?: string }
>('resolveMarket')
export const sellBet = cloudFunction('sellBet')
export const createUser: () => Promise<User | null> = () => {
let deviceToken = window.localStorage.getItem('device-token')
if (!deviceToken) {