diff --git a/common/antes.ts b/common/antes.ts index 979b9d87..c396edad 100644 --- a/common/antes.ts +++ b/common/antes.ts @@ -64,7 +64,7 @@ export function getAnteBets( export function getFreeAnswerAnte( creator: User, - contract: Contract<'MULTI'>, + contract: Contract, anteBetId: string ) { const { totalBets, totalShares } = contract @@ -73,7 +73,7 @@ export function getFreeAnswerAnte( const { createdTime } = contract - const anteBet: Bet<'MULTI'> = { + const anteBet: Bet = { id: anteBetId, userId: creator.id, contractId: contract.id, diff --git a/common/bet.ts b/common/bet.ts index 96429fa2..7da4b18c 100644 --- a/common/bet.ts +++ b/common/bet.ts @@ -1,13 +1,10 @@ -export type Bet = { +export type Bet = { id: string userId: string contractId: string amount: number // bet size; negative if SELL bet - outcome: { - BINARY: 'YES' | 'NO' - MULTI: string - }[outcomeType] + outcome: string shares: number // dynamic parimutuel pool weight; negative if SELL bet probBefore: number diff --git a/common/calculate.ts b/common/calculate.ts index b995bf4d..865d8caf 100644 --- a/common/calculate.ts +++ b/common/calculate.ts @@ -3,7 +3,8 @@ import { Bet } from './bet' import { Contract } from './contract' import { FEES } from './fees' -export function getProbability(totalShares: { YES: number; NO: number }) { +export function getProbability(totalShares: { [outcome: string]: number }) { + // For binary contracts only. return getOutcomeProbability(totalShares, 'YES') } @@ -70,9 +71,9 @@ export function calculateRawShareValue( return currentValue - postSaleValue } -export function calculateMoneyRatio( - contract: Contract, - bet: Bet, +export function calculateMoneyRatio( + contract: Contract, + bet: Bet, shareValue: number ) { const { totalShares, totalBets, pool } = contract @@ -115,11 +116,7 @@ export function calculateSaleAmount(contract: Contract, bet: Bet) { return deductFees(amount, winnings) } -export function calculatePayout( - contract: Contract, - bet: Bet, - outcome: 'YES' | 'NO' | 'CANCEL' | 'MKT' -) { +export function calculatePayout(contract: Contract, bet: Bet, outcome: string) { if (outcome === 'CANCEL') return calculateCancelPayout(contract, bet) if (outcome === 'MKT') return calculateMktPayout(contract, bet) @@ -182,7 +179,7 @@ function calculateMktPayout(contract: Contract, bet: Bet) { if (contract.outcomeType === 'BINARY') return calculateBinaryMktPayout(contract, bet) - const { totalShares, pool } = contract as any as Contract<'MULTI'> + const { totalShares, pool } = contract const totalPool = _.sum(Object.values(pool)) const sharesSquareSum = _.sumBy( @@ -206,16 +203,17 @@ function calculateMktPayout(contract: Contract, bet: Bet) { } function calculateBinaryMktPayout(contract: Contract, bet: Bet) { + const { resolutionProbability, totalShares, phantomShares } = contract const p = - contract.resolutionProbability !== undefined - ? contract.resolutionProbability - : getProbability(contract.totalShares) + resolutionProbability !== undefined + ? resolutionProbability + : getProbability(totalShares) const pool = contract.pool.YES + contract.pool.NO const weightedShareTotal = - p * (contract.totalShares.YES - contract.phantomShares.YES) + - (1 - p) * (contract.totalShares.NO - contract.phantomShares.NO) + p * (totalShares.YES - (phantomShares?.YES ?? 0)) + + (1 - p) * (totalShares.NO - (phantomShares?.NO ?? 0)) const { outcome, amount, shares } = bet diff --git a/common/contract.ts b/common/contract.ts index b77f7606..c977fdff 100644 --- a/common/contract.ts +++ b/common/contract.ts @@ -1,4 +1,4 @@ -export type Contract = { +export type Contract = { id: string slug: string // auto-generated; must be unique @@ -13,29 +13,14 @@ export type Contract = { lowercaseTags: string[] visibility: 'public' | 'unlisted' - outcomeType: outcomeType - outcomes: { - BINARY: undefined - MULTI: 'FREE_ANSWER' | string[] - }[outcomeType] + outcomeType: 'BINARY' | 'MULTI' + outcomes?: 'FREE_ANSWER' | string[] mechanism: 'dpm-2' - phantomShares: { - BINARY: { YES: number; NO: number } - MULTI: undefined - }[outcomeType] - pool: { - BINARY: { YES: number; NO: number } - MULTI: { [answerId: string]: number } - }[outcomeType] - totalShares: { - BINARY: { YES: number; NO: number } - MULTI: { [answerId: string]: number } - }[outcomeType] - totalBets: { - BINARY: { YES: number; NO: number } - MULTI: { [answerId: string]: number } - }[outcomeType] + phantomShares?: { [outcome: string]: number } + pool: { [outcome: string]: number } + totalShares: { [outcome: string]: number } + totalBets: { [outcome: string]: number } createdTime: number // Milliseconds since epoch lastUpdatedTime: number // If the question or description was changed @@ -43,14 +28,9 @@ export type Contract = { isResolved: boolean resolutionTime?: number // When the contract creator resolved the market - resolution?: { - BINARY: outcome - MULTI: string - }[outcomeType] + resolution?: string resolutionProbability?: number volume24Hours: number volume7Days: number } - -export type outcome = 'YES' | 'NO' | 'CANCEL' | 'MKT' diff --git a/common/new-bet.ts b/common/new-bet.ts index 78862b9d..29fd421a 100644 --- a/common/new-bet.ts +++ b/common/new-bet.ts @@ -61,7 +61,7 @@ export const getNewMultiBetInfo = ( user: User, outcome: string, amount: number, - contract: Contract<'MULTI'>, + contract: Contract, newBetId: string ) => { const { pool, totalShares, totalBets } = contract @@ -80,7 +80,7 @@ export const getNewMultiBetInfo = ( const probBefore = getOutcomeProbability(totalShares, outcome) const probAfter = getOutcomeProbability(newTotalShares, outcome) - const newBet: Bet<'MULTI'> = { + const newBet: Bet = { id: newBetId, userId: user.id, contractId: contract.id, diff --git a/common/new-contract.ts b/common/new-contract.ts index bc4f1beb..c7b56c90 100644 --- a/common/new-contract.ts +++ b/common/new-contract.ts @@ -26,7 +26,7 @@ export function getNewContract( ? getBinaryProps(initialProb, ante) : getFreeAnswerProps(ante) - const contract: Contract<'BINARY' | 'MULTI'> = removeUndefinedProps({ + const contract: Contract = removeUndefinedProps({ id, slug, mechanism: 'dpm-2', diff --git a/common/payouts.ts b/common/payouts.ts index e68f58c8..0b917943 100644 --- a/common/payouts.ts +++ b/common/payouts.ts @@ -2,7 +2,7 @@ import * as _ from 'lodash' import { Bet } from './bet' import { deductFees, getProbability } from './calculate' -import { Contract, outcome } from './contract' +import { Contract } from './contract' import { CREATOR_FEE, FEES } from './fees' export const getCancelPayouts = (contract: Contract, bets: Bet[]) => { @@ -100,7 +100,7 @@ export const getMktPayouts = ( } export const getPayouts = ( - outcome: outcome, + outcome: string, contract: Contract, bets: Bet[], resolutionProbability?: number diff --git a/functions/src/create-answer.ts b/functions/src/create-answer.ts index d8cc4a90..896dc953 100644 --- a/functions/src/create-answer.ts +++ b/functions/src/create-answer.ts @@ -42,7 +42,7 @@ export const createAnswer = functions.runWith({ minInstances: 1 }).https.onCall( const contractSnap = await transaction.get(contractDoc) if (!contractSnap.exists) return { status: 'error', message: 'Invalid contract' } - const contract = contractSnap.data() as Contract<'MULTI'> + const contract = contractSnap.data() as Contract if ( contract.outcomeType !== 'MULTI' || diff --git a/functions/src/create-contract.ts b/functions/src/create-contract.ts index bc02a0b8..18042768 100644 --- a/functions/src/create-contract.ts +++ b/functions/src/create-contract.ts @@ -100,7 +100,7 @@ export const createContract = functions const { yesBet, noBet } = getAnteBets( creator, - contract as Contract<'BINARY'>, + contract, yesBetDoc.id, noBetDoc.id ) @@ -116,11 +116,7 @@ export const createContract = functions const anteBetDoc = firestore .collection(`contracts/${contract.id}/bets`) .doc() - const anteBet = getFreeAnswerAnte( - creator, - contract as Contract<'MULTI'>, - anteBetDoc.id - ) + const anteBet = getFreeAnswerAnte(creator, contract, anteBetDoc.id) await anteBetDoc.set(anteBet) } } diff --git a/web/components/answers-panel.tsx b/web/components/answers-panel.tsx index da30729f..ac2d643c 100644 --- a/web/components/answers-panel.tsx +++ b/web/components/answers-panel.tsx @@ -35,10 +35,7 @@ import { useAnswers } from '../hooks/use-answers' import { ResolveConfirmationButton } from './confirmation-button' import { tradingAllowed } from '../lib/firebase/contracts' -export function AnswersPanel(props: { - contract: Contract<'MULTI'> - answers: Answer[] -}) { +export function AnswersPanel(props: { contract: Contract; answers: Answer[] }) { const { contract } = props const { creatorId, resolution } = contract @@ -101,7 +98,7 @@ export function AnswersPanel(props: { function AnswerItem(props: { answer: Answer - contract: Contract<'MULTI'> + contract: Contract showChoice: boolean isChosen: boolean onChoose: () => void @@ -209,7 +206,7 @@ function AnswerItem(props: { function AnswerBetPanel(props: { answer: Answer - contract: Contract<'MULTI'> + contract: Contract closePanel: () => void }) { const { answer, contract, closePanel } = props @@ -267,14 +264,11 @@ function AnswerBetPanel(props: { const shares = calculateShares(contract.totalShares, betAmount ?? 0, answerId) const currentPayout = betAmount - ? calculatePayoutAfterCorrectBet( - contract as any as Contract, - { - outcome: answerId, - amount: betAmount, - shares, - } as Bet - ) + ? calculatePayoutAfterCorrectBet(contract, { + outcome: answerId, + amount: betAmount, + shares, + } as Bet) : 0 const currentReturn = betAmount ? (currentPayout - betAmount) / betAmount : 0 @@ -351,7 +345,7 @@ function AnswerBetPanel(props: { ) } -function CreateAnswerInput(props: { contract: Contract<'MULTI'> }) { +function CreateAnswerInput(props: { contract: Contract }) { const { contract } = props const [text, setText] = useState('') const [betAmount, setBetAmount] = useState(10) @@ -426,7 +420,7 @@ function CreateAnswerInput(props: { contract: Contract<'MULTI'> }) { } function AnswerResolvePanel(props: { - contract: Contract<'MULTI'> + contract: Contract resolveOption: 'CHOOSE' | 'NONE' | 'CANCEL' | undefined setResolveOption: (option: 'CHOOSE' | 'NONE' | 'CANCEL' | undefined) => void answer: string | undefined diff --git a/web/components/bet-panel.tsx b/web/components/bet-panel.tsx index 3469b1c9..5eab2dd1 100644 --- a/web/components/bet-panel.tsx +++ b/web/components/bet-panel.tsx @@ -49,6 +49,7 @@ export function BetPanel(props: { }, []) const { contract, className, title, selected, onBetSuccess } = props + const { totalShares, phantomShares } = contract const user = useUser() @@ -179,8 +180,8 @@ export function BetPanel(props: { shares )} / ${formatWithCommas( shares + - contract.totalShares[betChoice] - - contract.phantomShares[betChoice] + totalShares[betChoice] - + (phantomShares ? phantomShares[betChoice] : 0) )} ${betChoice} shares`} /> diff --git a/web/components/bets-list.tsx b/web/components/bets-list.tsx index 657b35d3..514dd529 100644 --- a/web/components/bets-list.tsx +++ b/web/components/bets-list.tsx @@ -31,6 +31,7 @@ import { import { sellBet } from '../lib/firebase/api-call' import { ConfirmationButton } from './confirmation-button' import { OutcomeLabel, YesLabel, NoLabel } from './outcome-label' +import { filterDefined } from '../../common/util/array' type BetSort = 'newest' | 'profit' @@ -49,7 +50,7 @@ export function BetsList(props: { user: User }) { let disposed = false Promise.all(contractIds.map((id) => getContractFromId(id))).then( (contracts) => { - if (!disposed) setContracts(contracts.filter(Boolean) as Contract[]) + if (!disposed) setContracts(filterDefined(contracts)) } ) diff --git a/web/components/contract-feed.tsx b/web/components/contract-feed.tsx index 4df451a2..ec7d68f1 100644 --- a/web/components/contract-feed.tsx +++ b/web/components/contract-feed.tsx @@ -21,7 +21,6 @@ import { contractPath, updateContract, tradingAllowed, - getBinaryProbPercent, } from '../lib/firebase/contracts' import { useUser } from '../hooks/use-user' import { Linkify } from './linkify' @@ -38,7 +37,6 @@ import { useBets } from '../hooks/use-bets' import { Bet } from '../lib/firebase/bets' import { Comment, mapCommentsByBetId } from '../lib/firebase/comments' import { JoinSpans } from './join-spans' -import { outcome } from '../../common/contract' import { fromNow } from '../lib/util/time' import BetRow from './bet-row' import { parseTags } from '../../common/util/parse' @@ -382,7 +380,7 @@ function FeedDescription(props: { contract: Contract }) { ) } -function OutcomeIcon(props: { outcome?: outcome }) { +function OutcomeIcon(props: { outcome?: string }) { const { outcome } = props switch (outcome) { case 'YES': diff --git a/web/components/contract-prob-graph.tsx b/web/components/contract-prob-graph.tsx index 13e6c48d..23daabfb 100644 --- a/web/components/contract-prob-graph.tsx +++ b/web/components/contract-prob-graph.tsx @@ -13,7 +13,9 @@ export function ContractProbGraph(props: { contract: Contract; bets: Bet[] }) { const bets = useBetsWithoutAntes(contract, props.bets) - const startProb = getProbability(phantomShares) + const startProb = getProbability( + phantomShares as { [outcome: string]: number } + ) const times = bets ? [contract.createdTime, ...bets.map((bet) => bet.createdTime)].map( diff --git a/web/lib/firebase/contracts.ts b/web/lib/firebase/contracts.ts index a4231d52..88470a89 100644 --- a/web/lib/firebase/contracts.ts +++ b/web/lib/firebase/contracts.ts @@ -49,7 +49,7 @@ export function getBinaryProbPercent(contract: Contract) { return probPercent } -export function tradingAllowed(contract: Contract<'BINARY' | 'MULTI'>) { +export function tradingAllowed(contract: Contract) { return ( !contract.isResolved && (!contract.closeTime || contract.closeTime > Date.now()) @@ -84,9 +84,7 @@ export async function getContractFromSlug(slug: string) { const q = query(contractCollection, where('slug', '==', slug)) const snapshot = await getDocs(q) - return snapshot.empty - ? undefined - : (snapshot.docs[0].data() as Contract<'BINARY' | 'MULTI'>) + return snapshot.empty ? undefined : (snapshot.docs[0].data() as Contract) } export async function deleteContract(contractId: string) { diff --git a/web/pages/[username]/[contractSlug].tsx b/web/pages/[username]/[contractSlug].tsx index 570aca01..fec1880d 100644 --- a/web/pages/[username]/[contractSlug].tsx +++ b/web/pages/[username]/[contractSlug].tsx @@ -186,7 +186,7 @@ function BetsSection(props: { ) } -const getOpenGraphProps = (contract: Contract<'BINARY'>) => { +const getOpenGraphProps = (contract: Contract) => { const { resolution, question, creatorName, creatorUsername } = contract const probPercent = getBinaryProbPercent(contract) diff --git a/web/pages/make-predictions.tsx b/web/pages/make-predictions.tsx index 7af56478..3f37f535 100644 --- a/web/pages/make-predictions.tsx +++ b/web/pages/make-predictions.tsx @@ -26,7 +26,7 @@ type Prediction = { } function toPrediction(contract: Contract): Prediction { - const startProb = getProbability(contract.phantomShares) + const startProb = getProbability(contract.totalShares) return { question: contract.question, description: contract.description,