trade form ux improvements

This commit is contained in:
saml33 2022-12-22 15:19:00 +11:00
parent 2ef6070991
commit 201a8ad62a
16 changed files with 394 additions and 263 deletions

View File

@ -370,7 +370,7 @@ const AccountPage = () => {
<div className="col-span-4 flex border-t border-th-bkg-3 py-3 pl-6 lg:col-span-1 lg:border-l lg:border-t-0">
<div id="account-step-five">
<Tooltip
content="The value of collateral you have to open new trades or borrows. When your free collateral reaches $0 you won't be able to make withdrawals."
content="The amount of capital you have to use for trades and loans. When your free collateral reaches $0 you won't be able to trade, borrow or withdraw."
maxWidth="20rem"
placement="bottom"
delay={250}

View File

@ -4,7 +4,7 @@ interface ButtonGroupProps {
activeValue: string
className?: string
disabled?: boolean
onChange: (x: string) => void
onChange: (x: any) => void
unit?: string
values: Array<any>
names?: Array<string>

View File

@ -35,13 +35,25 @@ const TabButtons: FunctionComponent<TabButtonsProps> = ({
: ''
} ${
label === activeValue
? 'bg-th-bkg-2 text-th-active'
? label === 'buy'
? 'bg-th-up-dark font-display text-th-fgd-1'
: label === 'sell'
? 'bg-th-down-dark font-display text-th-fgd-1'
: 'bg-th-bkg-2 text-th-active'
: 'hover:cursor-pointer hover:text-th-fgd-2'
}`}
key={`${label}${i}`}
onClick={() => onChange(label)}
>
<span className="font-medium leading-tight">{t(label)}</span>
<span
className={`${
label === 'buy' || label === 'sell'
? 'font-display'
: 'font-medium'
} leading-tight`}
>
{t(label)}
</span>
{count ? (
<div
className={`ml-1.5 rounded ${

View File

@ -13,7 +13,13 @@ const TabUnderline = ({
}) => {
const { t } = useTranslation('common')
return (
<div className={`relative mb-3 border-b border-th-bkg-3 pb-1 md:-mt-2.5`}>
<div
className={`relative mb-3 border-b border-th-bkg-3 ${
values.includes('buy') || values.includes('sell')
? 'pb-1 font-display md:pb-2.5'
: 'pb-1 font-bold'
} md:-mt-2.5`}
>
<div
className={`default-transition absolute bottom-[-1px] left-0 h-0.5 ${
activeValue === 'buy'
@ -35,7 +41,7 @@ const TabUnderline = ({
<button
onClick={() => onChange(value)}
className={`default-transition relative flex h-10 w-1/2
cursor-pointer items-center justify-center whitespace-nowrap rounded py-1 font-bold md:h-auto md:rounded-none md:hover:opacity-100 ${
cursor-pointer items-center justify-center whitespace-nowrap rounded py-1 md:h-auto md:rounded-none md:hover:opacity-100 ${
small ? 'text-sm' : 'text-sm lg:text-base'
}
${

View File

@ -1,5 +1,4 @@
import {
HealthType,
PerpMarket,
PerpOrderSide,
PerpOrderType,
@ -10,7 +9,6 @@ import {
} from '@blockworks-foundation/mango-v4'
import Checkbox from '@components/forms/Checkbox'
import Button from '@components/shared/Button'
import TabButtons from '@components/shared/TabButtons'
import Tooltip from '@components/shared/Tooltip'
import mangoStore from '@store/mangoStore'
import Decimal from 'decimal.js'
@ -28,7 +26,6 @@ import { QuestionMarkCircleIcon } from '@heroicons/react/20/solid'
import Loading from '@components/shared/Loading'
import TabUnderline from '@components/shared/TabUnderline'
import PerpSlider from './PerpSlider'
import HealthImpact from '@components/shared/HealthImpact'
import useLocalStorageState from 'hooks/useLocalStorageState'
import { SIZE_INPUT_UI_KEY } from 'utils/constants'
import SpotButtonGroup from './SpotButtonGroup'
@ -36,20 +33,20 @@ import PerpButtonGroup from './PerpButtonGroup'
import SolBalanceWarnings from '@components/shared/SolBalanceWarnings'
import useJupiterMints from 'hooks/useJupiterMints'
import useSelectedMarket from 'hooks/useSelectedMarket'
import Slippage from './Slippage'
import { formatFixedDecimals, getDecimalCount } from 'utils/numbers'
import { getDecimalCount, trimDecimals } from 'utils/numbers'
import LogoWithFallback from '@components/shared/LogoWithFallback'
import useIpAddress from 'hooks/useIpAddress'
const TABS: [string, number][] = [
['Limit', 0],
['Market', 0],
]
import ButtonGroup from '@components/forms/ButtonGroup'
import TradeSummary from './TradeSummary'
import useMangoAccount from 'hooks/useMangoAccount'
import MaxAmountButton from '@components/shared/MaxAmountButton'
import { FadeInFadeOut } from '@components/shared/Transitions'
const set = mangoStore.getState().set
const AdvancedTradeForm = () => {
const { t } = useTranslation(['common', 'trade'])
const { mangoAccount } = useMangoAccount()
const tradeForm = mangoStore((s) => s.tradeForm)
const { mangoTokens } = useJupiterMints()
const { selectedMarket, price: oraclePrice } = useSelectedMarket()
@ -236,6 +233,42 @@ const AdvancedTradeForm = () => {
}
}, [oraclePrice, selectedMarket, tickDecimals, tradeForm])
const leverageMax = useMemo(() => {
const group = mangoStore.getState().group
if (!mangoAccount || !group || !selectedMarket) return 100
try {
if (selectedMarket instanceof Serum3Market) {
if (tradeForm.side === 'buy') {
return mangoAccount.getMaxQuoteForSerum3BidUi(
group,
selectedMarket.serumMarketExternal
)
} else {
return mangoAccount.getMaxBaseForSerum3AskUi(
group,
selectedMarket.serumMarketExternal
)
}
} else {
if (tradeForm.side === 'buy') {
return mangoAccount.getMaxQuoteForPerpBidUi(
group,
selectedMarket.perpMarketIndex
)
} else {
return mangoAccount.getMaxBaseForPerpAskUi(
group,
selectedMarket.perpMarketIndex
)
}
}
} catch (e) {
console.error('Error calculating max leverage: spot btn group: ', e)
return 0
}
}, [mangoAccount, tradeForm.side, selectedMarket])
const handlePlaceOrder = useCallback(async () => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group
@ -324,79 +357,99 @@ const AdvancedTradeForm = () => {
}
}, [])
const maintProjectedHealth = useMemo(() => {
const minOrderDecimals = useMemo(() => {
const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current
if (!mangoAccount || !group || !Number.isFinite(Number(tradeForm.baseSize)))
return 100
let simulatedHealthRatio = 0
try {
if (selectedMarket instanceof Serum3Market) {
simulatedHealthRatio =
tradeForm.side === 'sell'
? mangoAccount.simHealthRatioWithSerum3AskUiChanges(
group,
Number(tradeForm.baseSize),
selectedMarket.serumMarketExternal,
HealthType.maint
)
: mangoAccount.simHealthRatioWithSerum3BidUiChanges(
group,
Number(tradeForm.quoteSize),
selectedMarket.serumMarketExternal,
HealthType.maint
)
} else if (selectedMarket instanceof PerpMarket) {
simulatedHealthRatio =
tradeForm.side === 'sell'
? mangoAccount.simHealthRatioWithPerpAskUiChanges(
group,
selectedMarket.perpMarketIndex,
parseFloat(tradeForm.baseSize)
)
: mangoAccount.simHealthRatioWithPerpBidUiChanges(
group,
selectedMarket.perpMarketIndex,
parseFloat(tradeForm.baseSize)
)
}
} catch (e) {
console.warn('Error calculating projected health: ', e)
if (!group || !selectedMarket) return 1
let minOrderDecimals = 1
if (selectedMarket instanceof Serum3Market) {
const market = group.getSerum3ExternalMarket(
selectedMarket.serumMarketExternal
)
minOrderDecimals = getDecimalCount(market.minOrderSize)
} else {
minOrderDecimals = getDecimalCount(selectedMarket.minOrderSize)
}
return minOrderDecimals
}, [selectedMarket])
return simulatedHealthRatio > 100
? 100
: simulatedHealthRatio < 0
? 0
: Math.trunc(simulatedHealthRatio)
}, [selectedMarket, tradeForm.baseSize, tradeForm.side])
const handleMax = useCallback(() => {
const set = mangoStore.getState().set
set((state) => {
if (tradeForm.side === 'buy') {
state.tradeForm.quoteSize = trimDecimals(
leverageMax,
tickDecimals
).toFixed(tickDecimals)
if (tradeForm.tradeType === 'Market' || !tradeForm.price) {
state.tradeForm.baseSize = trimDecimals(
leverageMax / oraclePrice,
minOrderDecimals
).toFixed(minOrderDecimals)
} else {
state.tradeForm.baseSize = trimDecimals(
leverageMax / parseFloat(tradeForm.price),
minOrderDecimals
).toFixed(minOrderDecimals)
}
} else {
state.tradeForm.baseSize = trimDecimals(
leverageMax,
tickDecimals
).toFixed(tickDecimals)
if (tradeForm.tradeType === 'Market' || !tradeForm.price) {
state.tradeForm.quoteSize = trimDecimals(
leverageMax * oraclePrice,
minOrderDecimals
).toFixed(minOrderDecimals)
} else {
state.tradeForm.quoteSize = trimDecimals(
leverageMax * parseFloat(tradeForm.price),
minOrderDecimals
).toFixed(minOrderDecimals)
}
}
})
}, [leverageMax, tradeForm])
const maxAmount = useMemo(() => {
if (!tradeForm.price) return '0'
if (tradeForm.side === 'buy') {
return trimDecimals(
leverageMax / parseFloat(tradeForm.price),
tickDecimals
).toFixed(tickDecimals)
} else {
return trimDecimals(leverageMax, minOrderDecimals).toFixed(
minOrderDecimals
)
}
}, [leverageMax, minOrderDecimals, tickDecimals, tradeForm])
return (
<div>
<div className="border-b border-th-bkg-3 md:border-t lg:border-t-0">
<TabButtons
activeValue={tradeForm.tradeType}
onChange={(tab: 'Limit' | 'Market') => setTradeType(tab)}
values={TABS}
fillWidth
<div className="mt-1.5 px-2 md:mt-0 md:border-t md:border-th-bkg-3 md:px-4 md:pt-5 lg:mt-5 lg:border-t-0 lg:pt-0">
<TabUnderline
activeValue={tradeForm.side}
values={['buy', 'sell']}
onChange={(v) => handleSetSide(v)}
small
/>
</div>
<div className="px-3 md:px-4">
<SolBalanceWarnings />
</div>
<div className="mt-1 px-2 md:mt-6 md:px-4">
<TabUnderline
activeValue={tradeForm.side}
values={['buy', 'sell']}
onChange={(v) => handleSetSide(v)}
<div className="mt-1 px-2 md:mt-3 md:px-4">
<p className="mb-2 text-xs">{t('trade:order-type')}</p>
<ButtonGroup
activeValue={tradeForm.tradeType}
onChange={(tab: 'Limit' | 'Market') => setTradeType(tab)}
values={['Limit', 'Market']}
/>
</div>
<div className="mt-4 px-3 md:px-4">
<div className="mt-3 px-3 md:px-4">
{tradeForm.tradeType === 'Limit' ? (
<>
<div className="mb-2 mt-4 flex items-center justify-between">
<div className="mb-2 mt-3 flex items-center justify-between">
<p className="text-xs text-th-fgd-3">{t('trade:limit-price')}</p>
</div>
<div className="default-transition flex items-center rounded-md border border-th-input-border bg-th-input-bkg p-2 text-sm font-bold text-th-fgd-1 md:hover:border-th-input-border-hover lg:text-base">
@ -429,7 +482,15 @@ const AdvancedTradeForm = () => {
</>
) : null}
<div className="mb-2 mt-3 flex items-center justify-between">
<p className="text-xs text-th-fgd-3">{t('trade:amount')}</p>
<p className="text-xs text-th-fgd-3">{t('trade:size')}</p>
<FadeInFadeOut show={!!tradeForm.price}>
<MaxAmountButton
className="text-xs"
label={t('max')}
onClick={handleMax}
value={maxAmount}
/>
</FadeInFadeOut>
</div>
<div className="flex flex-col">
<div className="default-transition flex items-center rounded-md rounded-b-none border border-th-input-border bg-th-input-bkg p-2 text-sm font-bold text-th-fgd-1 md:hover:z-10 md:hover:border-th-input-border-hover lg:text-base">
@ -494,20 +555,36 @@ const AdvancedTradeForm = () => {
<div className="mt-2 flex">
{selectedMarket instanceof Serum3Market ? (
tradeFormSizeUi === 'slider' ? (
<SpotSlider />
<SpotSlider
max={leverageMax}
minOrderDecimals={minOrderDecimals}
tickDecimals={tickDecimals}
/>
) : (
<SpotButtonGroup />
<SpotButtonGroup
max={leverageMax}
minOrderDecimals={minOrderDecimals}
tickDecimals={tickDecimals}
/>
)
) : tradeFormSizeUi === 'slider' ? (
<PerpSlider />
<PerpSlider
max={leverageMax}
minOrderDecimals={minOrderDecimals}
tickDecimals={tickDecimals}
/>
) : (
<PerpButtonGroup />
<PerpButtonGroup
max={leverageMax}
minOrderDecimals={minOrderDecimals}
tickDecimals={tickDecimals}
/>
)}
</div>
<div className="flex flex-wrap px-5">
<div className="flex flex-wrap px-5 md:flex-nowrap">
{tradeForm.tradeType === 'Limit' ? (
<div className="flex">
<div className="mr-4 mt-4" id="trade-step-six">
<div className="mr-3 mt-4" id="trade-step-six">
<Tooltip
className="hidden md:block"
delay={250}
@ -522,7 +599,7 @@ const AdvancedTradeForm = () => {
</Checkbox>
</Tooltip>
</div>
<div className="mr-4 mt-4" id="trade-step-seven">
<div className="mr-3 mt-4" id="trade-step-seven">
<Tooltip
className="hidden md:block"
delay={250}
@ -558,7 +635,7 @@ const AdvancedTradeForm = () => {
</div>
) : null}
</div>
<div className="mt-6 flex px-3 md:px-4">
<div className="mt-6 mb-4 flex px-3 md:px-4">
{ipAllowed ? (
<Button
onClick={handlePlaceOrder}
@ -595,21 +672,7 @@ const AdvancedTradeForm = () => {
</div>
)}
</div>
<div className="mt-4 space-y-2 px-3 md:px-4 lg:mt-6">
{tradeForm.price && tradeForm.baseSize ? (
<div className="flex justify-between text-xs">
<p>{t('trade:order-value')}</p>
<p className="text-th-fgd-2">
{formatFixedDecimals(
parseFloat(tradeForm.price) * parseFloat(tradeForm.baseSize),
true
)}
</p>
</div>
) : null}
<HealthImpact maintProjectedHealth={maintProjectedHealth} small />
<Slippage />
</div>
<TradeSummary mangoAccount={mangoAccount} />
</div>
)
}

View File

@ -1,72 +1,54 @@
import { PerpMarket } from '@blockworks-foundation/mango-v4'
import ButtonGroup from '@components/forms/ButtonGroup'
import mangoStore from '@store/mangoStore'
import useMangoAccount from 'hooks/useMangoAccount'
import useSelectedMarket from 'hooks/useSelectedMarket'
import { useCallback, useMemo, useState } from 'react'
// import { notify } from 'utils/notifications'
import { useCallback, useState } from 'react'
import { trimDecimals } from 'utils/numbers'
const PerpButtonGroup = () => {
const side = mangoStore((s) => s.tradeForm.side)
const { selectedMarket } = useSelectedMarket()
const { mangoAccount } = useMangoAccount()
const PerpButtonGroup = ({
max,
minOrderDecimals,
tickDecimals,
}: {
max: number
minOrderDecimals: number
tickDecimals: number
}) => {
const [sizePercentage, setSizePercentage] = useState('')
const tradeFormPrice = mangoStore((s) => s.tradeForm.price)
const leverageMax = useMemo(() => {
const group = mangoStore.getState().group
if (!mangoAccount || !group || !selectedMarket) return 100
if (!(selectedMarket instanceof PerpMarket)) return 100
try {
if (side === 'buy') {
return mangoAccount.getMaxQuoteForPerpBidUi(
group,
selectedMarket.perpMarketIndex
)
} else {
return mangoAccount.getMaxBaseForPerpAskUi(
group,
selectedMarket.perpMarketIndex
)
}
} catch (e) {
console.error('Error calculating max leverage perp btn grp: ', e)
// notify({
// type: 'error',
// title: 'Error calculating max leverage.',
// })
return 0
}
}, [side, selectedMarket, mangoAccount, tradeFormPrice])
const handleSizePercentage = useCallback(
(percentage: string) => {
const set = mangoStore.getState().set
setSizePercentage(percentage)
const size = leverageMax * (Number(percentage) / 100)
const size = max * (Number(percentage) / 100)
set((s) => {
if (s.tradeForm.side === 'buy') {
s.tradeForm.quoteSize = size.toString()
s.tradeForm.quoteSize = trimDecimals(size, tickDecimals).toFixed(
tickDecimals
)
if (Number(s.tradeForm.price)) {
s.tradeForm.baseSize = (size / Number(s.tradeForm.price)).toString()
s.tradeForm.baseSize = trimDecimals(
size / Number(s.tradeForm.price),
minOrderDecimals
).toFixed(minOrderDecimals)
} else {
s.tradeForm.baseSize = ''
}
} else if (s.tradeForm.side === 'sell') {
s.tradeForm.baseSize = size.toString()
s.tradeForm.baseSize = trimDecimals(size, minOrderDecimals).toFixed(
minOrderDecimals
)
if (Number(s.tradeForm.price)) {
s.tradeForm.quoteSize = (
size * Number(s.tradeForm.price)
).toString()
s.tradeForm.quoteSize = trimDecimals(
size * Number(s.tradeForm.price),
tickDecimals
).toFixed(tickDecimals)
}
}
})
},
[leverageMax]
[max, minOrderDecimals, tickDecimals]
)
return (

View File

@ -1,15 +1,20 @@
import { PerpMarket } from '@blockworks-foundation/mango-v4'
import LeverageSlider from '@components/shared/LeverageSlider'
import mangoStore from '@store/mangoStore'
import useMangoAccount from 'hooks/useMangoAccount'
import useSelectedMarket from 'hooks/useSelectedMarket'
import { useCallback, useMemo } from 'react'
// import { notify } from 'utils/notifications'
import { trimDecimals } from 'utils/numbers'
const PerpSlider = () => {
const side = mangoStore((s) => s.tradeForm.side)
const PerpSlider = ({
max,
minOrderDecimals,
tickDecimals,
}: {
max: number
minOrderDecimals: number
tickDecimals: number
}) => {
const { selectedMarket, price: marketPrice } = useSelectedMarket()
const { mangoAccount } = useMangoAccount()
const tradeForm = mangoStore((s) => s.tradeForm)
const step = useMemo(() => {
@ -19,33 +24,6 @@ const PerpSlider = () => {
return 0.01
}, [selectedMarket])
const leverageMax = useMemo(() => {
const group = mangoStore.getState().group
if (!mangoAccount || !group || !selectedMarket) return 100
if (!(selectedMarket instanceof PerpMarket)) return 100
try {
if (side === 'buy') {
return mangoAccount.getMaxQuoteForPerpBidUi(
group,
selectedMarket.perpMarketIndex
)
} else {
return mangoAccount.getMaxBaseForPerpAskUi(
group,
selectedMarket.perpMarketIndex
)
}
} catch (e) {
console.error('Error calculating max leverage for PerpSlider: ', e)
// notify({
// type: 'error',
// title: 'Error calculating max leverage.',
// })
return 0
}
}, [side, selectedMarket, mangoAccount])
const handleSlide = useCallback(
(val: string) => {
const set = mangoStore.getState().set
@ -59,19 +37,25 @@ const PerpSlider = () => {
if (s.tradeForm.side === 'buy') {
s.tradeForm.quoteSize = val
if (Number(price)) {
s.tradeForm.baseSize = (parseFloat(val) / price).toString()
s.tradeForm.baseSize = trimDecimals(
parseFloat(val) / price,
minOrderDecimals
).toFixed(minOrderDecimals)
} else {
s.tradeForm.baseSize = ''
}
} else if (s.tradeForm.side === 'sell') {
s.tradeForm.baseSize = val
if (Number(price)) {
s.tradeForm.quoteSize = (parseFloat(val) * price).toString()
s.tradeForm.quoteSize = trimDecimals(
parseFloat(val) * price,
tickDecimals
).toFixed(tickDecimals)
}
}
})
},
[marketPrice]
[marketPrice, minOrderDecimals, tickDecimals]
)
return (
@ -82,7 +66,7 @@ const PerpSlider = () => {
? parseFloat(tradeForm.quoteSize)
: parseFloat(tradeForm.baseSize)
}
leverageMax={leverageMax}
leverageMax={max}
onChange={handleSlide}
step={step}
/>

View File

@ -1,71 +1,59 @@
import { Serum3Market } from '@blockworks-foundation/mango-v4'
import ButtonGroup from '@components/forms/ButtonGroup'
import mangoStore from '@store/mangoStore'
import useMangoAccount from 'hooks/useMangoAccount'
import useSelectedMarket from 'hooks/useSelectedMarket'
import { useCallback, useMemo, useState } from 'react'
// import { notify } from 'utils/notifications'
import { useCallback, useState } from 'react'
import { trimDecimals } from 'utils/numbers'
const SpotButtonGroup = () => {
const SpotButtonGroup = ({
max,
minOrderDecimals,
tickDecimals,
}: {
max: number
minOrderDecimals: number
tickDecimals: number
}) => {
const side = mangoStore((s) => s.tradeForm.side)
const { selectedMarket } = useSelectedMarket()
const { mangoAccount } = useMangoAccount()
const [sizePercentage, setSizePercentage] = useState('')
const leverageMax = useMemo(() => {
const group = mangoStore.getState().group
if (!mangoAccount || !group || !selectedMarket) return 100
if (!(selectedMarket instanceof Serum3Market)) return 100
try {
if (side === 'buy') {
return mangoAccount.getMaxQuoteForSerum3BidUi(
group,
selectedMarket.serumMarketExternal
)
} else {
return mangoAccount.getMaxBaseForSerum3AskUi(
group,
selectedMarket.serumMarketExternal
)
}
} catch (e) {
console.error('Error calculating max leverage: spot btn group: ', e)
// notify({
// type: 'error',
// title: 'Error calculating max leverage.',
// })
return 0
}
}, [side, selectedMarket, mangoAccount])
const handleSizePercentage = useCallback(
(percentage: string) => {
const set = mangoStore.getState().set
setSizePercentage(percentage)
const size = leverageMax * (Number(percentage) / 100)
const size = max * (Number(percentage) / 100)
set((s) => {
if (s.tradeForm.side === 'buy') {
s.tradeForm.quoteSize = size.toString()
s.tradeForm.quoteSize = trimDecimals(size, tickDecimals).toFixed(
tickDecimals
)
if (Number(s.tradeForm.price)) {
s.tradeForm.baseSize = (size / Number(s.tradeForm.price)).toString()
s.tradeForm.baseSize = trimDecimals(
size / Number(s.tradeForm.price),
minOrderDecimals
).toFixed(minOrderDecimals)
} else {
s.tradeForm.baseSize = ''
}
} else if (s.tradeForm.side === 'sell') {
s.tradeForm.baseSize = size.toString()
s.tradeForm.baseSize = trimDecimals(size, tickDecimals).toFixed(
minOrderDecimals
)
if (Number(s.tradeForm.price)) {
s.tradeForm.quoteSize = (
size * Number(s.tradeForm.price)
).toString()
s.tradeForm.quoteSize = trimDecimals(
size * Number(s.tradeForm.price),
tickDecimals
).toFixed(tickDecimals)
}
}
})
},
[side, selectedMarket, mangoAccount]
[side, selectedMarket, mangoAccount, minOrderDecimals, tickDecimals]
)
return (

View File

@ -1,44 +1,21 @@
import { Serum3Market } from '@blockworks-foundation/mango-v4'
import LeverageSlider from '@components/shared/LeverageSlider'
import mangoStore from '@store/mangoStore'
import useMangoAccount from 'hooks/useMangoAccount'
import useSelectedMarket from 'hooks/useSelectedMarket'
import { useCallback, useMemo } from 'react'
// import { notify } from 'utils/notifications'
import { useCallback } from 'react'
import { trimDecimals } from 'utils/numbers'
const SpotSlider = () => {
const side = mangoStore((s) => s.tradeForm.side)
const { selectedMarket, price: marketPrice } = useSelectedMarket()
const { mangoAccount } = useMangoAccount()
const SpotSlider = ({
max,
minOrderDecimals,
tickDecimals,
}: {
max: number
minOrderDecimals: number
tickDecimals: number
}) => {
const { price: marketPrice } = useSelectedMarket()
const tradeForm = mangoStore((s) => s.tradeForm)
const leverageMax = useMemo(() => {
const group = mangoStore.getState().group
if (!mangoAccount || !group || !selectedMarket) return 100
if (!(selectedMarket instanceof Serum3Market)) return 100
try {
if (side === 'buy') {
return mangoAccount.getMaxQuoteForSerum3BidUi(
group,
selectedMarket.serumMarketExternal
)
} else {
return mangoAccount.getMaxBaseForSerum3AskUi(
group,
selectedMarket.serumMarketExternal
)
}
} catch (e) {
console.error('Error calculating max leverage for spot slider: ', e)
// notify({
// type: 'error',
// title: 'Error calculating max leverage.',
// })
return 0
}
}, [side, selectedMarket, mangoAccount])
const handleSlide = useCallback(
(val: string) => {
const set = mangoStore.getState().set
@ -52,19 +29,25 @@ const SpotSlider = () => {
if (s.tradeForm.side === 'buy') {
s.tradeForm.quoteSize = val
if (Number(price)) {
s.tradeForm.baseSize = (parseFloat(val) / price).toString()
s.tradeForm.baseSize = trimDecimals(
parseFloat(val) / price,
minOrderDecimals
).toFixed(minOrderDecimals)
} else {
s.tradeForm.baseSize = ''
}
} else if (s.tradeForm.side === 'sell') {
s.tradeForm.baseSize = val
if (Number(price)) {
s.tradeForm.quoteSize = (parseFloat(val) * price).toString()
s.tradeForm.quoteSize = trimDecimals(
parseFloat(val) * price,
tickDecimals
).toFixed(tickDecimals)
}
}
})
},
[marketPrice]
[marketPrice, minOrderDecimals, tickDecimals]
)
return (
@ -75,7 +58,7 @@ const SpotSlider = () => {
? parseFloat(tradeForm.quoteSize)
: parseFloat(tradeForm.baseSize)
}
leverageMax={leverageMax}
leverageMax={max}
onChange={handleSlide}
step={0.01}
/>

View File

@ -140,7 +140,7 @@ const TradeAdvancedPage = () => {
{ i: 'tv-chart', x: 0, y: 1, w: 17, h: 464 },
{ i: 'balances', x: 0, y: 2, w: 17, h: 428 + marketHeaderHeight },
{ i: 'orderbook', x: 18, y: 2, w: 7, h: 428 + marketHeaderHeight },
{ i: 'trade-form', x: 18, y: 1, w: 7, h: 464 + marketHeaderHeight },
{ i: 'trade-form', x: 18, y: 1, w: 7, h: 492 + marketHeaderHeight },
],
}
}, [height])

View File

@ -0,0 +1,108 @@
import {
HealthType,
MangoAccount,
PerpMarket,
Serum3Market,
toUiDecimalsForQuote,
} from '@blockworks-foundation/mango-v4'
import HealthImpact from '@components/shared/HealthImpact'
import Tooltip from '@components/shared/Tooltip'
import mangoStore from '@store/mangoStore'
import useMangoGroup from 'hooks/useMangoGroup'
import useSelectedMarket from 'hooks/useSelectedMarket'
import { useTranslation } from 'next-i18next'
import { useMemo } from 'react'
import { formatFixedDecimals } from 'utils/numbers'
import Slippage from './Slippage'
const TradeSummary = ({
mangoAccount,
}: {
mangoAccount: MangoAccount | undefined
}) => {
const { t } = useTranslation(['common', 'trade'])
const { group } = useMangoGroup()
const tradeForm = mangoStore((s) => s.tradeForm)
const { selectedMarket } = useSelectedMarket()
const maintProjectedHealth = useMemo(() => {
if (!mangoAccount || !group || !Number.isFinite(Number(tradeForm.baseSize)))
return 100
let simulatedHealthRatio = 0
try {
if (selectedMarket instanceof Serum3Market) {
simulatedHealthRatio =
tradeForm.side === 'sell'
? mangoAccount.simHealthRatioWithSerum3AskUiChanges(
group,
Number(tradeForm.baseSize),
selectedMarket.serumMarketExternal,
HealthType.maint
)
: mangoAccount.simHealthRatioWithSerum3BidUiChanges(
group,
Number(tradeForm.quoteSize),
selectedMarket.serumMarketExternal,
HealthType.maint
)
} else if (selectedMarket instanceof PerpMarket) {
simulatedHealthRatio =
tradeForm.side === 'sell'
? mangoAccount.simHealthRatioWithPerpAskUiChanges(
group,
selectedMarket.perpMarketIndex,
parseFloat(tradeForm.baseSize)
)
: mangoAccount.simHealthRatioWithPerpBidUiChanges(
group,
selectedMarket.perpMarketIndex,
parseFloat(tradeForm.baseSize)
)
}
} catch (e) {
console.warn('Error calculating projected health: ', e)
}
return simulatedHealthRatio > 100
? 100
: simulatedHealthRatio < 0
? 0
: Math.trunc(simulatedHealthRatio)
}, [group, mangoAccount, selectedMarket, tradeForm.baseSize, tradeForm.side])
return (
<div className="space-y-2 px-3 md:px-4">
{tradeForm.price && tradeForm.baseSize ? (
<div className="flex justify-between text-xs">
<p>{t('trade:order-value')}</p>
<p className="text-th-fgd-2">
{formatFixedDecimals(
parseFloat(tradeForm.price) * parseFloat(tradeForm.baseSize),
true
)}
</p>
</div>
) : null}
<HealthImpact maintProjectedHealth={maintProjectedHealth} small />
<div className="flex justify-between text-xs">
<Tooltip content="The amount of capital you have to use for trades and loans. When your free collateral reaches $0 you won't be able to trade, borrow or withdraw.">
<p className="tooltip-underline">{t('free-collateral')}</p>
</Tooltip>
<p className="text-th-fgd-2">
{group && mangoAccount
? formatFixedDecimals(
toUiDecimalsForQuote(
mangoAccount.getCollateralValue(group)!.toNumber()
),
true
)
: ''}
</p>
</div>
<Slippage />
</div>
)
}
export default TradeSummary

View File

@ -21,9 +21,10 @@
"no-unsettled": "No unsettled funds",
"open-interest": "Open Interest",
"oracle-price": "Oracle Price",
"orders": "Orders",
"order-error": "Failed to place order",
"order-type": "Order Type",
"order-value": "Order Value",
"orders": "Orders",
"post": "Post",
"place-order": "Place {{side}} Order",
"placing-order": "Placing Order",

View File

@ -21,9 +21,10 @@
"no-unsettled": "No unsettled funds",
"open-interest": "Open Interest",
"oracle-price": "Oracle Price",
"orders": "Orders",
"order-error": "Failed to place order",
"order-type": "Order Type",
"order-value": "Order Value",
"orders": "Orders",
"post": "Post",
"place-order": "Place {{side}} Order",
"placing-order": "Placing Order",

View File

@ -21,9 +21,10 @@
"no-unsettled": "No unsettled funds",
"open-interest": "Open Interest",
"oracle-price": "Oracle Price",
"orders": "Orders",
"order-error": "Failed to place order",
"order-type": "Order Type",
"order-value": "Order Value",
"orders": "Orders",
"post": "Post",
"place-order": "Place {{side}} Order",
"placing-order": "Placing Order",

View File

@ -21,9 +21,10 @@
"no-unsettled": "No unsettled funds",
"open-interest": "Open Interest",
"oracle-price": "Oracle Price",
"orders": "Orders",
"order-error": "Failed to place order",
"order-type": "Order Type",
"order-value": "Order Value",
"orders": "Orders",
"post": "Post",
"place-order": "Place {{side}} Order",
"placing-order": "Placing Order",

View File

@ -21,9 +21,10 @@
"no-unsettled": "No unsettled funds",
"open-interest": "Open Interest",
"oracle-price": "Oracle Price",
"orders": "Orders",
"order-error": "Failed to place order",
"order-type": "Order Type",
"order-value": "Order Value",
"orders": "Orders",
"post": "Post",
"place-order": "Place {{side}} Order",
"placing-order": "Placing Order",