bucket input, numeric resolution panel
This commit is contained in:
parent
7cce2cc63f
commit
39b745b25a
|
@ -67,9 +67,11 @@ export function getNumericBets(
|
||||||
|
|
||||||
export const getMappedBucket = (value: number, contract: NumericContract) => {
|
export const getMappedBucket = (value: number, contract: NumericContract) => {
|
||||||
const { bucketCount, min, max } = contract
|
const { bucketCount, min, max } = contract
|
||||||
const cappedValue = Math.min(Math.max(min, value), max)
|
|
||||||
const mapped = Math.floor((bucketCount * (cappedValue - min)) / (max - min))
|
const index = Math.floor(((value - min) / (max - min)) * bucketCount)
|
||||||
return `${mapped}`
|
const bucket = Math.max(Math.min(index, bucketCount - 1), 0)
|
||||||
|
|
||||||
|
return `${bucket}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDpmOutcomeProbabilityAfterBet(
|
export function getDpmOutcomeProbabilityAfterBet(
|
||||||
|
|
40
web/components/bucket-input.tsx
Normal file
40
web/components/bucket-input.tsx
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import _ from 'lodash'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
import { NumericContract } from 'common/contract'
|
||||||
|
import { getMappedBucket } from 'common/calculate-dpm'
|
||||||
|
|
||||||
|
import { NumberInput } from './number-input'
|
||||||
|
|
||||||
|
export function BucketInput(props: {
|
||||||
|
contract: NumericContract
|
||||||
|
isSubmitting?: boolean
|
||||||
|
onBucketChange: (bucket?: string) => void
|
||||||
|
}) {
|
||||||
|
const { contract, isSubmitting, onBucketChange } = props
|
||||||
|
|
||||||
|
const [numberString, setNumberString] = useState('')
|
||||||
|
|
||||||
|
const onChange = (s: string) => {
|
||||||
|
setNumberString(s)
|
||||||
|
|
||||||
|
const value = parseFloat(s)
|
||||||
|
|
||||||
|
const bucket = isFinite(value)
|
||||||
|
? getMappedBucket(value, contract)
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
onBucketChange(bucket)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NumberInput
|
||||||
|
inputClassName="w-full max-w-none"
|
||||||
|
onChange={onChange}
|
||||||
|
error={undefined}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
numberString={numberString}
|
||||||
|
label="Value"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
|
@ -15,10 +15,10 @@ import { useUser } from '../hooks/use-user'
|
||||||
import { placeBet } from '../lib/firebase/api-call'
|
import { placeBet } from '../lib/firebase/api-call'
|
||||||
import { firebaseLogin, User } from '../lib/firebase/users'
|
import { firebaseLogin, User } from '../lib/firebase/users'
|
||||||
import { BuyAmountInput } from './amount-input'
|
import { BuyAmountInput } from './amount-input'
|
||||||
|
import { BucketInput } from './bucket-input'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
import { Spacer } from './layout/spacer'
|
import { Spacer } from './layout/spacer'
|
||||||
import { NumberInput } from './number-input'
|
|
||||||
|
|
||||||
export function NumericBetPanel(props: {
|
export function NumericBetPanel(props: {
|
||||||
contract: NumericContract
|
contract: NumericContract
|
||||||
|
@ -51,9 +51,10 @@ function NumericBuyPanel(props: {
|
||||||
onBuySuccess?: () => void
|
onBuySuccess?: () => void
|
||||||
}) {
|
}) {
|
||||||
const { contract, user, onBuySuccess } = props
|
const { contract, user, onBuySuccess } = props
|
||||||
const { min, max, bucketCount } = contract
|
|
||||||
|
|
||||||
const [numberString, setNumberString] = useState('')
|
const [bucketChoice, setBucketChoice] = useState<string | undefined>(
|
||||||
|
undefined
|
||||||
|
)
|
||||||
const [betAmount, setBetAmount] = useState<number | undefined>(undefined)
|
const [betAmount, setBetAmount] = useState<number | undefined>(undefined)
|
||||||
|
|
||||||
const [valueError, setValueError] = useState<string | undefined>()
|
const [valueError, setValueError] = useState<string | undefined>()
|
||||||
|
@ -61,19 +62,13 @@ function NumericBuyPanel(props: {
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
const [wasSubmitted, setWasSubmitted] = useState(false)
|
const [wasSubmitted, setWasSubmitted] = useState(false)
|
||||||
|
|
||||||
const value = parseFloat(numberString)
|
|
||||||
const index = Math.floor(((value - min) / (max - min)) * bucketCount)
|
|
||||||
const bucket = Math.max(Math.min(index, bucketCount - 1), 0)
|
|
||||||
const bucketChoice = `${bucket}`
|
|
||||||
console.log('value', value, 'bucket', bucket, 'min', min, 'max', max)
|
|
||||||
|
|
||||||
function onBetChange(newAmount: number | undefined) {
|
function onBetChange(newAmount: number | undefined) {
|
||||||
setWasSubmitted(false)
|
setWasSubmitted(false)
|
||||||
setBetAmount(newAmount)
|
setBetAmount(newAmount)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submitBet() {
|
async function submitBet() {
|
||||||
if (!user || !betAmount || !isFinite(bucket)) return
|
if (!user || !betAmount || bucketChoice === undefined) return
|
||||||
|
|
||||||
setError(undefined)
|
setError(undefined)
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
|
@ -134,13 +129,11 @@ function NumericBuyPanel(props: {
|
||||||
<div className="my-3 text-left text-sm text-gray-500">
|
<div className="my-3 text-left text-sm text-gray-500">
|
||||||
Predicted value
|
Predicted value
|
||||||
</div>
|
</div>
|
||||||
<NumberInput
|
|
||||||
inputClassName="w-full max-w-none"
|
<BucketInput
|
||||||
onChange={setNumberString}
|
contract={contract}
|
||||||
error={valueError}
|
isSubmitting={isSubmitting}
|
||||||
disabled={isSubmitting}
|
onBucketChange={setBucketChoice}
|
||||||
numberString={numberString}
|
|
||||||
label="Value"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="my-3 text-left text-sm text-gray-500">Bet amount</div>
|
<div className="my-3 text-left text-sm text-gray-500">Bet amount</div>
|
||||||
|
|
107
web/components/numeric-resolution-panel.tsx
Normal file
107
web/components/numeric-resolution-panel.tsx
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
import { Col } from './layout/col'
|
||||||
|
import { User } from 'web/lib/firebase/users'
|
||||||
|
import { NumberCancelSelector } from './yes-no-selector'
|
||||||
|
import { Spacer } from './layout/spacer'
|
||||||
|
import { ResolveConfirmationButton } from './confirmation-button'
|
||||||
|
import { resolveMarket } from 'web/lib/firebase/api-call'
|
||||||
|
import { NumericContract } from 'common/contract'
|
||||||
|
import { getMappedBucket } from 'common/calculate-dpm'
|
||||||
|
import { BucketInput } from './bucket-input'
|
||||||
|
|
||||||
|
export function NumericResolutionPanel(props: {
|
||||||
|
creator: User
|
||||||
|
contract: NumericContract
|
||||||
|
className?: string
|
||||||
|
}) {
|
||||||
|
useEffect(() => {
|
||||||
|
// warm up cloud function
|
||||||
|
resolveMarket({} as any).catch()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const { contract, className } = props
|
||||||
|
|
||||||
|
const [outcomeMode, setOutcomeMode] = useState<
|
||||||
|
'NUMBER' | 'CANCEL' | undefined
|
||||||
|
>()
|
||||||
|
const [outcome, setOutcome] = useState<string | undefined>()
|
||||||
|
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
|
const [error, setError] = useState<string | undefined>(undefined)
|
||||||
|
|
||||||
|
const resolve = async () => {
|
||||||
|
if (!outcome) return
|
||||||
|
|
||||||
|
let outcomeChoice = outcome
|
||||||
|
if (outcome !== 'CANCEL') {
|
||||||
|
const bucket = getMappedBucket(+outcome, contract)
|
||||||
|
outcomeChoice = `${bucket}`
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSubmitting(true)
|
||||||
|
|
||||||
|
const result = await resolveMarket({
|
||||||
|
outcome: outcomeChoice,
|
||||||
|
contractId: contract.id,
|
||||||
|
}).then((r) => r.data)
|
||||||
|
|
||||||
|
console.log('resolved', outcome, 'result:', result)
|
||||||
|
|
||||||
|
if (result?.status !== 'success') {
|
||||||
|
setError(result?.message || 'Error resolving market')
|
||||||
|
}
|
||||||
|
setIsSubmitting(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitButtonClass =
|
||||||
|
outcome === 'CANCEL'
|
||||||
|
? 'bg-yellow-400 hover:bg-yellow-500'
|
||||||
|
: outcome
|
||||||
|
? 'btn-primary'
|
||||||
|
: 'btn-disabled'
|
||||||
|
|
||||||
|
console.log('outcome', outcome)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col className={clsx('rounded-md bg-white px-8 py-6', className)}>
|
||||||
|
<div className="mb-6 whitespace-nowrap text-2xl">Resolve market</div>
|
||||||
|
|
||||||
|
<div className="mb-3 text-sm text-gray-500">Outcome</div>
|
||||||
|
|
||||||
|
<Spacer h={4} />
|
||||||
|
|
||||||
|
<NumberCancelSelector selected={outcomeMode} onSelect={setOutcomeMode} />
|
||||||
|
|
||||||
|
<Spacer h={4} />
|
||||||
|
|
||||||
|
{outcomeMode === 'NUMBER' && (
|
||||||
|
<BucketInput
|
||||||
|
contract={contract}
|
||||||
|
isSubmitting={isSubmitting}
|
||||||
|
onBucketChange={setOutcome}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{outcome === 'CANCEL' ? (
|
||||||
|
<>All trades will be returned with no fees.</>
|
||||||
|
) : (
|
||||||
|
<>Resolving this market will immediately pay out traders.</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Spacer h={4} />
|
||||||
|
|
||||||
|
{!!error && <div className="text-red-500">{error}</div>}
|
||||||
|
|
||||||
|
<ResolveConfirmationButton
|
||||||
|
onResolve={resolve}
|
||||||
|
isSubmitting={isSubmitting}
|
||||||
|
openModalButtonClass={clsx('w-full mt-2', submitButtonClass)}
|
||||||
|
submitButtonClass={submitButtonClass}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
)
|
||||||
|
}
|
|
@ -3,23 +3,15 @@ import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import { User } from 'web/lib/firebase/users'
|
import { User } from 'web/lib/firebase/users'
|
||||||
import { NumberCancelSelector, YesNoCancelSelector } from './yes-no-selector'
|
import { YesNoCancelSelector } from './yes-no-selector'
|
||||||
import { Spacer } from './layout/spacer'
|
import { Spacer } from './layout/spacer'
|
||||||
import { ResolveConfirmationButton } from './confirmation-button'
|
import { ResolveConfirmationButton } from './confirmation-button'
|
||||||
import { resolveMarket } from 'web/lib/firebase/api-call'
|
import { resolveMarket } from 'web/lib/firebase/api-call'
|
||||||
import { ProbabilitySelector } from './probability-selector'
|
import { ProbabilitySelector } from './probability-selector'
|
||||||
import { DPM_CREATOR_FEE } from 'common/fees'
|
import { DPM_CREATOR_FEE } from 'common/fees'
|
||||||
import { getProbability } from 'common/calculate'
|
import { getProbability } from 'common/calculate'
|
||||||
import {
|
import { Binary, CPMM, DPM, FullContract } from 'common/contract'
|
||||||
Binary,
|
|
||||||
CPMM,
|
|
||||||
DPM,
|
|
||||||
FullContract,
|
|
||||||
NumericContract,
|
|
||||||
} from 'common/contract'
|
|
||||||
import { formatMoney } from 'common/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
import { BucketAmountInput } from './amount-input'
|
|
||||||
import { getMappedBucket } from 'common/calculate-dpm'
|
|
||||||
|
|
||||||
export function ResolutionPanel(props: {
|
export function ResolutionPanel(props: {
|
||||||
creator: User
|
creator: User
|
||||||
|
@ -136,109 +128,3 @@ export function ResolutionPanel(props: {
|
||||||
</Col>
|
</Col>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NumericResolutionPanel(props: {
|
|
||||||
creator: User
|
|
||||||
contract: NumericContract
|
|
||||||
className?: string
|
|
||||||
}) {
|
|
||||||
useEffect(() => {
|
|
||||||
// warm up cloud function
|
|
||||||
resolveMarket({} as any).catch()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const { contract, className } = props
|
|
||||||
const { bucketCount, min, max } = contract
|
|
||||||
|
|
||||||
const [outcomeMode, setOutcomeMode] = useState<
|
|
||||||
'NUMBER' | 'CANCEL' | undefined
|
|
||||||
>()
|
|
||||||
const [outcome, setOutcome] = useState<string | undefined>()
|
|
||||||
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
||||||
const [valueError, setValueError] = useState<string | undefined>()
|
|
||||||
const [error, setError] = useState<string | undefined>(undefined)
|
|
||||||
|
|
||||||
const resolve = async () => {
|
|
||||||
if (!outcome) return
|
|
||||||
|
|
||||||
let outcomeChoice = outcome
|
|
||||||
if (outcome !== 'CANCEL') {
|
|
||||||
const bucket = getMappedBucket(+outcome, contract)
|
|
||||||
outcomeChoice = `${bucket}`
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsSubmitting(true)
|
|
||||||
|
|
||||||
const result = await resolveMarket({
|
|
||||||
outcome: outcomeChoice,
|
|
||||||
contractId: contract.id,
|
|
||||||
}).then((r) => r.data)
|
|
||||||
|
|
||||||
console.log('resolved', outcome, 'result:', result)
|
|
||||||
|
|
||||||
if (result?.status !== 'success') {
|
|
||||||
setError(result?.message || 'Error resolving market')
|
|
||||||
}
|
|
||||||
setIsSubmitting(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
const submitButtonClass =
|
|
||||||
outcome === 'CANCEL'
|
|
||||||
? 'bg-yellow-400 hover:bg-yellow-500'
|
|
||||||
: outcome
|
|
||||||
? 'btn-primary'
|
|
||||||
: 'btn-disabled'
|
|
||||||
|
|
||||||
console.log('outcome', outcome)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Col className={clsx('rounded-md bg-white px-8 py-6', className)}>
|
|
||||||
<div className="mb-6 whitespace-nowrap text-2xl">Resolve market</div>
|
|
||||||
|
|
||||||
<div className="mb-3 text-sm text-gray-500">Outcome</div>
|
|
||||||
|
|
||||||
<Spacer h={4} />
|
|
||||||
|
|
||||||
<NumberCancelSelector selected={outcomeMode} onSelect={setOutcomeMode} />
|
|
||||||
|
|
||||||
<Spacer h={4} />
|
|
||||||
|
|
||||||
{outcomeMode === 'NUMBER' && (
|
|
||||||
<>
|
|
||||||
<BucketAmountInput
|
|
||||||
bucket={outcome && outcome !== 'CANCEL' ? +outcome : undefined}
|
|
||||||
min={min}
|
|
||||||
max={max}
|
|
||||||
inputClassName="w-full max-w-none"
|
|
||||||
onChange={(outcome) =>
|
|
||||||
setOutcome(outcome ? `${outcome}` : undefined)
|
|
||||||
}
|
|
||||||
error={valueError}
|
|
||||||
setError={setValueError}
|
|
||||||
disabled={isSubmitting}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{outcome === 'CANCEL' ? (
|
|
||||||
<>All trades will be returned with no fees.</>
|
|
||||||
) : (
|
|
||||||
<>Resolving this market will immediately pay out traders.</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Spacer h={4} />
|
|
||||||
|
|
||||||
{!!error && <div className="text-red-500">{error}</div>}
|
|
||||||
|
|
||||||
<ResolveConfirmationButton
|
|
||||||
onResolve={resolve}
|
|
||||||
isSubmitting={isSubmitting}
|
|
||||||
openModalButtonClass={clsx('w-full mt-2', submitButtonClass)}
|
|
||||||
submitButtonClass={submitButtonClass}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,10 +7,7 @@ import { ContractOverview } from 'web/components/contract/contract-overview'
|
||||||
import { BetPanel } from 'web/components/bet-panel'
|
import { BetPanel } from 'web/components/bet-panel'
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Col } from 'web/components/layout/col'
|
||||||
import { useUser } from 'web/hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import {
|
import { ResolutionPanel } from 'web/components/resolution-panel'
|
||||||
NumericResolutionPanel,
|
|
||||||
ResolutionPanel,
|
|
||||||
} from 'web/components/resolution-panel'
|
|
||||||
import { Title } from 'web/components/title'
|
import { Title } from 'web/components/title'
|
||||||
import { Spacer } from 'web/components/layout/spacer'
|
import { Spacer } from 'web/components/layout/spacer'
|
||||||
import { listUsers, User } from 'web/lib/firebase/users'
|
import { listUsers, User } from 'web/lib/firebase/users'
|
||||||
|
@ -45,6 +42,7 @@ import { contractTextDetails } from 'web/components/contract/contract-details'
|
||||||
import { useWindowSize } from 'web/hooks/use-window-size'
|
import { useWindowSize } from 'web/hooks/use-window-size'
|
||||||
import Confetti from 'react-confetti'
|
import Confetti from 'react-confetti'
|
||||||
import { NumericBetPanel } from '../../components/numeric-bet-panel'
|
import { NumericBetPanel } from '../../components/numeric-bet-panel'
|
||||||
|
import { NumericResolutionPanel } from '../../components/numeric-resolution-panel'
|
||||||
|
|
||||||
export const getStaticProps = fromPropz(getStaticPropz)
|
export const getStaticProps = fromPropz(getStaticPropz)
|
||||||
export async function getStaticPropz(props: {
|
export async function getStaticPropz(props: {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user