diff --git a/functions/src/resolve-market.ts b/functions/src/resolve-market.ts
index dd412149..5da5b272 100644
--- a/functions/src/resolve-market.ts
+++ b/functions/src/resolve-market.ts
@@ -8,6 +8,7 @@ import { Bet } from '../../common/bet'
import { getUser, payUser } from './utils'
import { sendMarketResolutionEmail } from './emails'
import { getPayouts, getPayoutsMultiOutcome } from '../../common/payouts'
+import { removeUndefinedProps } from '../../common/util/object'
export const resolveMarket = functions
.runWith({ minInstances: 1 })
@@ -31,7 +32,7 @@ export const resolveMarket = functions
if (!contractSnap.exists)
return { status: 'error', message: 'Invalid contract' }
const contract = contractSnap.data() as Contract
- const { creatorId, outcomeType } = contract
+ const { creatorId, outcomeType, closeTime } = contract
if (outcomeType === 'BINARY') {
if (!['YES', 'NO', 'MKT', 'CANCEL'].includes(outcome))
@@ -68,15 +69,21 @@ export const resolveMarket = functions
const resolutionProbability =
probabilityInt !== undefined ? probabilityInt / 100 : undefined
- await contractDoc.update({
- isResolved: true,
- resolution: outcome,
- resolutionTime: Date.now(),
- ...(resolutionProbability === undefined
- ? {}
- : { resolutionProbability }),
- ...(resolutions === undefined ? {} : { resolutions }),
- })
+ const resolutionTime = Date.now()
+ const newCloseTime = closeTime
+ ? Math.min(closeTime, resolutionTime)
+ : closeTime
+
+ await contractDoc.update(
+ removeUndefinedProps({
+ isResolved: true,
+ resolution: outcome,
+ resolutionTime,
+ closeTime: newCloseTime,
+ resolutionProbability,
+ resolutions,
+ })
+ )
console.log('contract ', contractId, 'resolved to:', outcome)
diff --git a/web/components/bets-list.tsx b/web/components/bets-list.tsx
index c9822688..4638435e 100644
--- a/web/components/bets-list.tsx
+++ b/web/components/bets-list.tsx
@@ -157,8 +157,8 @@ export function BetsList(props: { user: User }) {
>
By value
By profit
- Newest
- Settled
+ Most recent
+ Resolved
@@ -358,22 +358,26 @@ export function MyBetsSummary(props: {
{formatMoney(expectation)}
*/}
-
-
- Payout if
-
-
- {formatMoney(yesWinnings)}
-
-
-
-
- Payout if
-
-
- {formatMoney(noWinnings)}
-
-
+ {isBinary && (
+ <>
+
+
+ Payout if
+
+
+ {formatMoney(yesWinnings)}
+
+
+
+
+ Payout if
+
+
+ {formatMoney(noWinnings)}
+
+
+ >
+ )}
{isBinary ? (
@@ -418,9 +422,10 @@ export function ContractBetsTable(props: {
- {isResolved ? <>Payout> : <>Sale price>}
Outcome
Amount
+ {isResolved ? <>Payout> : <>Sale price>}
+ {!isResolved && Payout if chosen }
Probability
Shares
Date
@@ -471,6 +476,11 @@ function BetRow(props: { bet: Bet; contract: Contract; saleBet?: Bet }) {
)
)
+ const payoutIfChosenDisplay =
+ bet.outcome === '0' && bet.isAnte
+ ? 'N/A'
+ : formatMoney(calculatePayout(contract, bet, bet.outcome))
+
return (
@@ -478,11 +488,12 @@ function BetRow(props: { bet: Bet; contract: Contract; saleBet?: Bet }) {
)}
- {saleDisplay}
{formatMoney(amount)}
+ {saleDisplay}
+ {!isResolved && {payoutIfChosenDisplay} }
{formatPercent(probBefore)} → {formatPercent(probAfter)}
@@ -499,6 +510,7 @@ function SellButton(props: { contract: Contract; bet: Bet }) {
}, [])
const { contract, bet } = props
+ const isBinary = contract.outcomeType === 'BINARY'
const [isSubmitting, setIsSubmitting] = useState(false)
const initialProb = getOutcomeProbability(
@@ -537,8 +549,9 @@ function SellButton(props: { contract: Contract; bet: Bet }) {
- Implied probability: {formatPercent(initialProb)} →{' '}
- {formatPercent(outcomeProb)}
+ ({isBinary ? 'Updated' : }{' '}
+ probability: {formatPercent(initialProb)} → {formatPercent(outcomeProb)}
+ )
)
diff --git a/web/components/contract-feed.tsx b/web/components/contract-feed.tsx
index 58dd5f01..c582a043 100644
--- a/web/components/contract-feed.tsx
+++ b/web/components/contract-feed.tsx
@@ -790,3 +790,27 @@ export function ContractFeed(props: {
)
}
+
+export function ContractSummaryFeed(props: {
+ contract: Contract
+ betRowClassName?: string
+}) {
+ const { contract, betRowClassName } = props
+ const { outcomeType } = contract
+ const isBinary = outcomeType === 'BINARY'
+
+ return (
+
+
+ {isBinary && tradingAllowed(contract) && (
+
+ )}
+
+ )
+}
diff --git a/web/components/contracts-list.tsx b/web/components/contracts-list.tsx
index 6fc49780..911e546e 100644
--- a/web/components/contracts-list.tsx
+++ b/web/components/contracts-list.tsx
@@ -205,9 +205,11 @@ export function SearchableGrid(props: {
}) {
const { contracts, query, setQuery, sort, setSort, byOneCreator } = props
+ const queryWords = query.toLowerCase().split(' ')
function check(corpus: String) {
- return corpus.toLowerCase().includes(query.toLowerCase())
+ return queryWords.every((word) => corpus.toLowerCase().includes(word))
}
+
let matches = contracts.filter(
(c) =>
check(c.question) ||
diff --git a/web/components/fast-fold-following.tsx b/web/components/fast-fold-following.tsx
index d4d350ee..577719b6 100644
--- a/web/components/fast-fold-following.tsx
+++ b/web/components/fast-fold-following.tsx
@@ -32,7 +32,7 @@ function FollowFoldButton(props: {
className={clsx(
'rounded-full border-2 px-4 py-1 shadow-md',
'cursor-pointer',
- followed ? 'bg-gray-300 border-gray-300' : 'bg-white'
+ followed ? 'border-gray-300 bg-gray-300' : 'bg-white'
)}
onClick={onClick}
>
@@ -101,7 +101,7 @@ export const FastFoldFollowing = (props: {
]}
/>
-
+
>
)
}
diff --git a/web/components/profile-menu.tsx b/web/components/profile-menu.tsx
index d1a42cd8..7cc00e6a 100644
--- a/web/components/profile-menu.tsx
+++ b/web/components/profile-menu.tsx
@@ -54,6 +54,10 @@ function getNavigationOptions(
name: 'Your trades',
href: '/trades',
},
+ {
+ name: 'Add funds',
+ href: '/add-funds',
+ },
{
name: 'Leaderboards',
href: '/leaderboards',
diff --git a/web/hooks/use-contracts.ts b/web/hooks/use-contracts.ts
index 60744c33..af36cd82 100644
--- a/web/hooks/use-contracts.ts
+++ b/web/hooks/use-contracts.ts
@@ -5,6 +5,7 @@ import {
listenForActiveContracts,
listenForContracts,
listenForHotContracts,
+ listenForInactiveContracts,
} from '../lib/firebase/contracts'
import { listenForTaggedContracts } from '../lib/firebase/folds'
@@ -28,6 +29,16 @@ export const useActiveContracts = () => {
return contracts
}
+export const useInactiveContracts = () => {
+ const [contracts, setContracts] = useState()
+
+ useEffect(() => {
+ return listenForInactiveContracts(setContracts)
+ }, [])
+
+ return contracts
+}
+
export const useUpdatedContracts = (initialContracts: Contract[]) => {
const [contracts, setContracts] = useState(initialContracts)
diff --git a/web/hooks/use-active-contracts.ts b/web/hooks/use-find-active-contracts.ts
similarity index 66%
rename from web/hooks/use-active-contracts.ts
rename to web/hooks/use-find-active-contracts.ts
index 1c4c1b91..f8aa5627 100644
--- a/web/hooks/use-active-contracts.ts
+++ b/web/hooks/use-find-active-contracts.ts
@@ -1,5 +1,5 @@
import _ from 'lodash'
-import { useRef } from 'react'
+import { useMemo, useRef } from 'react'
import { Fold } from '../../common/fold'
import { User } from '../../common/user'
@@ -9,7 +9,7 @@ import { Comment, getRecentComments } from '../lib/firebase/comments'
import { Contract, getActiveContracts } from '../lib/firebase/contracts'
import { listAllFolds } from '../lib/firebase/folds'
import { findActiveContracts } from '../pages/activity'
-import { useActiveContracts } from './use-contracts'
+import { useInactiveContracts } from './use-contracts'
import { useFollowedFolds } from './use-fold'
import { useUserBetContracts } from './use-user-bets'
@@ -28,24 +28,15 @@ export const getAllContractInfo = async () => {
return { contracts, recentBets, recentComments, folds }
}
-export const useFindActiveContracts = (
- props: {
- contracts: Contract[]
- folds: Fold[]
- recentBets: Bet[]
- recentComments: Comment[]
- },
- user: User | undefined | null
+export const useFilterYourContracts = (
+ user: User | undefined | null,
+ folds: Fold[],
+ contracts: Contract[]
) => {
- const { recentBets, recentComments } = props
- const contracts = useActiveContracts() ?? props.contracts
-
const followedFoldIds = useFollowedFolds(user)
const followedFolds = filterDefined(
- (followedFoldIds ?? []).map((id) =>
- props.folds.find((fold) => fold.id === id)
- )
+ (followedFoldIds ?? []).map((id) => folds.find((fold) => fold.id === id))
)
// Save the initial followed fold slugs.
@@ -64,20 +55,33 @@ export const useFindActiveContracts = (
: undefined
// Show no contracts before your info is loaded.
- let feedContracts: Contract[] = []
+ let yourContracts: Contract[] = []
if (yourBetContracts && followedFoldIds) {
// Show all contracts if no folds are followed.
- if (followedFoldIds.length === 0) feedContracts = contracts
+ if (followedFoldIds.length === 0) yourContracts = contracts
else
- feedContracts = contracts.filter(
+ yourContracts = contracts.filter(
(contract) =>
contract.lowercaseTags.some((tag) => tagSet.has(tag)) ||
yourBetContracts.has(contract.id)
)
}
+ return {
+ yourContracts,
+ initialFollowedFoldSlugs,
+ }
+}
+
+export const useFindActiveContracts = (props: {
+ contracts: Contract[]
+ recentBets: Bet[]
+ recentComments: Comment[]
+}) => {
+ const { contracts, recentBets, recentComments } = props
+
const activeContracts = findActiveContracts(
- feedContracts,
+ contracts,
recentComments,
recentBets
)
@@ -101,6 +105,24 @@ export const useFindActiveContracts = (
activeContracts,
activeBets,
activeComments,
- initialFollowedFoldSlugs,
}
}
+
+export const useExploreContracts = (maxContracts = 75) => {
+ const inactiveContracts = useInactiveContracts()
+
+ const contractsDict = _.fromPairs(
+ (inactiveContracts ?? []).map((c) => [c.id, c])
+ )
+
+ // Preserve random ordering once inactiveContracts loaded.
+ const exploreContractIds = useMemo(
+ () => _.shuffle(Object.keys(contractsDict)),
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [!!inactiveContracts]
+ ).slice(0, maxContracts)
+
+ if (!inactiveContracts) return undefined
+
+ return filterDefined(exploreContractIds.map((id) => contractsDict[id]))
+}
diff --git a/web/lib/firebase/contracts.ts b/web/lib/firebase/contracts.ts
index b22a547f..eb1b65e1 100644
--- a/web/lib/firebase/contracts.ts
+++ b/web/lib/firebase/contracts.ts
@@ -115,7 +115,7 @@ export function listenForContracts(
return listenForValues(q, setContracts)
}
-const activeContracts = query(
+const activeContractsQuery = query(
contractCollection,
where('isResolved', '==', false),
where('visibility', '==', 'public'),
@@ -123,13 +123,31 @@ const activeContracts = query(
)
export function getActiveContracts() {
- return getValues(activeContracts)
+ return getValues(activeContractsQuery)
}
export function listenForActiveContracts(
setContracts: (contracts: Contract[]) => void
) {
- return listenForValues(activeContracts, setContracts)
+ return listenForValues(activeContractsQuery, setContracts)
+}
+
+const inactiveContractsQuery = query(
+ contractCollection,
+ where('isResolved', '==', false),
+ where('closeTime', '>', Date.now()),
+ where('visibility', '==', 'public'),
+ where('volume24Hours', '==', 0)
+)
+
+export function getInactiveContracts() {
+ return getValues(inactiveContractsQuery)
+}
+
+export function listenForInactiveContracts(
+ setContracts: (contracts: Contract[]) => void
+) {
+ return listenForValues(inactiveContractsQuery, setContracts)
}
export function listenForContract(
diff --git a/web/pages/[username]/[contractSlug].tsx b/web/pages/[username]/[contractSlug].tsx
index f90da12e..076c4939 100644
--- a/web/pages/[username]/[contractSlug].tsx
+++ b/web/pages/[username]/[contractSlug].tsx
@@ -188,12 +188,8 @@ function BetsSection(props: {
return (
- {isBinary && (
- <>
-
-
- >
- )}
+
+
diff --git a/web/pages/about.tsx b/web/pages/about.tsx
index c818be8e..85e3eaee 100644
--- a/web/pages/about.tsx
+++ b/web/pages/about.tsx
@@ -139,6 +139,15 @@ function Contents() {
bettors that are correct more often will gain influence, leading to
better-calibrated forecasts over time.
+
+ Since our launch, we've seen hundreds of users trade each day, on over a
+ thousand different markets! You can track the popularity of our platform
+ at{' '}
+
+ http://manifold.markets/analytics
+
+ .
+
How are markets resolved?
The creator of the prediction market decides the outcome and earns{' '}
diff --git a/web/pages/activity.tsx b/web/pages/activity.tsx
index e546e1d1..bab58328 100644
--- a/web/pages/activity.tsx
+++ b/web/pages/activity.tsx
@@ -1,5 +1,5 @@
import _ from 'lodash'
-import { ContractFeed } from '../components/contract-feed'
+import { ContractFeed, ContractSummaryFeed } from '../components/contract-feed'
import { Page } from '../components/page'
import { Contract } from '../lib/firebase/contracts'
import { Comment } from '../lib/firebase/comments'
@@ -99,6 +99,24 @@ export function ActivityFeed(props: {
)
}
+export function SummaryActivityFeed(props: { contracts: Contract[] }) {
+ const { contracts } = props
+
+ return (
+
+
+
+ {contracts.map((contract) => (
+
+
+
+ ))}
+
+
+
+ )
+}
+
export default function ActivityPage() {
return (
diff --git a/web/pages/add-funds.tsx b/web/pages/add-funds.tsx
index dcf65088..339be265 100644
--- a/web/pages/add-funds.tsx
+++ b/web/pages/add-funds.tsx
@@ -19,11 +19,11 @@ export default function AddFundsPage() {
-
-
+
+
@@ -50,7 +50,7 @@ export default function AddFundsPage() {
diff --git a/web/public/stylized-crane-black.png b/web/public/stylized-crane-black.png
new file mode 100644
index 00000000..4bdf2bc6
Binary files /dev/null and b/web/public/stylized-crane-black.png differ