Implement resolving to multiple answers, weighted by probability
This commit is contained in:
parent
42f88766b3
commit
fd846254a0
|
@ -32,7 +32,8 @@ export type Contract = {
|
|||
isResolved: boolean
|
||||
resolutionTime?: number // When the contract creator resolved the market
|
||||
resolution?: string
|
||||
resolutionProbability?: number
|
||||
resolutionProbability?: number // Used for BINARY markets resolved to MKT
|
||||
resolutions?: { [outcome: string]: number } // Used for outcomeType FREE_RESPONSE resolved to MKT
|
||||
closeEmailsSent?: number
|
||||
|
||||
volume24Hours: number
|
||||
|
|
|
@ -118,3 +118,46 @@ export const getPayouts = (
|
|||
return getStandardPayouts(outcome, contract, bets)
|
||||
}
|
||||
}
|
||||
|
||||
export const getPayoutsMultiOutcome = (
|
||||
resolutions: { [outcome: string]: number },
|
||||
contract: Contract,
|
||||
bets: Bet[]
|
||||
) => {
|
||||
const poolTotal = _.sum(Object.values(contract.pool))
|
||||
const winningBets = bets.filter((bet) => resolutions[bet.outcome])
|
||||
|
||||
const betsByOutcome = _.groupBy(winningBets, (bet) => bet.outcome)
|
||||
const sharesByOutcome = _.mapValues(betsByOutcome, (bets) =>
|
||||
_.sumBy(bets, (bet) => bet.shares)
|
||||
)
|
||||
|
||||
const probTotal = _.sum(Object.values(resolutions))
|
||||
|
||||
const payouts = winningBets.map(({ userId, outcome, amount, shares }) => {
|
||||
const prob = resolutions[outcome] / probTotal
|
||||
const winnings = (shares / sharesByOutcome[outcome]) * prob * poolTotal
|
||||
const profit = winnings - amount
|
||||
|
||||
const payout = amount + (1 - FEES) * Math.max(0, profit)
|
||||
return { userId, profit, payout }
|
||||
})
|
||||
|
||||
const profits = _.sumBy(payouts, (po) => po.profit)
|
||||
const creatorPayout = CREATOR_FEE * profits
|
||||
|
||||
console.log(
|
||||
'resolved',
|
||||
resolutions,
|
||||
'pool',
|
||||
poolTotal,
|
||||
'profits',
|
||||
profits,
|
||||
'creator fee',
|
||||
creatorPayout
|
||||
)
|
||||
|
||||
return payouts
|
||||
.map(({ userId, payout }) => ({ userId, payout }))
|
||||
.concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee
|
||||
}
|
||||
|
|
|
@ -17,7 +17,13 @@ type market_resolved_template = {
|
|||
url: string
|
||||
}
|
||||
|
||||
const toDisplayResolution = (outcome: string, prob: number) => {
|
||||
const toDisplayResolution = (
|
||||
outcome: string,
|
||||
prob: number,
|
||||
resolutions?: { [outcome: string]: number }
|
||||
) => {
|
||||
if (outcome === 'MKT' && resolutions) return 'MULTI'
|
||||
|
||||
const display = {
|
||||
YES: 'YES',
|
||||
NO: 'NO',
|
||||
|
@ -33,8 +39,9 @@ export const sendMarketResolutionEmail = async (
|
|||
payout: number,
|
||||
creator: User,
|
||||
contract: Contract,
|
||||
resolution: 'YES' | 'NO' | 'CANCEL' | 'MKT' | string,
|
||||
resolutionProbability?: number
|
||||
resolution: string,
|
||||
resolutionProbability?: number,
|
||||
resolutions?: { [outcome: string]: number }
|
||||
) => {
|
||||
const privateUser = await getPrivateUser(userId)
|
||||
if (
|
||||
|
@ -49,7 +56,7 @@ export const sendMarketResolutionEmail = async (
|
|||
|
||||
const prob = resolutionProbability ?? getProbability(contract.totalShares)
|
||||
|
||||
const outcome = toDisplayResolution(resolution, prob)
|
||||
const outcome = toDisplayResolution(resolution, prob, resolutions)
|
||||
|
||||
const subject = `Resolved ${outcome}: ${contract.question}`
|
||||
|
||||
|
|
|
@ -7,23 +7,24 @@ import { User } from '../../common/user'
|
|||
import { Bet } from '../../common/bet'
|
||||
import { getUser, payUser } from './utils'
|
||||
import { sendMarketResolutionEmail } from './emails'
|
||||
import { getPayouts } from '../../common/payouts'
|
||||
import { getPayouts, getPayoutsMultiOutcome } from '../../common/payouts'
|
||||
|
||||
export const resolveMarket = functions
|
||||
.runWith({ minInstances: 1 })
|
||||
.https.onCall(
|
||||
async (
|
||||
data: {
|
||||
outcome: 'YES' | 'NO' | 'CANCEL' | 'MKT'
|
||||
outcome: string
|
||||
contractId: string
|
||||
probabilityInt?: number
|
||||
resolutions?: { [outcome: string]: number }
|
||||
},
|
||||
context
|
||||
) => {
|
||||
const userId = context?.auth?.uid
|
||||
if (!userId) return { status: 'error', message: 'Not authorized' }
|
||||
|
||||
const { outcome, contractId, probabilityInt } = data
|
||||
const { outcome, contractId, probabilityInt, resolutions } = data
|
||||
|
||||
const contractDoc = firestore.doc(`contracts/${contractId}`)
|
||||
const contractSnap = await contractDoc.get()
|
||||
|
@ -36,7 +37,11 @@ export const resolveMarket = functions
|
|||
if (!['YES', 'NO', 'MKT', 'CANCEL'].includes(outcome))
|
||||
return { status: 'error', message: 'Invalid outcome' }
|
||||
} else if (outcomeType === 'FREE_RESPONSE') {
|
||||
if (outcome !== 'CANCEL' && isNaN(+outcome))
|
||||
if (
|
||||
isNaN(+outcome) &&
|
||||
!(outcome === 'MKT' && resolutions) &&
|
||||
outcome !== 'CANCEL'
|
||||
)
|
||||
return { status: 'error', message: 'Invalid outcome' }
|
||||
} else {
|
||||
return { status: 'error', message: 'Invalid contract outcomeType' }
|
||||
|
@ -70,6 +75,7 @@ export const resolveMarket = functions
|
|||
...(resolutionProbability === undefined
|
||||
? {}
|
||||
: { resolutionProbability }),
|
||||
...(resolutions === undefined ? {} : { resolutions }),
|
||||
})
|
||||
|
||||
console.log('contract ', contractId, 'resolved to:', outcome)
|
||||
|
@ -81,12 +87,10 @@ export const resolveMarket = functions
|
|||
const bets = betsSnap.docs.map((doc) => doc.data() as Bet)
|
||||
const openBets = bets.filter((b) => !b.isSold && !b.sale)
|
||||
|
||||
const payouts = getPayouts(
|
||||
outcome,
|
||||
contract,
|
||||
openBets,
|
||||
resolutionProbability
|
||||
)
|
||||
const payouts =
|
||||
outcomeType === 'FREE_RESPONSE' && resolutions
|
||||
? getPayoutsMultiOutcome(resolutions, contract, openBets)
|
||||
: getPayouts(outcome, contract, openBets, resolutionProbability)
|
||||
|
||||
console.log('payouts:', payouts)
|
||||
|
||||
|
@ -109,7 +113,8 @@ export const resolveMarket = functions
|
|||
creator,
|
||||
contract,
|
||||
outcome,
|
||||
resolutionProbability
|
||||
resolutionProbability,
|
||||
resolutions
|
||||
)
|
||||
|
||||
return result
|
||||
|
@ -121,8 +126,9 @@ const sendResolutionEmails = async (
|
|||
userPayouts: { [userId: string]: number },
|
||||
creator: User,
|
||||
contract: Contract,
|
||||
outcome: 'YES' | 'NO' | 'CANCEL' | 'MKT' | string,
|
||||
resolutionProbability?: number
|
||||
outcome: string,
|
||||
resolutionProbability?: number,
|
||||
resolutions?: { [outcome: string]: number }
|
||||
) => {
|
||||
const nonWinners = _.difference(
|
||||
_.uniq(openBets.map(({ userId }) => userId)),
|
||||
|
@ -140,7 +146,8 @@ const sendResolutionEmails = async (
|
|||
creator,
|
||||
contract,
|
||||
outcome,
|
||||
resolutionProbability
|
||||
resolutionProbability,
|
||||
resolutions
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import clsx from 'clsx'
|
||||
import _ from 'lodash'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
|
||||
import Textarea from 'react-expanding-textarea'
|
||||
import { XIcon } from '@heroicons/react/solid'
|
||||
|
||||
|
@ -34,18 +34,22 @@ import { Bet } from '../../common/bet'
|
|||
import { useAnswers } from '../hooks/use-answers'
|
||||
import { ResolveConfirmationButton } from './confirmation-button'
|
||||
import { tradingAllowed } from '../lib/firebase/contracts'
|
||||
import { removeUndefinedProps } from '../../common/util/object'
|
||||
|
||||
export function AnswersPanel(props: { contract: Contract; answers: Answer[] }) {
|
||||
const { contract } = props
|
||||
const { creatorId, resolution } = contract
|
||||
const { creatorId, resolution, resolutions } = contract
|
||||
|
||||
const answers = useAnswers(contract.id) ?? props.answers
|
||||
const [chosenAnswer, otherAnswers] = _.partition(
|
||||
const [winningAnswers, otherAnswers] = _.partition(
|
||||
answers.filter((answer) => answer.id !== '0'),
|
||||
(answer) => answer.id === resolution
|
||||
(answer) =>
|
||||
answer.id === resolution || (resolutions && resolutions[answer.id])
|
||||
)
|
||||
const sortedAnswers = [
|
||||
...chosenAnswer,
|
||||
..._.sortBy(winningAnswers, (answer) =>
|
||||
resolutions ? -1 * resolutions[answer.id] : 0
|
||||
),
|
||||
..._.sortBy(
|
||||
otherAnswers,
|
||||
(answer) => -1 * getOutcomeProbability(contract.totalShares, answer.id)
|
||||
|
@ -55,13 +59,46 @@ export function AnswersPanel(props: { contract: Contract; answers: Answer[] }) {
|
|||
const user = useUser()
|
||||
|
||||
const [resolveOption, setResolveOption] = useState<
|
||||
'CHOOSE' | 'CANCEL' | undefined
|
||||
'CHOOSE' | 'CHOOSE_MULTIPLE' | 'CANCEL' | undefined
|
||||
>()
|
||||
const [answerChoice, setAnswerChoice] = useState<string | undefined>()
|
||||
const [chosenAnswers, setChosenAnswers] = useState<{
|
||||
[answerId: string]: number
|
||||
}>({})
|
||||
|
||||
useEffect(() => {
|
||||
if (resolveOption !== 'CHOOSE' && answerChoice) setAnswerChoice(undefined)
|
||||
}, [answerChoice, resolveOption])
|
||||
const chosenTotal = _.sum(Object.values(chosenAnswers))
|
||||
|
||||
const onChoose = (answerId: string, prob: number) => {
|
||||
if (resolveOption === 'CHOOSE') {
|
||||
setChosenAnswers({ [answerId]: prob })
|
||||
} else {
|
||||
setChosenAnswers((chosenAnswers) => {
|
||||
return {
|
||||
...chosenAnswers,
|
||||
[answerId]: prob,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const onDeselect = (answerId: string) => {
|
||||
setChosenAnswers((chosenAnswers) => {
|
||||
const newChosenAnswers = { ...chosenAnswers }
|
||||
delete newChosenAnswers[answerId]
|
||||
return newChosenAnswers
|
||||
})
|
||||
}
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setChosenAnswers({})
|
||||
}, [resolveOption])
|
||||
|
||||
const showChoice = resolution
|
||||
? undefined
|
||||
: resolveOption === 'CHOOSE'
|
||||
? 'radio'
|
||||
: resolveOption === 'CHOOSE_MULTIPLE'
|
||||
? 'checkbox'
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<Col className="gap-3">
|
||||
|
@ -70,9 +107,11 @@ export function AnswersPanel(props: { contract: Contract; answers: Answer[] }) {
|
|||
key={answer.id}
|
||||
answer={answer}
|
||||
contract={contract}
|
||||
showChoice={!resolution && resolveOption === 'CHOOSE'}
|
||||
isChosen={answer.id === answerChoice}
|
||||
onChoose={() => setAnswerChoice(answer.id)}
|
||||
showChoice={showChoice}
|
||||
chosenProb={chosenAnswers[answer.id]}
|
||||
totalChosenProb={chosenTotal}
|
||||
onChoose={onChoose}
|
||||
onDeselect={onDeselect}
|
||||
/>
|
||||
))}
|
||||
|
||||
|
@ -85,14 +124,16 @@ export function AnswersPanel(props: { contract: Contract; answers: Answer[] }) {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{tradingAllowed(contract) && <CreateAnswerInput contract={contract} />}
|
||||
{tradingAllowed(contract) && !resolveOption && (
|
||||
<CreateAnswerInput contract={contract} />
|
||||
)}
|
||||
|
||||
{user?.id === creatorId && !resolution && (
|
||||
<AnswerResolvePanel
|
||||
contract={contract}
|
||||
resolveOption={resolveOption}
|
||||
setResolveOption={setResolveOption}
|
||||
answer={answerChoice}
|
||||
chosenAnswers={chosenAnswers}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
|
@ -102,18 +143,31 @@ export function AnswersPanel(props: { contract: Contract; answers: Answer[] }) {
|
|||
function AnswerItem(props: {
|
||||
answer: Answer
|
||||
contract: Contract
|
||||
showChoice: boolean
|
||||
isChosen: boolean
|
||||
onChoose: () => void
|
||||
showChoice: 'radio' | 'checkbox' | undefined
|
||||
chosenProb: number | undefined
|
||||
totalChosenProb?: number
|
||||
onChoose: (answerId: string, prob: number) => void
|
||||
onDeselect: (answerId: string) => void
|
||||
}) {
|
||||
const { answer, contract, showChoice, isChosen, onChoose } = props
|
||||
const { resolution, totalShares } = contract
|
||||
const {
|
||||
answer,
|
||||
contract,
|
||||
showChoice,
|
||||
chosenProb,
|
||||
totalChosenProb,
|
||||
onChoose,
|
||||
onDeselect,
|
||||
} = props
|
||||
const { resolution, resolutions, totalShares } = contract
|
||||
const { username, avatarUrl, name, createdTime, number, text } = answer
|
||||
const isChosen = chosenProb !== undefined
|
||||
|
||||
const createdDate = dayjs(createdTime).format('MMM D')
|
||||
const prob = getOutcomeProbability(totalShares, answer.id)
|
||||
const roundedProb = Math.round(prob * 100)
|
||||
const probPercent = formatPercent(prob)
|
||||
const wasResolvedTo = resolution === answer.id
|
||||
const wasResolvedTo =
|
||||
resolution === answer.id || (resolutions && resolutions[answer.id])
|
||||
|
||||
const [isBetting, setIsBetting] = useState(false)
|
||||
|
||||
|
@ -122,10 +176,14 @@ function AnswerItem(props: {
|
|||
className={clsx(
|
||||
'p-4 sm:flex-row rounded gap-4',
|
||||
wasResolvedTo
|
||||
? 'bg-green-50 mb-8'
|
||||
: isChosen
|
||||
? resolution === 'MKT'
|
||||
? 'bg-blue-50 mb-2'
|
||||
: 'bg-green-50 mb-8'
|
||||
: chosenProb === undefined
|
||||
? 'bg-gray-50'
|
||||
: showChoice === 'radio'
|
||||
? 'bg-green-50'
|
||||
: 'bg-gray-50'
|
||||
: 'bg-blue-50'
|
||||
)}
|
||||
>
|
||||
<Col className="gap-3 flex-1">
|
||||
|
@ -158,8 +216,24 @@ function AnswerItem(props: {
|
|||
closePanel={() => setIsBetting(false)}
|
||||
/>
|
||||
) : (
|
||||
<Row className="self-end sm:self-start items-center gap-4">
|
||||
{!wasResolvedTo && (
|
||||
<Row className="self-end sm:self-start items-center gap-4 justify-end">
|
||||
{!wasResolvedTo &&
|
||||
(showChoice === 'checkbox' ? (
|
||||
<input
|
||||
className="input input-bordered text-2xl justify-self-end w-24"
|
||||
type="number"
|
||||
placeholder={`${roundedProb}`}
|
||||
maxLength={9}
|
||||
value={chosenProb ? Math.round(chosenProb) : ''}
|
||||
onChange={(e) => {
|
||||
const { value } = e.target
|
||||
const numberValue = value
|
||||
? parseInt(value.replace(/[^\d]/, ''))
|
||||
: 0
|
||||
if (!isNaN(numberValue)) onChoose(answer.id, numberValue)
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className={clsx(
|
||||
'text-2xl',
|
||||
|
@ -168,20 +242,45 @@ function AnswerItem(props: {
|
|||
>
|
||||
{probPercent}
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
{showChoice ? (
|
||||
<div className="form-control py-1">
|
||||
<label className="cursor-pointer label gap-2">
|
||||
<span className="label-text">Choose this answer</span>
|
||||
<label className="cursor-pointer label gap-3">
|
||||
<span className="">Choose this answer</span>
|
||||
{showChoice === 'radio' && (
|
||||
<input
|
||||
className={clsx('radio', isChosen && '!bg-green-500')}
|
||||
className={clsx('radio', chosenProb && '!bg-green-500')}
|
||||
type="radio"
|
||||
name="opt"
|
||||
checked={isChosen}
|
||||
onChange={onChoose}
|
||||
onChange={() => onChoose(answer.id, 1)}
|
||||
value={answer.id}
|
||||
/>
|
||||
)}
|
||||
{showChoice === 'checkbox' && (
|
||||
<input
|
||||
className={clsx('checkbox', chosenProb && '!bg-blue-500')}
|
||||
type="checkbox"
|
||||
name="opt"
|
||||
checked={isChosen}
|
||||
onChange={() => {
|
||||
if (isChosen) onDeselect(answer.id)
|
||||
else {
|
||||
onChoose(answer.id, 100 * prob)
|
||||
}
|
||||
}}
|
||||
value={answer.id}
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
{showChoice === 'checkbox' && (
|
||||
<div className="ml-1">
|
||||
{chosenProb && totalChosenProb
|
||||
? Math.round((100 * chosenProb) / totalChosenProb)
|
||||
: 0}
|
||||
% share
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
|
@ -195,7 +294,17 @@ function AnswerItem(props: {
|
|||
)}
|
||||
{wasResolvedTo && (
|
||||
<Col className="items-end">
|
||||
<div className="text-green-700 text-xl">Chosen</div>
|
||||
<div
|
||||
className={clsx(
|
||||
'text-xl',
|
||||
resolution === 'MKT' ? 'text-blue-700' : 'text-green-700'
|
||||
)}
|
||||
>
|
||||
Chosen{' '}
|
||||
{resolutions
|
||||
? `${Math.round(resolutions[answer.id])}%`
|
||||
: ''}
|
||||
</div>
|
||||
<div className="text-2xl text-gray-500">{probPercent}</div>
|
||||
</Col>
|
||||
)}
|
||||
|
@ -482,29 +591,48 @@ function CreateAnswerInput(props: { contract: Contract }) {
|
|||
|
||||
function AnswerResolvePanel(props: {
|
||||
contract: Contract
|
||||
resolveOption: 'CHOOSE' | 'CANCEL' | undefined
|
||||
setResolveOption: (option: 'CHOOSE' | 'CANCEL' | undefined) => void
|
||||
answer: string | undefined
|
||||
resolveOption: 'CHOOSE' | 'CHOOSE_MULTIPLE' | 'CANCEL' | undefined
|
||||
setResolveOption: (
|
||||
option: 'CHOOSE' | 'CHOOSE_MULTIPLE' | 'CANCEL' | undefined
|
||||
) => void
|
||||
chosenAnswers: { [answerId: string]: number }
|
||||
}) {
|
||||
const { contract, resolveOption, setResolveOption, answer } = props
|
||||
const { contract, resolveOption, setResolveOption, chosenAnswers } = props
|
||||
const answers = Object.keys(chosenAnswers)
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [error, setError] = useState<string | undefined>(undefined)
|
||||
|
||||
const onResolve = async () => {
|
||||
if (resolveOption === 'CHOOSE' && answer === undefined) return
|
||||
if (resolveOption === 'CHOOSE' && answers.length !== 1) return
|
||||
if (resolveOption === 'CHOOSE_MULTIPLE' && answers.length < 2) return
|
||||
|
||||
setIsSubmitting(true)
|
||||
|
||||
const result = await resolveMarket({
|
||||
outcome: resolveOption === 'CHOOSE' ? (answer as string) : 'CANCEL',
|
||||
contractId: contract.id,
|
||||
}).then((r) => r.data as any)
|
||||
const totalProb = _.sum(Object.values(chosenAnswers))
|
||||
const normalizedProbs = _.mapValues(
|
||||
chosenAnswers,
|
||||
(prob) => (100 * prob) / totalProb
|
||||
)
|
||||
|
||||
console.log('resolved', `#${answer}`, 'result:', result)
|
||||
const resolutionProps = removeUndefinedProps({
|
||||
outcome:
|
||||
resolveOption === 'CHOOSE'
|
||||
? answers[0]
|
||||
: resolveOption === 'CHOOSE_MULTIPLE'
|
||||
? 'MKT'
|
||||
: 'CANCEL',
|
||||
resolutions:
|
||||
resolveOption === 'CHOOSE_MULTIPLE' ? normalizedProbs : undefined,
|
||||
contractId: contract.id,
|
||||
})
|
||||
|
||||
const result = await resolveMarket(resolutionProps).then((r) => r.data)
|
||||
|
||||
console.log('resolved', resolutionProps, 'result:', result)
|
||||
|
||||
if (result?.status !== 'success') {
|
||||
setError(result?.error || 'Error resolving market')
|
||||
setError(result?.message || 'Error resolving market')
|
||||
}
|
||||
setResolveOption(undefined)
|
||||
setIsSubmitting(false)
|
||||
|
@ -513,8 +641,12 @@ function AnswerResolvePanel(props: {
|
|||
const resolutionButtonClass =
|
||||
resolveOption === 'CANCEL'
|
||||
? 'bg-yellow-400 hover:bg-yellow-500'
|
||||
: resolveOption === 'CHOOSE' && answer
|
||||
: resolveOption === 'CHOOSE' && answers.length
|
||||
? 'btn-primary'
|
||||
: resolveOption === 'CHOOSE_MULTIPLE' &&
|
||||
answers.length > 1 &&
|
||||
answers.every((answer) => chosenAnswers[answer] > 0)
|
||||
? 'bg-blue-400 hover:bg-blue-500'
|
||||
: 'btn-disabled'
|
||||
|
||||
return (
|
||||
|
|
|
@ -85,7 +85,7 @@ export function ResolutionOrChance(props: {
|
|||
{
|
||||
YES: 'YES',
|
||||
NO: 'NO',
|
||||
MKT: getBinaryProbPercent(contract),
|
||||
MKT: isBinary ? getBinaryProbPercent(contract) : 'MULTI',
|
||||
CANCEL: 'N/A',
|
||||
'': '',
|
||||
}[resolution || ''] ?? `#${resolution}`
|
||||
|
|
|
@ -43,12 +43,12 @@ export function ResolutionPanel(props: {
|
|||
outcome,
|
||||
contractId: contract.id,
|
||||
probabilityInt: prob,
|
||||
}).then((r) => r.data as any)
|
||||
}).then((r) => r.data)
|
||||
|
||||
console.log('resolved', outcome, 'result:', result)
|
||||
|
||||
if (result?.status !== 'success') {
|
||||
setError(result?.error || 'Error resolving market')
|
||||
setError(result?.message || 'Error resolving market')
|
||||
}
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
|
|
|
@ -91,8 +91,8 @@ export function YesNoCancelSelector(props: {
|
|||
}
|
||||
|
||||
export function ChooseCancelSelector(props: {
|
||||
selected: 'CHOOSE' | 'CANCEL' | undefined
|
||||
onSelect: (selected: 'CHOOSE' | 'CANCEL') => void
|
||||
selected: 'CHOOSE' | 'CHOOSE_MULTIPLE' | 'CANCEL' | undefined
|
||||
onSelect: (selected: 'CHOOSE' | 'CHOOSE_MULTIPLE' | 'CANCEL') => void
|
||||
className?: string
|
||||
btnClassName?: string
|
||||
}) {
|
||||
|
@ -110,6 +110,14 @@ export function ChooseCancelSelector(props: {
|
|||
Choose an answer
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
color={selected === 'CHOOSE_MULTIPLE' ? 'blue' : 'gray'}
|
||||
onClick={() => onSelect('CHOOSE_MULTIPLE')}
|
||||
className={clsx('whitespace-nowrap', btnClassName)}
|
||||
>
|
||||
Choose multiple
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
color={selected === 'CANCEL' ? 'yellow' : 'gray'}
|
||||
onClick={() => onSelect('CANCEL')}
|
||||
|
|
|
@ -33,6 +33,7 @@ export const resolveMarket = cloudFunction<
|
|||
outcome: string
|
||||
contractId: string
|
||||
probabilityInt?: number
|
||||
resolutions?: { [outcome: string]: number }
|
||||
},
|
||||
{ status: 'error' | 'success'; message?: string }
|
||||
>('resolveMarket')
|
||||
|
|
Loading…
Reference in New Issue
Block a user