diff --git a/common/calculate.ts b/common/calculate.ts index d25fd313..9b2d39be 100644 --- a/common/calculate.ts +++ b/common/calculate.ts @@ -24,6 +24,7 @@ import { FreeResponseContract, PseudoNumericContract, MultipleChoiceContract, + AnswerContract, } from './contract' import { floatingEqual } from './util/math' @@ -201,9 +202,7 @@ export function getContractBetNullMetrics() { } } -export function getTopAnswer( - contract: FreeResponseContract | MultipleChoiceContract -) { +export function getTopAnswer(contract: AnswerContract) { const { answers } = contract const top = maxBy( answers?.map((answer) => ({ diff --git a/common/contract.ts b/common/contract.ts index cad5e4b7..3120e16f 100644 --- a/common/contract.ts +++ b/common/contract.ts @@ -71,6 +71,10 @@ export type DPMContract = Contract & DPM export type CPMMContract = Contract & CPMM export type DPMBinaryContract = BinaryContract & DPM export type CPMMBinaryContract = BinaryContract & CPMM +export type AnswerContract = + | FreeResponseContract + | MultipleChoiceContract + | BountyContract export type DPM = { mechanism: 'dpm-2' diff --git a/common/new-bet.ts b/common/new-bet.ts index 576f35f8..c1cc386f 100644 --- a/common/new-bet.ts +++ b/common/new-bet.ts @@ -15,6 +15,7 @@ import { getCpmmProbability, } from './calculate-cpmm' import { + AnswerContract, CPMMBinaryContract, DPMBinaryContract, FreeResponseContract, @@ -323,7 +324,7 @@ export const getNewBinaryDpmBetInfo = ( export const getNewMultiBetInfo = ( outcome: string, amount: number, - contract: FreeResponseContract | MultipleChoiceContract, + contract: AnswerContract, loanAmount: number ) => { const { pool, totalShares, totalBets } = contract diff --git a/common/payouts-dpm.ts b/common/payouts-dpm.ts index 7d4a0185..75cb5156 100644 --- a/common/payouts-dpm.ts +++ b/common/payouts-dpm.ts @@ -3,6 +3,7 @@ import { sum, groupBy, sumBy, mapValues } from 'lodash' import { Bet, NumericBet } from './bet' import { deductDpmFees, getDpmProbability } from './calculate-dpm' import { + AnswerContract, DPMContract, FreeResponseContract, MultipleChoiceContract, @@ -184,7 +185,7 @@ export const getDpmMktPayouts = ( export const getPayoutsMultiOutcome = ( resolutions: { [outcome: string]: number }, - contract: FreeResponseContract | MultipleChoiceContract, + contract: AnswerContract, bets: Bet[] ) => { const poolTotal = sum(Object.values(contract.pool)) diff --git a/functions/src/create-answer.ts b/functions/src/create-answer.ts index 2abaf44d..cb936d9f 100644 --- a/functions/src/create-answer.ts +++ b/functions/src/create-answer.ts @@ -36,7 +36,10 @@ export const createanswer = newEndpoint(opts, async (req, auth) => { if (!contractSnap.exists) throw new APIError(400, 'Invalid contract') const contract = contractSnap.data() as Contract - if (contract.outcomeType !== 'FREE_RESPONSE') + if ( + contract.outcomeType !== 'FREE_RESPONSE' && + contract.outcomeType !== 'BOUNTY' + ) throw new APIError(400, 'Requires a free response contract') const { closeTime, volume } = contract diff --git a/functions/src/resolve-market.ts b/functions/src/resolve-market.ts index 7277f40b..b7faec78 100644 --- a/functions/src/resolve-market.ts +++ b/functions/src/resolve-market.ts @@ -3,6 +3,7 @@ import { z } from 'zod' import { difference, uniq, mapValues, groupBy, sumBy } from 'lodash' import { + AnswerContract, Contract, FreeResponseContract, MultipleChoiceContract, @@ -295,10 +296,7 @@ function getResolutionParams(contract: Contract, body: string) { throw new APIError(500, `Invalid outcome type: ${outcomeType}`) } -function validateAnswer( - contract: FreeResponseContract | MultipleChoiceContract, - answer: number -) { +function validateAnswer(contract: AnswerContract, answer: number) { const validIds = contract.answers.map((a) => a.id) if (!validIds.includes(answer.toString())) { throw new APIError(400, `${answer} is not a valid answer ID`) diff --git a/web/components/answers/answer-bet-panel.tsx b/web/components/answers/answer-bet-panel.tsx index 238c7783..74595d0d 100644 --- a/web/components/answers/answer-bet-panel.tsx +++ b/web/components/answers/answer-bet-panel.tsx @@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from 'react' import { XIcon } from '@heroicons/react/solid' import { Answer } from 'common/answer' -import { FreeResponseContract, MultipleChoiceContract } from 'common/contract' +import { AnswerContract } from 'common/contract' import { BuyAmountInput } from '../amount-input' import { Col } from '../layout/col' import { APIError, placeBet } from 'web/lib/firebase/api' @@ -30,7 +30,7 @@ import { AlertBox } from '../alert-box' export function AnswerBetPanel(props: { answer: Answer - contract: FreeResponseContract | MultipleChoiceContract + contract: AnswerContract closePanel: () => void className?: string isModal?: boolean diff --git a/web/components/answers/answer-item.tsx b/web/components/answers/answer-item.tsx index f1ab2f88..2049630f 100644 --- a/web/components/answers/answer-item.tsx +++ b/web/components/answers/answer-item.tsx @@ -1,7 +1,7 @@ import clsx from 'clsx' import { Answer } from 'common/answer' -import { FreeResponseContract, MultipleChoiceContract } from 'common/contract' +import { AnswerContract } from 'common/contract' import { Col } from '../layout/col' import { Row } from '../layout/row' import { Avatar } from '../avatar' @@ -13,7 +13,7 @@ import { Linkify } from '../linkify' export function AnswerItem(props: { answer: Answer - contract: FreeResponseContract | MultipleChoiceContract + contract: AnswerContract showChoice: 'radio' | 'checkbox' | undefined chosenProb: number | undefined totalChosenProb?: number diff --git a/web/components/answers/answer-resolve-panel.tsx b/web/components/answers/answer-resolve-panel.tsx index 0a4ac1e1..8d39b0b0 100644 --- a/web/components/answers/answer-resolve-panel.tsx +++ b/web/components/answers/answer-resolve-panel.tsx @@ -2,7 +2,7 @@ import clsx from 'clsx' import { sum } from 'lodash' import { useState } from 'react' -import { FreeResponseContract, MultipleChoiceContract } from 'common/contract' +import { AnswerContract } from 'common/contract' import { Col } from '../layout/col' import { APIError, resolveMarket } from 'web/lib/firebase/api' import { Row } from '../layout/row' @@ -11,7 +11,7 @@ import { ResolveConfirmationButton } from '../confirmation-button' import { removeUndefinedProps } from 'common/util/object' export function AnswerResolvePanel(props: { - contract: FreeResponseContract | MultipleChoiceContract + contract: AnswerContract resolveOption: 'CHOOSE' | 'CHOOSE_MULTIPLE' | 'CANCEL' | undefined setResolveOption: ( option: 'CHOOSE' | 'CHOOSE_MULTIPLE' | 'CANCEL' | undefined diff --git a/web/components/answers/answers-graph.tsx b/web/components/answers/answers-graph.tsx index 27152db9..d85a85bc 100644 --- a/web/components/answers/answers-graph.tsx +++ b/web/components/answers/answers-graph.tsx @@ -5,14 +5,18 @@ import { groupBy, sortBy, sumBy } from 'lodash' import { memo } from 'react' import { Bet } from 'common/bet' -import { FreeResponseContract, MultipleChoiceContract } from 'common/contract' +import { + AnswerContract, + FreeResponseContract, + MultipleChoiceContract, +} from 'common/contract' import { getOutcomeProbability } from 'common/calculate' import { useWindowSize } from 'web/hooks/use-window-size' const NUM_LINES = 6 export const AnswersGraph = memo(function AnswersGraph(props: { - contract: FreeResponseContract | MultipleChoiceContract + contract: AnswerContract bets: Bet[] height?: number }) { @@ -178,10 +182,7 @@ function formatTime( return d.format(format) } -const computeProbsByOutcome = ( - bets: Bet[], - contract: FreeResponseContract | MultipleChoiceContract -) => { +const computeProbsByOutcome = (bets: Bet[], contract: AnswerContract) => { const { totalBets, outcomeType } = contract const betsByOutcome = groupBy(bets, (bet) => bet.outcome) diff --git a/web/components/answers/answers-panel.tsx b/web/components/answers/answers-panel.tsx index 6e0bfef6..4b76d96b 100644 --- a/web/components/answers/answers-panel.tsx +++ b/web/components/answers/answers-panel.tsx @@ -1,7 +1,12 @@ import { sortBy, partition, sum, uniq } from 'lodash' import { useEffect, useState } from 'react' -import { FreeResponseContract, MultipleChoiceContract } from 'common/contract' +import { + AnswerContract, + BountyContract, + FreeResponseContract, + MultipleChoiceContract, +} from 'common/contract' import { Col } from '../layout/col' import { useUser } from 'web/hooks/use-user' import { getDpmOutcomeProbability } from 'common/calculate-dpm' @@ -25,9 +30,7 @@ import { UserLink } from 'web/components/user-page' import { Linkify } from 'web/components/linkify' import { BuyButton } from 'web/components/yes-no-selector' -export function AnswersPanel(props: { - contract: FreeResponseContract | MultipleChoiceContract -}) { +export function AnswersPanel(props: { contract: AnswerContract }) { const { contract } = props const { creatorId, resolution, resolutions, totalBets, outcomeType } = contract @@ -136,7 +139,7 @@ export function AnswersPanel(props: {
No answers yet...
)} - {outcomeType === 'FREE_RESPONSE' && + {(outcomeType === 'FREE_RESPONSE' || outcomeType === 'BOUNTY') && tradingAllowed(contract) && (!resolveOption || resolveOption === 'CANCEL') && ( @@ -158,7 +161,7 @@ export function AnswersPanel(props: { } function getAnswerItems( - contract: FreeResponseContract | MultipleChoiceContract, + contract: AnswerContract, answers: Answer[], user: User | undefined | null ) { @@ -184,7 +187,7 @@ function getAnswerItems( } function OpenAnswer(props: { - contract: FreeResponseContract | MultipleChoiceContract + contract: AnswerContract answer: Answer items: ActivityItem[] type: string diff --git a/web/components/answers/create-answer-panel.tsx b/web/components/answers/create-answer-panel.tsx index ce266778..715a9824 100644 --- a/web/components/answers/create-answer-panel.tsx +++ b/web/components/answers/create-answer-panel.tsx @@ -3,7 +3,7 @@ import { useState } from 'react' import Textarea from 'react-expanding-textarea' import { findBestMatch } from 'string-similarity' -import { FreeResponseContract } from 'common/contract' +import { BountyContract, FreeResponseContract } from 'common/contract' import { BuyAmountInput } from '../amount-input' import { Col } from '../layout/col' import { APIError, createAnswer } from 'web/lib/firebase/api' @@ -26,7 +26,9 @@ import { MAX_ANSWER_LENGTH } from 'common/answer' import { withTracking } from 'web/lib/service/analytics' import { lowerCase } from 'lodash' -export function CreateAnswerPanel(props: { contract: FreeResponseContract }) { +export function CreateAnswerPanel(props: { + contract: FreeResponseContract | BountyContract +}) { const { contract } = props const user = useUser() const [text, setText] = useState('') diff --git a/web/components/contract/contract-card.tsx b/web/components/contract/contract-card.tsx index 562a2bdc..52f4e40b 100644 --- a/web/components/contract/contract-card.tsx +++ b/web/components/contract/contract-card.tsx @@ -9,6 +9,7 @@ import { import { contractPath, getBinaryProbPercent } from 'web/lib/firebase/contracts' import { Col } from '../layout/col' import { + AnswerContract, BinaryContract, BountyContract, Contract, @@ -232,7 +233,7 @@ export function BountyValue(props: { } function FreeResponseTopAnswer(props: { - contract: FreeResponseContract | MultipleChoiceContract + contract: AnswerContract truncate: 'short' | 'long' | 'none' className?: string }) { diff --git a/web/components/contract/contract-tabs.tsx b/web/components/contract/contract-tabs.tsx index eb455df0..ecadb4ee 100644 --- a/web/components/contract/contract-tabs.tsx +++ b/web/components/contract/contract-tabs.tsx @@ -56,7 +56,8 @@ export function ContractTabs(props: { tips={tips} user={user} mode={ - contract.outcomeType === 'FREE_RESPONSE' + contract.outcomeType === 'FREE_RESPONSE' || + contract.outcomeType === 'BOUNTY' ? 'free-response-comment-answer-groups' : 'comments' } diff --git a/web/components/outcome-label.tsx b/web/components/outcome-label.tsx index b49ccdf9..414ca4fd 100644 --- a/web/components/outcome-label.tsx +++ b/web/components/outcome-label.tsx @@ -3,6 +3,7 @@ import { Answer } from 'common/answer' import { getProbability } from 'common/calculate' import { getValueFromBucket } from 'common/calculate-dpm' import { + AnswerContract, BinaryContract, BountyContract, Contract, @@ -78,7 +79,7 @@ export function BinaryContractOutcomeLabel(props: { } export function FreeResponseOutcomeLabel(props: { - contract: FreeResponseContract | MultipleChoiceContract | BountyContract + contract: AnswerContract resolution: string | 'CANCEL' | 'MKT' truncate: 'short' | 'long' | 'none' answerClassName?: string