From f06709c20f5675b8d0c2acab50942fa0cca634b1 Mon Sep 17 00:00:00 2001 From: James Grugett Date: Mon, 14 Feb 2022 22:27:15 -0600 Subject: [PATCH] Numbered answers. Layout & calculation tweaks --- common/answer.ts | 4 +- common/antes.ts | 6 +- common/calculate.ts | 8 +-- common/new-contract.ts | 6 +- functions/src/create-answer.ts | 19 +++++- functions/src/create-contract.ts | 6 +- web/components/answers-panel.tsx | 84 +++++++++++++------------ web/components/contract-overview.tsx | 6 +- web/components/outcome-label.tsx | 7 ++- web/lib/firebase/answers.ts | 26 +------- web/pages/[username]/[contractSlug].tsx | 1 + 11 files changed, 89 insertions(+), 84 deletions(-) diff --git a/common/answer.ts b/common/answer.ts index 9e63d60c..36dd0415 100644 --- a/common/answer.ts +++ b/common/answer.ts @@ -2,6 +2,7 @@ import { User } from './user' export type Answer = { id: string + number: number contractId: string createdTime: number @@ -17,7 +18,8 @@ export const getNoneAnswer = (contractId: string, creator: User) => { const { username, name, avatarUrl } = creator return { - id: 'NONE', + id: '0', + number: 0, contractId, createdTime: Date.now(), userId: creator.id, diff --git a/common/antes.ts b/common/antes.ts index 0bddf33c..52499f1d 100644 --- a/common/antes.ts +++ b/common/antes.ts @@ -79,9 +79,9 @@ export function getFreeAnswerAnte( contractId: contract.id, amount, shares, - outcome: 'NONE', - probBefore: 100, - probAfter: 100, + outcome: '0', + probBefore: 1, + probAfter: 1, createdTime, isAnte: true, } diff --git a/common/calculate.ts b/common/calculate.ts index 84259973..d030d14c 100644 --- a/common/calculate.ts +++ b/common/calculate.ts @@ -80,7 +80,7 @@ export function calculateMoneyRatio( const p = getOutcomeProbability(totalShares, outcome) - const actual = pool.YES + pool.NO - shareValue + const actual = _.sum(Object.values(pool)) - shareValue const betAmount = p * amount @@ -142,15 +142,15 @@ export function calculateStandardPayout( const { amount, outcome: betOutcome, shares } = bet if (betOutcome !== outcome) return 0 - const { totalShares, phantomShares } = contract + const { totalShares, phantomShares, pool } = contract if (!totalShares[outcome]) return 0 - const pool = _.sum(Object.values(totalShares)) + const poolTotal = _.sum(Object.values(pool)) const total = totalShares[outcome] - (phantomShares ? phantomShares[outcome] : 0) - const winnings = (shares / total) * pool + const winnings = (shares / total) * poolTotal // profit can be negative if using phantom shares return amount + (1 - FEES) * Math.max(0, winnings - amount) } diff --git a/common/new-contract.ts b/common/new-contract.ts index edfa976c..bc4f1beb 100644 --- a/common/new-contract.ts +++ b/common/new-contract.ts @@ -70,9 +70,9 @@ const getBinaryProps = (initialProb: number, ante: number) => { const getFreeAnswerProps = (ante: number) => { return { - pool: { NONE: ante }, - totalShares: { NONE: ante }, - totalBets: { NONE: ante }, + pool: { '0': ante }, + totalShares: { '0': ante }, + totalBets: { '0': ante }, phantomShares: undefined, outcomes: 'FREE_ANSWER' as const, } diff --git a/functions/src/create-answer.ts b/functions/src/create-answer.ts index 230c12a3..492cb10a 100644 --- a/functions/src/create-answer.ts +++ b/functions/src/create-answer.ts @@ -5,6 +5,7 @@ import { Contract } from '../../common/contract' import { User } from '../../common/user' import { getNewMultiBetInfo } from '../../common/new-bet' import { Answer } from '../../common/answer' +import { getValues } from './utils' export const createAnswer = functions.runWith({ minInstances: 1 }).https.onCall( async ( @@ -56,15 +57,29 @@ export const createAnswer = functions.runWith({ minInstances: 1 }).https.onCall( if (closeTime && Date.now() > closeTime) return { status: 'error', message: 'Trading is closed' } + const [lastAnswer] = await getValues( + firestore + .collection(`contracts/${contractId}/answers`) + .orderBy('number', 'desc') + .limit(1) + ) + + if (!lastAnswer) + return { status: 'error', message: 'Could not fetch last answer' } + + const number = lastAnswer.number + 1 + const id = `${number}` + const newAnswerDoc = firestore .collection(`contracts/${contractId}/answers`) - .doc() + .doc(id) const answerId = newAnswerDoc.id const { username, name, avatarUrl } = user const answer: Answer = { - id: answerId, + id, + number, contractId, createdTime: Date.now(), userId: user.id, diff --git a/functions/src/create-contract.ts b/functions/src/create-contract.ts index 74558ff1..bc02a0b8 100644 --- a/functions/src/create-contract.ts +++ b/functions/src/create-contract.ts @@ -107,9 +107,9 @@ export const createContract = functions await yesBetDoc.set(yesBet) await noBetDoc.set(noBet) } else if (outcomeType === 'MULTI') { - const noneAnswerDoc = firestore.doc( - `contracts/${contract.id}/answers/NONE` - ) + const noneAnswerDoc = firestore + .collection(`contracts/${contract.id}/answers`) + .doc('0') const noneAnswer = getNoneAnswer(contract.id, creator) await noneAnswerDoc.set(noneAnswer) diff --git a/web/components/answers-panel.tsx b/web/components/answers-panel.tsx index b0d7b628..21e3b799 100644 --- a/web/components/answers-panel.tsx +++ b/web/components/answers-panel.tsx @@ -1,4 +1,5 @@ import clsx from 'clsx' +import _ from 'lodash' import { useEffect, useRef, useState } from 'react' import Textarea from 'react-expanding-textarea' import { XIcon } from '@heroicons/react/solid' @@ -30,16 +31,23 @@ import { } from '../../common/calculate' import { firebaseLogin } from '../lib/firebase/users' import { Bet } from '../../common/bet' +import { useAnswers } from '../hooks/use-answers' export function AnswersPanel(props: { contract: Contract<'MULTI'> answers: Answer[] }) { - const { contract, answers } = props + const { contract } = props + + const answers = useAnswers(contract.id) ?? props.answers + const sortedAnswers = _.sortBy( + answers, + (answer) => -1 * getOutcomeProbability(contract.totalShares, answer.id) + ) return ( - {answers.map((answer) => ( + {sortedAnswers.map((answer) => ( ))} @@ -49,7 +57,7 @@ export function AnswersPanel(props: { function AnswerItem(props: { answer: Answer; contract: Contract<'MULTI'> }) { const { answer, contract } = props - const { username, avatarUrl, name, createdTime } = answer + const { username, avatarUrl, name, createdTime, number, text } = answer const createdDate = dayjs(createdTime).format('MMM D') const prob = getOutcomeProbability(contract.totalShares, answer.id) @@ -58,48 +66,46 @@ function AnswerItem(props: { answer: Answer; contract: Contract<'MULTI'> }) { const [isBetting, setIsBetting] = useState(false) return ( - - - -
{answer.text}
+ + +
{text}
- - - - -
{name}
-
-
+ + + + +
{name}
+
+
-
+
-
- - {createdDate} - -
-
- +
+ + {createdDate} + +
+
+
#{number}
+
+ - {!isBetting && ( - -
{probPercent}
- { - setIsBetting(true) - }} - /> - - )} -
- - {isBetting && ( + {isBetting ? ( setIsBetting(false)} /> + ) : ( + +
{probPercent}
+ { + setIsBetting(true) + }} + /> +
)} ) @@ -178,7 +184,7 @@ function AnswerBetPanel(props: { const currentReturnPercent = (currentReturn * 100).toFixed() + '%' return ( - +
Buy this answer
@@ -209,7 +215,7 @@ function AnswerBetPanel(props: { - Potential payout + Payout if chosen }) { Submit answer & bet - +
Bet amount
+ {folds.length === 0 ? ( ) : ( @@ -97,7 +97,7 @@ export const ContractOverview = (props: { - + {folds.length === 0 ? ( @@ -107,7 +107,7 @@ export const ContractOverview = (props: { {folds.length > 0 && ( - + )} {/* Show a delete button for contracts without any trading */} diff --git a/web/components/outcome-label.tsx b/web/components/outcome-label.tsx index 6d5acbe1..8b2d399e 100644 --- a/web/components/outcome-label.tsx +++ b/web/components/outcome-label.tsx @@ -6,7 +6,8 @@ export function OutcomeLabel(props: { if (outcome === 'YES') return if (outcome === 'NO') return if (outcome === 'MKT') return - return + if (outcome === 'CANCEL') return + return } export function YesLabel() { @@ -24,3 +25,7 @@ export function CancelLabel() { export function ProbLabel() { return PROB } + +export function AnswerNumberLabel(props: { number: string }) { + return #{props.number} +} diff --git a/web/lib/firebase/answers.ts b/web/lib/firebase/answers.ts index 8b50e859..ab41165b 100644 --- a/web/lib/firebase/answers.ts +++ b/web/lib/firebase/answers.ts @@ -1,37 +1,13 @@ -import { doc, collection, setDoc } from 'firebase/firestore' +import { collection } from 'firebase/firestore' import { getValues, listenForValues } from './utils' import { db } from './init' -import { User } from '../../../common/user' import { Answer } from '../../../common/answer' function getAnswersCollection(contractId: string) { return collection(db, 'contracts', contractId, 'answers') } -export async function createAnswer( - contractId: string, - text: string, - user: User -) { - const { id: userId, username, name, avatarUrl } = user - - const ref = doc(getAnswersCollection(contractId)) - - const answer: Answer = { - id: ref.id, - contractId, - createdTime: Date.now(), - userId, - username, - name, - avatarUrl, - text, - } - - return await setDoc(ref, answer) -} - export async function listAllAnswers(contractId: string) { const answers = await getValues(getAnswersCollection(contractId)) answers.sort((c1, c2) => c1.createdTime - c2.createdTime) diff --git a/web/pages/[username]/[contractSlug].tsx b/web/pages/[username]/[contractSlug].tsx index 0798407a..60661627 100644 --- a/web/pages/[username]/[contractSlug].tsx +++ b/web/pages/[username]/[contractSlug].tsx @@ -127,6 +127,7 @@ export default function ContractPage(props: { contract={contract as any} answers={props.answers} /> +
)}