liquidity button, dialog
This commit is contained in:
parent
b99f1c4668
commit
afce22e900
|
@ -60,6 +60,16 @@ export function formatLargeNumber(num: number, sigfigs = 2): string {
|
|||
return `${numStr}${suffix[i] ?? ''}`
|
||||
}
|
||||
|
||||
export function shortFormatNumber(num: number): string {
|
||||
if (num < 1000) return showPrecision(num, 3)
|
||||
|
||||
const suffix = ['', 'K', 'M', 'B', 'T', 'Q']
|
||||
const i = Math.floor(Math.log10(num) / 3)
|
||||
|
||||
const numStr = showPrecision(num / Math.pow(10, 3 * i), 2)
|
||||
return `${numStr}${suffix[i] ?? ''}`
|
||||
}
|
||||
|
||||
export function toCamelCase(words: string) {
|
||||
const camelCase = words
|
||||
.split(' ')
|
||||
|
|
|
@ -7,7 +7,6 @@ import { capitalize } from 'lodash'
|
|||
import { Contract } from 'common/contract'
|
||||
import { formatMoney, formatPercent } from 'common/util/format'
|
||||
import { contractPool, updateContract } from 'web/lib/firebase/contracts'
|
||||
import { LiquidityBountyPanel } from 'web/components/contract/liquidity-bounty-panel'
|
||||
import { Col } from '../layout/col'
|
||||
import { Modal } from '../layout/modal'
|
||||
import { Title } from '../title'
|
||||
|
@ -228,7 +227,6 @@ export function ContractInfoDialog(props: {
|
|||
<Row className="flex-wrap">
|
||||
<DuplicateContractButton contract={contract} />
|
||||
</Row>
|
||||
{!contract.resolution && <LiquidityBountyPanel contract={contract} />}
|
||||
</Col>
|
||||
</Modal>
|
||||
</>
|
||||
|
|
|
@ -9,6 +9,7 @@ import { FollowMarketButton } from 'web/components/follow-market-button'
|
|||
import { LikeMarketButton } from 'web/components/contract/like-market-button'
|
||||
import { ContractInfoDialog } from 'web/components/contract/contract-info-dialog'
|
||||
import { Tooltip } from '../tooltip'
|
||||
import { LiquidityButton } from './liquidity-button'
|
||||
|
||||
export function ExtraContractActionsRow(props: { contract: Contract }) {
|
||||
const { contract } = props
|
||||
|
@ -18,6 +19,9 @@ export function ExtraContractActionsRow(props: { contract: Contract }) {
|
|||
return (
|
||||
<Row>
|
||||
<FollowMarketButton contract={contract} user={user} />
|
||||
{contract.mechanism === 'cpmm-1' && (
|
||||
<LiquidityButton contract={contract} user={user} />
|
||||
)}
|
||||
<LikeMarketButton contract={contract} user={user} />
|
||||
<Tooltip text="Share" placement="bottom" noTap noFade>
|
||||
<Button
|
||||
|
|
|
@ -1,240 +0,0 @@
|
|||
import clsx from 'clsx'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { Contract, CPMMContract } from 'common/contract'
|
||||
import { formatMoney } from 'common/util/format'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import { addSubsidy, withdrawLiquidity } from 'web/lib/firebase/api'
|
||||
import { AmountInput } from 'web/components/amount-input'
|
||||
import { Row } from 'web/components/layout/row'
|
||||
import { useUserLiquidity } from 'web/hooks/use-liquidity'
|
||||
import { Tabs } from 'web/components/layout/tabs'
|
||||
import { NoLabel, YesLabel } from 'web/components/outcome-label'
|
||||
import { Col } from 'web/components/layout/col'
|
||||
import { track } from 'web/lib/service/analytics'
|
||||
import { InfoTooltip } from 'web/components/info-tooltip'
|
||||
import { BETTORS, PRESENT_BET } from 'common/user'
|
||||
import { buildArray } from 'common/util/array'
|
||||
import { useAdmin } from 'web/hooks/use-admin'
|
||||
|
||||
export function LiquidityBountyPanel(props: { contract: Contract }) {
|
||||
const { contract } = props
|
||||
|
||||
const isCPMM = contract.mechanism === 'cpmm-1'
|
||||
const user = useUser()
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const lpShares = isCPMM && useUserLiquidity(contract, user?.id ?? '')
|
||||
|
||||
const [showWithdrawal, setShowWithdrawal] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!showWithdrawal && lpShares && lpShares.YES && lpShares.NO)
|
||||
setShowWithdrawal(true)
|
||||
}, [showWithdrawal, lpShares])
|
||||
|
||||
const isCreator = user?.id === contract.creatorId
|
||||
const isAdmin = useAdmin()
|
||||
|
||||
if (!isCreator && !isAdmin && !showWithdrawal) return <></>
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
tabs={buildArray(
|
||||
(isCreator || isAdmin) &&
|
||||
isCPMM && {
|
||||
title: (isAdmin ? '[Admin] ' : '') + 'Subsidize',
|
||||
content: <AddLiquidityPanel contract={contract} />,
|
||||
},
|
||||
showWithdrawal &&
|
||||
isCPMM && {
|
||||
title: 'Withdraw',
|
||||
content: (
|
||||
<WithdrawLiquidityPanel
|
||||
contract={contract}
|
||||
lpShares={lpShares as { YES: number; NO: number }}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
||||
(isCreator || isAdmin) &&
|
||||
isCPMM && {
|
||||
title: 'Pool',
|
||||
content: <ViewLiquidityPanel contract={contract} />,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AddLiquidityPanel(props: { contract: CPMMContract }) {
|
||||
const { contract } = props
|
||||
const { id: contractId, slug } = contract
|
||||
|
||||
const user = useUser()
|
||||
|
||||
const [amount, setAmount] = useState<number | undefined>(undefined)
|
||||
const [error, setError] = useState<string | undefined>(undefined)
|
||||
const [isSuccess, setIsSuccess] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const onAmountChange = (amount: number | undefined) => {
|
||||
setIsSuccess(false)
|
||||
setAmount(amount)
|
||||
|
||||
// Check for errors.
|
||||
if (amount !== undefined) {
|
||||
if (user && user.balance < amount) {
|
||||
setError('Insufficient balance')
|
||||
} else if (amount < 1) {
|
||||
setError('Minimum amount: ' + formatMoney(1))
|
||||
} else {
|
||||
setError(undefined)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
if (!amount) return
|
||||
|
||||
setIsLoading(true)
|
||||
setIsSuccess(false)
|
||||
|
||||
addSubsidy({ amount, contractId })
|
||||
.then((_) => {
|
||||
setIsSuccess(true)
|
||||
setError(undefined)
|
||||
setIsLoading(false)
|
||||
})
|
||||
.catch((_) => setError('Server error'))
|
||||
|
||||
track('add liquidity', { amount, contractId, slug })
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-4 text-gray-500">
|
||||
Contribute your M$ to make this market more accurate.{' '}
|
||||
<InfoTooltip
|
||||
text={`More liquidity stabilizes the market, encouraging ${BETTORS} to ${PRESENT_BET}. You can withdraw your subsidy at any time.`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Row>
|
||||
<AmountInput
|
||||
amount={amount}
|
||||
onChange={onAmountChange}
|
||||
label="M$"
|
||||
error={error}
|
||||
disabled={isLoading}
|
||||
inputClassName="w-28"
|
||||
/>
|
||||
<button
|
||||
className={clsx('btn btn-primary ml-2', isLoading && 'btn-disabled')}
|
||||
onClick={submit}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</Row>
|
||||
|
||||
{isSuccess && amount && (
|
||||
<div>Success! Added {formatMoney(amount)} in liquidity.</div>
|
||||
)}
|
||||
|
||||
{isLoading && <div>Processing...</div>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function ViewLiquidityPanel(props: { contract: CPMMContract }) {
|
||||
const { contract } = props
|
||||
const { pool } = contract
|
||||
const { YES: yesShares, NO: noShares } = pool
|
||||
|
||||
return (
|
||||
<Col className="mb-4">
|
||||
<div className="mb-4 text-gray-500">
|
||||
The liquidity pool for this market currently contains:
|
||||
</div>
|
||||
<span>
|
||||
{yesShares.toFixed(2)} <YesLabel /> shares
|
||||
</span>
|
||||
|
||||
<span>
|
||||
{noShares.toFixed(2)} <NoLabel /> shares
|
||||
</span>
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
function WithdrawLiquidityPanel(props: {
|
||||
contract: CPMMContract
|
||||
lpShares: { YES: number; NO: number }
|
||||
}) {
|
||||
const { contract, lpShares } = props
|
||||
const { YES: yesShares, NO: noShares } = lpShares
|
||||
|
||||
const [_error, setError] = useState<string | undefined>(undefined)
|
||||
const [isSuccess, setIsSuccess] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const submit = () => {
|
||||
setIsLoading(true)
|
||||
setIsSuccess(false)
|
||||
|
||||
withdrawLiquidity({ contractId: contract.id })
|
||||
.then((_) => {
|
||||
setIsSuccess(true)
|
||||
setError(undefined)
|
||||
setIsLoading(false)
|
||||
})
|
||||
.catch((_) => setError('Server error'))
|
||||
|
||||
track('withdraw liquidity')
|
||||
}
|
||||
|
||||
if (isSuccess)
|
||||
return (
|
||||
<div className="text-gray-500">
|
||||
Success! Your liquidity was withdrawn.
|
||||
</div>
|
||||
)
|
||||
|
||||
if (!yesShares && !noShares)
|
||||
return (
|
||||
<div className="text-gray-500">
|
||||
You do not have any liquidity positions to withdraw.
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<Col>
|
||||
<div className="mb-4 text-gray-500">
|
||||
Your liquidity position is currently:
|
||||
</div>
|
||||
|
||||
<span>
|
||||
{yesShares.toFixed(2)} <YesLabel /> shares
|
||||
</span>
|
||||
|
||||
<span>
|
||||
{noShares.toFixed(2)} <NoLabel /> shares
|
||||
</span>
|
||||
|
||||
<Row className="mt-4 mb-2">
|
||||
<button
|
||||
className={clsx(
|
||||
'btn btn-outline btn-sm ml-2',
|
||||
isLoading && 'btn-disabled'
|
||||
)}
|
||||
onClick={submit}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Withdraw
|
||||
</button>
|
||||
</Row>
|
||||
|
||||
{isLoading && <div>Processing...</div>}
|
||||
</Col>
|
||||
)
|
||||
}
|
88
web/components/contract/liquidity-button.tsx
Normal file
88
web/components/contract/liquidity-button.tsx
Normal file
|
@ -0,0 +1,88 @@
|
|||
import { useState } from 'react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import { Button } from 'web/components/button'
|
||||
import { formatMoney, shortFormatNumber } from 'common/util/format'
|
||||
import { Col } from 'web/components/layout/col'
|
||||
import { Tooltip } from '../tooltip'
|
||||
import { CPMMContract } from 'common/contract'
|
||||
import { User } from 'common/user'
|
||||
import { useLiquidity } from 'web/hooks/use-liquidity'
|
||||
import { LiquidityModal } from './liquidity-modal'
|
||||
|
||||
export function LiquidityButton(props: {
|
||||
contract: CPMMContract
|
||||
user: User | undefined | null
|
||||
}) {
|
||||
const { contract, user } = props
|
||||
const { totalLiquidity: total } = contract
|
||||
|
||||
const lp = useLiquidity(contract.id)
|
||||
const userActive = lp?.find((l) => l.userId === user?.id) !== undefined
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
text={`${formatMoney(total)} in liquidity subsidies`}
|
||||
placement="bottom"
|
||||
noTap
|
||||
noFade
|
||||
>
|
||||
<LiquidityIconButton
|
||||
total={total}
|
||||
userActive={userActive}
|
||||
onClick={() => setOpen(true)}
|
||||
/>
|
||||
<LiquidityModal contract={contract} isOpen={open} setOpen={setOpen} />
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
function LiquidityIconButton(props: {
|
||||
total: number
|
||||
onClick: () => void
|
||||
userActive: boolean
|
||||
isCompact?: boolean
|
||||
disabled?: boolean
|
||||
}) {
|
||||
const { total, userActive, isCompact, onClick, disabled } = props
|
||||
|
||||
return (
|
||||
<Button
|
||||
size={'sm'}
|
||||
className={clsx(
|
||||
'max-w-xs self-center pt-1',
|
||||
isCompact && 'px-0 py-0',
|
||||
disabled && 'hover:bg-inherit'
|
||||
)}
|
||||
color={'gray-white'}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Col className={'relative items-center sm:flex-row'}>
|
||||
<span
|
||||
className={clsx(
|
||||
'text-xl sm:text-2xl',
|
||||
total > 0 ? 'mr-2' : '',
|
||||
userActive ? '' : 'grayscale'
|
||||
)}
|
||||
>
|
||||
💧
|
||||
</span>
|
||||
{total > 0 && (
|
||||
<div
|
||||
className={clsx(
|
||||
'bg-greyscale-5 absolute ml-3.5 mt-2 h-4 w-4 rounded-full align-middle text-white sm:mt-3 sm:h-5 sm:w-5 sm:px-1',
|
||||
total > 99
|
||||
? 'text-[0.4rem] sm:text-[0.5rem]'
|
||||
: 'sm:text-2xs text-[0.5rem]'
|
||||
)}
|
||||
>
|
||||
{shortFormatNumber(total)}
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Button>
|
||||
)
|
||||
}
|
107
web/components/contract/liquidity-modal.tsx
Normal file
107
web/components/contract/liquidity-modal.tsx
Normal file
|
@ -0,0 +1,107 @@
|
|||
import { CPMMContract } from 'common/contract'
|
||||
import { formatMoney } from 'common/util/format'
|
||||
import { useState } from 'react'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import { addSubsidy } from 'web/lib/firebase/api'
|
||||
import { track } from 'web/lib/service/analytics'
|
||||
import { AmountInput } from '../amount-input'
|
||||
import { Button } from '../button'
|
||||
import { InfoTooltip } from '../info-tooltip'
|
||||
import { Col } from '../layout/col'
|
||||
import { Modal } from '../layout/modal'
|
||||
import { Row } from '../layout/row'
|
||||
import { Title } from '../title'
|
||||
|
||||
export function LiquidityModal(props: {
|
||||
contract: CPMMContract
|
||||
isOpen: boolean
|
||||
setOpen: (open: boolean) => void
|
||||
}) {
|
||||
const { contract, isOpen, setOpen } = props
|
||||
const { totalLiquidity } = contract
|
||||
|
||||
return (
|
||||
<Modal open={isOpen} setOpen={setOpen} size="md">
|
||||
<Col className="gap-2.5 rounded bg-white p-4 pb-8 sm:gap-4">
|
||||
<Title className="!mt-0 !mb-2" text="💧 Add liquidity" />
|
||||
|
||||
<div>Total liquidity subsidies: {formatMoney(totalLiquidity)}</div>
|
||||
<AddLiquidityPanel contract={contract as CPMMContract} />
|
||||
</Col>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
function AddLiquidityPanel(props: { contract: CPMMContract }) {
|
||||
const { contract } = props
|
||||
const { id: contractId, slug } = contract
|
||||
|
||||
const user = useUser()
|
||||
|
||||
const [amount, setAmount] = useState<number | undefined>(undefined)
|
||||
const [error, setError] = useState<string | undefined>(undefined)
|
||||
const [isSuccess, setIsSuccess] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const onAmountChange = (amount: number | undefined) => {
|
||||
setIsSuccess(false)
|
||||
setAmount(amount)
|
||||
|
||||
// Check for errors.
|
||||
if (amount !== undefined) {
|
||||
if (user && user.balance < amount) {
|
||||
setError('Insufficient balance')
|
||||
} else if (amount < 1) {
|
||||
setError('Minimum amount: ' + formatMoney(1))
|
||||
} else {
|
||||
setError(undefined)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
if (!amount) return
|
||||
|
||||
setIsLoading(true)
|
||||
setIsSuccess(false)
|
||||
|
||||
addSubsidy({ amount, contractId })
|
||||
.then((_) => {
|
||||
setIsSuccess(true)
|
||||
setError(undefined)
|
||||
setIsLoading(false)
|
||||
})
|
||||
.catch((_) => setError('Server error'))
|
||||
|
||||
track('add liquidity', { amount, contractId, slug })
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-4 text-gray-500">
|
||||
Contribute your M$ to make this market more accurate by subsidizing
|
||||
trader participation. <InfoTooltip text="Liquidity is how much money traders can make if they're right. The more traders can earn, the greater the incentive to find out what the correct probability is." />
|
||||
</div>
|
||||
|
||||
<Row>
|
||||
<AmountInput
|
||||
amount={amount}
|
||||
onChange={onAmountChange}
|
||||
label="M$"
|
||||
error={error}
|
||||
disabled={isLoading}
|
||||
inputClassName="w-16 mr-4"
|
||||
/>
|
||||
<Button size="md" color="blue" onClick={submit} disabled={isLoading}>
|
||||
Add
|
||||
</Button>
|
||||
</Row>
|
||||
|
||||
{isSuccess && amount && (
|
||||
<div>Success! Added {formatMoney(amount)} in liquidity.</div>
|
||||
)}
|
||||
|
||||
{isLoading && <div>Processing...</div>}
|
||||
</>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user