add missing parts to market swap trade form

This commit is contained in:
saml33 2023-07-25 12:35:27 +10:00
parent 01f03022bf
commit a151aa5190
4 changed files with 318 additions and 114 deletions

View File

@ -4,14 +4,18 @@ import { useEffect, useMemo, useState } from 'react'
import { floorToDecimal } from 'utils/numbers'
import { useTokenMax } from './useTokenMax'
const DEFAULT_VALUES = ['10', '25', '50', '75', '100']
const PercentageSelectButtons = ({
amountIn,
setAmountIn,
useMargin,
values,
}: {
amountIn: string
setAmountIn: (x: string) => void
useMargin: boolean
values?: string[]
}) => {
const [sizePercentage, setSizePercentage] = useState('')
const {
@ -49,7 +53,7 @@ const PercentageSelectButtons = ({
<ButtonGroup
activeValue={sizePercentage}
onChange={(p) => handleSizePercentage(p)}
values={['10', '25', '50', '75', '100']}
values={values || DEFAULT_VALUES}
unit="%"
/>
</div>

View File

@ -509,9 +509,7 @@ const AdvancedTradeForm = () => {
</div>
{tradeForm.tradeType === 'Market' &&
selectedMarket instanceof Serum3Market ? (
<>
<SpotMarketOrderSwapForm />
</>
) : (
<>
<form onSubmit={(e) => handleSubmit(e)}>

View File

@ -0,0 +1,61 @@
import MaxAmountButton from '@components/shared/MaxAmountButton'
import { useTokenMax } from '@components/swap/useTokenMax'
import mangoStore from '@store/mangoStore'
import useSelectedMarket from 'hooks/useSelectedMarket'
import { useTranslation } from 'next-i18next'
import { useCallback, useMemo } from 'react'
import { floorToDecimal, getDecimalCount } from 'utils/numbers'
const MaxMarketSwapAmount = ({
setAmountIn,
useMargin,
}: {
setAmountIn: (x: string) => void
useMargin: boolean
}) => {
const { t } = useTranslation('common')
const { price: oraclePrice, serumOrPerpMarket } = useSelectedMarket()
const mangoAccountLoading = mangoStore((s) => s.mangoAccount.initialLoad)
const {
amount: tokenMax,
amountWithBorrow,
decimals,
} = useTokenMax(useMargin)
const tickDecimals = useMemo(() => {
if (!serumOrPerpMarket) return decimals
return getDecimalCount(serumOrPerpMarket.tickSize)
}, [decimals, serumOrPerpMarket])
const maxAmount = useMemo(() => {
const balanceMax = useMargin ? amountWithBorrow : tokenMax
const { side } = mangoStore.getState().tradeForm
const sideMax =
side === 'buy' ? balanceMax.toNumber() / oraclePrice : balanceMax
return floorToDecimal(sideMax, tickDecimals).toFixed()
}, [amountWithBorrow, oraclePrice, tickDecimals, tokenMax, useMargin])
const setMax = useCallback(() => {
const { side } = mangoStore.getState().tradeForm
const max = useMargin ? amountWithBorrow : tokenMax
const maxDecimals = side === 'buy' ? tickDecimals : decimals
setAmountIn(floorToDecimal(max, maxDecimals).toFixed())
}, [decimals, setAmountIn, tickDecimals, useMargin])
if (mangoAccountLoading) return null
return (
<div className="mb-2 mt-3 flex items-center justify-between w-full">
<p className="text-xs text-th-fgd-3">{t('trade:size')}</p>
<MaxAmountButton
className="text-xs"
decimals={decimals}
label={t('max')}
onClick={() => setMax()}
value={maxAmount}
/>
</div>
)
}
export default MaxMarketSwapAmount

View File

@ -4,6 +4,7 @@ import NumberFormat, {
SourceInfo,
} from 'react-number-format'
import {
DEFAULT_CHECKBOX_SETTINGS,
INPUT_PREFIX_CLASSNAMES,
INPUT_SUFFIX_CLASSNAMES,
} from './AdvancedTradeForm'
@ -29,12 +30,16 @@ import * as sentry from '@sentry/nextjs'
import { isMangoError } from 'types'
import SwapSlider from '@components/swap/SwapSlider'
import PercentageSelectButtons from '@components/swap/PercentageSelectButtons'
import { SIZE_INPUT_UI_KEY } from 'utils/constants'
import { SIZE_INPUT_UI_KEY, TRADE_CHECKBOXES_KEY } from 'utils/constants'
import useLocalStorageState from 'hooks/useLocalStorageState'
import MaxSwapAmount from '@components/swap/MaxSwapAmount'
import useUnownedAccount from 'hooks/useUnownedAccount'
import HealthImpact from '@components/shared/HealthImpact'
import Tooltip from '@components/shared/Tooltip'
import Checkbox from '@components/forms/Checkbox'
import MaxMarketSwapAmount from './MaxMarketSwapAmount'
import { floorToDecimal, formatNumericValue } from 'utils/numbers'
import { formatTokenSymbol } from 'utils/tokens'
import FormatNumericValue from '@components/shared/FormatNumericValue'
const set = mangoStore.getState().set
const slippage = 100
@ -55,6 +60,8 @@ export default function SpotMarketOrderSwapForm() {
const { ipAllowed, ipCountry } = useIpAddress()
const { connected, publicKey, connect } = useWallet()
const [swapFormSizeUi] = useLocalStorageState(SIZE_INPUT_UI_KEY, 'slider')
const [savedCheckboxSettings, setSavedCheckboxSettings] =
useLocalStorageState(TRADE_CHECKBOXES_KEY, DEFAULT_CHECKBOX_SETTINGS)
const {
selectedMarket,
price: oraclePrice,
@ -274,28 +281,51 @@ export default function SpotMarketOrderSwapForm() {
: Math.trunc(simulatedHealthRatio)
}, [inputBank, outputBank, baseSize, quoteSize, side])
const [balance, borrowAmount] = useMemo(() => {
if (!inputBank) return [0, 0]
const mangoAccount = mangoStore.getState().mangoAccount.current
if (!mangoAccount) return [0, 0]
let borrowAmount
const balance = mangoAccount.getTokenDepositsUi(inputBank)
if (side === 'buy') {
const remainingBalance = balance - parseFloat(quoteSize)
borrowAmount = remainingBalance < 0 ? Math.abs(remainingBalance) : 0
} else {
const remainingBalance = balance - parseFloat(baseSize)
borrowAmount = remainingBalance < 0 ? Math.abs(remainingBalance) : 0
}
return [balance, borrowAmount]
}, [baseSize, inputBank, quoteSize])
const orderValue = useMemo(() => {
if (!outputBank || !oraclePrice || !baseSize || isNaN(parseFloat(baseSize)))
return 0
const basePriceDecimal = new Decimal(oraclePrice)
const quotePriceDecimal = new Decimal(outputBank.uiPrice)
const sizeDecimal = new Decimal(baseSize)
return floorToDecimal(
basePriceDecimal.mul(quotePriceDecimal).mul(sizeDecimal),
2,
)
}, [baseSize, outputBank, oraclePrice])
const disabled =
(connected && (!baseSize || !price)) ||
!serumOrPerpMarket ||
parseFloat(baseSize) < serumOrPerpMarket.minOrderSize ||
isLoading
const useMargin = true
return (
<>
<form onSubmit={(e) => handleSubmit(e)}>
<div className="mt-3 px-3 md:px-4">
<div className="mb-2 flex items-end justify-end">
{!isUnownedAccount ? (
<>
<MaxSwapAmount
useMargin={useMargin}
<MaxMarketSwapAmount
useMargin={savedCheckboxSettings.margin}
setAmountIn={setAmountFromSlider}
/>
</>
) : null}
</div>
<div className="flex flex-col">
<div className="relative">
<NumberFormat
@ -363,11 +393,9 @@ export default function SpotMarketOrderSwapForm() {
<div className={INPUT_SUFFIX_CLASSNAMES}>{quoteSymbol}</div>
</div>
</div>
</div>
<div className="mt-6 mb-4 flex px-3 md:px-4">
{swapFormSizeUi === 'slider' ? (
<SwapSlider
useMargin={useMargin}
useMargin={savedCheckboxSettings.margin}
amount={
side === 'buy'
? stringToNumberOrZero(quoteSize)
@ -380,11 +408,31 @@ export default function SpotMarketOrderSwapForm() {
<PercentageSelectButtons
amountIn={side === 'buy' ? quoteSize : baseSize}
setAmountIn={setAmountFromSlider}
useMargin={useMargin}
useMargin={savedCheckboxSettings.margin}
values={['25', '50', '75', '100']}
/>
)}
<div className="mt-4">
<Tooltip
className="hidden md:block"
delay={100}
placement="left"
content={t('trade:tooltip-enable-margin')}
>
<Checkbox
checked={savedCheckboxSettings.margin}
onChange={(e) =>
setSavedCheckboxSettings({
...savedCheckboxSettings,
margin: e.target.checked,
})
}
>
{t('trade:margin')}
</Checkbox>
</Tooltip>
</div>
<div className="mt-6 mb-4 flex px-3 md:px-4">
<div className="mt-6 mb-4 flex">
{ipAllowed ? (
<Button
className={`flex w-full items-center justify-center ${
@ -433,21 +481,30 @@ export default function SpotMarketOrderSwapForm() {
</Button>
)}
</div>
<div className="space-y-2 px-3 md:px-4">
<div className="">
<HealthImpact maintProjectedHealth={maintProjectedHealth} small />
<div className="space-y-2">
<div className="flex justify-between text-xs">
<p>{t('trade:order-value')}</p>
<p className="font-mono text-th-fgd-2">
{orderValue ? (
<FormatNumericValue value={orderValue} isUsd />
) : (
''
)}
</p>
</div>
<HealthImpact maintProjectedHealth={maintProjectedHealth} small />
<div className="flex justify-between text-xs">
<Tooltip
content={
<>
<p>
The price impact is the difference observed between the
total value of the entry tokens swapped and the destination
tokens obtained.
total value of the entry tokens swapped and the
destination tokens obtained.
</p>
<p className="mt-1">
The bigger the trade is, the bigger the price impact can be.
The bigger the trade is, the bigger the price impact can
be.
</p>
</>
}
@ -462,10 +519,92 @@ export default function SpotMarketOrderSwapForm() {
: '-'}
</p>
</div>
{borrowAmount && inputBank ? (
<>
<div className="flex justify-between text-xs">
<Tooltip
content={
balance
? t('trade:tooltip-borrow-balance', {
balance: formatNumericValue(balance),
borrowAmount: formatNumericValue(borrowAmount),
token: formatTokenSymbol(inputBank.name),
rate: formatNumericValue(
inputBank.getBorrowRateUi(),
2,
),
})
: t('trade:tooltip-borrow-no-balance', {
borrowAmount: formatNumericValue(borrowAmount),
token: formatTokenSymbol(inputBank.name),
rate: formatNumericValue(
inputBank.getBorrowRateUi(),
2,
),
})
}
delay={100}
>
<p className="tooltip-underline">{t('borrow-amount')}</p>
</Tooltip>
<p className="text-right font-mono text-th-fgd-2">
<FormatNumericValue
value={borrowAmount}
decimals={inputBank.mintDecimals}
/>{' '}
<span className="font-body text-th-fgd-4">
{formatTokenSymbol(inputBank.name)}
</span>
</p>
</div>
<div className="flex justify-between text-xs">
<Tooltip
content={t('loan-origination-fee-tooltip', {
fee: `${(
inputBank.loanOriginationFeeRate.toNumber() * 100
).toFixed(3)}%`,
})}
delay={100}
>
<p className="tooltip-underline">
{t('loan-origination-fee')}
</p>
</Tooltip>
<p className="text-right font-mono text-th-fgd-2">
<FormatNumericValue
value={
borrowAmount *
inputBank.loanOriginationFeeRate.toNumber()
}
decimals={inputBank.mintDecimals}
/>{' '}
<span className="font-body text-th-fgd-4">
{formatTokenSymbol(inputBank.name)}
</span>
</p>
</div>
</>
) : null}
<div className="flex items-center justify-between text-xs">
<p className="pr-2 text-th-fgd-3">{t('common:route')}</p>
<div className="flex items-center overflow-hidden text-th-fgd-3">
<div className="truncate whitespace-nowrap">
<div className="flex items-center overflow-hidden text-th-fgd-2">
<Tooltip
content={selectedRoute?.marketInfos.map((info, index) => {
let includeSeparator = false
if (
selectedRoute?.marketInfos.length > 1 &&
index !== selectedRoute?.marketInfos.length - 1
) {
includeSeparator = true
}
return (
<span key={index}>{`${info?.label} ${
includeSeparator ? 'x ' : ''
}`}</span>
)
})}
>
<div className="tooltip-underline truncate whitespace-nowrap max-w-[140px]">
{selectedRoute?.marketInfos.map((info, index) => {
let includeSeparator = false
if (
@ -481,6 +620,8 @@ export default function SpotMarketOrderSwapForm() {
)
})}
</div>
</Tooltip>
</div>
</div>
</div>
</div>