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] ?? ''}`
|
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) {
|
export function toCamelCase(words: string) {
|
||||||
const camelCase = words
|
const camelCase = words
|
||||||
.split(' ')
|
.split(' ')
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { capitalize } from 'lodash'
|
||||||
import { Contract } from 'common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { formatMoney, formatPercent } from 'common/util/format'
|
import { formatMoney, formatPercent } from 'common/util/format'
|
||||||
import { contractPool, updateContract } from 'web/lib/firebase/contracts'
|
import { contractPool, updateContract } from 'web/lib/firebase/contracts'
|
||||||
import { LiquidityBountyPanel } from 'web/components/contract/liquidity-bounty-panel'
|
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { Modal } from '../layout/modal'
|
import { Modal } from '../layout/modal'
|
||||||
import { Title } from '../title'
|
import { Title } from '../title'
|
||||||
|
@ -228,7 +227,6 @@ export function ContractInfoDialog(props: {
|
||||||
<Row className="flex-wrap">
|
<Row className="flex-wrap">
|
||||||
<DuplicateContractButton contract={contract} />
|
<DuplicateContractButton contract={contract} />
|
||||||
</Row>
|
</Row>
|
||||||
{!contract.resolution && <LiquidityBountyPanel contract={contract} />}
|
|
||||||
</Col>
|
</Col>
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { FollowMarketButton } from 'web/components/follow-market-button'
|
||||||
import { LikeMarketButton } from 'web/components/contract/like-market-button'
|
import { LikeMarketButton } from 'web/components/contract/like-market-button'
|
||||||
import { ContractInfoDialog } from 'web/components/contract/contract-info-dialog'
|
import { ContractInfoDialog } from 'web/components/contract/contract-info-dialog'
|
||||||
import { Tooltip } from '../tooltip'
|
import { Tooltip } from '../tooltip'
|
||||||
|
import { LiquidityButton } from './liquidity-button'
|
||||||
|
|
||||||
export function ExtraContractActionsRow(props: { contract: Contract }) {
|
export function ExtraContractActionsRow(props: { contract: Contract }) {
|
||||||
const { contract } = props
|
const { contract } = props
|
||||||
|
@ -18,6 +19,9 @@ export function ExtraContractActionsRow(props: { contract: Contract }) {
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Row>
|
||||||
<FollowMarketButton contract={contract} user={user} />
|
<FollowMarketButton contract={contract} user={user} />
|
||||||
|
{contract.mechanism === 'cpmm-1' && (
|
||||||
|
<LiquidityButton contract={contract} user={user} />
|
||||||
|
)}
|
||||||
<LikeMarketButton contract={contract} user={user} />
|
<LikeMarketButton contract={contract} user={user} />
|
||||||
<Tooltip text="Share" placement="bottom" noTap noFade>
|
<Tooltip text="Share" placement="bottom" noTap noFade>
|
||||||
<Button
|
<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