Merge branch 'main' into custom-feed

This commit is contained in:
James Grugett 2022-02-03 17:33:12 -06:00
commit e5f553fa1a
16 changed files with 82 additions and 43 deletions

View File

@ -104,7 +104,7 @@ export function calculateShareValue(contract: Contract, bet: Bet) {
} }
export function calculateSaleAmount(contract: Contract, bet: Bet) { export function calculateSaleAmount(contract: Contract, bet: Bet) {
return (1 - 2 * FEES) * calculateShareValue(contract, bet) return (1 - FEES) * calculateShareValue(contract, bet)
} }
export function calculatePayout( export function calculatePayout(
@ -145,7 +145,7 @@ export function calculateStandardPayout(
totalShares[outcome] - phantomShares[outcome] - totalBets[outcome] totalShares[outcome] - phantomShares[outcome] - totalBets[outcome]
const winningsPool = truePool - totalBets[outcome] const winningsPool = truePool - totalBets[outcome]
return amount + (1 - 2 * FEES) * ((shares - amount) / total) * winningsPool return amount + (1 - FEES) * ((shares - amount) / total) * winningsPool
} }
export function calculatePayoutAfterCorrectBet(contract: Contract, bet: Bet) { export function calculatePayoutAfterCorrectBet(contract: Contract, bet: Bet) {
@ -204,7 +204,7 @@ function calculateMktPayout(contract: Contract, bet: Bet) {
return ( return (
betP * bet.amount + betP * bet.amount +
(1 - 2 * FEES) * (1 - FEES) *
((betP * (bet.shares - bet.amount)) / weightedShareTotal) * ((betP * (bet.shares - bet.amount)) / weightedShareTotal) *
winningsPool winningsPool
) )

View File

@ -1,4 +1,4 @@
export const PLATFORM_FEE = 0.01 // == 1% export const PLATFORM_FEE = 0.01
export const CREATOR_FEE = 0.01 export const CREATOR_FEE = 0.09
export const FEES = PLATFORM_FEE + CREATOR_FEE export const FEES = PLATFORM_FEE + CREATOR_FEE

View File

@ -38,12 +38,12 @@ export const getStandardPayouts = (
userId: bet.userId, userId: bet.userId,
payout: payout:
bet.amount + bet.amount +
(1 - 2 * FEES) * (1 - FEES) *
((bet.shares - bet.amount) / shareDifferenceSum) * ((bet.shares - bet.amount) / shareDifferenceSum) *
winningsPool, winningsPool,
})) }))
const creatorPayout = 2 * CREATOR_FEE * winningsPool const creatorPayout = CREATOR_FEE * winningsPool
console.log( console.log(
'resolved', 'resolved',
@ -98,7 +98,7 @@ export const getMktPayouts = (
userId: bet.userId, userId: bet.userId,
payout: payout:
p * bet.amount + p * bet.amount +
(1 - 2 * FEES) * (1 - FEES) *
((p * (bet.shares - bet.amount)) / weightedShareTotal) * ((p * (bet.shares - bet.amount)) / weightedShareTotal) *
winningsPool, winningsPool,
})) }))
@ -107,12 +107,12 @@ export const getMktPayouts = (
userId: bet.userId, userId: bet.userId,
payout: payout:
(1 - p) * bet.amount + (1 - p) * bet.amount +
(1 - 2 * FEES) * (1 - FEES) *
(((1 - p) * (bet.shares - bet.amount)) / weightedShareTotal) * (((1 - p) * (bet.shares - bet.amount)) / weightedShareTotal) *
winningsPool, winningsPool,
})) }))
const creatorPayout = 2 * CREATOR_FEE * winningsPool const creatorPayout = CREATOR_FEE * winningsPool
return [ return [
...yesPayouts, ...yesPayouts,

View File

@ -36,8 +36,8 @@ export const getSellBetInfo = (
const probBefore = getProbability(contract.totalShares) const probBefore = getProbability(contract.totalShares)
const probAfter = getProbability(newTotalShares) const probAfter = getProbability(newTotalShares)
const creatorFee = 2 * CREATOR_FEE * adjShareValue const creatorFee = CREATOR_FEE * adjShareValue
const saleAmount = (1 - 2 * FEES) * adjShareValue const saleAmount = (1 - FEES) * adjShareValue
console.log( console.log(
'SELL M$', 'SELL M$',

View File

@ -130,6 +130,11 @@ function FeedBet(props: { activityItem: any }) {
className="textarea textarea-bordered w-full" className="textarea textarea-bordered w-full"
placeholder="Add a comment..." placeholder="Add a comment..."
rows={3} rows={3}
onKeyDown={(e) => {
if (e.key === 'Enter' && e.ctrlKey) {
submitComment()
}
}}
/> />
<button <button
className="btn btn-outline btn-sm mt-1" className="btn btn-outline btn-sm mt-1"
@ -195,6 +200,11 @@ export function ContractDescription(props: {
description.length description.length
) )
} }
onKeyDown={(e) => {
if (e.key === 'Enter' && e.ctrlKey) {
saveDescription(e)
}
}}
/> />
<Row className="gap-2"> <Row className="gap-2">
<button <button
@ -540,7 +550,8 @@ function FeedBetGroup(props: { activityItem: any }) {
const [yesBets, noBets] = _.partition(bets, (bet) => bet.outcome === 'YES') const [yesBets, noBets] = _.partition(bets, (bet) => bet.outcome === 'YES')
const createdTime = bets[0].createdTime // Use the time of the last bet for the entire group
const createdTime = bets[bets.length - 1].createdTime
return ( return (
<> <>

View File

@ -262,7 +262,7 @@ export function SearchableGrid(props: {
onChange={(e) => setSort(e.target.value as Sort)} onChange={(e) => setSort(e.target.value as Sort)}
> >
<option value="most-traded">Most traded</option> <option value="most-traded">Most traded</option>
<option value="24-hour-vol">24 hour volume</option> <option value="24-hour-vol">24h volume</option>
<option value="close-date">Closing soon</option> <option value="close-date">Closing soon</option>
<option value="newest">Newest</option> <option value="newest">Newest</option>

View File

@ -105,7 +105,7 @@ export function EditFoldButton(props: { fold: Fold; className?: string }) {
</div> </div>
<Spacer h={4} /> <Spacer h={4} />
<TagsList tags={tags.map((tag) => `#${tag}`)} noLink noLabel /> <TagsList tags={tags} noLink noLabel />
<Spacer h={4} /> <Spacer h={4} />
<div className="modal-action"> <div className="modal-action">

View File

@ -37,10 +37,7 @@ export function FollowFoldButton(props: { fold: Fold; className?: string }) {
} }
return ( return (
<button <button className={clsx('btn btn-sm', className)} onClick={onFollow}>
className={clsx('btn btn-secondary bg-indigo-500 btn-sm', className)}
onClick={onFollow}
>
Follow Follow
</button> </button>
) )

View File

@ -11,6 +11,7 @@ import { ConfirmationButton as ConfirmationButton } from './confirmation-button'
import { resolveMarket } from '../lib/firebase/api-call' import { resolveMarket } from '../lib/firebase/api-call'
import { ProbabilitySelector } from './probability-selector' import { ProbabilitySelector } from './probability-selector'
import { getProbability } from '../../common/calculate' import { getProbability } from '../../common/calculate'
import { CREATOR_FEE } from '../../common/fees'
export function ResolutionPanel(props: { export function ResolutionPanel(props: {
creator: User creator: User
@ -79,16 +80,20 @@ export function ResolutionPanel(props: {
<div> <div>
{outcome === 'YES' ? ( {outcome === 'YES' ? (
<> <>
Winnings will be paid out to YES bettors. You earn 1% of the pool. Winnings will be paid out to YES bettors. You earn{' '}
{CREATOR_FEE * 100}%.
</> </>
) : outcome === 'NO' ? ( ) : outcome === 'NO' ? (
<>Winnings will be paid out to NO bettors. You earn 1% of the pool.</> <>
Winnings will be paid out to NO bettors. You earn{' '}
{CREATOR_FEE * 100}%.
</>
) : outcome === 'CANCEL' ? ( ) : outcome === 'CANCEL' ? (
<>The pool will be returned to traders with no fees.</> <>The pool will be returned to traders with no fees.</>
) : outcome === 'MKT' ? ( ) : outcome === 'MKT' ? (
<> <>
Traders will be paid out at the probability you specify. You earn 1% Traders will be paid out at the probability you specify. You earn{' '}
of the pool. {CREATOR_FEE * 100}%.
</> </>
) : ( ) : (
<>Resolving this market will immediately pay out traders.</> <>Resolving this market will immediately pay out traders.</>

View File

@ -27,7 +27,7 @@ export function TagsInput(props: { contract: Contract; className?: string }) {
return ( return (
<Col className={clsx('gap-4', className)}> <Col className={clsx('gap-4', className)}>
<TagsList tags={newTags.map((tag) => `#${tag}`)} /> <TagsList tags={newTags} />
<Row className="items-center gap-4"> <Row className="items-center gap-4">
<input <input

View File

@ -3,7 +3,7 @@ import { Row } from './layout/row'
import { SiteLink } from './site-link' import { SiteLink } from './site-link'
import { Fold } from '../../common/fold' import { Fold } from '../../common/fold'
export function Hashtag(props: { tag: string; noLink?: boolean }) { function Hashtag(props: { tag: string; noLink?: boolean }) {
const { tag, noLink } = props const { tag, noLink } = props
const body = ( const body = (
<div <div
@ -35,7 +35,11 @@ export function TagsList(props: {
<Row className={clsx('items-center flex-wrap gap-2', className)}> <Row className={clsx('items-center flex-wrap gap-2', className)}>
{!noLabel && <div className="text-gray-500 mr-1">Tags</div>} {!noLabel && <div className="text-gray-500 mr-1">Tags</div>}
{tags.map((tag) => ( {tags.map((tag) => (
<Hashtag key={tag} tag={tag} noLink={noLink} /> <Hashtag
key={tag}
tag={tag.startsWith('#') ? tag : `#${tag}`}
noLink={noLink}
/>
))} ))}
</Row> </Row>
) )

View File

@ -1,7 +1,7 @@
import { cloneElement } from 'react' import { cloneElement } from 'react'
import { CREATOR_FEE } from '../../common/fees'
import { Page } from '../components/page' import { Page } from '../components/page'
import { SEO } from '../components/SEO' import { SEO } from '../components/SEO'
import { useContracts } from '../hooks/use-contracts'
import styles from './about.module.css' import styles from './about.module.css'
export default function About() { export default function About() {
@ -132,8 +132,9 @@ function Contents() {
</p> </p>
<h3 id="how-are-markets-resolved-">How are markets resolved?</h3> <h3 id="how-are-markets-resolved-">How are markets resolved?</h3>
<p> <p>
The creator of the prediction market decides the outcome and earns 1% of The creator of the prediction market decides the outcome and earns{' '}
the betting pool for their effort. {CREATOR_FEE * 100}% of the winnings as a commission for creating and
resolving the market.
</p> </p>
<p> <p>
This simple resolution mechanism has surprising benefits in allowing a This simple resolution mechanism has surprising benefits in allowing a

View File

@ -177,7 +177,7 @@ export function NewContract(props: { question: string; tag?: string }) {
<span>Market ante</span> <span>Market ante</span>
<InfoTooltip <InfoTooltip
text={`Subsidize your market to encourage trading. Ante bets are set to match your initial probability. text={`Subsidize your market to encourage trading. Ante bets are set to match your initial probability.
You earn ${CREATOR_FEE * 100}% of trading volume.`} You earn ${CREATOR_FEE * 100}% of the winnings.`}
/> />
</label> </label>
<AmountInput <AmountInput

View File

@ -48,6 +48,10 @@ export async function getStaticProps(props: { params: { slugs: string[] } }) {
const contracts = fold ? await getFoldContracts(fold).catch((_) => []) : [] const contracts = fold ? await getFoldContracts(fold).catch((_) => []) : []
const betsPromise = Promise.all(
contracts.map((contract) => listAllBets(contract.id))
)
const [contractComments, contractRecentBets] = await Promise.all([ const [contractComments, contractRecentBets] = await Promise.all([
Promise.all( Promise.all(
contracts.map((contract) => listAllComments(contract.id).catch((_) => [])) contracts.map((contract) => listAllComments(contract.id).catch((_) => []))
@ -79,17 +83,16 @@ export async function getStaticProps(props: { params: { slugs: string[] } }) {
contractComments[contracts.findIndex((c) => c.id === contract.id)] contractComments[contracts.findIndex((c) => c.id === contract.id)]
) )
const curator = await curatorPromise const bets = await betsPromise
const bets = await Promise.all(
contracts.map((contract) => listAllBets(contract.id))
)
const creatorScores = scoreCreators(contracts, bets) const creatorScores = scoreCreators(contracts, bets)
const topCreators = await toTopUsers(creatorScores)
const traderScores = scoreTraders(contracts, bets) const traderScores = scoreTraders(contracts, bets)
const topTraders = await toTopUsers(traderScores) const [topCreators, topTraders] = await Promise.all([
toTopUsers(creatorScores),
toTopUsers(traderScores),
])
const curator = await curatorPromise
return { return {
props: { props: {
@ -338,7 +341,7 @@ function FoldOverview(props: { fold: Fold; curator: User }) {
Includes markets matching any of these tags: Includes markets matching any of these tags:
</div> </div>
<TagsList tags={tags.map((tag) => `#${tag}`)} noLabel /> <TagsList tags={tags} noLabel />
</Col> </Col>
</Col> </Col>
) )

View File

@ -8,6 +8,7 @@ import { Col } from '../components/layout/col'
import { Row } from '../components/layout/row' import { Row } from '../components/layout/row'
import { Page } from '../components/page' import { Page } from '../components/page'
import { SiteLink } from '../components/site-link' import { SiteLink } from '../components/site-link'
import { TagsList } from '../components/tags-list'
import { Title } from '../components/title' import { Title } from '../components/title'
import { UserLink } from '../components/user-page' import { UserLink } from '../components/user-page'
import { useFolds } from '../hooks/use-fold' import { useFolds } from '../hooks/use-fold'
@ -63,7 +64,7 @@ export default function Folds(props: {
return ( return (
<Page> <Page>
<Col className="items-center"> <Col className="items-center">
<Col className="max-w-lg w-full"> <Col className="max-w-xl w-full">
<Col className="px-4 sm:px-0"> <Col className="px-4 sm:px-0">
<Row className="justify-between items-center"> <Row className="justify-between items-center">
<Title text="Explore communities" /> <Title text="Explore communities" />
@ -76,7 +77,7 @@ export default function Folds(props: {
</div> </div>
</Col> </Col>
<Col className="gap-2"> <Col className="gap-4">
{folds.map((fold) => ( {folds.map((fold) => (
<FoldCard <FoldCard
key={fold.id} key={fold.id}
@ -93,16 +94,17 @@ export default function Folds(props: {
function FoldCard(props: { fold: Fold; curator: User | undefined }) { function FoldCard(props: { fold: Fold; curator: User | undefined }) {
const { fold, curator } = props const { fold, curator } = props
const tags = fold.tags.slice(1)
return ( return (
<Col <Col
key={fold.id} key={fold.id}
className="bg-white hover:bg-gray-100 p-4 rounded-xl gap-1 shadow-md relative" className="bg-white hover:bg-gray-100 p-8 rounded-xl gap-1 shadow-md relative"
> >
<Link href={foldPath(fold)}> <Link href={foldPath(fold)}>
<a className="absolute left-0 right-0 top-0 bottom-0" /> <a className="absolute left-0 right-0 top-0 bottom-0" />
</Link> </Link>
<Row className="justify-between items-center gap-2"> <Row className="justify-between items-center gap-2">
<SiteLink href={foldPath(fold)}>{fold.name}</SiteLink> <span className="text-xl">{fold.name}</span>
<FollowFoldButton className="z-10 mb-1" fold={fold} /> <FollowFoldButton className="z-10 mb-1" fold={fold} />
</Row> </Row>
<Row className="items-center gap-2 text-gray-500 text-sm"> <Row className="items-center gap-2 text-gray-500 text-sm">
@ -118,6 +120,9 @@ function FoldCard(props: { fold: Fold; curator: User | undefined }) {
</Row> </Row>
</Row> </Row>
<div className="text-gray-500 text-sm">{fold.about}</div> <div className="text-gray-500 text-sm">{fold.about}</div>
{tags.length > 0 && (
<TagsList className="mt-4" tags={tags} noLink noLabel />
)}
</Col> </Col>
) )
} }

View File

@ -17,6 +17,7 @@ import { Fold } from '../../common/fold'
import { filterDefined } from '../../common/util/array' import { filterDefined } from '../../common/util/array'
import { useUserBets } from '../hooks/use-user-bets' import { useUserBets } from '../hooks/use-user-bets'
import { LoadingIndicator } from '../components/loading-indicator' import { LoadingIndicator } from '../components/loading-indicator'
import { TagsList } from '../components/tags-list'
export async function getStaticProps() { export async function getStaticProps() {
const [contracts, folds] = await Promise.all([ const [contracts, folds] = await Promise.all([
@ -131,6 +132,18 @@ const Home = (props: { contracts: Contract[]; folds: Fold[] }) => {
<Col className="max-w-3xl w-full"> <Col className="max-w-3xl w-full">
<FeedCreate user={user ?? undefined} /> <FeedCreate user={user ?? undefined} />
<Spacer h={4} /> <Spacer h={4} />
<TagsList
className="mx-2"
tags={[
'#politics',
'#crypto',
'#covid',
'#sports',
'#meta',
'#science',
]}
/>
<Spacer h={4} />
{activeContracts ? ( {activeContracts ? (
<ActivityFeed <ActivityFeed
contracts={activeContracts} contracts={activeContracts}