Merge branch 'main' into user-profile
This commit is contained in:
commit
e0cfb71a8c
|
@ -174,7 +174,10 @@ export function calculatePayoutAfterCorrectBet(contract: Contract, bet: Bet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateMktPayout(contract: Contract, bet: Bet) {
|
function calculateMktPayout(contract: Contract, bet: Bet) {
|
||||||
const p = getProbability(contract.totalShares)
|
const p =
|
||||||
|
contract.resolutionProbability !== undefined
|
||||||
|
? contract.resolutionProbability
|
||||||
|
: getProbability(contract.totalShares)
|
||||||
|
|
||||||
const weightedTotal =
|
const weightedTotal =
|
||||||
p * contract.totalBets.YES + (1 - p) * contract.totalBets.NO
|
p * contract.totalBets.YES + (1 - p) * contract.totalBets.NO
|
||||||
|
|
|
@ -27,6 +27,7 @@ export type Contract = {
|
||||||
isResolved: boolean
|
isResolved: boolean
|
||||||
resolutionTime?: number // When the contract creator resolved the market
|
resolutionTime?: number // When the contract creator resolved the market
|
||||||
resolution?: outcome // Chosen by creator; must be one of outcomes
|
resolution?: outcome // Chosen by creator; must be one of outcomes
|
||||||
|
resolutionProbability?: number
|
||||||
|
|
||||||
volume24Hours: number
|
volume24Hours: number
|
||||||
volume7Days: number
|
volume7Days: number
|
||||||
|
|
|
@ -19,7 +19,7 @@ export function getNewContract(
|
||||||
calcStartPool(initialProb, ante)
|
calcStartPool(initialProb, ante)
|
||||||
|
|
||||||
const tags = parseTags(
|
const tags = parseTags(
|
||||||
`${extraTags.map((tag) => `#${tag}`).join(' ')} ${question} ${description}`
|
`${question} ${description} ${extraTags.map((tag) => `#${tag}`).join(' ')}`
|
||||||
)
|
)
|
||||||
const lowercaseTags = tags.map((tag) => tag.toLowerCase())
|
const lowercaseTags = tags.map((tag) => tag.toLowerCase())
|
||||||
|
|
||||||
|
|
|
@ -59,8 +59,16 @@ export const getStandardPayouts = (
|
||||||
]) // add creator fee
|
]) // add creator fee
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getMktPayouts = (contract: Contract, bets: Bet[]) => {
|
export const getMktPayouts = (
|
||||||
const p = getProbability(contract.totalShares)
|
contract: Contract,
|
||||||
|
bets: Bet[],
|
||||||
|
resolutionProbability?: number
|
||||||
|
) => {
|
||||||
|
const p =
|
||||||
|
resolutionProbability === undefined
|
||||||
|
? getProbability(contract.totalShares)
|
||||||
|
: resolutionProbability
|
||||||
|
|
||||||
const poolTotal = contract.pool.YES + contract.pool.NO
|
const poolTotal = contract.pool.YES + contract.pool.NO
|
||||||
console.log('Resolved MKT at p=', p, 'pool: $M', poolTotal)
|
console.log('Resolved MKT at p=', p, 'pool: $M', poolTotal)
|
||||||
|
|
||||||
|
@ -116,14 +124,15 @@ export const getMktPayouts = (contract: Contract, bets: Bet[]) => {
|
||||||
export const getPayouts = (
|
export const getPayouts = (
|
||||||
outcome: outcome,
|
outcome: outcome,
|
||||||
contract: Contract,
|
contract: Contract,
|
||||||
bets: Bet[]
|
bets: Bet[],
|
||||||
|
resolutionProbability?: number
|
||||||
) => {
|
) => {
|
||||||
switch (outcome) {
|
switch (outcome) {
|
||||||
case 'YES':
|
case 'YES':
|
||||||
case 'NO':
|
case 'NO':
|
||||||
return getStandardPayouts(outcome, contract, bets)
|
return getStandardPayouts(outcome, contract, bets)
|
||||||
case 'MKT':
|
case 'MKT':
|
||||||
return getMktPayouts(contract, bets)
|
return getMktPayouts(contract, bets, resolutionProbability)
|
||||||
case 'CANCEL':
|
case 'CANCEL':
|
||||||
return getCancelPayouts(contract, bets)
|
return getCancelPayouts(contract, bets)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,19 +3,25 @@ export function parseTags(text: string) {
|
||||||
const matches = (text.match(regex) || []).map((match) =>
|
const matches = (text.match(regex) || []).map((match) =>
|
||||||
match.trim().substring(1)
|
match.trim().substring(1)
|
||||||
)
|
)
|
||||||
const tagSet = new Set(matches)
|
const tagSet = new Set()
|
||||||
const uniqueTags: string[] = []
|
const uniqueTags: string[] = []
|
||||||
tagSet.forEach((tag) => uniqueTags.push(tag))
|
// Keep casing of last tag.
|
||||||
|
matches.reverse()
|
||||||
|
for (const tag of matches) {
|
||||||
|
const lowercase = tag.toLowerCase()
|
||||||
|
if (!tagSet.has(lowercase)) {
|
||||||
|
tagSet.add(lowercase)
|
||||||
|
uniqueTags.push(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uniqueTags.reverse()
|
||||||
return uniqueTags
|
return uniqueTags
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseWordsAsTags(text: string) {
|
export function parseWordsAsTags(text: string) {
|
||||||
const regex = /(?:^|\s)(?:#?[a-z0-9_]+)/gi
|
const taggedText = text
|
||||||
const matches = (text.match(regex) || [])
|
.split(/\s+/)
|
||||||
.map((match) => match.replace('#', '').trim())
|
.map((tag) => `#${tag}`)
|
||||||
.filter((tag) => tag)
|
.join(' ')
|
||||||
const tagSet = new Set(matches)
|
return parseTags(taggedText)
|
||||||
const uniqueTags: string[] = []
|
|
||||||
tagSet.forEach((tag) => uniqueTags.push(tag))
|
|
||||||
return uniqueTags
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
import { getProbability } from '../../common/calculate'
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from '../../common/contract'
|
||||||
import { User } from '../../common/user'
|
import { User } from '../../common/user'
|
||||||
|
import { formatPercent } from '../../common/util/format'
|
||||||
import { sendTemplateEmail } from './send-email'
|
import { sendTemplateEmail } from './send-email'
|
||||||
import { getPrivateUser, getUser } from './utils'
|
import { getPrivateUser, getUser } from './utils'
|
||||||
|
|
||||||
|
@ -18,7 +20,8 @@ export const sendMarketResolutionEmail = async (
|
||||||
payout: number,
|
payout: number,
|
||||||
creator: User,
|
creator: User,
|
||||||
contract: Contract,
|
contract: Contract,
|
||||||
resolution: 'YES' | 'NO' | 'CANCEL' | 'MKT'
|
resolution: 'YES' | 'NO' | 'CANCEL' | 'MKT',
|
||||||
|
resolutionProbability?: number
|
||||||
) => {
|
) => {
|
||||||
const privateUser = await getPrivateUser(userId)
|
const privateUser = await getPrivateUser(userId)
|
||||||
if (
|
if (
|
||||||
|
@ -31,6 +34,14 @@ export const sendMarketResolutionEmail = async (
|
||||||
const user = await getUser(userId)
|
const user = await getUser(userId)
|
||||||
if (!user) return
|
if (!user) return
|
||||||
|
|
||||||
|
const prob = resolutionProbability ?? getProbability(contract.totalShares)
|
||||||
|
|
||||||
|
const toDisplayResolution = {
|
||||||
|
YES: 'YES',
|
||||||
|
NO: 'NO',
|
||||||
|
CANCEL: 'N/A',
|
||||||
|
MKT: formatPercent(prob),
|
||||||
|
}
|
||||||
const outcome = toDisplayResolution[resolution]
|
const outcome = toDisplayResolution[resolution]
|
||||||
|
|
||||||
const subject = `Resolved ${outcome}: ${contract.question}`
|
const subject = `Resolved ${outcome}: ${contract.question}`
|
||||||
|
@ -56,5 +67,3 @@ export const sendMarketResolutionEmail = async (
|
||||||
templateData
|
templateData
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const toDisplayResolution = { YES: 'YES', NO: 'NO', CANCEL: 'N/A', MKT: 'MKT' }
|
|
||||||
|
|
|
@ -16,17 +16,24 @@ export const resolveMarket = functions
|
||||||
data: {
|
data: {
|
||||||
outcome: 'YES' | 'NO' | 'CANCEL' | 'MKT'
|
outcome: 'YES' | 'NO' | 'CANCEL' | 'MKT'
|
||||||
contractId: string
|
contractId: string
|
||||||
|
probabilityInt?: number
|
||||||
},
|
},
|
||||||
context
|
context
|
||||||
) => {
|
) => {
|
||||||
const userId = context?.auth?.uid
|
const userId = context?.auth?.uid
|
||||||
if (!userId) return { status: 'error', message: 'Not authorized' }
|
if (!userId) return { status: 'error', message: 'Not authorized' }
|
||||||
|
|
||||||
const { outcome, contractId } = data
|
const { outcome, contractId, probabilityInt } = data
|
||||||
|
|
||||||
if (!['YES', 'NO', 'MKT', 'CANCEL'].includes(outcome))
|
if (!['YES', 'NO', 'MKT', 'CANCEL'].includes(outcome))
|
||||||
return { status: 'error', message: 'Invalid outcome' }
|
return { status: 'error', message: 'Invalid outcome' }
|
||||||
|
|
||||||
|
if (
|
||||||
|
probabilityInt !== undefined &&
|
||||||
|
(probabilityInt < 1 || probabilityInt > 99 || !isFinite(probabilityInt))
|
||||||
|
)
|
||||||
|
return { status: 'error', message: 'Invalid probability' }
|
||||||
|
|
||||||
const contractDoc = firestore.doc(`contracts/${contractId}`)
|
const contractDoc = firestore.doc(`contracts/${contractId}`)
|
||||||
const contractSnap = await contractDoc.get()
|
const contractSnap = await contractDoc.get()
|
||||||
if (!contractSnap.exists)
|
if (!contractSnap.exists)
|
||||||
|
@ -42,10 +49,16 @@ export const resolveMarket = functions
|
||||||
const creator = await getUser(contract.creatorId)
|
const creator = await getUser(contract.creatorId)
|
||||||
if (!creator) return { status: 'error', message: 'Creator not found' }
|
if (!creator) return { status: 'error', message: 'Creator not found' }
|
||||||
|
|
||||||
|
const resolutionProbability =
|
||||||
|
probabilityInt !== undefined ? probabilityInt / 100 : undefined
|
||||||
|
|
||||||
await contractDoc.update({
|
await contractDoc.update({
|
||||||
isResolved: true,
|
isResolved: true,
|
||||||
resolution: outcome,
|
resolution: outcome,
|
||||||
resolutionTime: Date.now(),
|
resolutionTime: Date.now(),
|
||||||
|
...(resolutionProbability === undefined
|
||||||
|
? {}
|
||||||
|
: { resolutionProbability }),
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('contract ', contractId, 'resolved to:', outcome)
|
console.log('contract ', contractId, 'resolved to:', outcome)
|
||||||
|
@ -57,7 +70,12 @@ export const resolveMarket = functions
|
||||||
const bets = betsSnap.docs.map((doc) => doc.data() as Bet)
|
const bets = betsSnap.docs.map((doc) => doc.data() as Bet)
|
||||||
const openBets = bets.filter((b) => !b.isSold && !b.sale)
|
const openBets = bets.filter((b) => !b.isSold && !b.sale)
|
||||||
|
|
||||||
const payouts = getPayouts(outcome, contract, openBets)
|
const payouts = getPayouts(
|
||||||
|
outcome,
|
||||||
|
contract,
|
||||||
|
openBets,
|
||||||
|
resolutionProbability
|
||||||
|
)
|
||||||
|
|
||||||
console.log('payouts:', payouts)
|
console.log('payouts:', payouts)
|
||||||
|
|
||||||
|
@ -79,7 +97,8 @@ export const resolveMarket = functions
|
||||||
userPayouts,
|
userPayouts,
|
||||||
creator,
|
creator,
|
||||||
contract,
|
contract,
|
||||||
outcome
|
outcome,
|
||||||
|
resolutionProbability
|
||||||
)
|
)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
@ -91,7 +110,8 @@ const sendResolutionEmails = async (
|
||||||
userPayouts: { [userId: string]: number },
|
userPayouts: { [userId: string]: number },
|
||||||
creator: User,
|
creator: User,
|
||||||
contract: Contract,
|
contract: Contract,
|
||||||
outcome: 'YES' | 'NO' | 'CANCEL' | 'MKT'
|
outcome: 'YES' | 'NO' | 'CANCEL' | 'MKT',
|
||||||
|
resolutionProbability?: number
|
||||||
) => {
|
) => {
|
||||||
const nonWinners = _.difference(
|
const nonWinners = _.difference(
|
||||||
_.uniq(openBets.map(({ userId }) => userId)),
|
_.uniq(openBets.map(({ userId }) => userId)),
|
||||||
|
@ -103,7 +123,14 @@ const sendResolutionEmails = async (
|
||||||
]
|
]
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
emailPayouts.map(([userId, payout]) =>
|
emailPayouts.map(([userId, payout]) =>
|
||||||
sendMarketResolutionEmail(userId, payout, creator, contract, outcome)
|
sendMarketResolutionEmail(
|
||||||
|
userId,
|
||||||
|
payout,
|
||||||
|
creator,
|
||||||
|
contract,
|
||||||
|
outcome,
|
||||||
|
resolutionProbability
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,14 @@
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import * as _ from 'lodash'
|
import * as _ from 'lodash'
|
||||||
|
|
||||||
|
import { initAdmin } from './script-init'
|
||||||
|
initAdmin('stephen')
|
||||||
|
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from '../../../common/bet'
|
||||||
import { getProbability } from '../../../common/calculate'
|
import { getProbability } from '../../../common/calculate'
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from '../../../common/contract'
|
||||||
|
|
||||||
type DocRef = admin.firestore.DocumentReference
|
type DocRef = admin.firestore.DocumentReference
|
||||||
|
|
||||||
// Generate your own private key, and set the path below:
|
|
||||||
// https://console.firebase.google.com/u/0/project/mantic-markets/settings/serviceaccounts/adminsdk
|
|
||||||
// const serviceAccount = require('../../../../../../Downloads/dev-mantic-markets-firebase-adminsdk-sir5m-b2d27f8970.json')
|
|
||||||
const serviceAccount = require('../../../../../../Downloads/mantic-markets-firebase-adminsdk-1ep46-351a65eca3.json')
|
|
||||||
|
|
||||||
admin.initializeApp({
|
|
||||||
credential: admin.credential.cert(serviceAccount),
|
|
||||||
})
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
async function migrateContract(contractRef: DocRef, contract: Contract) {
|
async function migrateContract(contractRef: DocRef, contract: Contract) {
|
||||||
|
|
|
@ -1,17 +1,11 @@
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import * as _ from 'lodash'
|
import * as _ from 'lodash'
|
||||||
|
|
||||||
|
import { initAdmin } from './script-init'
|
||||||
|
initAdmin('stephen')
|
||||||
|
|
||||||
import { PrivateUser, STARTING_BALANCE, User } from '../../../common/user'
|
import { PrivateUser, STARTING_BALANCE, User } from '../../../common/user'
|
||||||
|
|
||||||
// Generate your own private key, and set the path below:
|
|
||||||
// https://console.firebase.google.com/u/0/project/mantic-markets/settings/serviceaccounts/adminsdk
|
|
||||||
|
|
||||||
const serviceAccount = require('../../../../../../Downloads/dev-mantic-markets-firebase-adminsdk-sir5m-b2d27f8970.json')
|
|
||||||
|
|
||||||
admin.initializeApp({
|
|
||||||
credential: admin.credential.cert(serviceAccount),
|
|
||||||
})
|
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
|
|
@ -1,16 +1,11 @@
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import * as _ from 'lodash'
|
import * as _ from 'lodash'
|
||||||
|
|
||||||
|
import { initAdmin } from './script-init'
|
||||||
|
initAdmin('james')
|
||||||
|
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from '../../../common/contract'
|
||||||
|
|
||||||
// Generate your own private key, and set the path below:
|
|
||||||
// https://console.firebase.google.com/u/0/project/mantic-markets/settings/serviceaccounts/adminsdk
|
|
||||||
// const serviceAccount = require('../../../../Downloads/dev-mantic-markets-firebase-adminsdk-sir5m-b2d27f8970.json')
|
|
||||||
const serviceAccount = require('../../../../../../Downloads/mantic-markets-firebase-adminsdk-1ep46-820891bb87.json')
|
|
||||||
|
|
||||||
admin.initializeApp({
|
|
||||||
credential: admin.credential.cert(serviceAccount),
|
|
||||||
})
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
async function makeContractsPublic() {
|
async function makeContractsPublic() {
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import * as _ from 'lodash'
|
import * as _ from 'lodash'
|
||||||
|
|
||||||
|
import { initAdmin } from './script-init'
|
||||||
|
initAdmin('james')
|
||||||
|
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from '../../../common/bet'
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from '../../../common/contract'
|
||||||
|
|
||||||
type DocRef = admin.firestore.DocumentReference
|
type DocRef = admin.firestore.DocumentReference
|
||||||
|
|
||||||
// Generate your own private key, and set the path below:
|
|
||||||
// https://console.firebase.google.com/u/0/project/mantic-markets/settings/serviceaccounts/adminsdk
|
|
||||||
const serviceAccount = require('../../../../Downloads/mantic-markets-firebase-adminsdk-1ep46-820891bb87.json')
|
|
||||||
|
|
||||||
admin.initializeApp({
|
|
||||||
credential: admin.credential.cert(serviceAccount),
|
|
||||||
})
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
async function migrateBet(contractRef: DocRef, bet: Bet) {
|
async function migrateBet(contractRef: DocRef, bet: Bet) {
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import * as _ from 'lodash'
|
import * as _ from 'lodash'
|
||||||
|
|
||||||
|
import { initAdmin } from './script-init'
|
||||||
|
initAdmin('stephenDev')
|
||||||
|
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from '../../../common/contract'
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from '../../../common/bet'
|
||||||
import { calculateShares, getProbability } from '../../../common/calculate'
|
import { calculateShares, getProbability } from '../../../common/calculate'
|
||||||
|
@ -9,15 +12,6 @@ import { User } from '../../../common/user'
|
||||||
|
|
||||||
type DocRef = admin.firestore.DocumentReference
|
type DocRef = admin.firestore.DocumentReference
|
||||||
|
|
||||||
// Generate your own private key, and set the path below:
|
|
||||||
// https://console.firebase.google.com/u/0/project/mantic-markets/settings/serviceaccounts/adminsdk
|
|
||||||
// const serviceAccount = require('../../../../../../Downloads/dev-mantic-markets-firebase-adminsdk-sir5m-b2d27f8970.json')
|
|
||||||
const serviceAccount = require('../../../../../../Downloads/mantic-markets-firebase-adminsdk-1ep46-351a65eca3.json')
|
|
||||||
|
|
||||||
admin.initializeApp({
|
|
||||||
credential: admin.credential.cert(serviceAccount),
|
|
||||||
})
|
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
async function recalculateContract(
|
async function recalculateContract(
|
||||||
|
|
|
@ -1,19 +1,14 @@
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import * as _ from 'lodash'
|
import * as _ from 'lodash'
|
||||||
|
|
||||||
|
import { initAdmin } from './script-init'
|
||||||
|
initAdmin('james')
|
||||||
|
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from '../../../common/bet'
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from '../../../common/contract'
|
||||||
|
|
||||||
type DocRef = admin.firestore.DocumentReference
|
type DocRef = admin.firestore.DocumentReference
|
||||||
|
|
||||||
// Generate your own private key, and set the path below:
|
|
||||||
// https://console.firebase.google.com/u/0/project/mantic-markets/settings/serviceaccounts/adminsdk
|
|
||||||
// const serviceAccount = require('../../../../Downloads/dev-mantic-markets-firebase-adminsdk-sir5m-b2d27f8970.json')
|
|
||||||
const serviceAccount = require('../../../../Downloads/mantic-markets-firebase-adminsdk-1ep46-820891bb87.json')
|
|
||||||
|
|
||||||
admin.initializeApp({
|
|
||||||
credential: admin.credential.cert(serviceAccount),
|
|
||||||
})
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
async function recalculateContract(contractRef: DocRef, contract: Contract) {
|
async function recalculateContract(contractRef: DocRef, contract: Contract) {
|
||||||
|
|
|
@ -1,19 +1,12 @@
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import * as _ from 'lodash'
|
import * as _ from 'lodash'
|
||||||
|
|
||||||
|
import { initAdmin } from './script-init'
|
||||||
|
initAdmin('stephenDev')
|
||||||
|
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from '../../../common/contract'
|
||||||
import { getValues } from '../utils'
|
import { getValues } from '../utils'
|
||||||
|
|
||||||
// Generate your own private key, and set the path below:
|
|
||||||
// https://console.firebase.google.com/u/0/project/mantic-markets/settings/serviceaccounts/adminsdk
|
|
||||||
// James:
|
|
||||||
const serviceAccount = require('../../../../Downloads/mantic-markets-firebase-adminsdk-1ep46-820891bb87.json')
|
|
||||||
// Stephen:
|
|
||||||
// const serviceAccount = require('../../../../Downloads/dev-mantic-markets-firebase-adminsdk-sir5m-b2d27f8970.json')
|
|
||||||
|
|
||||||
admin.initializeApp({
|
|
||||||
credential: admin.credential.cert(serviceAccount),
|
|
||||||
})
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
async function renameUserContracts(
|
async function renameUserContracts(
|
||||||
|
|
32
functions/src/scripts/script-init.ts
Normal file
32
functions/src/scripts/script-init.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import * as admin from 'firebase-admin'
|
||||||
|
|
||||||
|
// Generate your own private key, and set the path below:
|
||||||
|
// Prod:
|
||||||
|
// https://console.firebase.google.com/u/0/project/mantic-markets/settings/serviceaccounts/adminsdk
|
||||||
|
// Dev:
|
||||||
|
// https://console.firebase.google.com/u/0/project/dev-mantic-markets/settings/serviceaccounts/adminsdk
|
||||||
|
|
||||||
|
const pathsToPrivateKey = {
|
||||||
|
james:
|
||||||
|
'/Users/jahooma/mantic-markets-firebase-adminsdk-1ep46-820891bb87.json',
|
||||||
|
jamesDev:
|
||||||
|
'/Users/jahooma/dev-mantic-markets-firebase-adminsdk-sir5m-f38cdbee37.json',
|
||||||
|
stephen:
|
||||||
|
'../../../../../../Downloads/mantic-markets-firebase-adminsdk-1ep46-351a65eca3.json',
|
||||||
|
stephenDev:
|
||||||
|
'../../../../Downloads/dev-mantic-markets-firebase-adminsdk-sir5m-b2d27f8970.json',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initAdmin = (who: keyof typeof pathsToPrivateKey) => {
|
||||||
|
const serviceAccount = require(pathsToPrivateKey[who])
|
||||||
|
|
||||||
|
admin.initializeApp({
|
||||||
|
credential: admin.credential.cert(serviceAccount),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then:
|
||||||
|
// yarn watch (or yarn build)
|
||||||
|
// firebase use dev (or firebase use prod)
|
||||||
|
// Run script:
|
||||||
|
// node lib/functions/src/scripts/update-contract-tags.js
|
|
@ -1,22 +1,15 @@
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import * as _ from 'lodash'
|
import * as _ from 'lodash'
|
||||||
|
|
||||||
// Generate your own private key, and set the path below:
|
import { initAdmin } from './script-init'
|
||||||
// https://console.firebase.google.com/u/0/project/mantic-markets/settings/serviceaccounts/adminsdk
|
initAdmin('jamesDev')
|
||||||
// James:
|
|
||||||
const serviceAccount = require('/Users/jahooma/mantic-markets-firebase-adminsdk-1ep46-820891bb87.json')
|
|
||||||
// Stephen:
|
|
||||||
// const serviceAccount = require('../../../../Downloads/dev-mantic-markets-firebase-adminsdk-sir5m-b2d27f8970.json')
|
|
||||||
admin.initializeApp({
|
|
||||||
credential: admin.credential.cert(serviceAccount),
|
|
||||||
})
|
|
||||||
const firestore = admin.firestore()
|
|
||||||
|
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from '../../../common/contract'
|
||||||
import { parseTags } from '../../../common/util/parse'
|
import { parseTags } from '../../../common/util/parse'
|
||||||
import { getValues } from '../utils'
|
import { getValues } from '../utils'
|
||||||
|
|
||||||
async function updateContractTags() {
|
async function updateContractTags() {
|
||||||
|
const firestore = admin.firestore()
|
||||||
console.log('Updating contracts tags')
|
console.log('Updating contracts tags')
|
||||||
|
|
||||||
const contracts = await getValues<Contract>(firestore.collection('contracts'))
|
const contracts = await getValues<Contract>(firestore.collection('contracts'))
|
||||||
|
@ -48,4 +41,6 @@ async function updateContractTags() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (require.main === module) updateContractTags().then(() => process.exit())
|
if (require.main === module) {
|
||||||
|
updateContractTags().then(() => process.exit())
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { useUser } from '../hooks/use-user'
|
import { useUser } from '../hooks/use-user'
|
||||||
import { formatMoney } from '../lib/util/format'
|
import { formatMoney } from '../../common/util/format'
|
||||||
import { AddFundsButton } from './add-funds-button'
|
import { AddFundsButton } from './add-funds-button'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
formatMoney,
|
formatMoney,
|
||||||
formatPercent,
|
formatPercent,
|
||||||
formatWithCommas,
|
formatWithCommas,
|
||||||
} from '../lib/util/format'
|
} from '../../common/util/format'
|
||||||
import { Title } from './title'
|
import { Title } from './title'
|
||||||
import {
|
import {
|
||||||
getProbability,
|
getProbability,
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
formatMoney,
|
formatMoney,
|
||||||
formatPercent,
|
formatPercent,
|
||||||
formatWithCommas,
|
formatWithCommas,
|
||||||
} from '../lib/util/format'
|
} from '../../common/util/format'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import { Spacer } from './layout/spacer'
|
import { Spacer } from './layout/spacer'
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { Row } from '../components/layout/row'
|
import { Row } from '../components/layout/row'
|
||||||
import { formatMoney } from '../lib/util/format'
|
import { formatMoney } from '../../common/util/format'
|
||||||
import { UserLink } from './user-page'
|
import { UserLink } from './user-page'
|
||||||
import {
|
import {
|
||||||
Contract,
|
Contract,
|
||||||
|
@ -74,7 +74,7 @@ export function ResolutionOrChance(props: {
|
||||||
const resolutionText = {
|
const resolutionText = {
|
||||||
YES: 'YES',
|
YES: 'YES',
|
||||||
NO: 'NO',
|
NO: 'NO',
|
||||||
MKT: 'MKT',
|
MKT: probPercent,
|
||||||
CANCEL: 'N/A',
|
CANCEL: 'N/A',
|
||||||
'': '',
|
'': '',
|
||||||
}[resolution || '']
|
}[resolution || '']
|
||||||
|
@ -142,12 +142,9 @@ export function AbbrContractDetails(props: {
|
||||||
|
|
||||||
export function ContractDetails(props: { contract: Contract }) {
|
export function ContractDetails(props: { contract: Contract }) {
|
||||||
const { contract } = props
|
const { contract } = props
|
||||||
const { question, description, closeTime, creatorName, creatorUsername } =
|
const { closeTime, creatorName, creatorUsername } = contract
|
||||||
contract
|
|
||||||
const { truePool, createdDate, resolvedDate } = contractMetrics(contract)
|
const { truePool, createdDate, resolvedDate } = contractMetrics(contract)
|
||||||
|
|
||||||
const tags = parseTags(`${question} ${description}`).map((tag) => `#${tag}`)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col className="text-sm text-gray-500 gap-2 sm:flex-row sm:flex-wrap">
|
<Col className="text-sm text-gray-500 gap-2 sm:flex-row sm:flex-wrap">
|
||||||
<Row className="gap-2 flex-wrap">
|
<Row className="gap-2 flex-wrap">
|
||||||
|
@ -199,10 +196,10 @@ export function ContractDetails(props: { contract: Contract }) {
|
||||||
|
|
||||||
// String version of the above, to send to the OpenGraph image generator
|
// String version of the above, to send to the OpenGraph image generator
|
||||||
export function contractTextDetails(contract: Contract) {
|
export function contractTextDetails(contract: Contract) {
|
||||||
const { question, description, closeTime } = contract
|
const { closeTime, tags } = contract
|
||||||
const { truePool, createdDate, resolvedDate } = contractMetrics(contract)
|
const { truePool, createdDate, resolvedDate } = contractMetrics(contract)
|
||||||
|
|
||||||
const tags = parseTags(`${question} ${description}`).map((tag) => `#${tag}`)
|
const hashtags = tags.map((tag) => `#${tag}`)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
`${resolvedDate ? `${createdDate} - ${resolvedDate}` : createdDate}` +
|
`${resolvedDate ? `${createdDate} - ${resolvedDate}` : createdDate}` +
|
||||||
|
@ -212,6 +209,6 @@ export function contractTextDetails(contract: Contract) {
|
||||||
).format('MMM D, h:mma')}`
|
).format('MMM D, h:mma')}`
|
||||||
: '') +
|
: '') +
|
||||||
` • ${formatMoney(truePool)} pool` +
|
` • ${formatMoney(truePool)} pool` +
|
||||||
(tags.length > 0 ? ` • ${tags.join(' ')}` : '')
|
(hashtags.length > 0 ? ` • ${hashtags.join(' ')}` : '')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import { Linkify } from './linkify'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
import { createComment } from '../lib/firebase/comments'
|
import { createComment } from '../lib/firebase/comments'
|
||||||
import { useComments } from '../hooks/use-comments'
|
import { useComments } from '../hooks/use-comments'
|
||||||
import { formatMoney } from '../lib/util/format'
|
import { formatMoney } from '../../common/util/format'
|
||||||
import { ResolutionOrChance } from './contract-card'
|
import { ResolutionOrChance } from './contract-card'
|
||||||
import { SiteLink } from './site-link'
|
import { SiteLink } from './site-link'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
|
@ -160,7 +160,9 @@ export function ContractDescription(props: {
|
||||||
setEditing(false)
|
setEditing(false)
|
||||||
|
|
||||||
const newDescription = `${contract.description}\n\n${description}`.trim()
|
const newDescription = `${contract.description}\n\n${description}`.trim()
|
||||||
const tags = parseTags(`${contract.tags.join(' ')} ${newDescription}`)
|
const tags = parseTags(
|
||||||
|
`${newDescription} ${contract.tags.map((tag) => `#${tag}`).join(' ')}`
|
||||||
|
)
|
||||||
const lowercaseTags = tags.map((tag) => tag.toLowerCase())
|
const lowercaseTags = tags.map((tag) => tag.toLowerCase())
|
||||||
await updateContract(contract.id, {
|
await updateContract(contract.id, {
|
||||||
description: newDescription,
|
description: newDescription,
|
||||||
|
@ -685,10 +687,7 @@ export function ContractFeed(props: {
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
{tradingAllowed(contract) && (
|
{tradingAllowed(contract) && (
|
||||||
<BetRow
|
<BetRow contract={contract} className={clsx('mb-2', betRowClassName)} />
|
||||||
contract={contract}
|
|
||||||
className={clsx('-mt-4', betRowClassName)}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {
|
||||||
Contract,
|
Contract,
|
||||||
deleteContract,
|
deleteContract,
|
||||||
contractPath,
|
contractPath,
|
||||||
|
tradingAllowed,
|
||||||
} from '../lib/firebase/contracts'
|
} from '../lib/firebase/contracts'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import { Spacer } from './layout/spacer'
|
import { Spacer } from './layout/spacer'
|
||||||
|
@ -58,11 +59,13 @@ export const ContractOverview = (props: {
|
||||||
large
|
large
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<BetRow
|
{tradingAllowed(contract) && (
|
||||||
contract={contract}
|
<BetRow
|
||||||
className="md:hidden"
|
contract={contract}
|
||||||
labelClassName="hidden"
|
className="md:hidden"
|
||||||
/>
|
labelClassName="hidden"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<ContractDetails contract={contract} />
|
<ContractDetails contract={contract} />
|
||||||
|
|
|
@ -11,7 +11,6 @@ import {
|
||||||
import { User } from '../lib/firebase/users'
|
import { User } from '../lib/firebase/users'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import { SiteLink } from './site-link'
|
import { SiteLink } from './site-link'
|
||||||
import { parseTags } from '../../common/util/parse'
|
|
||||||
import { ContractCard } from './contract-card'
|
import { ContractCard } from './contract-card'
|
||||||
import { Sort, useQueryAndSortParams } from '../hooks/use-sort-and-query-params'
|
import { Sort, useQueryAndSortParams } from '../hooks/use-sort-and-query-params'
|
||||||
|
|
||||||
|
@ -122,12 +121,13 @@ function CreatorContractsGrid(props: { contracts: Contract[] }) {
|
||||||
function TagContractsGrid(props: { contracts: Contract[] }) {
|
function TagContractsGrid(props: { contracts: Contract[] }) {
|
||||||
const { contracts } = props
|
const { contracts } = props
|
||||||
|
|
||||||
const contractTags = _.flatMap(contracts, (contract) =>
|
const contractTags = _.flatMap(contracts, (contract) => {
|
||||||
parseTags(contract.question + ' ' + contract.description).map((tag) => ({
|
const { tags } = contract
|
||||||
|
return tags.map((tag) => ({
|
||||||
tag,
|
tag,
|
||||||
contract,
|
contract,
|
||||||
}))
|
}))
|
||||||
)
|
})
|
||||||
const groupedByTag = _.groupBy(contractTags, ({ tag }) => tag)
|
const groupedByTag = _.groupBy(contractTags, ({ tag }) => tag)
|
||||||
const byTag = _.mapValues(groupedByTag, (contractTags) =>
|
const byTag = _.mapValues(groupedByTag, (contractTags) =>
|
||||||
contractTags.map(({ contract }) => contract)
|
contractTags.map(({ contract }) => contract)
|
||||||
|
@ -210,7 +210,8 @@ export function SearchableGrid(props: {
|
||||||
check(c.question) ||
|
check(c.question) ||
|
||||||
check(c.description) ||
|
check(c.description) ||
|
||||||
check(c.creatorName) ||
|
check(c.creatorName) ||
|
||||||
check(c.creatorUsername)
|
check(c.creatorUsername) ||
|
||||||
|
check(c.lowercaseTags.map((tag) => `#${tag}`).join(' '))
|
||||||
)
|
)
|
||||||
|
|
||||||
if (sort === 'newest' || sort === 'all') {
|
if (sort === 'newest' || sort === 'all') {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useState } from 'react'
|
||||||
import { parseWordsAsTags } from '../../common/util/parse'
|
import { parseWordsAsTags } from '../../common/util/parse'
|
||||||
import { createFold } from '../lib/firebase/api-call'
|
import { createFold } from '../lib/firebase/api-call'
|
||||||
import { foldPath } from '../lib/firebase/folds'
|
import { foldPath } from '../lib/firebase/folds'
|
||||||
import { toCamelCase } from '../lib/util/format'
|
import { toCamelCase } from '../../common/util/format'
|
||||||
import { ConfirmationButton } from './confirmation-button'
|
import { ConfirmationButton } from './confirmation-button'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import { Spacer } from './layout/spacer'
|
import { Spacer } from './layout/spacer'
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { PencilIcon } from '@heroicons/react/outline'
|
||||||
import { Fold } from '../../common/fold'
|
import { Fold } from '../../common/fold'
|
||||||
import { parseWordsAsTags } from '../../common/util/parse'
|
import { parseWordsAsTags } from '../../common/util/parse'
|
||||||
import { deleteFold, updateFold } from '../lib/firebase/folds'
|
import { deleteFold, updateFold } from '../lib/firebase/folds'
|
||||||
import { toCamelCase } from '../lib/util/format'
|
import { toCamelCase } from '../../common/util/format'
|
||||||
import { Spacer } from './layout/spacer'
|
import { Spacer } from './layout/spacer'
|
||||||
import { TagsList } from './tags-list'
|
import { TagsList } from './tags-list'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
|
|
@ -5,7 +5,7 @@ export function OutcomeLabel(props: {
|
||||||
|
|
||||||
if (outcome === 'YES') return <YesLabel />
|
if (outcome === 'YES') return <YesLabel />
|
||||||
if (outcome === 'NO') return <NoLabel />
|
if (outcome === 'NO') return <NoLabel />
|
||||||
if (outcome === 'MKT') return <MarketLabel />
|
if (outcome === 'MKT') return <ProbLabel />
|
||||||
return <CancelLabel />
|
return <CancelLabel />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,3 +24,7 @@ export function CancelLabel() {
|
||||||
export function MarketLabel() {
|
export function MarketLabel() {
|
||||||
return <span className="text-blue-400">MKT</span>
|
return <span className="text-blue-400">MKT</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ProbLabel() {
|
||||||
|
return <span className="text-blue-400">PROB</span>
|
||||||
|
}
|
||||||
|
|
36
web/components/probability-selector.tsx
Normal file
36
web/components/probability-selector.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { Row } from './layout/row'
|
||||||
|
|
||||||
|
export function ProbabilitySelector(props: {
|
||||||
|
probabilityInt: number
|
||||||
|
setProbabilityInt: (p: number) => void
|
||||||
|
isSubmitting?: boolean
|
||||||
|
}) {
|
||||||
|
const { probabilityInt, setProbabilityInt, isSubmitting } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row className="items-center gap-2">
|
||||||
|
<label className="input-group input-group-lg w-fit text-lg">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={probabilityInt}
|
||||||
|
className="input input-bordered input-md text-lg"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
min={1}
|
||||||
|
max={99}
|
||||||
|
onChange={(e) =>
|
||||||
|
setProbabilityInt(parseInt(e.target.value.substring(0, 2)))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span>%</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
className="range range-primary"
|
||||||
|
min={1}
|
||||||
|
max={99}
|
||||||
|
value={probabilityInt}
|
||||||
|
onChange={(e) => setProbabilityInt(parseInt(e.target.value))}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { firebaseLogout, User } from '../lib/firebase/users'
|
import { firebaseLogout, User } from '../lib/firebase/users'
|
||||||
import { formatMoney } from '../lib/util/format'
|
import { formatMoney } from '../../common/util/format'
|
||||||
import { Avatar } from './avatar'
|
import { Avatar } from './avatar'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import { MenuButton } from './menu'
|
import { MenuButton } from './menu'
|
||||||
|
|
|
@ -9,6 +9,8 @@ import { YesNoCancelSelector } from './yes-no-selector'
|
||||||
import { Spacer } from './layout/spacer'
|
import { Spacer } from './layout/spacer'
|
||||||
import { ConfirmationButton as ConfirmationButton } from './confirmation-button'
|
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 { getProbability } from '../../common/calculate'
|
||||||
|
|
||||||
export function ResolutionPanel(props: {
|
export function ResolutionPanel(props: {
|
||||||
creator: User
|
creator: User
|
||||||
|
@ -26,6 +28,8 @@ export function ResolutionPanel(props: {
|
||||||
'YES' | 'NO' | 'MKT' | 'CANCEL' | undefined
|
'YES' | 'NO' | 'MKT' | 'CANCEL' | undefined
|
||||||
>()
|
>()
|
||||||
|
|
||||||
|
const [prob, setProb] = useState(getProbability(contract.totalShares) * 100)
|
||||||
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
const [error, setError] = useState<string | undefined>(undefined)
|
const [error, setError] = useState<string | undefined>(undefined)
|
||||||
|
|
||||||
|
@ -35,6 +39,7 @@ export function ResolutionPanel(props: {
|
||||||
const result = await resolveMarket({
|
const result = await resolveMarket({
|
||||||
outcome,
|
outcome,
|
||||||
contractId: contract.id,
|
contractId: contract.id,
|
||||||
|
probabilityInt: prob,
|
||||||
}).then((r) => r.data as any)
|
}).then((r) => r.data as any)
|
||||||
|
|
||||||
console.log('resolved', outcome, 'result:', result)
|
console.log('resolved', outcome, 'result:', result)
|
||||||
|
@ -82,8 +87,8 @@ export function ResolutionPanel(props: {
|
||||||
<>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 current implied probability. You
|
Traders will be paid out at the probability you specify. You earn 1%
|
||||||
earn 1% of the pool.
|
of the pool.
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>Resolving this market will immediately pay out traders.</>
|
<>Resolving this market will immediately pay out traders.</>
|
||||||
|
@ -113,7 +118,20 @@ export function ResolutionPanel(props: {
|
||||||
}}
|
}}
|
||||||
onSubmit={resolve}
|
onSubmit={resolve}
|
||||||
>
|
>
|
||||||
<p>Are you sure you want to resolve this market?</p>
|
{outcome === 'MKT' ? (
|
||||||
|
<>
|
||||||
|
<p className="mb-4">
|
||||||
|
What probability would you like to resolve the market to?
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ProbabilitySelector
|
||||||
|
probabilityInt={Math.round(prob)}
|
||||||
|
setProbabilityInt={setProb}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<p>Are you sure you want to resolve this market?</p>
|
||||||
|
)}
|
||||||
</ConfirmationButton>
|
</ConfirmationButton>
|
||||||
</Col>
|
</Col>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { formatMoney } from '../lib/util/format'
|
import { formatMoney } from '../../common/util/format'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ export function YesNoCancelSelector(props: {
|
||||||
onClick={() => onSelect('MKT')}
|
onClick={() => onSelect('MKT')}
|
||||||
className={clsx(btnClassName, 'btn-sm')}
|
className={clsx(btnClassName, 'btn-sm')}
|
||||||
>
|
>
|
||||||
MKT
|
PROB
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
listenForContracts,
|
listenForContracts,
|
||||||
listenForHotContracts,
|
listenForHotContracts,
|
||||||
} from '../lib/firebase/contracts'
|
} from '../lib/firebase/contracts'
|
||||||
|
import { listenForTaggedContracts } from '../lib/firebase/folds'
|
||||||
|
|
||||||
export const useContracts = () => {
|
export const useContracts = () => {
|
||||||
const [contracts, setContracts] = useState<Contract[] | undefined>()
|
const [contracts, setContracts] = useState<Contract[] | undefined>()
|
||||||
|
@ -16,6 +17,21 @@ export const useContracts = () => {
|
||||||
return contracts
|
return contracts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useTaggedContracts = (tags: string[] | undefined) => {
|
||||||
|
const [contracts, setContracts] = useState<Contract[] | undefined>(
|
||||||
|
tags && tags.length === 0 ? [] : undefined
|
||||||
|
)
|
||||||
|
const tagsKey = tags?.map((tag) => tag.toLowerCase()).join(',') ?? ''
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!tags || tags.length === 0) return
|
||||||
|
return listenForTaggedContracts(tags, setContracts)
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [tagsKey])
|
||||||
|
|
||||||
|
return contracts
|
||||||
|
}
|
||||||
|
|
||||||
export const useHotContracts = () => {
|
export const useHotContracts = () => {
|
||||||
const [hotContracts, setHotContracts] = useState<Contract[] | undefined>()
|
const [hotContracts, setHotContracts] = useState<Contract[] | undefined>()
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ import {
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
import { app } from './init'
|
import { app } from './init'
|
||||||
import { getValues, listenForValues } from './utils'
|
import { getValues, listenForValue, listenForValues } from './utils'
|
||||||
import { Contract } from '../../../common/contract'
|
import { Contract } from '../../../common/contract'
|
||||||
import { getProbability } from '../../../common/calculate'
|
import { getProbability } from '../../../common/calculate'
|
||||||
import { createRNG, shuffle } from '../../../common/util/random'
|
import { createRNG, shuffle } from '../../../common/util/random'
|
||||||
|
@ -35,10 +35,11 @@ export function contractMetrics(contract: Contract) {
|
||||||
createdTime,
|
createdTime,
|
||||||
resolutionTime,
|
resolutionTime,
|
||||||
isResolved,
|
isResolved,
|
||||||
|
resolutionProbability,
|
||||||
} = contract
|
} = contract
|
||||||
|
|
||||||
const truePool = pool.YES + pool.NO
|
const truePool = pool.YES + pool.NO
|
||||||
const prob = getProbability(totalShares)
|
const prob = resolutionProbability ?? getProbability(totalShares)
|
||||||
const probPercent = Math.round(prob * 100) + '%'
|
const probPercent = Math.round(prob * 100) + '%'
|
||||||
|
|
||||||
const startProb = getProbability(phantomShares)
|
const startProb = getProbability(phantomShares)
|
||||||
|
@ -125,9 +126,7 @@ export function listenForContract(
|
||||||
setContract: (contract: Contract | null) => void
|
setContract: (contract: Contract | null) => void
|
||||||
) {
|
) {
|
||||||
const contractRef = doc(contractCollection, contractId)
|
const contractRef = doc(contractCollection, contractId)
|
||||||
return onSnapshot(contractRef, (contractSnap) => {
|
return listenForValue<Contract>(contractRef, setContract)
|
||||||
setContract((contractSnap.data() ?? null) as Contract | null)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function chooseRandomSubset(contracts: Contract[], count: number) {
|
function chooseRandomSubset(contracts: Contract[], count: number) {
|
||||||
|
|
|
@ -45,6 +45,17 @@ export async function getFoldBySlug(slug: string) {
|
||||||
return folds.length === 0 ? null : folds[0]
|
return folds.length === 0 ? null : folds[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function contractsByTagsQuery(tags: string[]) {
|
||||||
|
return query(
|
||||||
|
contractCollection,
|
||||||
|
where(
|
||||||
|
'lowercaseTags',
|
||||||
|
'array-contains-any',
|
||||||
|
tags.map((tag) => tag.toLowerCase())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export async function getFoldContracts(fold: Fold) {
|
export async function getFoldContracts(fold: Fold) {
|
||||||
const {
|
const {
|
||||||
tags,
|
tags,
|
||||||
|
@ -56,18 +67,7 @@ export async function getFoldContracts(fold: Fold) {
|
||||||
|
|
||||||
const [tagsContracts, includedContracts] = await Promise.all([
|
const [tagsContracts, includedContracts] = await Promise.all([
|
||||||
// TODO: if tags.length > 10, execute multiple parallel queries
|
// TODO: if tags.length > 10, execute multiple parallel queries
|
||||||
tags.length > 0
|
tags.length > 0 ? getValues<Contract>(contractsByTagsQuery(tags)) : [],
|
||||||
? getValues<Contract>(
|
|
||||||
query(
|
|
||||||
contractCollection,
|
|
||||||
where(
|
|
||||||
'lowercaseTags',
|
|
||||||
'array-contains-any',
|
|
||||||
tags.map((tag) => tag.toLowerCase())
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: [],
|
|
||||||
|
|
||||||
// TODO: if contractIds.length > 10, execute multiple parallel queries
|
// TODO: if contractIds.length > 10, execute multiple parallel queries
|
||||||
contractIds.length > 0
|
contractIds.length > 0
|
||||||
|
@ -97,6 +97,13 @@ export async function getFoldContracts(fold: Fold) {
|
||||||
return [...approvedContracts, ...includedContracts]
|
return [...approvedContracts, ...includedContracts]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function listenForTaggedContracts(
|
||||||
|
tags: string[],
|
||||||
|
setContracts: (contracts: Contract[]) => void
|
||||||
|
) {
|
||||||
|
return listenForValues<Contract>(contractsByTagsQuery(tags), setContracts)
|
||||||
|
}
|
||||||
|
|
||||||
export function listenForFold(
|
export function listenForFold(
|
||||||
foldId: string,
|
foldId: string,
|
||||||
setFold: (fold: Fold | null) => void
|
setFold: (fold: Fold | null) => void
|
||||||
|
|
|
@ -27,13 +27,18 @@ export function scoreTraders(contracts: Contract[], bets: Bet[][]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function scoreUsersByContract(contract: Contract, bets: Bet[]) {
|
function scoreUsersByContract(contract: Contract, bets: Bet[]) {
|
||||||
const { resolution } = contract
|
const { resolution, resolutionProbability } = contract
|
||||||
|
|
||||||
const [closedBets, openBets] = _.partition(
|
const [closedBets, openBets] = _.partition(
|
||||||
bets,
|
bets,
|
||||||
(bet) => bet.isSold || bet.sale
|
(bet) => bet.isSold || bet.sale
|
||||||
)
|
)
|
||||||
const resolvePayouts = getPayouts(resolution ?? 'MKT', contract, openBets)
|
const resolvePayouts = getPayouts(
|
||||||
|
resolution ?? 'MKT',
|
||||||
|
contract,
|
||||||
|
openBets,
|
||||||
|
resolutionProbability
|
||||||
|
)
|
||||||
|
|
||||||
const salePayouts = closedBets.map((bet) => {
|
const salePayouts = closedBets.map((bet) => {
|
||||||
const { userId, sale } = bet
|
const { userId, sale } = bet
|
||||||
|
|
|
@ -23,6 +23,8 @@ export function listenForValue<T>(
|
||||||
setValue: (value: T | null) => void
|
setValue: (value: T | null) => void
|
||||||
) {
|
) {
|
||||||
return onSnapshot(docRef, (snapshot) => {
|
return onSnapshot(docRef, (snapshot) => {
|
||||||
|
if (snapshot.metadata.fromCache) return
|
||||||
|
|
||||||
const value = snapshot.exists() ? (snapshot.data() as T) : null
|
const value = snapshot.exists() ? (snapshot.data() as T) : null
|
||||||
setValue(value)
|
setValue(value)
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,13 +8,13 @@ import { Spacer } from '../components/layout/spacer'
|
||||||
import { useUser } from '../hooks/use-user'
|
import { useUser } from '../hooks/use-user'
|
||||||
import { Contract, contractPath } from '../lib/firebase/contracts'
|
import { Contract, contractPath } from '../lib/firebase/contracts'
|
||||||
import { createContract } from '../lib/firebase/api-call'
|
import { createContract } from '../lib/firebase/api-call'
|
||||||
import { Row } from '../components/layout/row'
|
|
||||||
import { AmountInput } from '../components/amount-input'
|
import { AmountInput } from '../components/amount-input'
|
||||||
import { MINIMUM_ANTE } from '../../common/antes'
|
import { MINIMUM_ANTE } from '../../common/antes'
|
||||||
import { InfoTooltip } from '../components/info-tooltip'
|
import { InfoTooltip } from '../components/info-tooltip'
|
||||||
import { CREATOR_FEE } from '../../common/fees'
|
import { CREATOR_FEE } from '../../common/fees'
|
||||||
import { Page } from '../components/page'
|
import { Page } from '../components/page'
|
||||||
import { Title } from '../components/title'
|
import { Title } from '../components/title'
|
||||||
|
import { ProbabilitySelector } from '../components/probability-selector'
|
||||||
|
|
||||||
export default function Create() {
|
export default function Create() {
|
||||||
const [question, setQuestion] = useState('')
|
const [question, setQuestion] = useState('')
|
||||||
|
@ -72,8 +72,8 @@ export function NewContract(props: { question: string; tag?: string }) {
|
||||||
|
|
||||||
const [anteError, setAnteError] = useState<string | undefined>()
|
const [anteError, setAnteError] = useState<string | undefined>()
|
||||||
// By default, close the market a week from today
|
// By default, close the market a week from today
|
||||||
// const weekFromToday = dayjs().add(7, 'day').format('YYYY-MM-DD')
|
const weekFromToday = dayjs().add(7, 'day').format('YYYY-MM-DDT23:59')
|
||||||
const [closeDate, setCloseDate] = useState<undefined | string>(undefined)
|
const [closeDate, setCloseDate] = useState<undefined | string>(weekFromToday)
|
||||||
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
|
|
||||||
|
@ -127,30 +127,11 @@ export function NewContract(props: { question: string; tag?: string }) {
|
||||||
<label className="label">
|
<label className="label">
|
||||||
<span className="mb-1">Initial probability</span>
|
<span className="mb-1">Initial probability</span>
|
||||||
</label>
|
</label>
|
||||||
<Row className="items-center gap-2">
|
|
||||||
<label className="input-group input-group-lg w-fit text-lg">
|
<ProbabilitySelector
|
||||||
<input
|
probabilityInt={initialProb}
|
||||||
type="number"
|
setProbabilityInt={setInitialProb}
|
||||||
value={initialProb}
|
/>
|
||||||
className="input input-bordered input-md text-lg"
|
|
||||||
disabled={isSubmitting}
|
|
||||||
min={1}
|
|
||||||
max={99}
|
|
||||||
onChange={(e) =>
|
|
||||||
setInitialProb(parseInt(e.target.value.substring(0, 2)))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<span>%</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
className="range range-primary"
|
|
||||||
min={1}
|
|
||||||
max={99}
|
|
||||||
value={initialProb}
|
|
||||||
onChange={(e) => setInitialProb(parseInt(e.target.value))}
|
|
||||||
/>
|
|
||||||
</Row>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Spacer h={4} />
|
<Spacer h={4} />
|
||||||
|
@ -175,15 +156,15 @@ export function NewContract(props: { question: string; tag?: string }) {
|
||||||
|
|
||||||
<div className="form-control items-start mb-1">
|
<div className="form-control items-start mb-1">
|
||||||
<label className="label gap-2 mb-1">
|
<label className="label gap-2 mb-1">
|
||||||
<span>Last trading day</span>
|
<span>Market close</span>
|
||||||
<InfoTooltip text="Trading allowed through 11:59 pm local time on this date." />
|
<InfoTooltip text="Trading will be halted after this time (local timezone)." />
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="datetime-local"
|
||||||
className="input input-bordered"
|
className="input input-bordered"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
onChange={(e) => setCloseDate(e.target.value || '')}
|
onChange={(e) => setCloseDate(e.target.value || '')}
|
||||||
min={new Date().toISOString().split('T')[0]}
|
min={Date.now()}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
value={closeDate}
|
value={closeDate}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -28,12 +28,13 @@ import { useRouter } from 'next/router'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { scoreCreators, scoreTraders } from '../../../lib/firebase/scoring'
|
import { scoreCreators, scoreTraders } from '../../../lib/firebase/scoring'
|
||||||
import { Leaderboard } from '../../../components/leaderboard'
|
import { Leaderboard } from '../../../components/leaderboard'
|
||||||
import { formatMoney, toCamelCase } from '../../../lib/util/format'
|
import { formatMoney, toCamelCase } from '../../../../common/util/format'
|
||||||
import { EditFoldButton } from '../../../components/edit-fold-button'
|
import { EditFoldButton } from '../../../components/edit-fold-button'
|
||||||
import Custom404 from '../../404'
|
import Custom404 from '../../404'
|
||||||
import { FollowFoldButton } from '../../../components/follow-fold-button'
|
import { FollowFoldButton } from '../../../components/follow-fold-button'
|
||||||
import FeedCreate from '../../../components/feed-create'
|
import FeedCreate from '../../../components/feed-create'
|
||||||
import { SEO } from '../../../components/SEO'
|
import { SEO } from '../../../components/SEO'
|
||||||
|
import { useTaggedContracts } from '../../../hooks/use-contracts'
|
||||||
|
|
||||||
export async function getStaticProps(props: { params: { slugs: string[] } }) {
|
export async function getStaticProps(props: { params: { slugs: string[] } }) {
|
||||||
const { slugs } = props.params
|
const { slugs } = props.params
|
||||||
|
@ -104,8 +105,14 @@ async function toUserScores(userScores: { [userId: string]: number }) {
|
||||||
const topUsers = await Promise.all(
|
const topUsers = await Promise.all(
|
||||||
topUserPairs.map(([userId]) => getUser(userId))
|
topUserPairs.map(([userId]) => getUser(userId))
|
||||||
)
|
)
|
||||||
const topUserScores = topUserPairs.map(([_, score]) => score)
|
const existingPairs = topUserPairs.filter(([id, _]) =>
|
||||||
return [topUsers, topUserScores] as const
|
topUsers.find((user) => user?.id === id)
|
||||||
|
)
|
||||||
|
const topExistingUsers = existingPairs.map(
|
||||||
|
([id]) => topUsers.find((user) => user?.id === id) as User
|
||||||
|
)
|
||||||
|
const topUserScores = existingPairs.map(([_, score]) => score)
|
||||||
|
return [topExistingUsers, topUserScores] as const
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
|
@ -127,8 +134,6 @@ export default function FoldPage(props: {
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
curator,
|
curator,
|
||||||
contracts,
|
|
||||||
activeContracts,
|
|
||||||
activeContractBets,
|
activeContractBets,
|
||||||
activeContractComments,
|
activeContractComments,
|
||||||
topTraders,
|
topTraders,
|
||||||
|
@ -151,6 +156,16 @@ export default function FoldPage(props: {
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
const isCurator = user && fold && user.id === fold.curatorId
|
const isCurator = user && fold && user.id === fold.curatorId
|
||||||
|
|
||||||
|
const taggedContracts = useTaggedContracts(fold?.tags) ?? props.contracts
|
||||||
|
const contractsMap = _.fromPairs(
|
||||||
|
taggedContracts.map((contract) => [contract.id, contract])
|
||||||
|
)
|
||||||
|
|
||||||
|
const contracts = props.contracts.map((contract) => contractsMap[contract.id])
|
||||||
|
const activeContracts = props.activeContracts.map(
|
||||||
|
(contract) => contractsMap[contract.id]
|
||||||
|
)
|
||||||
|
|
||||||
if (fold === null || !foldSubpages.includes(page) || slugs[2]) {
|
if (fold === null || !foldSubpages.includes(page) || slugs[2]) {
|
||||||
return <Custom404 />
|
return <Custom404 />
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Router from 'next/router'
|
import Router from 'next/router'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
import {
|
import { Contract, listAllContracts } from '../lib/firebase/contracts'
|
||||||
Contract,
|
|
||||||
getClosingSoonContracts,
|
|
||||||
getHotContracts,
|
|
||||||
listAllContracts,
|
|
||||||
} from '../lib/firebase/contracts'
|
|
||||||
import { Page } from '../components/page'
|
import { Page } from '../components/page'
|
||||||
import { ActivityFeed, findActiveContracts } from './activity'
|
import { ActivityFeed, findActiveContracts } from './activity'
|
||||||
import {
|
import {
|
||||||
|
@ -19,15 +15,13 @@ import FeedCreate from '../components/feed-create'
|
||||||
import { Spacer } from '../components/layout/spacer'
|
import { Spacer } from '../components/layout/spacer'
|
||||||
import { Col } from '../components/layout/col'
|
import { Col } from '../components/layout/col'
|
||||||
import { useUser } from '../hooks/use-user'
|
import { useUser } from '../hooks/use-user'
|
||||||
|
import { useContracts } from '../hooks/use-contracts'
|
||||||
|
|
||||||
export async function getStaticProps() {
|
export async function getStaticProps() {
|
||||||
const [contracts, recentComments, hotContracts, closingSoonContracts] =
|
const [contracts, recentComments] = await Promise.all([
|
||||||
await Promise.all([
|
listAllContracts().catch((_) => []),
|
||||||
listAllContracts().catch((_) => []),
|
getRecentComments().catch(() => []),
|
||||||
getRecentComments().catch(() => []),
|
])
|
||||||
getHotContracts().catch(() => []),
|
|
||||||
getClosingSoonContracts().catch(() => []),
|
|
||||||
])
|
|
||||||
|
|
||||||
const activeContracts = findActiveContracts(contracts, recentComments)
|
const activeContracts = findActiveContracts(contracts, recentComments)
|
||||||
const activeContractBets = await Promise.all(
|
const activeContractBets = await Promise.all(
|
||||||
|
@ -44,8 +38,6 @@ export async function getStaticProps() {
|
||||||
activeContracts,
|
activeContracts,
|
||||||
activeContractBets,
|
activeContractBets,
|
||||||
activeContractComments,
|
activeContractComments,
|
||||||
hotContracts,
|
|
||||||
closingSoonContracts,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
revalidate: 60, // regenerate after a minute
|
revalidate: 60, // regenerate after a minute
|
||||||
|
@ -56,27 +48,18 @@ const Home = (props: {
|
||||||
activeContracts: Contract[]
|
activeContracts: Contract[]
|
||||||
activeContractBets: Bet[][]
|
activeContractBets: Bet[][]
|
||||||
activeContractComments: Comment[][]
|
activeContractComments: Comment[][]
|
||||||
hotContracts: Contract[]
|
|
||||||
closingSoonContracts: Contract[]
|
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const { activeContracts, activeContractBets, activeContractComments } = props
|
||||||
activeContracts,
|
|
||||||
activeContractBets,
|
|
||||||
activeContractComments,
|
|
||||||
// hotContracts,
|
|
||||||
// closingSoonContracts,
|
|
||||||
} = props
|
|
||||||
|
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
|
|
||||||
// const initialActiveContracts = props.activeContracts ?? []
|
const contracts = useContracts() ?? activeContracts
|
||||||
// const contracts = useContracts()
|
const contractsMap = _.fromPairs(
|
||||||
// const recentComments = useRecentComments()
|
contracts.map((contract) => [contract.id, contract])
|
||||||
// const activeContracts =
|
)
|
||||||
// recentComments && contracts
|
const updatedContracts = activeContracts.map(
|
||||||
// ? findActiveContracts(contracts, recentComments)
|
(contract) => contractsMap[contract.id]
|
||||||
// : initialActiveContracts
|
)
|
||||||
// TODO: get activeContractBets, activeContractComments associated with activeContracts
|
|
||||||
|
|
||||||
if (user === null) {
|
if (user === null) {
|
||||||
Router.replace('/')
|
Router.replace('/')
|
||||||
|
@ -89,17 +72,10 @@ const Home = (props: {
|
||||||
<Col className="max-w-3xl">
|
<Col className="max-w-3xl">
|
||||||
<FeedCreate user={user ?? undefined} />
|
<FeedCreate user={user ?? undefined} />
|
||||||
<Spacer h={4} />
|
<Spacer h={4} />
|
||||||
|
|
||||||
{/* <HotMarkets contracts={hotContracts?.slice(0, 4) ?? []} />
|
|
||||||
<Spacer h={4} />
|
|
||||||
|
|
||||||
<ClosingSoonMarkets contracts={closingSoonContracts ?? []} />
|
|
||||||
<Spacer h={10} /> */}
|
|
||||||
|
|
||||||
<ActivityFeed
|
<ActivityFeed
|
||||||
contracts={activeContracts ?? []}
|
contracts={updatedContracts}
|
||||||
contractBets={activeContractBets ?? []}
|
contractBets={activeContractBets}
|
||||||
contractComments={activeContractComments ?? []}
|
contractComments={activeContractComments}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
import { Col } from '../components/layout/col'
|
import { Col } from '../components/layout/col'
|
||||||
import { Leaderboard } from '../components/leaderboard'
|
import { Leaderboard } from '../components/leaderboard'
|
||||||
import { Page } from '../components/page'
|
import { Page } from '../components/page'
|
||||||
import { getTopCreators, getTopTraders, User } from '../lib/firebase/users'
|
import { getTopCreators, getTopTraders, User } from '../lib/firebase/users'
|
||||||
import { formatMoney } from '../lib/util/format'
|
import { formatMoney } from '../../common/util/format'
|
||||||
|
|
||||||
export async function getStaticProps() {
|
export async function getStaticProps() {
|
||||||
const [topTraders, topCreators] = await Promise.all([
|
const [topTraders, topCreators] = await Promise.all([
|
||||||
|
|
Loading…
Reference in New Issue
Block a user