mango-v4-ui/components/trade/SpotMarketOrderSwapForm.tsx

786 lines
27 KiB
TypeScript
Raw Normal View History

2023-07-16 20:41:13 -07:00
import mangoStore from '@store/mangoStore'
import NumberFormat, {
NumberFormatValues,
SourceInfo,
} from 'react-number-format'
import {
DEFAULT_CHECKBOX_SETTINGS,
2023-07-16 20:41:13 -07:00
INPUT_PREFIX_CLASSNAMES,
INPUT_SUFFIX_CLASSNAMES,
} from './AdvancedTradeForm'
import LogoWithFallback from '@components/shared/LogoWithFallback'
import { LinkIcon, QuestionMarkCircleIcon } from '@heroicons/react/20/solid'
import useSelectedMarket from 'hooks/useSelectedMarket'
import { useWallet } from '@solana/wallet-adapter-react'
import useIpAddress from 'hooks/useIpAddress'
import { useTranslation } from 'next-i18next'
2023-08-25 10:52:00 -07:00
import { FormEvent, useCallback, useMemo, useState } from 'react'
2023-07-16 20:41:13 -07:00
import Loading from '@components/shared/Loading'
import Button from '@components/shared/Button'
import Image from 'next/image'
import useQuoteRoutes from '@components/swap/useQuoteRoutes'
2023-12-09 03:01:19 -08:00
import {
HealthType,
PerpMarket,
Serum3Market,
} from '@blockworks-foundation/mango-v4'
2023-07-16 20:41:13 -07:00
import Decimal from 'decimal.js'
import { notify } from 'utils/notifications'
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, TRADE_CHECKBOXES_KEY } from 'utils/constants'
2023-07-16 20:41:13 -07:00
import useLocalStorageState from 'hooks/useLocalStorageState'
2023-07-18 11:55:13 -07:00
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'
2023-09-12 21:44:48 -07:00
import {
floorToDecimal,
formatNumericValue,
getDecimalCount,
} from 'utils/numbers'
import { formatTokenSymbol } from 'utils/tokens'
import FormatNumericValue from '@components/shared/FormatNumericValue'
2023-07-24 19:56:28 -07:00
import { useTokenMax } from '@components/swap/useTokenMax'
2023-07-24 20:15:00 -07:00
import SheenLoader from '@components/shared/SheenLoader'
2023-08-13 21:26:39 -07:00
import { fetchJupiterTransaction } from '@components/swap/SwapReviewRouteInfo'
2023-09-12 21:44:48 -07:00
import MaxMarketTradeAmount from './MaxMarketTradeAmount'
import useMangoAccount from 'hooks/useMangoAccount'
2023-12-08 04:26:20 -08:00
import InlineNotification from '@components/shared/InlineNotification'
2024-01-09 02:36:44 -08:00
import { debounce } from 'lodash'
import { isTokenInsured } from '@components/DepositForm'
import UninsuredNotification from '@components/shared/UninsuredNotification'
2023-07-16 20:41:13 -07:00
const set = mangoStore.getState().set
function stringToNumberOrZero(s: string): number {
const n = parseFloat(s)
if (isNaN(n)) {
return 0
}
return n
}
export default function SpotMarketOrderSwapForm() {
2023-07-18 11:55:13 -07:00
const { t } = useTranslation()
2023-07-24 19:56:28 -07:00
const { baseSize, quoteSize, side } = mangoStore((s) => s.tradeForm)
2023-07-18 11:55:13 -07:00
const { isUnownedAccount } = useUnownedAccount()
2023-07-16 20:41:13 -07:00
const [placingOrder, setPlacingOrder] = useState(false)
const [isDraggingSlider, setIsDraggingSlider] = useState(false)
2023-07-16 20:41:13 -07:00
const { ipAllowed, ipCountry } = useIpAddress()
const { connected, publicKey, connect } = useWallet()
const { mangoAccount } = useMangoAccount()
2023-07-16 20:41:13 -07:00
const [swapFormSizeUi] = useLocalStorageState(SIZE_INPUT_UI_KEY, 'slider')
const [savedCheckboxSettings, setSavedCheckboxSettings] =
useLocalStorageState(TRADE_CHECKBOXES_KEY, DEFAULT_CHECKBOX_SETTINGS)
2023-07-16 20:41:13 -07:00
const {
price: oraclePrice,
baseLogoURI,
baseSymbol,
quoteLogoURI,
quoteSymbol,
2023-12-09 03:01:19 -08:00
selectedMarket,
2023-07-16 20:41:13 -07:00
serumOrPerpMarket,
} = useSelectedMarket()
2023-07-24 19:56:28 -07:00
const { amount: tokenMax, amountWithBorrow } = useTokenMax(
savedCheckboxSettings.margin,
)
2023-07-16 20:41:13 -07:00
2023-12-08 04:26:20 -08:00
const [inputBank, outputBank] = useMemo(() => {
const group = mangoStore.getState().group
2023-12-09 03:01:19 -08:00
if (!group || !(selectedMarket instanceof Serum3Market)) return []
const quoteBank = group?.getFirstBankByTokenIndex(
selectedMarket.quoteTokenIndex,
)
const baseBank = group.getFirstBankByTokenIndex(
selectedMarket.baseTokenIndex,
)
2023-12-08 04:26:20 -08:00
if (side === 'buy') {
set((s) => {
s.swap.inputBank = quoteBank
s.swap.outputBank = baseBank
})
return [quoteBank, baseBank]
} else {
set((s) => {
s.swap.inputBank = baseBank
s.swap.outputBank = quoteBank
})
return [baseBank, quoteBank]
}
2023-12-09 03:01:19 -08:00
}, [selectedMarket, side])
2023-12-08 04:26:20 -08:00
const isInsured = useMemo(() => {
const group = mangoStore.getState().group
return isTokenInsured(outputBank, group)
}, [outputBank])
2023-07-16 20:41:13 -07:00
const handleBaseSizeChange = useCallback(
2024-01-09 02:36:44 -08:00
debounce((e: NumberFormatValues, info: SourceInfo) => {
2023-07-16 20:41:13 -07:00
if (info.source !== 'event') return
set((s) => {
s.tradeForm.baseSize = e.value
2023-12-09 03:01:19 -08:00
if (oraclePrice && e.value !== '' && !Number.isNaN(Number(e.value))) {
s.tradeForm.quoteSize = new Decimal(oraclePrice)
.mul(e.value)
.toFixed()
2023-07-16 20:41:13 -07:00
} else {
s.tradeForm.quoteSize = ''
}
})
2024-01-09 02:36:44 -08:00
}, 500),
2023-12-09 03:01:19 -08:00
[oraclePrice],
2023-07-16 20:41:13 -07:00
)
const handleQuoteSizeChange = useCallback(
2024-01-09 02:36:44 -08:00
debounce((e: NumberFormatValues, info: SourceInfo) => {
2023-07-16 20:41:13 -07:00
if (info.source !== 'event') return
set((s) => {
s.tradeForm.quoteSize = e.value
2023-12-09 03:01:19 -08:00
if (oraclePrice && e.value !== '' && !Number.isNaN(Number(e.value))) {
s.tradeForm.baseSize = new Decimal(e.value).div(oraclePrice).toFixed()
2023-07-16 20:41:13 -07:00
} else {
s.tradeForm.baseSize = ''
}
})
2024-01-09 02:36:44 -08:00
}, 500),
2023-12-09 03:01:19 -08:00
[oraclePrice],
2023-07-16 20:41:13 -07:00
)
2023-09-12 21:44:48 -07:00
const handleMaxAmount = useCallback(
(useMargin: boolean) => {
const group = mangoStore.getState().group
if (
!group ||
!serumOrPerpMarket ||
serumOrPerpMarket instanceof PerpMarket
)
return { max: new Decimal(0), decimals: 6 }
const max = useMargin ? amountWithBorrow : tokenMax
const decimals = getDecimalCount(serumOrPerpMarket.minOrderSize)
if (side === 'sell') {
return { max, decimals }
} else {
const baseMax = max.div(new Decimal(oraclePrice))
return { max: baseMax, decimals }
}
},
[amountWithBorrow, oraclePrice, serumOrPerpMarket, side, tokenMax],
)
const setMaxFromButton = useCallback(
(amount: string) => {
handleBaseSizeChange(
{ value: amount } as NumberFormatValues,
{ source: 'event' } as SourceInfo,
)
},
[handleBaseSizeChange],
)
const handleSliderDrag = useCallback(() => {
if (!isDraggingSlider) {
setIsDraggingSlider(true)
}
}, [isDraggingSlider])
const handleSliderDragEnd = useCallback(() => {
if (isDraggingSlider) {
setIsDraggingSlider(false)
}
}, [isDraggingSlider])
2023-07-16 20:41:13 -07:00
const setAmountFromSlider = useCallback(
(amount: string) => {
if (side === 'buy') {
handleQuoteSizeChange(
{ value: amount } as NumberFormatValues,
2023-07-21 11:47:53 -07:00
{ source: 'event' } as SourceInfo,
2023-07-16 20:41:13 -07:00
)
} else {
handleBaseSizeChange(
{ value: amount } as NumberFormatValues,
2023-07-21 11:47:53 -07:00
{ source: 'event' } as SourceInfo,
2023-07-16 20:41:13 -07:00
)
}
},
2023-07-21 11:47:53 -07:00
[side, handleBaseSizeChange, handleQuoteSizeChange],
2023-07-16 20:41:13 -07:00
)
2023-12-08 04:26:20 -08:00
const slippage = mangoStore.getState().swap.slippage
2023-12-09 03:01:19 -08:00
const jupiterQuoteAmount = side === 'buy' ? quoteSize : baseSize
const roundedQuoteAmount = useMemo(() => {
if (!jupiterQuoteAmount) return ''
return new Decimal(jupiterQuoteAmount)
.toDecimalPlaces(inputBank?.mintDecimals || 6, Decimal.ROUND_FLOOR)
.toFixed()
}, [jupiterQuoteAmount, inputBank])
2023-07-16 20:41:13 -07:00
const { bestRoute: selectedRoute, isInitialLoading: loadingRoute } =
useQuoteRoutes({
inputMint: inputBank?.mint.toString() || '',
outputMint: outputBank?.mint.toString() || '',
2023-12-09 03:01:19 -08:00
amount: roundedQuoteAmount,
slippage,
swapMode: 'ExactIn',
wallet: publicKey?.toBase58(),
mangoAccount,
mode: 'JUPITER',
enabled: () =>
!!(
inputBank?.mint &&
outputBank?.mint &&
jupiterQuoteAmount &&
!isDraggingSlider
),
})
2023-07-16 20:41:13 -07:00
2023-08-25 10:52:00 -07:00
const handlePlaceOrder = useCallback(async () => {
const client = mangoStore.getState().client
2023-07-16 20:41:13 -07:00
const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current
2023-08-25 10:52:00 -07:00
const { baseSize, quoteSize, side } = mangoStore.getState().tradeForm
const actions = mangoStore.getState().actions
2023-07-16 20:41:13 -07:00
const connection = mangoStore.getState().connection
if (
!mangoAccount ||
!group ||
!inputBank ||
!outputBank ||
!publicKey ||
!selectedRoute
2023-07-18 11:55:13 -07:00
)
2023-07-16 20:41:13 -07:00
return
2023-08-25 10:52:00 -07:00
setPlacingOrder(true)
2023-07-16 20:41:13 -07:00
const [ixs, alts] = await fetchJupiterTransaction(
connection,
selectedRoute,
publicKey,
slippage,
inputBank.mint,
2023-07-21 11:47:53 -07:00
outputBank.mint,
2023-07-16 20:41:13 -07:00
)
try {
const { signature: tx, slot } = await client.marginTrade({
2023-07-16 20:41:13 -07:00
group,
mangoAccount,
inputMintPk: inputBank.mint,
amountIn:
side === 'buy'
? stringToNumberOrZero(quoteSize)
: stringToNumberOrZero(baseSize),
outputMintPk: outputBank.mint,
userDefinedInstructions: ixs,
userDefinedAlts: alts,
flashLoanType: { swap: {} },
})
set((s) => {
s.successAnimation.swap = true
})
// if (soundSettings['swap-success']) {
// successSound.play()
// }
notify({
title: 'Transaction confirmed',
type: 'success',
txid: tx,
noSound: true,
})
actions.fetchGroup()
actions.fetchSwapHistory(mangoAccount.publicKey.toString(), 30000)
await actions.reloadMangoAccount(slot)
2023-07-16 20:41:13 -07:00
set((s) => {
s.tradeForm.baseSize = ''
s.tradeForm.quoteSize = ''
})
} catch (e) {
console.error('onSwap error: ', e)
sentry.captureException(e)
if (isMangoError(e)) {
notify({
title: 'Transaction failed',
description: e.message,
txid: e?.txid,
type: 'error',
})
}
} finally {
setPlacingOrder(false)
}
2023-08-25 10:52:00 -07:00
}, [inputBank, outputBank, publicKey, selectedRoute])
2023-07-16 20:41:13 -07:00
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
connected ? handlePlaceOrder() : connect()
2023-07-16 20:41:13 -07:00
}
2023-07-18 11:55:13 -07:00
const maintProjectedHealth = useMemo(() => {
const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current
if (!inputBank || !mangoAccount || !outputBank || !group) return 0
const simulatedHealthRatio =
mangoAccount.simHealthRatioWithTokenPositionUiChanges(
group,
[
{
mintPk: inputBank.mint,
uiTokenAmount:
(side === 'buy'
? stringToNumberOrZero(quoteSize)
: stringToNumberOrZero(baseSize)) * -1,
},
{
mintPk: outputBank.mint,
uiTokenAmount:
side === 'buy'
? stringToNumberOrZero(baseSize)
: stringToNumberOrZero(quoteSize),
},
],
2023-07-21 11:47:53 -07:00
HealthType.maint,
2023-07-18 11:55:13 -07:00
)
return simulatedHealthRatio > 100
? 100
: simulatedHealthRatio < 0
? 0
: 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(() => {
2023-07-24 19:56:28 -07:00
if (
!inputBank ||
!outputBank ||
!oraclePrice ||
!baseSize ||
isNaN(parseFloat(baseSize))
)
return 0
2023-07-24 19:56:28 -07:00
const quotePriceDecimal =
side === 'buy'
? new Decimal(inputBank.uiPrice)
: new Decimal(outputBank.uiPrice)
const basePriceDecimal = new Decimal(oraclePrice)
const sizeDecimal = new Decimal(baseSize)
return floorToDecimal(
basePriceDecimal.mul(quotePriceDecimal).mul(sizeDecimal),
2,
)
2023-07-24 19:56:28 -07:00
}, [baseSize, inputBank, outputBank, oraclePrice, side])
const tooMuchSize = useMemo(() => {
if (!baseSize || !quoteSize || !amountWithBorrow || !tokenMax) return false
const size = side === 'buy' ? new Decimal(quoteSize) : new Decimal(baseSize)
const useMargin = savedCheckboxSettings.margin
return useMargin ? size.gt(amountWithBorrow) : size.gt(tokenMax)
}, [
amountWithBorrow,
baseSize,
quoteSize,
side,
tokenMax,
savedCheckboxSettings.margin,
])
2023-12-09 03:01:19 -08:00
const quoteError =
!!selectedRoute?.error ||
!!(
!selectedRoute &&
!loadingRoute &&
!isDraggingSlider &&
jupiterQuoteAmount
)
2023-07-16 20:41:13 -07:00
const disabled =
2023-07-24 19:56:28 -07:00
(connected && (!baseSize || !oraclePrice)) ||
2023-07-16 20:41:13 -07:00
!serumOrPerpMarket ||
2023-08-25 10:52:00 -07:00
loadingRoute ||
2023-12-08 04:26:20 -08:00
tooMuchSize ||
2023-12-09 03:01:19 -08:00
quoteError
2023-07-16 20:41:13 -07:00
return (
<>
<form onSubmit={(e) => handleSubmit(e)}>
<div className="mt-3 px-3 md:px-4">
2023-09-12 21:44:48 -07:00
<div className="mb-2 mt-3 flex items-center justify-between">
<p className="text-xs text-th-fgd-3">{t('trade:size')}</p>
{!isUnownedAccount ? (
<MaxMarketTradeAmount
useMargin={savedCheckboxSettings.margin}
setAmountIn={setMaxFromButton}
maxAmount={handleMaxAmount}
/>
) : null}
</div>
2023-07-16 20:41:13 -07:00
<div className="flex flex-col">
<div className="relative">
<NumberFormat
inputMode="decimal"
thousandSeparator=","
allowNegative={false}
isNumericString={true}
decimalScale={
side === 'buy'
? outputBank?.mintDecimals
: inputBank?.mintDecimals
}
name="base"
id="base"
className="relative flex w-full items-center rounded-md rounded-b-none border border-th-input-border bg-th-input-bkg p-2 pl-9 font-mono text-sm font-bold text-th-fgd-1 focus:z-10 focus:border-th-fgd-4 focus:outline-none md:hover:z-10 md:hover:border-th-input-border-hover md:hover:focus:border-th-fgd-4 lg:text-base"
placeholder="0.00"
value={baseSize}
onValueChange={handleBaseSizeChange}
/>
<div className={`z-10 ${INPUT_PREFIX_CLASSNAMES}`}>
<LogoWithFallback
alt=""
className="drop-shadow-md"
width={'24'}
height={'24'}
src={baseLogoURI || `/icons/${baseSymbol?.toLowerCase()}.svg`}
fallback={
<QuestionMarkCircleIcon
className={`h-5 w-5 text-th-fgd-3`}
/>
}
/>
</div>
<div className={`z-10 ${INPUT_SUFFIX_CLASSNAMES}`}>
{baseSymbol}
</div>
</div>
<div className="relative">
{quoteLogoURI ? (
<div className={INPUT_PREFIX_CLASSNAMES}>
<Image alt="" width="20" height="20" src={quoteLogoURI} />
</div>
) : (
<div className={INPUT_PREFIX_CLASSNAMES}>
<QuestionMarkCircleIcon className="h-5 w-5 text-th-fgd-3" />
</div>
)}
<NumberFormat
inputMode="decimal"
thousandSeparator=","
allowNegative={false}
isNumericString={true}
decimalScale={
side === 'buy'
? inputBank?.mintDecimals
: outputBank?.mintDecimals
}
name="quote"
id="quote"
2023-08-25 10:52:00 -07:00
className="mt-[-1px] flex w-full items-center rounded-md rounded-t-none border border-th-input-border bg-th-input-bkg p-2 pl-9 font-mono text-sm font-bold text-th-fgd-1 focus:border-th-fgd-4 focus:outline-none md:hover:border-th-input-border-hover md:hover:focus:border-th-fgd-4 lg:text-base"
2023-07-16 20:41:13 -07:00
placeholder="0.00"
value={quoteSize}
onValueChange={handleQuoteSizeChange}
/>
<div className={INPUT_SUFFIX_CLASSNAMES}>{quoteSymbol}</div>
</div>
</div>
{swapFormSizeUi === 'slider' ? (
2023-07-24 20:08:43 -07:00
<div className="mt-2">
<SwapSlider
useMargin={savedCheckboxSettings.margin}
amount={
side === 'buy'
? stringToNumberOrZero(quoteSize)
: stringToNumberOrZero(baseSize)
}
onChange={setAmountFromSlider}
step={1 / 10 ** (inputBank?.mintDecimals || 6)}
2023-09-07 00:59:04 -07:00
maxAmount={useTokenMax}
handleStartDrag={handleSliderDrag}
handleEndDrag={handleSliderDragEnd}
2023-07-24 20:08:43 -07:00
/>
</div>
2023-07-16 20:41:13 -07:00
) : (
<PercentageSelectButtons
amountIn={side === 'buy' ? quoteSize : baseSize}
2023-07-18 11:55:13 -07:00
setAmountIn={setAmountFromSlider}
useMargin={savedCheckboxSettings.margin}
values={['25', '50', '75', '100']}
2023-07-16 20:41:13 -07:00
/>
)}
<div className="mt-4">
2023-07-18 11:55:13 -07:00
<Tooltip
className="hidden md:block"
delay={100}
placement="left"
content={t('trade:tooltip-enable-margin')}
2023-07-18 11:55:13 -07:00
>
<Checkbox
checked={savedCheckboxSettings.margin}
onChange={(e) =>
setSavedCheckboxSettings({
...savedCheckboxSettings,
margin: e.target.checked,
})
}
>
{t('trade:margin')}
</Checkbox>
2023-07-18 11:55:13 -07:00
</Tooltip>
</div>
2023-08-25 10:52:00 -07:00
<div className="mb-4 mt-6 flex">
{ipAllowed ? (
<Button
className={`flex w-full items-center justify-center ${
!connected
? ''
: side === 'buy'
? 'bg-th-up-dark text-white md:hover:bg-th-up-dark md:hover:brightness-90'
: 'bg-th-down-dark text-white md:hover:bg-th-down-dark md:hover:brightness-90'
}`}
disabled={disabled}
size="large"
type="submit"
>
2023-08-25 10:52:00 -07:00
{loadingRoute ? (
<div className="flex items-center space-x-2">
<Loading />
<span className="hidden sm:block">
{t('common:fetching-route')}
</span>
</div>
) : !connected ? (
<div className="flex items-center">
<LinkIcon className="mr-2 h-5 w-5" />
{t('connect')}
</div>
2023-07-24 19:56:28 -07:00
) : tooMuchSize ? (
<span>
{t('swap:insufficient-balance', {
symbol: '',
})}
</span>
) : !placingOrder ? (
<span>
{t('trade:place-order', {
side: side === 'buy' ? t('buy') : t('sell'),
})}
</span>
) : (
<div className="flex items-center space-x-2">
<Loading />
<span className="hidden sm:block">
{t('trade:placing-order')}
</span>
</div>
)}
</Button>
) : (
<Button disabled className="w-full leading-tight" size="large">
{t('country-not-allowed', {
country: ipCountry ? `(${ipCountry})` : '',
2023-07-18 11:55:13 -07:00
})}
</Button>
)}
</div>
2023-12-09 03:01:19 -08:00
{quoteError ? (
2023-12-08 04:26:20 -08:00
<div className="mb-4">
<InlineNotification
type="error"
desc={t('trade:error-no-route')}
/>
</div>
) : null}
{!isInsured ? (
<div className="mb-4">
<UninsuredNotification name={outputBank?.name} />
</div>
) : null}
<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.
</p>
<p className="mt-1">
The bigger the trade is, the bigger the price impact can
be.
</p>
</>
}
>
<p className="tooltip-underline">{t('swap:price-impact')}</p>
</Tooltip>
2023-08-25 10:52:00 -07:00
{loadingRoute ? (
2023-07-24 20:15:00 -07:00
<SheenLoader>
<div className="h-3.5 w-12 bg-th-bkg-2" />
</SheenLoader>
) : (
<p className="text-right font-mono text-th-fgd-2">
2023-12-08 04:26:20 -08:00
{selectedRoute?.priceImpactPct
? selectedRoute.priceImpactPct * 100 < 0.1
2023-07-24 20:15:00 -07:00
? '<0.1%'
: `${(selectedRoute?.priceImpactPct * 100).toFixed(2)}%`
2023-12-08 04:26:20 -08:00
: ''}
2023-07-24 20:15:00 -07:00
</p>
)}
</div>
2023-12-09 03:01:19 -08:00
{borrowAmount && inputBank && savedCheckboxSettings.margin ? (
<>
<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>
2023-08-25 10:52:00 -07:00
{loadingRoute ? (
2023-07-24 20:15:00 -07:00
<SheenLoader>
<div className="h-3.5 w-20 bg-th-bkg-2" />
</SheenLoader>
2023-12-08 04:26:20 -08:00
) : !selectedRoute || selectedRoute?.error ? (
<span className="text-th-fgd-2"></span>
2023-07-24 20:15:00 -07:00
) : (
<div className="flex items-center overflow-hidden text-th-fgd-2">
<Tooltip
2023-11-14 09:38:17 -08:00
content={selectedRoute?.routePlan?.map((info, index) => {
let includeSeparator = false
if (
2023-11-14 09:38:17 -08:00
selectedRoute?.routePlan &&
selectedRoute?.routePlan?.length > 1 &&
index !== selectedRoute?.routePlan?.length - 1
) {
includeSeparator = true
}
return (
2023-10-16 21:12:46 -07:00
<span key={index}>{`${info?.swapInfo.label} ${
includeSeparator ? 'x ' : ''
}`}</span>
)
})}
2023-07-24 20:15:00 -07:00
>
<div className="tooltip-underline max-w-[140px] truncate whitespace-nowrap">
2023-11-14 09:38:17 -08:00
{selectedRoute?.routePlan?.map((info, index) => {
2023-07-24 20:15:00 -07:00
let includeSeparator = false
if (
2023-11-14 09:38:17 -08:00
selectedRoute?.routePlan &&
selectedRoute?.routePlan?.length > 1 &&
index !== selectedRoute?.routePlan?.length - 1
2023-07-24 20:15:00 -07:00
) {
includeSeparator = true
}
return (
2023-10-16 21:12:46 -07:00
<span key={index}>{`${info?.swapInfo.label} ${
2023-07-24 20:15:00 -07:00
includeSeparator ? 'x ' : ''
}`}</span>
)
})}
</div>
</Tooltip>
</div>
)}
2023-07-18 11:55:13 -07:00
</div>
</div>
</div>
2023-07-16 20:41:13 -07:00
</form>
</>
)
}