Sell bets (#12)
* sell bet * dev mode * single-pot no-refund payoff; bet selling * Increase default fetch size 25 -> 99 * Fix about page numbering * Don't flash no markets when loading on tag page. * Change Title to use body font * Make a bunch of predictions at once (#9) * Set up a page to make bulk predictions * Integrate preview into the same card * List created predictions * Make changes per James's comments * Increase the starting balance (#11) * Remove references to paying for our Mantic Dollars * Update simulator to use new calculations * Change simulator random to be evenly random again * Sell bet UI * Migrate contracts and bets script * Add comment to script * bets => trades; exclude sold bets * change sale formula * Change current value to uncapped sell value. * Disable sell button while selling * Update some 'bet' to 'trade' Co-authored-by: Austin Chen <akrolsmir@gmail.com> Co-authored-by: jahooma <jahooma@gmail.com>
This commit is contained in:
		
							parent
							
								
									856a2453a1
								
							
						
					
					
						commit
						f48ae0170b
					
				| 
						 | 
					@ -1,5 +1,7 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  "projects": {
 | 
					  "projects": {
 | 
				
			||||||
    "default": "mantic-markets"
 | 
					    "default": "mantic-markets",
 | 
				
			||||||
 | 
					    "prod": "mantic-markets",
 | 
				
			||||||
 | 
					    "dev": "dev-mantic-markets"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@
 | 
				
			||||||
  "name": "functions",
 | 
					  "name": "functions",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "build": "tsc",
 | 
					    "build": "tsc",
 | 
				
			||||||
 | 
					    "watch": "tsc -w",
 | 
				
			||||||
    "serve": "yarn build && firebase emulators:start --only functions",
 | 
					    "serve": "yarn build && firebase emulators:start --only functions",
 | 
				
			||||||
    "shell": "yarn build && firebase functions:shell",
 | 
					    "shell": "yarn build && firebase functions:shell",
 | 
				
			||||||
    "start": "yarn shell",
 | 
					    "start": "yarn shell",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@ import * as admin from 'firebase-admin'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
admin.initializeApp()
 | 
					admin.initializeApp()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export * from './keep-awake'
 | 
					// export * from './keep-awake'
 | 
				
			||||||
export * from './place-bet'
 | 
					export * from './place-bet'
 | 
				
			||||||
export * from './resolve-market'
 | 
					export * from './resolve-market'
 | 
				
			||||||
 | 
					export * from './sell-bet'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,7 +43,7 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
 | 
				
			||||||
        .collection(`contracts/${contractId}/bets`)
 | 
					        .collection(`contracts/${contractId}/bets`)
 | 
				
			||||||
        .doc()
 | 
					        .doc()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const { newBet, newPool, newDpmWeights, newBalance } = getNewBetInfo(
 | 
					      const { newBet, newPool, newTotalShares, newBalance } = getNewBetInfo(
 | 
				
			||||||
        user,
 | 
					        user,
 | 
				
			||||||
        outcome,
 | 
					        outcome,
 | 
				
			||||||
        amount,
 | 
					        amount,
 | 
				
			||||||
| 
						 | 
					@ -54,7 +54,7 @@ export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
 | 
				
			||||||
      transaction.create(newBetDoc, newBet)
 | 
					      transaction.create(newBetDoc, newBet)
 | 
				
			||||||
      transaction.update(contractDoc, {
 | 
					      transaction.update(contractDoc, {
 | 
				
			||||||
        pool: newPool,
 | 
					        pool: newPool,
 | 
				
			||||||
        dpmWeights: newDpmWeights,
 | 
					        totalShares: newTotalShares,
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      transaction.update(userDoc, { balance: newBalance })
 | 
					      transaction.update(userDoc, { balance: newBalance })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -79,29 +79,19 @@ const getNewBetInfo = (
 | 
				
			||||||
      ? { YES: yesPool + amount, NO: noPool }
 | 
					      ? { YES: yesPool + amount, NO: noPool }
 | 
				
			||||||
      : { YES: yesPool, NO: noPool + amount }
 | 
					      : { YES: yesPool, NO: noPool + amount }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const dpmWeight =
 | 
					  const shares =
 | 
				
			||||||
    outcome === 'YES'
 | 
					    outcome === 'YES'
 | 
				
			||||||
      ? (amount * noPool ** 2) / (yesPool ** 2 + amount * yesPool)
 | 
					      ? amount + (amount * noPool ** 2) / (yesPool ** 2 + amount * yesPool)
 | 
				
			||||||
      : (amount * yesPool ** 2) / (noPool ** 2 + amount * noPool)
 | 
					      : amount + (amount * yesPool ** 2) / (noPool ** 2 + amount * noPool)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const { YES: yesWeight, NO: noWeight } = contract.dpmWeights || {
 | 
					  const { YES: yesShares, NO: noShares } = contract.totalShares
 | 
				
			||||||
    YES: 0,
 | 
					 | 
				
			||||||
    NO: 0,
 | 
					 | 
				
			||||||
  } // only nesc for old contracts
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const newDpmWeights =
 | 
					  const newTotalShares =
 | 
				
			||||||
    outcome === 'YES'
 | 
					    outcome === 'YES'
 | 
				
			||||||
      ? { YES: yesWeight + dpmWeight, NO: noWeight }
 | 
					      ? { YES: yesShares + shares, NO: noShares }
 | 
				
			||||||
      : { YES: yesWeight, NO: noWeight + dpmWeight }
 | 
					      : { YES: yesShares, NO: noShares + shares }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const probBefore = yesPool ** 2 / (yesPool ** 2 + noPool ** 2)
 | 
					  const probBefore = yesPool ** 2 / (yesPool ** 2 + noPool ** 2)
 | 
				
			||||||
 | 
					 | 
				
			||||||
  const probAverage =
 | 
					 | 
				
			||||||
    (amount +
 | 
					 | 
				
			||||||
      noPool * Math.atan(yesPool / noPool) -
 | 
					 | 
				
			||||||
      noPool * Math.atan((amount + yesPool) / noPool)) /
 | 
					 | 
				
			||||||
    amount
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const probAfter = newPool.YES ** 2 / (newPool.YES ** 2 + newPool.NO ** 2)
 | 
					  const probAfter = newPool.YES ** 2 / (newPool.YES ** 2 + newPool.NO ** 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const newBet: Bet = {
 | 
					  const newBet: Bet = {
 | 
				
			||||||
| 
						 | 
					@ -109,15 +99,14 @@ const getNewBetInfo = (
 | 
				
			||||||
    userId: user.id,
 | 
					    userId: user.id,
 | 
				
			||||||
    contractId: contract.id,
 | 
					    contractId: contract.id,
 | 
				
			||||||
    amount,
 | 
					    amount,
 | 
				
			||||||
    dpmWeight,
 | 
					    shares,
 | 
				
			||||||
    outcome,
 | 
					    outcome,
 | 
				
			||||||
    probBefore,
 | 
					    probBefore,
 | 
				
			||||||
    probAverage,
 | 
					 | 
				
			||||||
    probAfter,
 | 
					    probAfter,
 | 
				
			||||||
    createdTime: Date.now(),
 | 
					    createdTime: Date.now(),
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const newBalance = user.balance - amount
 | 
					  const newBalance = user.balance - amount
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return { newBet, newPool, newDpmWeights, newBalance }
 | 
					  return { newBet, newPool, newTotalShares, newBalance }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -78,22 +78,27 @@ export const resolveMarket = functions
 | 
				
			||||||
const firestore = admin.firestore()
 | 
					const firestore = admin.firestore()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getPayouts = (outcome: string, contract: Contract, bets: Bet[]) => {
 | 
					const getPayouts = (outcome: string, contract: Contract, bets: Bet[]) => {
 | 
				
			||||||
  const [yesBets, noBets] = _.partition(bets, (bet) => bet.outcome === 'YES')
 | 
					  const openBets = bets.filter((b) => !b.isSold && !b.sale)
 | 
				
			||||||
 | 
					  const [yesBets, noBets] = _.partition(
 | 
				
			||||||
 | 
					    openBets,
 | 
				
			||||||
 | 
					    (bet) => bet.outcome === 'YES'
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const [pool, winningBets] =
 | 
					  const startPool = contract.startPool.YES + contract.startPool.NO
 | 
				
			||||||
 | 
					  const truePool = contract.pool.YES + contract.pool.NO - startPool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [totalShares, winningBets] =
 | 
				
			||||||
    outcome === 'YES'
 | 
					    outcome === 'YES'
 | 
				
			||||||
      ? [contract.pool.NO - contract.startPool.NO, yesBets]
 | 
					      ? [contract.totalShares.YES, yesBets]
 | 
				
			||||||
      : [contract.pool.YES - contract.startPool.YES, noBets]
 | 
					      : [contract.totalShares.NO, noBets]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const finalPool = (1 - PLATFORM_FEE - CREATOR_FEE) * pool
 | 
					  const finalPool = (1 - PLATFORM_FEE - CREATOR_FEE) * truePool
 | 
				
			||||||
  const creatorPayout = CREATOR_FEE * pool
 | 
					  const creatorPayout = CREATOR_FEE * truePool
 | 
				
			||||||
  console.log('final pool:', finalPool, 'creator fee:', creatorPayout)
 | 
					  console.log('final pool:', finalPool, 'creator fee:', creatorPayout)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const sumWeights = _.sumBy(winningBets, (bet) => bet.dpmWeight)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const winnerPayouts = winningBets.map((bet) => ({
 | 
					  const winnerPayouts = winningBets.map((bet) => ({
 | 
				
			||||||
    userId: bet.userId,
 | 
					    userId: bet.userId,
 | 
				
			||||||
    payout: bet.amount + (bet.dpmWeight / sumWeights) * finalPool,
 | 
					    payout: (bet.shares / totalShares) * finalPool,
 | 
				
			||||||
  }))
 | 
					  }))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return winnerPayouts.concat([
 | 
					  return winnerPayouts.concat([
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										58
									
								
								functions/src/scripts/migrate-contract.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								functions/src/scripts/migrate-contract.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,58 @@
 | 
				
			||||||
 | 
					import * as admin from 'firebase-admin'
 | 
				
			||||||
 | 
					import * as _ from 'lodash'
 | 
				
			||||||
 | 
					import { Bet } from '../types/bet'
 | 
				
			||||||
 | 
					import { Contract } from '../types/contract'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function migrateBet(contractRef: DocRef, bet: Bet) {
 | 
				
			||||||
 | 
					  const { dpmWeight, amount, id } = bet as Bet & { dpmWeight: number }
 | 
				
			||||||
 | 
					  const shares = dpmWeight + amount
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  await contractRef.collection('bets').doc(id).update({ shares })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function migrateContract(contractRef: DocRef, contract: Contract) {
 | 
				
			||||||
 | 
					  const bets = await contractRef
 | 
				
			||||||
 | 
					    .collection('bets')
 | 
				
			||||||
 | 
					    .get()
 | 
				
			||||||
 | 
					    .then((snap) => snap.docs.map((bet) => bet.data() as Bet))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const totalShares = {
 | 
				
			||||||
 | 
					    YES: _.sumBy(bets, (bet) => (bet.outcome === 'YES' ? bet.shares : 0)),
 | 
				
			||||||
 | 
					    NO: _.sumBy(bets, (bet) => (bet.outcome === 'NO' ? bet.shares : 0)),
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  await contractRef.update({ totalShares })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function migrateContracts() {
 | 
				
			||||||
 | 
					  console.log('Migrating contracts')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const snapshot = await firestore.collection('contracts').get()
 | 
				
			||||||
 | 
					  const contracts = snapshot.docs.map((doc) => doc.data() as Contract)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  console.log('Loaded contracts', contracts.length)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const contract of contracts) {
 | 
				
			||||||
 | 
					    const contractRef = firestore.doc(`contracts/${contract.id}`)
 | 
				
			||||||
 | 
					    const betsSnapshot = await contractRef.collection('bets').get()
 | 
				
			||||||
 | 
					    const bets = betsSnapshot.docs.map((bet) => bet.data() as Bet)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log('contract', contract.question, 'bets', bets.length)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const bet of bets) await migrateBet(contractRef, bet)
 | 
				
			||||||
 | 
					    await migrateContract(contractRef, contract)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (require.main === module) migrateContracts().then(() => process.exit())
 | 
				
			||||||
							
								
								
									
										162
									
								
								functions/src/sell-bet.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								functions/src/sell-bet.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,162 @@
 | 
				
			||||||
 | 
					import * as admin from 'firebase-admin'
 | 
				
			||||||
 | 
					import * as functions from 'firebase-functions'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { CREATOR_FEE, PLATFORM_FEE } from './resolve-market'
 | 
				
			||||||
 | 
					import { Bet } from './types/bet'
 | 
				
			||||||
 | 
					import { Contract } from './types/contract'
 | 
				
			||||||
 | 
					import { User } from './types/user'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall(
 | 
				
			||||||
 | 
					  async (
 | 
				
			||||||
 | 
					    data: {
 | 
				
			||||||
 | 
					      contractId: string
 | 
				
			||||||
 | 
					      betId: string
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    context
 | 
				
			||||||
 | 
					  ) => {
 | 
				
			||||||
 | 
					    const userId = context?.auth?.uid
 | 
				
			||||||
 | 
					    if (!userId) return { status: 'error', message: 'Not authorized' }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { contractId, betId } = data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // run as transaction to prevent race conditions
 | 
				
			||||||
 | 
					    return await firestore.runTransaction(async (transaction) => {
 | 
				
			||||||
 | 
					      const userDoc = firestore.doc(`users/${userId}`)
 | 
				
			||||||
 | 
					      const userSnap = await transaction.get(userDoc)
 | 
				
			||||||
 | 
					      if (!userSnap.exists)
 | 
				
			||||||
 | 
					        return { status: 'error', message: 'User not found' }
 | 
				
			||||||
 | 
					      const user = userSnap.data() as User
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const contractDoc = firestore.doc(`contracts/${contractId}`)
 | 
				
			||||||
 | 
					      const contractSnap = await transaction.get(contractDoc)
 | 
				
			||||||
 | 
					      if (!contractSnap.exists)
 | 
				
			||||||
 | 
					        return { status: 'error', message: 'Invalid contract' }
 | 
				
			||||||
 | 
					      const contract = contractSnap.data() as Contract
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const betDoc = firestore.doc(`contracts/${contractId}/bets/${betId}`)
 | 
				
			||||||
 | 
					      const betSnap = await transaction.get(betDoc)
 | 
				
			||||||
 | 
					      if (!betSnap.exists) return { status: 'error', message: 'Invalid bet' }
 | 
				
			||||||
 | 
					      const bet = betSnap.data() as Bet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (bet.isSold) return { status: 'error', message: 'Bet already sold' }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const newBetDoc = firestore
 | 
				
			||||||
 | 
					        .collection(`contracts/${contractId}/bets`)
 | 
				
			||||||
 | 
					        .doc()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const { newBet, newPool, newTotalShares, newBalance, creatorFee } =
 | 
				
			||||||
 | 
					        getSellBetInfo(user, bet, contract, newBetDoc.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const creatorDoc = firestore.doc(`users/${contract.creatorId}`)
 | 
				
			||||||
 | 
					      const creatorSnap = await transaction.get(creatorDoc)
 | 
				
			||||||
 | 
					      if (creatorSnap.exists) {
 | 
				
			||||||
 | 
					        const creator = creatorSnap.data() as User
 | 
				
			||||||
 | 
					        const creatorNewBalance = creator.balance + creatorFee
 | 
				
			||||||
 | 
					        transaction.update(creatorDoc, { balance: creatorNewBalance })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      transaction.update(betDoc, { isSold: true })
 | 
				
			||||||
 | 
					      transaction.create(newBetDoc, newBet)
 | 
				
			||||||
 | 
					      transaction.update(contractDoc, {
 | 
				
			||||||
 | 
					        pool: newPool,
 | 
				
			||||||
 | 
					        totalShares: newTotalShares,
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      transaction.update(userDoc, { balance: newBalance })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return { status: 'success' }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const firestore = admin.firestore()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getSellBetInfo = (
 | 
				
			||||||
 | 
					  user: User,
 | 
				
			||||||
 | 
					  bet: Bet,
 | 
				
			||||||
 | 
					  contract: Contract,
 | 
				
			||||||
 | 
					  newBetId: string
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
 | 
					  const { id: betId, amount, shares, outcome } = bet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { YES: yesPool, NO: noPool } = contract.pool
 | 
				
			||||||
 | 
					  const { YES: yesStart, NO: noStart } = contract.startPool
 | 
				
			||||||
 | 
					  const { YES: yesShares, NO: noShares } = contract.totalShares
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [y, n, s] = [yesPool, noPool, shares]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const shareValue =
 | 
				
			||||||
 | 
					    outcome === 'YES'
 | 
				
			||||||
 | 
					      ? // https://www.wolframalpha.com/input/?i=b+%2B+%28b+n%5E2%29%2F%28y+%28-b+%2B+y%29%29+%3D+c+solve+b
 | 
				
			||||||
 | 
					        (n ** 2 +
 | 
				
			||||||
 | 
					          s * y +
 | 
				
			||||||
 | 
					          y ** 2 -
 | 
				
			||||||
 | 
					          Math.sqrt(
 | 
				
			||||||
 | 
					            n ** 4 + (s - y) ** 2 * y ** 2 + 2 * n ** 2 * y * (s + y)
 | 
				
			||||||
 | 
					          )) /
 | 
				
			||||||
 | 
					        (2 * y)
 | 
				
			||||||
 | 
					      : (y ** 2 +
 | 
				
			||||||
 | 
					          s * n +
 | 
				
			||||||
 | 
					          n ** 2 -
 | 
				
			||||||
 | 
					          Math.sqrt(
 | 
				
			||||||
 | 
					            y ** 4 + (s - n) ** 2 * n ** 2 + 2 * y ** 2 * n * (s + n)
 | 
				
			||||||
 | 
					          )) /
 | 
				
			||||||
 | 
					        (2 * n)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const startPool = yesStart + noStart
 | 
				
			||||||
 | 
					  const pool = yesPool + noPool - startPool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const probBefore = yesPool ** 2 / (yesPool ** 2 + noPool ** 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const f = pool / (probBefore * yesShares + (1 - probBefore) * noShares)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const myPool = outcome === 'YES' ? yesPool - yesStart : noPool - noStart
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const adjShareValue = Math.min(Math.min(1, f) * shareValue, myPool)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const newPool =
 | 
				
			||||||
 | 
					    outcome === 'YES'
 | 
				
			||||||
 | 
					      ? { YES: yesPool - adjShareValue, NO: noPool }
 | 
				
			||||||
 | 
					      : { YES: yesPool, NO: noPool - adjShareValue }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const newTotalShares =
 | 
				
			||||||
 | 
					    outcome === 'YES'
 | 
				
			||||||
 | 
					      ? { YES: yesShares - shares, NO: noShares }
 | 
				
			||||||
 | 
					      : { YES: yesShares, NO: noShares - shares }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const probAfter = newPool.YES ** 2 / (newPool.YES ** 2 + newPool.NO ** 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const creatorFee = CREATOR_FEE * adjShareValue
 | 
				
			||||||
 | 
					  const saleAmount = (1 - CREATOR_FEE - PLATFORM_FEE) * adjShareValue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  console.log(
 | 
				
			||||||
 | 
					    'SELL M$',
 | 
				
			||||||
 | 
					    amount,
 | 
				
			||||||
 | 
					    outcome,
 | 
				
			||||||
 | 
					    'for M$',
 | 
				
			||||||
 | 
					    saleAmount,
 | 
				
			||||||
 | 
					    'M$/share:',
 | 
				
			||||||
 | 
					    f,
 | 
				
			||||||
 | 
					    'creator fee: M$',
 | 
				
			||||||
 | 
					    creatorFee
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const newBet: Bet = {
 | 
				
			||||||
 | 
					    id: newBetId,
 | 
				
			||||||
 | 
					    userId: user.id,
 | 
				
			||||||
 | 
					    contractId: contract.id,
 | 
				
			||||||
 | 
					    amount: -adjShareValue,
 | 
				
			||||||
 | 
					    shares: -shares,
 | 
				
			||||||
 | 
					    outcome,
 | 
				
			||||||
 | 
					    probBefore,
 | 
				
			||||||
 | 
					    probAfter,
 | 
				
			||||||
 | 
					    createdTime: Date.now(),
 | 
				
			||||||
 | 
					    sale: {
 | 
				
			||||||
 | 
					      amount: saleAmount,
 | 
				
			||||||
 | 
					      betId,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const newBalance = user.balance + saleAmount
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return { newBet, newPool, newTotalShares, newBalance, creatorFee }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -2,11 +2,20 @@ export type Bet = {
 | 
				
			||||||
  id: string
 | 
					  id: string
 | 
				
			||||||
  userId: string
 | 
					  userId: string
 | 
				
			||||||
  contractId: string
 | 
					  contractId: string
 | 
				
			||||||
  amount: number // Amount of bet
 | 
					
 | 
				
			||||||
  outcome: 'YES' | 'NO' // Chosen outcome
 | 
					  amount: number // bet size; negative if SELL bet
 | 
				
			||||||
  createdTime: number
 | 
					  outcome: 'YES' | 'NO'
 | 
				
			||||||
 | 
					  shares: number // dynamic parimutuel pool weight; negative if SELL bet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  probBefore: number
 | 
					  probBefore: number
 | 
				
			||||||
  probAverage: number
 | 
					 | 
				
			||||||
  probAfter: number
 | 
					  probAfter: number
 | 
				
			||||||
  dpmWeight: number // Dynamic Parimutuel weight
 | 
					
 | 
				
			||||||
 | 
					  sale?: {
 | 
				
			||||||
 | 
					    amount: number // amount user makes from sale
 | 
				
			||||||
 | 
					    betId: string // id of bet being sold
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  isSold?: boolean // true if this BUY bet has been sold
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  createdTime: number
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,7 @@ export type Contract = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  startPool: { YES: number; NO: number }
 | 
					  startPool: { YES: number; NO: number }
 | 
				
			||||||
  pool: { YES: number; NO: number }
 | 
					  pool: { YES: number; NO: number }
 | 
				
			||||||
  dpmWeights: { YES: number; NO: number }
 | 
					  totalShares: { YES: number; NO: number }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  createdTime: number // Milliseconds since epoch
 | 
					  createdTime: number // Milliseconds since epoch
 | 
				
			||||||
  lastUpdatedTime: number // If the question or description was changed
 | 
					  lastUpdatedTime: number // If the question or description was changed
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,9 +12,9 @@ import { formatMoney, formatPercent } from '../lib/util/format'
 | 
				
			||||||
import { Title } from './title'
 | 
					import { Title } from './title'
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  getProbability,
 | 
					  getProbability,
 | 
				
			||||||
  getDpmWeight,
 | 
					  calculateShares,
 | 
				
			||||||
  getProbabilityAfterBet,
 | 
					  getProbabilityAfterBet,
 | 
				
			||||||
} from '../lib/calculation/contract'
 | 
					} from '../lib/calculate'
 | 
				
			||||||
import { firebaseLogin } from '../lib/firebase/users'
 | 
					import { firebaseLogin } from '../lib/firebase/users'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function BetPanel(props: { contract: Contract; className?: string }) {
 | 
					export function BetPanel(props: { contract: Contract; className?: string }) {
 | 
				
			||||||
| 
						 | 
					@ -84,9 +84,9 @@ export function BetPanel(props: { contract: Contract; className?: string }) {
 | 
				
			||||||
    betChoice,
 | 
					    betChoice,
 | 
				
			||||||
    betAmount ?? 0
 | 
					    betAmount ?? 0
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
  const dpmWeight = getDpmWeight(contract.pool, betAmount ?? 0, betChoice)
 | 
					  const shares = calculateShares(contract.pool, betAmount ?? 0, betChoice)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const estimatedWinnings = Math.floor((betAmount ?? 0) + dpmWeight)
 | 
					  const estimatedWinnings = Math.floor(shares)
 | 
				
			||||||
  const estimatedReturn = betAmount
 | 
					  const estimatedReturn = betAmount
 | 
				
			||||||
    ? (estimatedWinnings - betAmount) / betAmount
 | 
					    ? (estimatedWinnings - betAmount) / betAmount
 | 
				
			||||||
    : 0
 | 
					    : 0
 | 
				
			||||||
| 
						 | 
					@ -98,7 +98,7 @@ export function BetPanel(props: { contract: Contract; className?: string }) {
 | 
				
			||||||
    <Col
 | 
					    <Col
 | 
				
			||||||
      className={clsx('bg-gray-100 shadow-xl px-8 py-6 rounded-md', className)}
 | 
					      className={clsx('bg-gray-100 shadow-xl px-8 py-6 rounded-md', className)}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <Title className="!mt-0 whitespace-nowrap" text="Place a bet" />
 | 
					      <Title className="!mt-0 whitespace-nowrap" text="Place a trade" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <div className="mt-2 mb-1 text-sm text-gray-400">Outcome</div>
 | 
					      <div className="mt-2 mb-1 text-sm text-gray-400">Outcome</div>
 | 
				
			||||||
      <YesNoSelector
 | 
					      <YesNoSelector
 | 
				
			||||||
| 
						 | 
					@ -107,7 +107,7 @@ export function BetPanel(props: { contract: Contract; className?: string }) {
 | 
				
			||||||
        onSelect={(choice) => onBetChoice(choice)}
 | 
					        onSelect={(choice) => onBetChoice(choice)}
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <div className="mt-3 mb-1 text-sm text-gray-400">Bet amount</div>
 | 
					      <div className="mt-3 mb-1 text-sm text-gray-400">Amount</div>
 | 
				
			||||||
      <Col className="my-2">
 | 
					      <Col className="my-2">
 | 
				
			||||||
        <label className="input-group">
 | 
					        <label className="input-group">
 | 
				
			||||||
          <span className="text-sm bg-gray-200">M$</span>
 | 
					          <span className="text-sm bg-gray-200">M$</span>
 | 
				
			||||||
| 
						 | 
					@ -168,18 +168,18 @@ export function BetPanel(props: { contract: Contract; className?: string }) {
 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
          onClick={betDisabled ? undefined : submitBet}
 | 
					          onClick={betDisabled ? undefined : submitBet}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          {isSubmitting ? 'Submitting...' : 'Place bet'}
 | 
					          {isSubmitting ? 'Submitting...' : 'Submit trade'}
 | 
				
			||||||
        </button>
 | 
					        </button>
 | 
				
			||||||
      ) : (
 | 
					      ) : (
 | 
				
			||||||
        <button
 | 
					        <button
 | 
				
			||||||
          className="btn mt-4 border-none normal-case text-lg font-medium px-10 bg-gradient-to-r from-teal-500 to-green-500 hover:from-teal-600 hover:to-green-600"
 | 
					          className="btn mt-4 border-none normal-case text-lg font-medium px-10 bg-gradient-to-r from-teal-500 to-green-500 hover:from-teal-600 hover:to-green-600"
 | 
				
			||||||
          onClick={firebaseLogin}
 | 
					          onClick={firebaseLogin}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          Sign in to bet!
 | 
					          Sign in to trade!
 | 
				
			||||||
        </button>
 | 
					        </button>
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      {wasSubmitted && <div className="mt-4">Bet submitted!</div>}
 | 
					      {wasSubmitted && <div className="mt-4">Trade submitted!</div>}
 | 
				
			||||||
    </Col>
 | 
					    </Col>
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,10 +13,13 @@ import { Row } from './layout/row'
 | 
				
			||||||
import { UserLink } from './user-page'
 | 
					import { UserLink } from './user-page'
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  calculatePayout,
 | 
					  calculatePayout,
 | 
				
			||||||
 | 
					  calculateSaleAmount,
 | 
				
			||||||
  currentValue,
 | 
					  currentValue,
 | 
				
			||||||
  resolvedPayout,
 | 
					  resolvedPayout,
 | 
				
			||||||
} from '../lib/calculation/contract'
 | 
					} from '../lib/calculate'
 | 
				
			||||||
import clsx from 'clsx'
 | 
					import clsx from 'clsx'
 | 
				
			||||||
 | 
					import { cloudFunction } from '../lib/firebase/api-call'
 | 
				
			||||||
 | 
					import { ConfirmationButton } from './confirmation-button'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function BetsList(props: { user: User }) {
 | 
					export function BetsList(props: { user: User }) {
 | 
				
			||||||
  const { user } = props
 | 
					  const { user } = props
 | 
				
			||||||
| 
						 | 
					@ -65,19 +68,26 @@ export function BetsList(props: { user: User }) {
 | 
				
			||||||
    contracts,
 | 
					    contracts,
 | 
				
			||||||
    (contract) => contract.isResolved
 | 
					    (contract) => contract.isResolved
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const currentBets = _.sumBy(unresolved, (contract) =>
 | 
					  const currentBets = _.sumBy(unresolved, (contract) =>
 | 
				
			||||||
    _.sumBy(contractBets[contract.id], (bet) => bet.amount)
 | 
					    _.sumBy(contractBets[contract.id], (bet) => {
 | 
				
			||||||
 | 
					      if (bet.isSold || bet.sale) return 0
 | 
				
			||||||
 | 
					      return bet.amount
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const currentBetsValue = _.sumBy(unresolved, (contract) =>
 | 
					  const currentBetsValue = _.sumBy(unresolved, (contract) =>
 | 
				
			||||||
    _.sumBy(contractBets[contract.id], (bet) => currentValue(contract, bet))
 | 
					    _.sumBy(contractBets[contract.id], (bet) => {
 | 
				
			||||||
 | 
					      if (bet.isSold || bet.sale) return 0
 | 
				
			||||||
 | 
					      return currentValue(contract, bet)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Col className="mt-6 gap-6">
 | 
					    <Col className="mt-6 gap-6">
 | 
				
			||||||
      <Row className="gap-8">
 | 
					      <Row className="gap-8">
 | 
				
			||||||
        <Col>
 | 
					        <Col>
 | 
				
			||||||
          <div className="text-sm text-gray-500">Active bets</div>
 | 
					          <div className="text-sm text-gray-500">Currently invested</div>
 | 
				
			||||||
          <div>{formatMoney(currentBets)}</div>
 | 
					          <div>{formatMoney(currentBets)}</div>
 | 
				
			||||||
        </Col>
 | 
					        </Col>
 | 
				
			||||||
        <Col>
 | 
					        <Col>
 | 
				
			||||||
| 
						 | 
					@ -173,16 +183,17 @@ export function MyBetsSummary(props: {
 | 
				
			||||||
  const { bets, contract, className } = props
 | 
					  const { bets, contract, className } = props
 | 
				
			||||||
  const { resolution } = contract
 | 
					  const { resolution } = contract
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const betsTotal = _.sumBy(bets, (bet) => bet.amount)
 | 
					  const excludeSales = bets.filter((b) => !b.isSold && !b.sale)
 | 
				
			||||||
 | 
					  const betsTotal = _.sumBy(excludeSales, (bet) => bet.amount)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const betsPayout = resolution
 | 
					  const betsPayout = resolution
 | 
				
			||||||
    ? _.sumBy(bets, (bet) => resolvedPayout(contract, bet))
 | 
					    ? _.sumBy(bets, (bet) => resolvedPayout(contract, bet))
 | 
				
			||||||
    : 0
 | 
					    : 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const yesWinnings = _.sumBy(bets, (bet) =>
 | 
					  const yesWinnings = _.sumBy(excludeSales, (bet) =>
 | 
				
			||||||
    calculatePayout(contract, bet, 'YES')
 | 
					    calculatePayout(contract, bet, 'YES')
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
  const noWinnings = _.sumBy(bets, (bet) =>
 | 
					  const noWinnings = _.sumBy(excludeSales, (bet) =>
 | 
				
			||||||
    calculatePayout(contract, bet, 'NO')
 | 
					    calculatePayout(contract, bet, 'NO')
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -190,7 +201,7 @@ export function MyBetsSummary(props: {
 | 
				
			||||||
    <Row className={clsx('gap-4 sm:gap-6', className)}>
 | 
					    <Row className={clsx('gap-4 sm:gap-6', className)}>
 | 
				
			||||||
      <Col>
 | 
					      <Col>
 | 
				
			||||||
        <div className="text-sm text-gray-500 whitespace-nowrap">
 | 
					        <div className="text-sm text-gray-500 whitespace-nowrap">
 | 
				
			||||||
          Total bets
 | 
					          Amount invested
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div className="whitespace-nowrap">{formatMoney(betsTotal)}</div>
 | 
					        <div className="whitespace-nowrap">{formatMoney(betsTotal)}</div>
 | 
				
			||||||
      </Col>
 | 
					      </Col>
 | 
				
			||||||
| 
						 | 
					@ -228,6 +239,11 @@ export function ContractBetsTable(props: {
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
  const { contract, bets, className } = props
 | 
					  const { contract, bets, className } = props
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [sales, buys] = _.partition(bets, (bet) => bet.sale)
 | 
				
			||||||
 | 
					  const salesDict = _.fromPairs(
 | 
				
			||||||
 | 
					    sales.map((sale) => [sale.sale?.betId ?? '', sale])
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const { isResolved } = contract
 | 
					  const { isResolved } = contract
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
| 
						 | 
					@ -237,15 +253,21 @@ export function ContractBetsTable(props: {
 | 
				
			||||||
          <tr className="p-2">
 | 
					          <tr className="p-2">
 | 
				
			||||||
            <th>Date</th>
 | 
					            <th>Date</th>
 | 
				
			||||||
            <th>Outcome</th>
 | 
					            <th>Outcome</th>
 | 
				
			||||||
            <th>Bet</th>
 | 
					            <th>Amount</th>
 | 
				
			||||||
            <th>Probability</th>
 | 
					            <th>Probability</th>
 | 
				
			||||||
            {!isResolved && <th>Est. max payout</th>}
 | 
					            {!isResolved && <th>Est. max payout</th>}
 | 
				
			||||||
            <th>{isResolved ? <>Payout</> : <>Current value</>}</th>
 | 
					            <th>{isResolved ? <>Payout</> : <>Current value</>}</th>
 | 
				
			||||||
 | 
					            <th></th>
 | 
				
			||||||
          </tr>
 | 
					          </tr>
 | 
				
			||||||
        </thead>
 | 
					        </thead>
 | 
				
			||||||
        <tbody>
 | 
					        <tbody>
 | 
				
			||||||
          {bets.map((bet) => (
 | 
					          {buys.map((bet) => (
 | 
				
			||||||
            <BetRow key={bet.id} bet={bet} contract={contract} />
 | 
					            <BetRow
 | 
				
			||||||
 | 
					              key={bet.id}
 | 
				
			||||||
 | 
					              bet={bet}
 | 
				
			||||||
 | 
					              sale={salesDict[bet.id]}
 | 
				
			||||||
 | 
					              contract={contract}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
          ))}
 | 
					          ))}
 | 
				
			||||||
        </tbody>
 | 
					        </tbody>
 | 
				
			||||||
      </table>
 | 
					      </table>
 | 
				
			||||||
| 
						 | 
					@ -253,14 +275,22 @@ export function ContractBetsTable(props: {
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function BetRow(props: { bet: Bet; contract: Contract }) {
 | 
					function BetRow(props: { bet: Bet; contract: Contract; sale?: Bet }) {
 | 
				
			||||||
  const { bet, contract } = props
 | 
					  const { bet, sale, contract } = props
 | 
				
			||||||
  const { amount, outcome, createdTime, probBefore, probAfter, dpmWeight } = bet
 | 
					  const {
 | 
				
			||||||
 | 
					    amount,
 | 
				
			||||||
 | 
					    outcome,
 | 
				
			||||||
 | 
					    createdTime,
 | 
				
			||||||
 | 
					    probBefore,
 | 
				
			||||||
 | 
					    probAfter,
 | 
				
			||||||
 | 
					    shares,
 | 
				
			||||||
 | 
					    isSold,
 | 
				
			||||||
 | 
					  } = bet
 | 
				
			||||||
  const { isResolved } = contract
 | 
					  const { isResolved } = contract
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <tr>
 | 
					    <tr>
 | 
				
			||||||
      <td>{dayjs(createdTime).format('MMM D, H:mma')}</td>
 | 
					      <td>{dayjs(createdTime).format('MMM D, h:mma')}</td>
 | 
				
			||||||
      <td>
 | 
					      <td>
 | 
				
			||||||
        <OutcomeLabel outcome={outcome} />
 | 
					        <OutcomeLabel outcome={outcome} />
 | 
				
			||||||
      </td>
 | 
					      </td>
 | 
				
			||||||
| 
						 | 
					@ -268,18 +298,63 @@ function BetRow(props: { bet: Bet; contract: Contract }) {
 | 
				
			||||||
      <td>
 | 
					      <td>
 | 
				
			||||||
        {formatPercent(probBefore)} → {formatPercent(probAfter)}
 | 
					        {formatPercent(probBefore)} → {formatPercent(probAfter)}
 | 
				
			||||||
      </td>
 | 
					      </td>
 | 
				
			||||||
      {!isResolved && <td>{formatMoney(amount + dpmWeight)}</td>}
 | 
					      {!isResolved && <td>{formatMoney(shares)}</td>}
 | 
				
			||||||
      <td>
 | 
					      <td>
 | 
				
			||||||
        {formatMoney(
 | 
					        {bet.isSold
 | 
				
			||||||
          isResolved
 | 
					          ? 'N/A'
 | 
				
			||||||
            ? resolvedPayout(contract, bet)
 | 
					          : formatMoney(
 | 
				
			||||||
            : currentValue(contract, bet)
 | 
					              isResolved
 | 
				
			||||||
        )}
 | 
					                ? resolvedPayout(contract, bet)
 | 
				
			||||||
 | 
					                : bet.sale
 | 
				
			||||||
 | 
					                ? bet.sale.amount ?? 0
 | 
				
			||||||
 | 
					                : currentValue(contract, bet)
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
      </td>
 | 
					      </td>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {sale ? (
 | 
				
			||||||
 | 
					        <td>SOLD for {formatMoney(Math.abs(sale.amount))}</td>
 | 
				
			||||||
 | 
					      ) : (
 | 
				
			||||||
 | 
					        !isResolved &&
 | 
				
			||||||
 | 
					        !isSold && (
 | 
				
			||||||
 | 
					          <td className="text-neutral">
 | 
				
			||||||
 | 
					            <SellButton contract={contract} bet={bet} />
 | 
				
			||||||
 | 
					          </td>
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
    </tr>
 | 
					    </tr>
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const sellBet = cloudFunction('sellBet')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function SellButton(props: { contract: Contract; bet: Bet }) {
 | 
				
			||||||
 | 
					  const { contract, bet } = props
 | 
				
			||||||
 | 
					  const [isSubmitting, setIsSubmitting] = useState(false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <ConfirmationButton
 | 
				
			||||||
 | 
					      id={`sell-${bet.id}`}
 | 
				
			||||||
 | 
					      openModelBtn={{
 | 
				
			||||||
 | 
					        className: clsx('btn-sm', isSubmitting && 'btn-disabled loading'),
 | 
				
			||||||
 | 
					        label: 'Sell',
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					      submitBtn={{ className: 'btn-primary' }}
 | 
				
			||||||
 | 
					      onSubmit={async () => {
 | 
				
			||||||
 | 
					        setIsSubmitting(true)
 | 
				
			||||||
 | 
					        await sellBet({ contractId: contract.id, betId: bet.id })
 | 
				
			||||||
 | 
					        setIsSubmitting(false)
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <div className="text-2xl mb-4">Sell</div>
 | 
				
			||||||
 | 
					      <div>
 | 
				
			||||||
 | 
					        Do you want to sell your {formatMoney(bet.amount)} position on{' '}
 | 
				
			||||||
 | 
					        <OutcomeLabel outcome={bet.outcome} /> for{' '}
 | 
				
			||||||
 | 
					        {formatMoney(calculateSaleAmount(contract, bet))}?
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </ConfirmationButton>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function OutcomeLabel(props: { outcome: 'YES' | 'NO' | 'CANCEL' }) {
 | 
					function OutcomeLabel(props: { outcome: 'YES' | 'NO' | 'CANCEL' }) {
 | 
				
			||||||
  const { outcome } = props
 | 
					  const { outcome } = props
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -90,7 +90,7 @@ export const ContractOverview = (props: {
 | 
				
			||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
  const { contract, className } = props
 | 
					  const { contract, className } = props
 | 
				
			||||||
  const { resolution, creatorId } = contract
 | 
					  const { resolution, creatorId } = contract
 | 
				
			||||||
  const { probPercent, volume } = compute(contract)
 | 
					  const { probPercent, truePool } = compute(contract)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const user = useUser()
 | 
					  const user = useUser()
 | 
				
			||||||
  const isCreator = user?.id === creatorId
 | 
					  const isCreator = user?.id === creatorId
 | 
				
			||||||
| 
						 | 
					@ -140,7 +140,7 @@ export const ContractOverview = (props: {
 | 
				
			||||||
      <ContractDescription contract={contract} isCreator={isCreator} />
 | 
					      <ContractDescription contract={contract} isCreator={isCreator} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      {/* Show a delete button for contracts without any trading */}
 | 
					      {/* Show a delete button for contracts without any trading */}
 | 
				
			||||||
      {isCreator && volume === 0 && (
 | 
					      {isCreator && truePool === 0 && (
 | 
				
			||||||
        <>
 | 
					        <>
 | 
				
			||||||
          <Spacer h={8} />
 | 
					          <Spacer h={8} />
 | 
				
			||||||
          <button
 | 
					          <button
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,7 +17,7 @@ import { Linkify } from './linkify'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function ContractDetails(props: { contract: Contract }) {
 | 
					export function ContractDetails(props: { contract: Contract }) {
 | 
				
			||||||
  const { contract } = props
 | 
					  const { contract } = props
 | 
				
			||||||
  const { volume, createdDate, resolvedDate } = compute(contract)
 | 
					  const { truePool, createdDate, resolvedDate } = compute(contract)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Row className="flex-wrap text-sm text-gray-500">
 | 
					    <Row className="flex-wrap text-sm text-gray-500">
 | 
				
			||||||
| 
						 | 
					@ -29,7 +29,7 @@ export function ContractDetails(props: { contract: Contract }) {
 | 
				
			||||||
        {resolvedDate ? `${createdDate} - ${resolvedDate}` : createdDate}
 | 
					        {resolvedDate ? `${createdDate} - ${resolvedDate}` : createdDate}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div className="mx-2">•</div>
 | 
					      <div className="mx-2">•</div>
 | 
				
			||||||
      <div className="whitespace-nowrap">{formatMoney(volume)} volume</div>
 | 
					      <div className="whitespace-nowrap">{formatMoney(truePool)} pool</div>
 | 
				
			||||||
    </Row>
 | 
					    </Row>
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -110,14 +110,14 @@ function ContractsGrid(props: { contracts: Contract[] }) {
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Sort = 'createdTime' | 'volume' | 'resolved' | 'all'
 | 
					type Sort = 'createdTime' | 'pool' | 'resolved' | 'all'
 | 
				
			||||||
export function SearchableGrid(props: {
 | 
					export function SearchableGrid(props: {
 | 
				
			||||||
  contracts: Contract[]
 | 
					  contracts: Contract[]
 | 
				
			||||||
  defaultSort?: Sort
 | 
					  defaultSort?: Sort
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
  const { contracts, defaultSort } = props
 | 
					  const { contracts, defaultSort } = props
 | 
				
			||||||
  const [query, setQuery] = useState('')
 | 
					  const [query, setQuery] = useState('')
 | 
				
			||||||
  const [sort, setSort] = useState(defaultSort || 'volume')
 | 
					  const [sort, setSort] = useState(defaultSort || 'pool')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function check(corpus: String) {
 | 
					  function check(corpus: String) {
 | 
				
			||||||
    return corpus.toLowerCase().includes(query.toLowerCase())
 | 
					    return corpus.toLowerCase().includes(query.toLowerCase())
 | 
				
			||||||
| 
						 | 
					@ -132,8 +132,8 @@ export function SearchableGrid(props: {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (sort === 'createdTime' || sort === 'resolved' || sort === 'all') {
 | 
					  if (sort === 'createdTime' || sort === 'resolved' || sort === 'all') {
 | 
				
			||||||
    matches.sort((a, b) => b.createdTime - a.createdTime)
 | 
					    matches.sort((a, b) => b.createdTime - a.createdTime)
 | 
				
			||||||
  } else if (sort === 'volume') {
 | 
					  } else if (sort === 'pool') {
 | 
				
			||||||
    matches.sort((a, b) => compute(b).volume - compute(a).volume)
 | 
					    matches.sort((a, b) => compute(b).truePool - compute(a).truePool)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (sort !== 'all') {
 | 
					  if (sort !== 'all') {
 | 
				
			||||||
| 
						 | 
					@ -159,7 +159,7 @@ export function SearchableGrid(props: {
 | 
				
			||||||
          value={sort}
 | 
					          value={sort}
 | 
				
			||||||
          onChange={(e) => setSort(e.target.value as Sort)}
 | 
					          onChange={(e) => setSort(e.target.value as Sort)}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          <option value="volume">Most traded</option>
 | 
					          <option value="pool">Most traded</option>
 | 
				
			||||||
          <option value="createdTime">Newest first</option>
 | 
					          <option value="createdTime">Newest first</option>
 | 
				
			||||||
          <option value="resolved">Resolved</option>
 | 
					          <option value="resolved">Resolved</option>
 | 
				
			||||||
          <option value="all">All markets</option>
 | 
					          <option value="all">All markets</option>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -41,8 +41,8 @@ function getNavigationOptions(user: User, options: { mobile: boolean }) {
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
      : []),
 | 
					      : []),
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      name: 'Your bets',
 | 
					      name: 'Your trades',
 | 
				
			||||||
      href: '/bets',
 | 
					      href: '/trades',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      name: 'Your markets',
 | 
					      name: 'Your markets',
 | 
				
			||||||
| 
						 | 
					@ -64,7 +64,7 @@ function ProfileSummary(props: { user: User }) {
 | 
				
			||||||
      <div className="rounded-full w-10 h-10 mr-4">
 | 
					      <div className="rounded-full w-10 h-10 mr-4">
 | 
				
			||||||
        <Image src={user.avatarUrl} width={40} height={40} />
 | 
					        <Image src={user.avatarUrl} width={40} height={40} />
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div className="truncate" style={{ maxWidth: 175 }}>
 | 
					      <div className="truncate text-left" style={{ maxWidth: 175 }}>
 | 
				
			||||||
        {user.name}
 | 
					        {user.name}
 | 
				
			||||||
        <div className="text-gray-700 text-sm">{formatMoney(user.balance)}</div>
 | 
					        <div className="text-gray-700 text-sm">{formatMoney(user.balance)}</div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										134
									
								
								web/lib/calculate.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								web/lib/calculate.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,134 @@
 | 
				
			||||||
 | 
					import { Bet } from './firebase/bets'
 | 
				
			||||||
 | 
					import { Contract } from './firebase/contracts'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const fees = 0.02
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getProbability(pool: { YES: number; NO: number }) {
 | 
				
			||||||
 | 
					  const [yesPool, noPool] = [pool.YES, pool.NO]
 | 
				
			||||||
 | 
					  const numerator = Math.pow(yesPool, 2)
 | 
				
			||||||
 | 
					  const denominator = Math.pow(yesPool, 2) + Math.pow(noPool, 2)
 | 
				
			||||||
 | 
					  return numerator / denominator
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getProbabilityAfterBet(
 | 
				
			||||||
 | 
					  pool: { YES: number; NO: number },
 | 
				
			||||||
 | 
					  outcome: 'YES' | 'NO',
 | 
				
			||||||
 | 
					  bet: number
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					  const [YES, NO] = [
 | 
				
			||||||
 | 
					    pool.YES + (outcome === 'YES' ? bet : 0),
 | 
				
			||||||
 | 
					    pool.NO + (outcome === 'NO' ? bet : 0),
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					  return getProbability({ YES, NO })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function calculateShares(
 | 
				
			||||||
 | 
					  pool: { YES: number; NO: number },
 | 
				
			||||||
 | 
					  bet: number,
 | 
				
			||||||
 | 
					  betChoice: 'YES' | 'NO'
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					  const [yesPool, noPool] = [pool.YES, pool.NO]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return betChoice === 'YES'
 | 
				
			||||||
 | 
					    ? bet + (bet * noPool ** 2) / (yesPool ** 2 + bet * yesPool)
 | 
				
			||||||
 | 
					    : bet + (bet * yesPool ** 2) / (noPool ** 2 + bet * noPool)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function calculatePayout(
 | 
				
			||||||
 | 
					  contract: Contract,
 | 
				
			||||||
 | 
					  bet: Bet,
 | 
				
			||||||
 | 
					  outcome: 'YES' | 'NO' | 'CANCEL'
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					  const { amount, outcome: betOutcome, shares } = bet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (outcome === 'CANCEL') return amount
 | 
				
			||||||
 | 
					  if (betOutcome !== outcome) return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { totalShares } = contract
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (totalShares[outcome] === 0) return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const startPool = contract.startPool.YES + contract.startPool.NO
 | 
				
			||||||
 | 
					  const pool = contract.pool.YES + contract.pool.NO - startPool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (1 - fees) * (shares / totalShares[outcome]) * pool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function resolvedPayout(contract: Contract, bet: Bet) {
 | 
				
			||||||
 | 
					  if (contract.resolution)
 | 
				
			||||||
 | 
					    return calculatePayout(contract, bet, contract.resolution)
 | 
				
			||||||
 | 
					  throw new Error('Contract was not resolved')
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function currentValue(contract: Contract, bet: Bet) {
 | 
				
			||||||
 | 
					  // const prob = getProbability(contract.pool)
 | 
				
			||||||
 | 
					  // const yesPayout = calculatePayout(contract, bet, 'YES')
 | 
				
			||||||
 | 
					  // const noPayout = calculatePayout(contract, bet, 'NO')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // return prob * yesPayout + (1 - prob) * noPayout
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { shares, outcome } = bet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { YES: yesPool, NO: noPool } = contract.pool
 | 
				
			||||||
 | 
					  const [y, n, s] = [yesPool, noPool, shares]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const shareValue =
 | 
				
			||||||
 | 
					    outcome === 'YES'
 | 
				
			||||||
 | 
					      ? // https://www.wolframalpha.com/input/?i=b+%2B+%28b+n%5E2%29%2F%28y+%28-b+%2B+y%29%29+%3D+c+solve+b
 | 
				
			||||||
 | 
					        (n ** 2 +
 | 
				
			||||||
 | 
					          s * y +
 | 
				
			||||||
 | 
					          y ** 2 -
 | 
				
			||||||
 | 
					          Math.sqrt(
 | 
				
			||||||
 | 
					            n ** 4 + (s - y) ** 2 * y ** 2 + 2 * n ** 2 * y * (s + y)
 | 
				
			||||||
 | 
					          )) /
 | 
				
			||||||
 | 
					        (2 * y)
 | 
				
			||||||
 | 
					      : (y ** 2 +
 | 
				
			||||||
 | 
					          s * n +
 | 
				
			||||||
 | 
					          n ** 2 -
 | 
				
			||||||
 | 
					          Math.sqrt(
 | 
				
			||||||
 | 
					            y ** 4 + (s - n) ** 2 * n ** 2 + 2 * y ** 2 * n * (s + n)
 | 
				
			||||||
 | 
					          )) /
 | 
				
			||||||
 | 
					        (2 * n)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (1 - fees) * shareValue
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					export function calculateSaleAmount(contract: Contract, bet: Bet) {
 | 
				
			||||||
 | 
					  const { shares, outcome } = bet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { YES: yesPool, NO: noPool } = contract.pool
 | 
				
			||||||
 | 
					  const { YES: yesStart, NO: noStart } = contract.startPool
 | 
				
			||||||
 | 
					  const { YES: yesShares, NO: noShares } = contract.totalShares
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [y, n, s] = [yesPool, noPool, shares]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const shareValue =
 | 
				
			||||||
 | 
					    outcome === 'YES'
 | 
				
			||||||
 | 
					      ? // https://www.wolframalpha.com/input/?i=b+%2B+%28b+n%5E2%29%2F%28y+%28-b+%2B+y%29%29+%3D+c+solve+b
 | 
				
			||||||
 | 
					        (n ** 2 +
 | 
				
			||||||
 | 
					          s * y +
 | 
				
			||||||
 | 
					          y ** 2 -
 | 
				
			||||||
 | 
					          Math.sqrt(
 | 
				
			||||||
 | 
					            n ** 4 + (s - y) ** 2 * y ** 2 + 2 * n ** 2 * y * (s + y)
 | 
				
			||||||
 | 
					          )) /
 | 
				
			||||||
 | 
					        (2 * y)
 | 
				
			||||||
 | 
					      : (y ** 2 +
 | 
				
			||||||
 | 
					          s * n +
 | 
				
			||||||
 | 
					          n ** 2 -
 | 
				
			||||||
 | 
					          Math.sqrt(
 | 
				
			||||||
 | 
					            y ** 4 + (s - n) ** 2 * n ** 2 + 2 * y ** 2 * n * (s + n)
 | 
				
			||||||
 | 
					          )) /
 | 
				
			||||||
 | 
					        (2 * n)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const startPool = yesStart + noStart
 | 
				
			||||||
 | 
					  const pool = yesPool + noPool - startPool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const probBefore = yesPool ** 2 / (yesPool ** 2 + noPool ** 2)
 | 
				
			||||||
 | 
					  const f = pool / (probBefore * yesShares + (1 - probBefore) * noShares)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const myPool = outcome === 'YES' ? yesPool - yesStart : noPool - noStart
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const adjShareValue = Math.min(Math.min(1, f) * shareValue, myPool)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const saleAmount = (1 - fees) * adjShareValue
 | 
				
			||||||
 | 
					  return saleAmount
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,72 +0,0 @@
 | 
				
			||||||
import { Bet } from '../firebase/bets'
 | 
					 | 
				
			||||||
import { Contract } from '../firebase/contracts'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const fees = 0.02
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function getProbability(pool: { YES: number; NO: number }) {
 | 
					 | 
				
			||||||
  const [yesPool, noPool] = [pool.YES, pool.NO]
 | 
					 | 
				
			||||||
  const numerator = Math.pow(yesPool, 2)
 | 
					 | 
				
			||||||
  const denominator = Math.pow(yesPool, 2) + Math.pow(noPool, 2)
 | 
					 | 
				
			||||||
  return numerator / denominator
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function getProbabilityAfterBet(
 | 
					 | 
				
			||||||
  pool: { YES: number; NO: number },
 | 
					 | 
				
			||||||
  outcome: 'YES' | 'NO',
 | 
					 | 
				
			||||||
  bet: number
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
  const [YES, NO] = [
 | 
					 | 
				
			||||||
    pool.YES + (outcome === 'YES' ? bet : 0),
 | 
					 | 
				
			||||||
    pool.NO + (outcome === 'NO' ? bet : 0),
 | 
					 | 
				
			||||||
  ]
 | 
					 | 
				
			||||||
  return getProbability({ YES, NO })
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function getDpmWeight(
 | 
					 | 
				
			||||||
  pool: { YES: number; NO: number },
 | 
					 | 
				
			||||||
  bet: number,
 | 
					 | 
				
			||||||
  betChoice: 'YES' | 'NO'
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
  const [yesPool, noPool] = [pool.YES, pool.NO]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return betChoice === 'YES'
 | 
					 | 
				
			||||||
    ? (bet * Math.pow(noPool, 2)) / (Math.pow(yesPool, 2) + bet * yesPool)
 | 
					 | 
				
			||||||
    : (bet * Math.pow(yesPool, 2)) / (Math.pow(noPool, 2) + bet * noPool)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function calculatePayout(
 | 
					 | 
				
			||||||
  contract: Contract,
 | 
					 | 
				
			||||||
  bet: Bet,
 | 
					 | 
				
			||||||
  outcome: 'YES' | 'NO' | 'CANCEL'
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
  const { amount, outcome: betOutcome, dpmWeight } = bet
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (outcome === 'CANCEL') return amount
 | 
					 | 
				
			||||||
  if (betOutcome !== outcome) return 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let { dpmWeights, pool, startPool } = contract
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Fake data if not set.
 | 
					 | 
				
			||||||
  if (!dpmWeights) dpmWeights = { YES: 100, NO: 100 }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Fake data if not set.
 | 
					 | 
				
			||||||
  if (!pool) pool = { YES: 100, NO: 100 }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const otherOutcome = outcome === 'YES' ? 'NO' : 'YES'
 | 
					 | 
				
			||||||
  const poolSize = pool[otherOutcome] - startPool[otherOutcome]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (1 - fees) * (dpmWeight / dpmWeights[outcome]) * poolSize + amount
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
export function resolvedPayout(contract: Contract, bet: Bet) {
 | 
					 | 
				
			||||||
  if (contract.resolution)
 | 
					 | 
				
			||||||
    return calculatePayout(contract, bet, contract.resolution)
 | 
					 | 
				
			||||||
  throw new Error('Contract was not resolved')
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function currentValue(contract: Contract, bet: Bet) {
 | 
					 | 
				
			||||||
  const prob = getProbability(contract.pool)
 | 
					 | 
				
			||||||
  const yesPayout = calculatePayout(contract, bet, 'YES')
 | 
					 | 
				
			||||||
  const noPayout = calculatePayout(contract, bet, 'NO')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return prob * yesPayout + (1 - prob) * noPayout
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										6
									
								
								web/lib/firebase/api-call.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								web/lib/firebase/api-call.ts
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					import { getFunctions, httpsCallable } from 'firebase/functions'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const functions = getFunctions()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const cloudFunction = (name: string) => httpsCallable(functions, name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,12 +11,21 @@ export type Bet = {
 | 
				
			||||||
  id: string
 | 
					  id: string
 | 
				
			||||||
  userId: string
 | 
					  userId: string
 | 
				
			||||||
  contractId: string
 | 
					  contractId: string
 | 
				
			||||||
  amount: number // Amount of bet
 | 
					
 | 
				
			||||||
  outcome: 'YES' | 'NO' // Chosen outcome
 | 
					  amount: number // bet size; negative if SELL bet
 | 
				
			||||||
  dpmWeight: number // Dynamic Parimutuel weight
 | 
					  outcome: 'YES' | 'NO'
 | 
				
			||||||
 | 
					  shares: number // dynamic parimutuel pool weight; negative if SELL bet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  probBefore: number
 | 
					  probBefore: number
 | 
				
			||||||
  probAverage: number
 | 
					 | 
				
			||||||
  probAfter: number
 | 
					  probAfter: number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  sale?: {
 | 
				
			||||||
 | 
					    amount: number // amount user makes from sale
 | 
				
			||||||
 | 
					    betId: string // id of bet being sold
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  isSold?: boolean // true if this BUY bet has been sold
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  createdTime: number
 | 
					  createdTime: number
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,7 +30,7 @@ export type Contract = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  startPool: { YES: number; NO: number }
 | 
					  startPool: { YES: number; NO: number }
 | 
				
			||||||
  pool: { YES: number; NO: number }
 | 
					  pool: { YES: number; NO: number }
 | 
				
			||||||
  dpmWeights: { YES: number; NO: number }
 | 
					  totalShares: { YES: number; NO: number }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  createdTime: number // Milliseconds since epoch
 | 
					  createdTime: number // Milliseconds since epoch
 | 
				
			||||||
  lastUpdatedTime: number // If the question or description was changed
 | 
					  lastUpdatedTime: number // If the question or description was changed
 | 
				
			||||||
| 
						 | 
					@ -48,7 +48,7 @@ export function path(contract: Contract) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function compute(contract: Contract) {
 | 
					export function compute(contract: Contract) {
 | 
				
			||||||
  const { pool, startPool, createdTime, resolutionTime, isResolved } = contract
 | 
					  const { pool, startPool, createdTime, resolutionTime, isResolved } = contract
 | 
				
			||||||
  const volume = pool.YES + pool.NO - startPool.YES - startPool.NO
 | 
					  const truePool = pool.YES + pool.NO - startPool.YES - startPool.NO
 | 
				
			||||||
  const prob = pool.YES ** 2 / (pool.YES ** 2 + pool.NO ** 2)
 | 
					  const prob = pool.YES ** 2 / (pool.YES ** 2 + pool.NO ** 2)
 | 
				
			||||||
  const probPercent = Math.round(prob * 100) + '%'
 | 
					  const probPercent = Math.round(prob * 100) + '%'
 | 
				
			||||||
  const startProb =
 | 
					  const startProb =
 | 
				
			||||||
| 
						 | 
					@ -57,7 +57,7 @@ export function compute(contract: Contract) {
 | 
				
			||||||
  const resolvedDate = isResolved
 | 
					  const resolvedDate = isResolved
 | 
				
			||||||
    ? dayjs(resolutionTime).format('MMM D')
 | 
					    ? dayjs(resolutionTime).format('MMM D')
 | 
				
			||||||
    : undefined
 | 
					    : undefined
 | 
				
			||||||
  return { volume, probPercent, startProb, createdDate, resolvedDate }
 | 
					  return { truePool, probPercent, startProb, createdDate, resolvedDate }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const db = getFirestore(app)
 | 
					const db = getFirestore(app)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,24 +1,28 @@
 | 
				
			||||||
import { getFirestore } from '@firebase/firestore'
 | 
					import { getFirestore } from '@firebase/firestore'
 | 
				
			||||||
import { initializeApp } from 'firebase/app'
 | 
					import { initializeApp } from 'firebase/app'
 | 
				
			||||||
const firebaseConfig = {
 | 
					
 | 
				
			||||||
  apiKey: 'AIzaSyDp3J57vLeAZCzxLD-vcPaGIkAmBoGOSYw',
 | 
					export const isProd = process.env.NODE_ENV === 'production'
 | 
				
			||||||
  authDomain: 'mantic-markets.firebaseapp.com',
 | 
					
 | 
				
			||||||
  projectId: 'mantic-markets',
 | 
					const firebaseConfig = isProd
 | 
				
			||||||
  storageBucket: 'mantic-markets.appspot.com',
 | 
					  ? {
 | 
				
			||||||
  messagingSenderId: '128925704902',
 | 
					      apiKey: 'AIzaSyDp3J57vLeAZCzxLD-vcPaGIkAmBoGOSYw',
 | 
				
			||||||
  appId: '1:128925704902:web:f61f86944d8ffa2a642dc7',
 | 
					      authDomain: 'mantic-markets.firebaseapp.com',
 | 
				
			||||||
  measurementId: 'G-SSFK1Q138D',
 | 
					      projectId: 'mantic-markets',
 | 
				
			||||||
}
 | 
					      storageBucket: 'mantic-markets.appspot.com',
 | 
				
			||||||
 | 
					      messagingSenderId: '128925704902',
 | 
				
			||||||
 | 
					      appId: '1:128925704902:web:f61f86944d8ffa2a642dc7',
 | 
				
			||||||
 | 
					      measurementId: 'G-SSFK1Q138D',
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  : {
 | 
				
			||||||
 | 
					      apiKey: 'AIzaSyBoq3rzUa8Ekyo3ZaTnlycQYPRCA26VpOw',
 | 
				
			||||||
 | 
					      authDomain: 'dev-mantic-markets.firebaseapp.com',
 | 
				
			||||||
 | 
					      projectId: 'dev-mantic-markets',
 | 
				
			||||||
 | 
					      storageBucket: 'dev-mantic-markets.appspot.com',
 | 
				
			||||||
 | 
					      messagingSenderId: '134303100058',
 | 
				
			||||||
 | 
					      appId: '1:134303100058:web:27f9ea8b83347251f80323',
 | 
				
			||||||
 | 
					      measurementId: 'G-YJC9E37P37',
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Initialize Firebase
 | 
					// Initialize Firebase
 | 
				
			||||||
export const app = initializeApp(firebaseConfig)
 | 
					export const app = initializeApp(firebaseConfig)
 | 
				
			||||||
export const db = getFirestore(app)
 | 
					export const db = getFirestore(app)
 | 
				
			||||||
 | 
					 | 
				
			||||||
// try {
 | 
					 | 
				
			||||||
//   // Note: this is still throwing a console error atm...
 | 
					 | 
				
			||||||
//   import('firebase/analytics').then((analytics) => {
 | 
					 | 
				
			||||||
//     analytics.getAnalytics(app)
 | 
					 | 
				
			||||||
//   })
 | 
					 | 
				
			||||||
// } catch (e) {
 | 
					 | 
				
			||||||
//   console.warn('Analytics were blocked')
 | 
					 | 
				
			||||||
// }
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,6 @@ import {
 | 
				
			||||||
  Contract,
 | 
					  Contract,
 | 
				
			||||||
  getContractFromSlug,
 | 
					  getContractFromSlug,
 | 
				
			||||||
  pushNewContract,
 | 
					  pushNewContract,
 | 
				
			||||||
  setContract,
 | 
					 | 
				
			||||||
} from '../firebase/contracts'
 | 
					} from '../firebase/contracts'
 | 
				
			||||||
import { User } from '../firebase/users'
 | 
					import { User } from '../firebase/users'
 | 
				
			||||||
import { randomString } from '../util/random-string'
 | 
					import { randomString } from '../util/random-string'
 | 
				
			||||||
| 
						 | 
					@ -38,7 +37,7 @@ export async function createContract(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    startPool: { YES: startYes, NO: startNo },
 | 
					    startPool: { YES: startYes, NO: startNo },
 | 
				
			||||||
    pool: { YES: startYes, NO: startNo },
 | 
					    pool: { YES: startYes, NO: startNo },
 | 
				
			||||||
    dpmWeights: { YES: 0, NO: 0 },
 | 
					    totalShares: { YES: 0, NO: 0 },
 | 
				
			||||||
    isResolved: false,
 | 
					    isResolved: false,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO: Set create time to Firestore timestamp
 | 
					    // TODO: Set create time to Firestore timestamp
 | 
				
			||||||
| 
						 | 
					@ -49,8 +48,8 @@ export async function createContract(
 | 
				
			||||||
  return await pushNewContract(contract)
 | 
					  return await pushNewContract(contract)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function calcStartPool(initialProb: number, initialCapital = 200) {
 | 
					export function calcStartPool(initialProbInt: number, initialCapital = 200) {
 | 
				
			||||||
  const p = initialProb / 100.0
 | 
					  const p = initialProbInt / 100.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const startYes =
 | 
					  const startYes =
 | 
				
			||||||
    p === 0.5
 | 
					    p === 0.5
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,10 +21,13 @@ function makeWeights(bids: Bid[]) {
 | 
				
			||||||
  // First pass: calculate all the weights
 | 
					  // First pass: calculate all the weights
 | 
				
			||||||
  for (const { yesBid, noBid } of bids) {
 | 
					  for (const { yesBid, noBid } of bids) {
 | 
				
			||||||
    const yesWeight =
 | 
					    const yesWeight =
 | 
				
			||||||
      (yesBid * Math.pow(noPot, 2)) / (Math.pow(yesPot, 2) + yesBid * yesPot) ||
 | 
					      yesBid +
 | 
				
			||||||
      0
 | 
					        (yesBid * Math.pow(noPot, 2)) /
 | 
				
			||||||
 | 
					          (Math.pow(yesPot, 2) + yesBid * yesPot) || 0
 | 
				
			||||||
    const noWeight =
 | 
					    const noWeight =
 | 
				
			||||||
      (noBid * Math.pow(yesPot, 2)) / (Math.pow(noPot, 2) + noBid * noPot) || 0
 | 
					      noBid +
 | 
				
			||||||
 | 
					        (noBid * Math.pow(yesPot, 2)) / (Math.pow(noPot, 2) + noBid * noPot) ||
 | 
				
			||||||
 | 
					      0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Note: Need to calculate weights BEFORE updating pot
 | 
					    // Note: Need to calculate weights BEFORE updating pot
 | 
				
			||||||
    yesPot += yesBid
 | 
					    yesPot += yesBid
 | 
				
			||||||
| 
						 | 
					@ -53,15 +56,15 @@ export function makeEntries(bids: Bid[]): Entry[] {
 | 
				
			||||||
  const yesWeightsSum = weights.reduce((sum, entry) => sum + entry.yesWeight, 0)
 | 
					  const yesWeightsSum = weights.reduce((sum, entry) => sum + entry.yesWeight, 0)
 | 
				
			||||||
  const noWeightsSum = weights.reduce((sum, entry) => sum + entry.noWeight, 0)
 | 
					  const noWeightsSum = weights.reduce((sum, entry) => sum + entry.noWeight, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const potSize = yesPot + noPot - YES_SEED - NO_SEED
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Second pass: calculate all the payouts
 | 
					  // Second pass: calculate all the payouts
 | 
				
			||||||
  const entries: Entry[] = []
 | 
					  const entries: Entry[] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  for (const weight of weights) {
 | 
					  for (const weight of weights) {
 | 
				
			||||||
    const { yesBid, noBid, yesWeight, noWeight } = weight
 | 
					    const { yesBid, noBid, yesWeight, noWeight } = weight
 | 
				
			||||||
    // Payout: You get your initial bid back, as well as your share of the
 | 
					    const yesPayout = (yesWeight / yesWeightsSum) * potSize
 | 
				
			||||||
    // (noPot - seed) according to your yesWeight
 | 
					    const noPayout = (noWeight / noWeightsSum) * potSize
 | 
				
			||||||
    const yesPayout = yesBid + (yesWeight / yesWeightsSum) * (noPot - NO_SEED)
 | 
					 | 
				
			||||||
    const noPayout = noBid + (noWeight / noWeightsSum) * (yesPot - YES_SEED)
 | 
					 | 
				
			||||||
    const yesReturn = (yesPayout - yesBid) / yesBid
 | 
					    const yesReturn = (yesPayout - yesBid) / yesBid
 | 
				
			||||||
    const noReturn = (noPayout - noBid) / noBid
 | 
					    const noReturn = (noPayout - noBid) / noBid
 | 
				
			||||||
    entries.push({ ...weight, yesPayout, noPayout, yesReturn, noReturn })
 | 
					    entries.push({ ...weight, yesPayout, noPayout, yesReturn, noReturn })
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,11 +6,11 @@ const formatter = new Intl.NumberFormat('en-US', {
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function formatMoney(amount: number) {
 | 
					export function formatMoney(amount: number) {
 | 
				
			||||||
  return 'M$ ' + formatter.format(amount).substring(1)
 | 
					  return 'M$ ' + formatter.format(amount).replace('$', '')
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function formatWithCommas(amount: number) {
 | 
					export function formatWithCommas(amount: number) {
 | 
				
			||||||
  return formatter.format(amount).substring(1)
 | 
					  return formatter.format(amount).replace('$', '')
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function formatPercent(zeroToOne: number) {
 | 
					export function formatPercent(zeroToOne: number) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -97,7 +97,7 @@ function BetsSection(props: { contract: Contract; user: User | null }) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
      <Title text="Your bets" />
 | 
					      <Title text="Your trades" />
 | 
				
			||||||
      <MyBetsSummary contract={contract} bets={userBets} />
 | 
					      <MyBetsSummary contract={contract} bets={userBets} />
 | 
				
			||||||
      <Spacer h={6} />
 | 
					      <Spacer h={6} />
 | 
				
			||||||
      <ContractBetsTable contract={contract} bets={userBets} />
 | 
					      <ContractBetsTable contract={contract} bets={userBets} />
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -162,8 +162,8 @@ 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 0.5%
 | 
					        The creator of the prediction market decides the outcome and earns 1% of
 | 
				
			||||||
        of the trade volume for their effort.
 | 
					        the betting pool for their effort.
 | 
				
			||||||
      </p>
 | 
					      </p>
 | 
				
			||||||
      <p>
 | 
					      <p>
 | 
				
			||||||
        This simple resolution mechanism has surprising benefits in allowing a
 | 
					        This simple resolution mechanism has surprising benefits in allowing a
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -86,7 +86,7 @@ function TableRowEnd(props: { entry: Entry | null; isNew?: boolean }) {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <>
 | 
					      <>
 | 
				
			||||||
        <td>{(entry.prob * 100).toFixed(1)}%</td>
 | 
					        <td>{(entry.prob * 100).toFixed(1)}%</td>
 | 
				
			||||||
        <td>${(entry.yesBid + entry.yesWeight).toFixed(0)}</td>
 | 
					        <td>${entry.yesWeight.toFixed(0)}</td>
 | 
				
			||||||
        {!props.isNew && (
 | 
					        {!props.isNew && (
 | 
				
			||||||
          <>
 | 
					          <>
 | 
				
			||||||
            <td>${entry.yesPayout.toFixed(0)}</td>
 | 
					            <td>${entry.yesPayout.toFixed(0)}</td>
 | 
				
			||||||
| 
						 | 
					@ -99,7 +99,7 @@ function TableRowEnd(props: { entry: Entry | null; isNew?: boolean }) {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <>
 | 
					      <>
 | 
				
			||||||
        <td>{(entry.prob * 100).toFixed(1)}%</td>
 | 
					        <td>{(entry.prob * 100).toFixed(1)}%</td>
 | 
				
			||||||
        <td>${(entry.noBid + entry.noWeight).toFixed(0)}</td>
 | 
					        <td>${entry.noWeight.toFixed(0)}</td>
 | 
				
			||||||
        {!props.isNew && (
 | 
					        {!props.isNew && (
 | 
				
			||||||
          <>
 | 
					          <>
 | 
				
			||||||
            <td>${entry.noPayout.toFixed(0)}</td>
 | 
					            <td>${entry.noPayout.toFixed(0)}</td>
 | 
				
			||||||
| 
						 | 
					@ -149,9 +149,9 @@ function NewBidTable(props: {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function randomBid() {
 | 
					  function randomBid() {
 | 
				
			||||||
    const bidType = Math.random() < 0.5 ? 'YES' : 'NO'
 | 
					    const bidType = Math.random() < 0.5 ? 'YES' : 'NO'
 | 
				
			||||||
    const p = bidType === 'YES' ? nextEntry.prob : 1 - nextEntry.prob
 | 
					    // const p = bidType === 'YES' ? nextEntry.prob : 1 - nextEntry.prob
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const amount = Math.round(p * Math.random() * 300) + 1
 | 
					    const amount = Math.floor(Math.random() * 300) + 1
 | 
				
			||||||
    const bid = makeBid(bidType, amount)
 | 
					    const bid = makeBid(bidType, amount)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bids.splice(steps, 0, bid)
 | 
					    bids.splice(steps, 0, bid)
 | 
				
			||||||
| 
						 | 
					@ -238,7 +238,7 @@ function NewBidTable(props: {
 | 
				
			||||||
// Show a hello world React page
 | 
					// Show a hello world React page
 | 
				
			||||||
export default function Simulator() {
 | 
					export default function Simulator() {
 | 
				
			||||||
  const [steps, setSteps] = useState(1)
 | 
					  const [steps, setSteps] = useState(1)
 | 
				
			||||||
  const [bids, setBids] = useState([{ yesBid: 550, noBid: 450 }])
 | 
					  const [bids, setBids] = useState([{ yesBid: 100, noBid: 100 }])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const entries = useMemo(
 | 
					  const entries = useMemo(
 | 
				
			||||||
    () => makeEntries(bids.slice(0, steps)),
 | 
					    () => makeEntries(bids.slice(0, steps)),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,13 +4,13 @@ import { SEO } from '../components/SEO'
 | 
				
			||||||
import { Title } from '../components/title'
 | 
					import { Title } from '../components/title'
 | 
				
			||||||
import { useUser } from '../hooks/use-user'
 | 
					import { useUser } from '../hooks/use-user'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function BetsPage() {
 | 
					export default function TradesPage() {
 | 
				
			||||||
  const user = useUser()
 | 
					  const user = useUser()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Page>
 | 
					    <Page>
 | 
				
			||||||
      <SEO title="Your bets" description="Your bets" url="/bets" />
 | 
					      <SEO title="Your trades" description="Your trades" url="/trades" />
 | 
				
			||||||
      <Title text="Your bets" />
 | 
					      <Title text="Your trades" />
 | 
				
			||||||
      {user && <BetsList user={user} />}
 | 
					      {user && <BetsList user={user} />}
 | 
				
			||||||
    </Page>
 | 
					    </Page>
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user