2023-06-16 05:23:50 -07:00
|
|
|
import {
|
|
|
|
useState,
|
|
|
|
useCallback,
|
|
|
|
useEffect,
|
|
|
|
useMemo,
|
|
|
|
Dispatch,
|
|
|
|
SetStateAction,
|
2023-08-03 19:06:09 -07:00
|
|
|
useLayoutEffect,
|
2023-06-16 05:23:50 -07:00
|
|
|
} from 'react'
|
2023-09-07 06:26:55 -07:00
|
|
|
import { ArrowsRightLeftIcon, LinkIcon } from '@heroicons/react/20/solid'
|
2023-06-16 05:23:50 -07:00
|
|
|
import NumberFormat, {
|
|
|
|
NumberFormatValues,
|
|
|
|
SourceInfo,
|
|
|
|
} from 'react-number-format'
|
|
|
|
import Decimal from 'decimal.js'
|
|
|
|
import mangoStore from '@store/mangoStore'
|
|
|
|
import { useTranslation } from 'next-i18next'
|
2023-08-16 19:58:35 -07:00
|
|
|
import { SIZE_INPUT_UI_KEY } from '../../utils/constants'
|
2023-06-16 05:23:50 -07:00
|
|
|
import useLocalStorageState from 'hooks/useLocalStorageState'
|
|
|
|
import SwapSlider from './SwapSlider'
|
|
|
|
import PercentageSelectButtons from './PercentageSelectButtons'
|
2023-09-07 02:43:29 -07:00
|
|
|
import {
|
|
|
|
floorToDecimal,
|
|
|
|
floorToDecimalSignificance,
|
|
|
|
formatCurrencyValue,
|
|
|
|
} from 'utils/numbers'
|
2023-07-25 19:48:13 -07:00
|
|
|
import { withValueLimit } from './MarketSwapForm'
|
2023-09-06 06:06:57 -07:00
|
|
|
import ReduceInputTokenInput from './ReduceInputTokenInput'
|
|
|
|
import ReduceOutputTokenInput from './ReduceOutputTokenInput'
|
2023-08-08 06:26:12 -07:00
|
|
|
import { notify } from 'utils/notifications'
|
|
|
|
import * as sentry from '@sentry/nextjs'
|
|
|
|
import { isMangoError } from 'types'
|
2023-08-04 05:03:19 -07:00
|
|
|
import Button, { LinkButton } from '@components/shared/Button'
|
2023-07-30 23:05:26 -07:00
|
|
|
import Loading from '@components/shared/Loading'
|
2023-07-31 05:25:46 -07:00
|
|
|
import TokenLogo from '@components/shared/TokenLogo'
|
2023-07-31 17:57:53 -07:00
|
|
|
import InlineNotification from '@components/shared/InlineNotification'
|
2023-08-03 06:44:23 -07:00
|
|
|
import Select from '@components/forms/Select'
|
2023-08-03 19:06:09 -07:00
|
|
|
import useIpAddress from 'hooks/useIpAddress'
|
2023-09-07 06:26:55 -07:00
|
|
|
import { Bank } from '@blockworks-foundation/mango-v4'
|
2023-08-08 19:53:59 -07:00
|
|
|
import useMangoAccount from 'hooks/useMangoAccount'
|
2023-08-10 17:52:42 -07:00
|
|
|
import { useWallet } from '@solana/wallet-adapter-react'
|
2023-09-07 06:26:55 -07:00
|
|
|
import { useAbsInputPosition } from './useTokenMax'
|
2023-08-27 23:06:16 -07:00
|
|
|
import useRemainingBorrowsInPeriod from 'hooks/useRemainingBorrowsInPeriod'
|
|
|
|
import dayjs from 'dayjs'
|
|
|
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
2023-09-06 06:06:57 -07:00
|
|
|
import { SwapFormTokenListType } from './SwapFormTokenList'
|
2023-09-07 06:26:55 -07:00
|
|
|
import { formatTokenSymbol } from 'utils/tokens'
|
2023-09-09 06:00:45 -07:00
|
|
|
import Tooltip from '@components/shared/Tooltip'
|
2023-09-10 06:26:52 -07:00
|
|
|
import Link from 'next/link'
|
|
|
|
import useTokenPositionsFull from 'hooks/useTokenPositionsFull'
|
2023-10-20 08:11:19 -07:00
|
|
|
import TopBarStore from '@store/topBarStore'
|
2023-08-27 23:06:16 -07:00
|
|
|
|
|
|
|
dayjs.extend(relativeTime)
|
2023-06-16 05:23:50 -07:00
|
|
|
|
2023-09-07 02:43:29 -07:00
|
|
|
const priceToDisplayString = (price: number | Decimal | string): string => {
|
|
|
|
const val = floorToDecimalSignificance(price, 6)
|
|
|
|
return val.toFixed(val.dp())
|
|
|
|
}
|
|
|
|
|
2023-09-10 06:26:52 -07:00
|
|
|
type TriggerSwapFormProps = {
|
2023-09-07 06:59:24 -07:00
|
|
|
showTokenSelect: SwapFormTokenListType
|
2023-09-06 06:06:57 -07:00
|
|
|
setShowTokenSelect: Dispatch<SetStateAction<SwapFormTokenListType>>
|
2023-06-16 05:23:50 -07:00
|
|
|
}
|
|
|
|
|
2023-09-10 06:26:52 -07:00
|
|
|
type TriggerSwapForm = {
|
2023-08-01 22:32:20 -07:00
|
|
|
amountIn: number
|
2023-07-31 17:57:53 -07:00
|
|
|
triggerPrice: string
|
|
|
|
}
|
2023-08-03 23:14:41 -07:00
|
|
|
|
2023-09-10 06:26:52 -07:00
|
|
|
type FormErrors = Partial<Record<keyof TriggerSwapForm, string>>
|
2023-07-31 17:57:53 -07:00
|
|
|
|
2023-08-03 06:44:23 -07:00
|
|
|
enum OrderTypes {
|
|
|
|
STOP_LOSS = 'trade:stop-loss',
|
|
|
|
TAKE_PROFIT = 'trade:take-profit',
|
|
|
|
}
|
|
|
|
|
2023-09-07 05:37:33 -07:00
|
|
|
const ORDER_TYPES = [OrderTypes.STOP_LOSS, OrderTypes.TAKE_PROFIT]
|
2023-08-03 06:44:23 -07:00
|
|
|
|
2023-07-25 19:48:13 -07:00
|
|
|
const set = mangoStore.getState().set
|
2023-06-16 05:23:50 -07:00
|
|
|
|
2023-09-07 05:37:33 -07:00
|
|
|
export const getInputTokenBalance = (inputBank: Bank | undefined) => {
|
2023-08-08 06:26:12 -07:00
|
|
|
const mangoAccount = mangoStore.getState().mangoAccount.current
|
|
|
|
if (!inputBank || !mangoAccount) return 0
|
|
|
|
const balance = mangoAccount.getTokenBalanceUi(inputBank)
|
|
|
|
return balance
|
|
|
|
}
|
2023-08-03 23:14:41 -07:00
|
|
|
|
2023-09-07 19:39:39 -07:00
|
|
|
const getOutputTokenBalance = (outputBank: Bank | undefined) => {
|
|
|
|
const mangoAccount = mangoStore.getState().mangoAccount.current
|
|
|
|
if (!outputBank || !mangoAccount) return 0
|
|
|
|
const balance = mangoAccount.getTokenBalanceUi(outputBank)
|
|
|
|
return balance
|
|
|
|
}
|
|
|
|
|
2023-09-07 01:22:31 -07:00
|
|
|
const getOrderTypeMultiplier = (
|
|
|
|
orderType: OrderTypes,
|
|
|
|
flipPrices: boolean,
|
|
|
|
reducingShort: boolean,
|
|
|
|
) => {
|
2023-08-03 23:14:41 -07:00
|
|
|
if (orderType === OrderTypes.STOP_LOSS) {
|
2023-09-07 04:55:56 -07:00
|
|
|
return reducingShort ? (flipPrices ? 0.9 : 1.1) : flipPrices ? 1.1 : 0.9
|
2023-09-07 02:50:51 -07:00
|
|
|
} else if (orderType === OrderTypes.TAKE_PROFIT) {
|
2023-09-07 04:55:56 -07:00
|
|
|
return reducingShort ? (flipPrices ? 1.1 : 0.9) : flipPrices ? 0.9 : 1.1
|
2023-08-03 23:14:41 -07:00
|
|
|
} else {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-10 06:26:52 -07:00
|
|
|
const TriggerSwapForm = ({
|
2023-09-07 06:59:24 -07:00
|
|
|
showTokenSelect,
|
|
|
|
setShowTokenSelect,
|
2023-09-10 06:26:52 -07:00
|
|
|
}: TriggerSwapFormProps) => {
|
2023-06-16 05:23:50 -07:00
|
|
|
const { t } = useTranslation(['common', 'swap', 'trade'])
|
2023-09-07 05:37:33 -07:00
|
|
|
const { mangoAccountAddress } = useMangoAccount()
|
2023-08-03 19:06:09 -07:00
|
|
|
const { ipAllowed, ipCountry } = useIpAddress()
|
2023-10-20 08:11:19 -07:00
|
|
|
const { setShowSettingsModal } = TopBarStore()
|
2023-09-07 20:35:30 -07:00
|
|
|
// const [triggerPrice, setTriggerPrice] = useState('')
|
2023-08-03 06:44:23 -07:00
|
|
|
const [orderType, setOrderType] = useState(ORDER_TYPES[0])
|
2023-08-08 06:26:12 -07:00
|
|
|
const [submitting, setSubmitting] = useState(false)
|
2023-06-16 05:23:50 -07:00
|
|
|
const [swapFormSizeUi] = useLocalStorageState(SIZE_INPUT_UI_KEY, 'slider')
|
2023-07-31 17:57:53 -07:00
|
|
|
const [formErrors, setFormErrors] = useState<FormErrors>({})
|
2023-08-27 23:06:16 -07:00
|
|
|
const { remainingBorrowsInPeriod, timeToNextPeriod } =
|
2023-09-10 06:26:52 -07:00
|
|
|
useRemainingBorrowsInPeriod(false, true)
|
2023-06-16 05:23:50 -07:00
|
|
|
|
|
|
|
const {
|
|
|
|
inputBank,
|
|
|
|
outputBank,
|
|
|
|
amountIn: amountInFormValue,
|
|
|
|
amountOut: amountOutFormValue,
|
2023-08-16 19:58:35 -07:00
|
|
|
flipPrices,
|
2023-09-07 20:35:30 -07:00
|
|
|
triggerPrice,
|
2023-06-16 05:23:50 -07:00
|
|
|
} = mangoStore((s) => s.swap)
|
|
|
|
|
2023-09-10 06:26:52 -07:00
|
|
|
const tokenPositionsFull = useTokenPositionsFull([outputBank])
|
|
|
|
|
2023-08-10 17:52:42 -07:00
|
|
|
const { connected, connect } = useWallet()
|
|
|
|
|
2023-08-04 06:48:17 -07:00
|
|
|
const [inputBankName, outputBankName, inputBankDecimals, outputBankDecimals] =
|
|
|
|
useMemo(() => {
|
|
|
|
if (!inputBank || !outputBank) return ['', '', 0, 0]
|
|
|
|
return [
|
|
|
|
inputBank.name,
|
|
|
|
outputBank.name,
|
|
|
|
inputBank.mintDecimals,
|
|
|
|
outputBank.mintDecimals,
|
|
|
|
]
|
|
|
|
}, [inputBank, outputBank])
|
|
|
|
|
2023-06-16 05:23:50 -07:00
|
|
|
const amountInAsDecimal: Decimal | null = useMemo(() => {
|
2023-07-30 23:05:26 -07:00
|
|
|
return Number(amountInFormValue)
|
|
|
|
? new Decimal(amountInFormValue)
|
2023-06-16 05:23:50 -07:00
|
|
|
: new Decimal(0)
|
2023-07-30 23:05:26 -07:00
|
|
|
}, [amountInFormValue])
|
2023-06-16 05:23:50 -07:00
|
|
|
|
2023-09-09 04:32:28 -07:00
|
|
|
const amountOutAsDecimal: Decimal | null = useMemo(() => {
|
|
|
|
return Number(amountOutFormValue)
|
|
|
|
? new Decimal(amountOutFormValue)
|
|
|
|
: new Decimal(0)
|
|
|
|
}, [amountOutFormValue])
|
|
|
|
|
2023-06-16 05:23:50 -07:00
|
|
|
const setAmountInFormValue = useCallback((amountIn: string) => {
|
|
|
|
set((s) => {
|
|
|
|
s.swap.amountIn = amountIn
|
|
|
|
if (!parseFloat(amountIn)) {
|
|
|
|
s.swap.amountOut = ''
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
const setAmountOutFormValue = useCallback((amountOut: string) => {
|
|
|
|
set((s) => {
|
|
|
|
s.swap.amountOut = amountOut
|
|
|
|
if (!parseFloat(amountOut)) {
|
|
|
|
s.swap.amountIn = ''
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}, [])
|
|
|
|
|
2023-08-03 03:34:57 -07:00
|
|
|
const quotePrice = useMemo(() => {
|
|
|
|
if (!inputBank || !outputBank) return 0
|
2023-09-07 02:43:29 -07:00
|
|
|
return flipPrices
|
|
|
|
? outputBank.uiPrice / inputBank.uiPrice
|
|
|
|
: inputBank.uiPrice / outputBank.uiPrice
|
2023-08-03 03:34:57 -07:00
|
|
|
}, [flipPrices, inputBank, outputBank])
|
2023-07-31 17:57:53 -07:00
|
|
|
|
2023-09-07 02:43:29 -07:00
|
|
|
const isReducingShort = useMemo(() => {
|
2023-09-07 05:37:33 -07:00
|
|
|
if (!mangoAccountAddress || !inputBank) return false
|
|
|
|
const inputBalance = getInputTokenBalance(inputBank)
|
|
|
|
return inputBalance < 0
|
|
|
|
}, [inputBank, mangoAccountAddress])
|
2023-09-07 02:43:29 -07:00
|
|
|
|
2023-08-03 06:44:23 -07:00
|
|
|
// set default trigger price
|
2023-07-30 23:05:26 -07:00
|
|
|
useEffect(() => {
|
2023-09-07 06:59:24 -07:00
|
|
|
if (!quotePrice || triggerPrice || showTokenSelect) return
|
2023-09-07 01:22:31 -07:00
|
|
|
const multiplier = getOrderTypeMultiplier(
|
2023-09-07 06:59:24 -07:00
|
|
|
orderType,
|
2023-09-07 01:22:31 -07:00
|
|
|
flipPrices,
|
2023-09-07 02:43:29 -07:00
|
|
|
isReducingShort,
|
2023-09-07 01:22:31 -07:00
|
|
|
)
|
2023-09-07 20:35:30 -07:00
|
|
|
set((state) => {
|
|
|
|
state.swap.triggerPrice = priceToDisplayString(
|
|
|
|
new Decimal(quotePrice).mul(new Decimal(multiplier)),
|
|
|
|
)
|
|
|
|
})
|
2023-09-07 06:59:24 -07:00
|
|
|
}, [
|
|
|
|
flipPrices,
|
|
|
|
isReducingShort,
|
|
|
|
orderType,
|
|
|
|
quotePrice,
|
|
|
|
showTokenSelect,
|
|
|
|
triggerPrice,
|
|
|
|
])
|
2023-07-31 17:57:53 -07:00
|
|
|
|
2023-08-03 23:14:41 -07:00
|
|
|
// flip trigger price and set amount out when chart direction is flipped
|
2023-08-03 19:06:09 -07:00
|
|
|
useLayoutEffect(() => {
|
|
|
|
if (!quotePrice) return
|
2023-09-07 01:22:31 -07:00
|
|
|
const multiplier = getOrderTypeMultiplier(
|
|
|
|
orderType,
|
|
|
|
flipPrices,
|
2023-09-07 02:43:29 -07:00
|
|
|
isReducingShort,
|
2023-09-07 01:22:31 -07:00
|
|
|
)
|
2023-09-07 20:35:30 -07:00
|
|
|
const price = priceToDisplayString(
|
|
|
|
new Decimal(quotePrice).mul(new Decimal(multiplier)),
|
|
|
|
)
|
|
|
|
set((state) => {
|
|
|
|
state.swap.triggerPrice = price
|
|
|
|
})
|
2023-08-03 23:14:41 -07:00
|
|
|
if (amountInAsDecimal?.gt(0)) {
|
2023-08-08 06:26:12 -07:00
|
|
|
const amountOut = getAmountOut(
|
|
|
|
amountInAsDecimal.toString(),
|
|
|
|
flipPrices,
|
|
|
|
price,
|
|
|
|
)
|
2023-08-03 23:14:41 -07:00
|
|
|
setAmountOutFormValue(amountOut.toString())
|
|
|
|
}
|
2023-09-07 02:43:29 -07:00
|
|
|
}, [flipPrices, orderType, isReducingShort])
|
2023-08-03 19:06:09 -07:00
|
|
|
|
2023-08-01 22:32:20 -07:00
|
|
|
const triggerPriceDifference = useMemo(() => {
|
2023-08-03 03:34:57 -07:00
|
|
|
if (!quotePrice) return 0
|
2023-07-31 17:57:53 -07:00
|
|
|
const triggerDifference = triggerPrice
|
2023-08-03 03:34:57 -07:00
|
|
|
? ((parseFloat(triggerPrice) - quotePrice) / quotePrice) * 100
|
2023-07-31 17:57:53 -07:00
|
|
|
: 0
|
2023-08-01 22:32:20 -07:00
|
|
|
return triggerDifference
|
2023-08-08 06:26:12 -07:00
|
|
|
}, [quotePrice, triggerPrice])
|
2023-08-01 22:32:20 -07:00
|
|
|
|
2023-09-06 06:06:57 -07:00
|
|
|
const handleTokenSelect = (type: SwapFormTokenListType) => {
|
2023-08-01 22:32:20 -07:00
|
|
|
setShowTokenSelect(type)
|
2023-08-08 21:31:49 -07:00
|
|
|
setFormErrors({})
|
2023-09-07 20:35:30 -07:00
|
|
|
set((state) => {
|
|
|
|
state.swap.triggerPrice = ''
|
|
|
|
})
|
2023-08-01 22:32:20 -07:00
|
|
|
}
|
|
|
|
|
2023-09-09 04:32:28 -07:00
|
|
|
// check if the borrowed amount exceeds the net borrow limit in the current period. Only currently applies to reducing shorts
|
2023-08-27 23:06:16 -07:00
|
|
|
const borrowExceedsLimitInPeriod = useMemo(() => {
|
|
|
|
const mangoAccount = mangoStore.getState().mangoAccount.current
|
2023-09-09 04:32:28 -07:00
|
|
|
if (
|
|
|
|
!mangoAccount ||
|
|
|
|
!outputBank ||
|
|
|
|
!isReducingShort ||
|
|
|
|
!remainingBorrowsInPeriod
|
|
|
|
)
|
|
|
|
return false
|
2023-08-27 23:06:16 -07:00
|
|
|
|
2023-09-09 04:32:28 -07:00
|
|
|
const balance = mangoAccount.getTokenDepositsUi(outputBank)
|
|
|
|
const remainingBalance = balance - amountOutAsDecimal.toNumber()
|
2023-08-27 23:06:16 -07:00
|
|
|
const borrowAmount = remainingBalance < 0 ? Math.abs(remainingBalance) : 0
|
|
|
|
|
2023-09-09 04:32:28 -07:00
|
|
|
return borrowAmount > remainingBorrowsInPeriod
|
2023-08-27 23:06:16 -07:00
|
|
|
}, [
|
2023-09-09 04:32:28 -07:00
|
|
|
amountOutAsDecimal,
|
|
|
|
outputBank,
|
|
|
|
isReducingShort,
|
2023-08-27 23:06:16 -07:00
|
|
|
mangoAccountAddress,
|
|
|
|
remainingBorrowsInPeriod,
|
|
|
|
])
|
|
|
|
|
2023-08-08 06:26:12 -07:00
|
|
|
const isFormValid = useCallback(
|
2023-09-10 06:26:52 -07:00
|
|
|
(form: TriggerSwapForm) => {
|
2023-08-08 06:26:12 -07:00
|
|
|
const invalidFields: FormErrors = {}
|
|
|
|
setFormErrors({})
|
2023-09-10 06:26:52 -07:00
|
|
|
const requiredFields: (keyof TriggerSwapForm)[] = [
|
2023-08-08 06:26:12 -07:00
|
|
|
'amountIn',
|
|
|
|
'triggerPrice',
|
|
|
|
]
|
|
|
|
const triggerPriceNumber = parseFloat(form.triggerPrice)
|
2023-09-07 05:37:33 -07:00
|
|
|
const inputTokenBalance = getInputTokenBalance(inputBank)
|
2023-09-07 06:26:55 -07:00
|
|
|
const shouldFlip = flipPrices !== isReducingShort
|
2023-08-08 06:26:12 -07:00
|
|
|
for (const key of requiredFields) {
|
|
|
|
const value = form[key] as string
|
|
|
|
if (!value) {
|
|
|
|
invalidFields[key] = t('settings:error-required-field')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (orderType === OrderTypes.STOP_LOSS) {
|
2023-09-07 06:26:55 -07:00
|
|
|
if (shouldFlip && triggerPriceNumber <= quotePrice) {
|
2023-08-08 06:26:12 -07:00
|
|
|
invalidFields.triggerPrice =
|
|
|
|
'Trigger price must be above oracle price'
|
|
|
|
}
|
2023-09-07 06:26:55 -07:00
|
|
|
if (!shouldFlip && triggerPriceNumber >= quotePrice) {
|
2023-08-08 06:26:12 -07:00
|
|
|
invalidFields.triggerPrice =
|
|
|
|
'Trigger price must be below oracle price'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (orderType === OrderTypes.TAKE_PROFIT) {
|
2023-09-07 06:26:55 -07:00
|
|
|
if (shouldFlip && triggerPriceNumber >= quotePrice) {
|
2023-08-08 06:26:12 -07:00
|
|
|
invalidFields.triggerPrice =
|
|
|
|
'Trigger price must be below oracle price'
|
|
|
|
}
|
2023-09-07 06:26:55 -07:00
|
|
|
if (!shouldFlip && triggerPriceNumber <= quotePrice) {
|
2023-08-08 06:26:12 -07:00
|
|
|
invalidFields.triggerPrice =
|
|
|
|
'Trigger price must be above oracle price'
|
|
|
|
}
|
|
|
|
}
|
2023-09-07 06:26:55 -07:00
|
|
|
if (form.amountIn > Math.abs(inputTokenBalance)) {
|
2023-08-08 06:26:12 -07:00
|
|
|
invalidFields.amountIn = t('swap:insufficient-balance', {
|
|
|
|
symbol: inputBank?.name,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (Object.keys(invalidFields).length) {
|
|
|
|
setFormErrors(invalidFields)
|
|
|
|
}
|
|
|
|
return invalidFields
|
|
|
|
},
|
2023-09-07 06:26:55 -07:00
|
|
|
[
|
|
|
|
flipPrices,
|
|
|
|
inputBank,
|
|
|
|
isReducingShort,
|
|
|
|
orderType,
|
|
|
|
quotePrice,
|
|
|
|
setFormErrors,
|
|
|
|
],
|
2023-08-08 06:26:12 -07:00
|
|
|
)
|
2023-08-03 23:14:41 -07:00
|
|
|
|
2023-08-01 03:49:01 -07:00
|
|
|
// get the out amount from the in amount and trigger or limit price
|
|
|
|
const getAmountOut = useCallback(
|
2023-08-08 06:26:12 -07:00
|
|
|
(amountIn: string, flipPrices: boolean, price: string) => {
|
|
|
|
const amountOut = flipPrices
|
2023-08-01 22:32:20 -07:00
|
|
|
? floorToDecimal(
|
2023-08-03 03:34:57 -07:00
|
|
|
parseFloat(amountIn) / parseFloat(price),
|
2023-08-01 22:32:20 -07:00
|
|
|
outputBank?.mintDecimals || 0,
|
|
|
|
)
|
|
|
|
: floorToDecimal(
|
2023-08-03 03:34:57 -07:00
|
|
|
parseFloat(amountIn) * parseFloat(price),
|
2023-08-01 22:32:20 -07:00
|
|
|
outputBank?.mintDecimals || 0,
|
|
|
|
)
|
2023-08-01 03:49:01 -07:00
|
|
|
return amountOut
|
|
|
|
},
|
2023-08-08 06:26:12 -07:00
|
|
|
[outputBank],
|
2023-08-01 03:49:01 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
// get the in amount from the out amount and trigger or limit price
|
|
|
|
const getAmountIn = useCallback(
|
2023-08-08 06:26:12 -07:00
|
|
|
(amountOut: string, flipPrices: boolean, price: string) => {
|
|
|
|
const amountIn = flipPrices
|
2023-08-01 22:32:20 -07:00
|
|
|
? floorToDecimal(
|
2023-08-03 03:34:57 -07:00
|
|
|
parseFloat(amountOut) * parseFloat(price),
|
2023-08-01 22:32:20 -07:00
|
|
|
inputBank?.mintDecimals || 0,
|
|
|
|
)
|
|
|
|
: floorToDecimal(
|
2023-08-03 03:34:57 -07:00
|
|
|
parseFloat(amountOut) / parseFloat(price),
|
2023-08-01 22:32:20 -07:00
|
|
|
inputBank?.mintDecimals || 0,
|
|
|
|
)
|
2023-08-01 03:49:01 -07:00
|
|
|
return amountIn
|
|
|
|
},
|
2023-08-08 06:26:12 -07:00
|
|
|
[inputBank, outputBank],
|
2023-08-01 03:49:01 -07:00
|
|
|
)
|
|
|
|
|
2023-08-01 05:21:19 -07:00
|
|
|
const handleMax = useCallback(
|
|
|
|
(amountIn: string) => {
|
|
|
|
setAmountInFormValue(amountIn)
|
2023-08-01 22:32:20 -07:00
|
|
|
if (parseFloat(amountIn) > 0 && triggerPrice) {
|
2023-08-08 06:26:12 -07:00
|
|
|
const amountOut = getAmountOut(amountIn, flipPrices, triggerPrice)
|
2023-08-01 05:21:19 -07:00
|
|
|
setAmountOutFormValue(amountOut.toString())
|
|
|
|
}
|
|
|
|
},
|
2023-08-08 06:26:12 -07:00
|
|
|
[
|
|
|
|
flipPrices,
|
|
|
|
getAmountOut,
|
|
|
|
setAmountInFormValue,
|
|
|
|
setAmountOutFormValue,
|
|
|
|
triggerPrice,
|
|
|
|
],
|
2023-08-01 05:21:19 -07:00
|
|
|
)
|
|
|
|
|
2023-06-16 05:23:50 -07:00
|
|
|
const handleAmountInChange = useCallback(
|
|
|
|
(e: NumberFormatValues, info: SourceInfo) => {
|
|
|
|
if (info.source !== 'event') return
|
2023-08-01 22:32:20 -07:00
|
|
|
setFormErrors({})
|
2023-06-16 05:23:50 -07:00
|
|
|
setAmountInFormValue(e.value)
|
2023-08-01 22:32:20 -07:00
|
|
|
if (parseFloat(e.value) > 0 && triggerPrice) {
|
2023-08-08 06:26:12 -07:00
|
|
|
const amountOut = getAmountOut(e.value, flipPrices, triggerPrice)
|
2023-08-01 03:49:01 -07:00
|
|
|
setAmountOutFormValue(amountOut.toString())
|
2023-06-16 05:23:50 -07:00
|
|
|
}
|
|
|
|
},
|
2023-08-04 03:25:43 -07:00
|
|
|
[
|
2023-08-08 06:26:12 -07:00
|
|
|
flipPrices,
|
2023-08-04 03:25:43 -07:00
|
|
|
getAmountOut,
|
|
|
|
setAmountInFormValue,
|
|
|
|
setAmountOutFormValue,
|
|
|
|
setFormErrors,
|
|
|
|
triggerPrice,
|
|
|
|
],
|
2023-06-16 05:23:50 -07:00
|
|
|
)
|
|
|
|
|
2023-06-17 05:22:05 -07:00
|
|
|
const handleAmountOutChange = useCallback(
|
|
|
|
(e: NumberFormatValues, info: SourceInfo) => {
|
|
|
|
if (info.source !== 'event') return
|
2023-08-03 23:14:41 -07:00
|
|
|
setFormErrors({})
|
2023-06-17 05:22:05 -07:00
|
|
|
setAmountOutFormValue(e.value)
|
2023-08-01 22:32:20 -07:00
|
|
|
if (parseFloat(e.value) > 0 && triggerPrice) {
|
2023-08-08 06:26:12 -07:00
|
|
|
const amountIn = getAmountIn(e.value, flipPrices, triggerPrice)
|
2023-08-01 03:49:01 -07:00
|
|
|
setAmountInFormValue(amountIn.toString())
|
2023-06-17 05:22:05 -07:00
|
|
|
}
|
|
|
|
},
|
2023-08-04 03:25:43 -07:00
|
|
|
[
|
2023-08-08 06:26:12 -07:00
|
|
|
flipPrices,
|
2023-08-04 03:25:43 -07:00
|
|
|
getAmountIn,
|
|
|
|
setAmountInFormValue,
|
|
|
|
setAmountOutFormValue,
|
|
|
|
setFormErrors,
|
|
|
|
triggerPrice,
|
|
|
|
],
|
2023-06-17 05:22:05 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
const handleAmountInUi = useCallback(
|
2023-06-16 05:23:50 -07:00
|
|
|
(amountIn: string) => {
|
|
|
|
setAmountInFormValue(amountIn)
|
2023-07-31 17:57:53 -07:00
|
|
|
setFormErrors({})
|
2023-08-01 22:32:20 -07:00
|
|
|
if (triggerPrice) {
|
2023-08-08 06:26:12 -07:00
|
|
|
const amountOut = getAmountOut(amountIn, flipPrices, triggerPrice)
|
2023-08-01 03:49:01 -07:00
|
|
|
setAmountOutFormValue(amountOut.toString())
|
2023-06-17 05:22:05 -07:00
|
|
|
}
|
2023-06-16 05:23:50 -07:00
|
|
|
},
|
2023-08-04 03:25:43 -07:00
|
|
|
[
|
2023-08-08 06:26:12 -07:00
|
|
|
flipPrices,
|
2023-08-04 03:25:43 -07:00
|
|
|
getAmountOut,
|
|
|
|
setAmountInFormValue,
|
|
|
|
setAmountOutFormValue,
|
|
|
|
setFormErrors,
|
|
|
|
triggerPrice,
|
|
|
|
],
|
2023-06-16 05:23:50 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
const handleTriggerPrice = useCallback(
|
|
|
|
(e: NumberFormatValues, info: SourceInfo) => {
|
|
|
|
if (info.source !== 'event') return
|
2023-07-31 17:57:53 -07:00
|
|
|
setFormErrors({})
|
2023-09-07 20:35:30 -07:00
|
|
|
set((state) => {
|
|
|
|
state.swap.triggerPrice = e.value
|
|
|
|
})
|
2023-08-01 22:32:20 -07:00
|
|
|
if (parseFloat(e.value) > 0 && parseFloat(amountInFormValue) > 0) {
|
2023-08-08 06:26:12 -07:00
|
|
|
const amountOut = getAmountOut(amountInFormValue, flipPrices, e.value)
|
2023-08-01 03:49:01 -07:00
|
|
|
setAmountOutFormValue(amountOut.toString())
|
2023-07-31 17:57:53 -07:00
|
|
|
}
|
2023-06-16 05:23:50 -07:00
|
|
|
},
|
2023-09-07 20:35:30 -07:00
|
|
|
[amountInFormValue, flipPrices, setFormErrors],
|
2023-06-16 05:23:50 -07:00
|
|
|
)
|
|
|
|
|
2023-08-08 06:26:12 -07:00
|
|
|
const handlePlaceStopLoss = useCallback(async () => {
|
|
|
|
const invalidFields = isFormValid({
|
|
|
|
amountIn: amountInAsDecimal.toNumber(),
|
|
|
|
triggerPrice,
|
|
|
|
})
|
|
|
|
if (Object.keys(invalidFields).length) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
const client = mangoStore.getState().client
|
|
|
|
const group = mangoStore.getState().group
|
|
|
|
const actions = mangoStore.getState().actions
|
|
|
|
const mangoAccount = mangoStore.getState().mangoAccount.current
|
|
|
|
const inputBank = mangoStore.getState().swap.inputBank
|
|
|
|
const outputBank = mangoStore.getState().swap.outputBank
|
|
|
|
|
|
|
|
if (!mangoAccount || !group || !inputBank || !outputBank || !triggerPrice)
|
|
|
|
return
|
|
|
|
setSubmitting(true)
|
|
|
|
|
|
|
|
const amountIn = amountInAsDecimal.toNumber()
|
|
|
|
|
2023-09-07 02:43:29 -07:00
|
|
|
const isReduceLong = !isReducingShort
|
2023-09-06 21:29:31 -07:00
|
|
|
|
2023-08-08 06:26:12 -07:00
|
|
|
try {
|
|
|
|
let tx
|
|
|
|
if (orderType === OrderTypes.STOP_LOSS) {
|
2023-09-06 21:29:31 -07:00
|
|
|
if (isReduceLong) {
|
|
|
|
tx = await client.tcsStopLossOnDeposit(
|
|
|
|
group,
|
|
|
|
mangoAccount,
|
|
|
|
inputBank,
|
|
|
|
outputBank,
|
|
|
|
parseFloat(triggerPrice),
|
|
|
|
flipPrices,
|
|
|
|
amountIn,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
tx = await client.tcsStopLossOnBorrow(
|
|
|
|
group,
|
|
|
|
mangoAccount,
|
|
|
|
outputBank,
|
|
|
|
inputBank,
|
|
|
|
parseFloat(triggerPrice),
|
2023-09-08 04:52:34 -07:00
|
|
|
!flipPrices,
|
2023-09-06 21:29:31 -07:00
|
|
|
amountIn,
|
|
|
|
null,
|
2023-09-08 04:38:17 -07:00
|
|
|
true,
|
2023-09-06 21:29:31 -07:00
|
|
|
null,
|
|
|
|
)
|
|
|
|
}
|
2023-08-08 06:26:12 -07:00
|
|
|
}
|
|
|
|
if (orderType === OrderTypes.TAKE_PROFIT) {
|
2023-09-06 21:29:31 -07:00
|
|
|
if (isReduceLong) {
|
|
|
|
tx = await client.tcsTakeProfitOnDeposit(
|
|
|
|
group,
|
|
|
|
mangoAccount,
|
|
|
|
inputBank,
|
|
|
|
outputBank,
|
|
|
|
parseFloat(triggerPrice),
|
|
|
|
flipPrices,
|
|
|
|
amountIn,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
tx = await client.tcsTakeProfitOnBorrow(
|
|
|
|
group,
|
|
|
|
mangoAccount,
|
|
|
|
outputBank,
|
|
|
|
inputBank,
|
|
|
|
parseFloat(triggerPrice),
|
2023-09-08 04:52:34 -07:00
|
|
|
!flipPrices,
|
2023-09-06 21:29:31 -07:00
|
|
|
amountIn,
|
|
|
|
null,
|
2023-09-08 04:38:17 -07:00
|
|
|
true,
|
2023-09-06 21:29:31 -07:00
|
|
|
null,
|
|
|
|
)
|
|
|
|
}
|
2023-08-08 06:26:12 -07:00
|
|
|
}
|
|
|
|
notify({
|
|
|
|
title: 'Transaction confirmed',
|
|
|
|
type: 'success',
|
2023-08-13 04:21:08 -07:00
|
|
|
txid: tx?.signature,
|
2023-08-08 06:26:12 -07:00
|
|
|
noSound: true,
|
|
|
|
})
|
|
|
|
actions.fetchGroup()
|
2023-08-13 04:21:08 -07:00
|
|
|
await actions.reloadMangoAccount(tx?.slot)
|
2023-08-08 06:26:12 -07:00
|
|
|
} 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',
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
console.error('Swap error:', e)
|
|
|
|
} finally {
|
|
|
|
setSubmitting(false)
|
|
|
|
}
|
|
|
|
}, [
|
|
|
|
flipPrices,
|
|
|
|
orderType,
|
|
|
|
quotePrice,
|
|
|
|
triggerPrice,
|
|
|
|
amountInAsDecimal,
|
|
|
|
amountOutFormValue,
|
2023-09-07 02:43:29 -07:00
|
|
|
isReducingShort,
|
2023-08-08 06:26:12 -07:00
|
|
|
])
|
2023-07-30 23:05:26 -07:00
|
|
|
|
2023-08-02 06:20:09 -07:00
|
|
|
const orderDescription = useMemo(() => {
|
|
|
|
if (
|
|
|
|
!amountInFormValue ||
|
|
|
|
!amountOutFormValue ||
|
2023-08-04 06:48:17 -07:00
|
|
|
!inputBankName ||
|
|
|
|
!outputBankName ||
|
2023-08-02 06:20:09 -07:00
|
|
|
!triggerPrice
|
|
|
|
)
|
|
|
|
return
|
2023-08-03 19:06:09 -07:00
|
|
|
|
2023-09-07 06:26:55 -07:00
|
|
|
const formattedInputTokenName = formatTokenSymbol(inputBankName)
|
|
|
|
const formattedOutputTokenName = formatTokenSymbol(outputBankName)
|
|
|
|
|
2023-08-08 20:14:59 -07:00
|
|
|
const quoteString = flipPrices
|
2023-09-07 06:26:55 -07:00
|
|
|
? `${formattedInputTokenName} per ${formattedOutputTokenName}`
|
|
|
|
: `${formattedOutputTokenName} per ${formattedInputTokenName}`
|
2023-08-03 19:06:09 -07:00
|
|
|
|
2023-09-07 06:26:55 -07:00
|
|
|
const action = isReducingShort ? t('buy') : t('sell')
|
2023-09-07 05:37:33 -07:00
|
|
|
|
2023-09-07 19:39:39 -07:00
|
|
|
// calc borrowed amount when reducing short
|
|
|
|
let borrowToReduceShort = 0
|
|
|
|
if (isReducingShort && mangoAccountAddress) {
|
|
|
|
const balance = getOutputTokenBalance(outputBank)
|
|
|
|
if (balance >= 0 && parseFloat(amountOutFormValue) > balance) {
|
|
|
|
const amount = new Decimal(balance)
|
|
|
|
.sub(new Decimal(amountOutFormValue))
|
|
|
|
.toNumber()
|
|
|
|
borrowToReduceShort = Math.abs(amount)
|
2023-09-08 04:38:17 -07:00
|
|
|
}
|
|
|
|
if (balance < 0) {
|
|
|
|
borrowToReduceShort = parseFloat(amountOutFormValue)
|
|
|
|
}
|
2023-09-07 19:39:39 -07:00
|
|
|
}
|
|
|
|
|
2023-09-07 05:37:33 -07:00
|
|
|
// xor of two flip flags
|
|
|
|
const shouldFlip = flipPrices !== isReducingShort
|
|
|
|
const orderTypeString =
|
|
|
|
orderType === OrderTypes.STOP_LOSS
|
|
|
|
? shouldFlip
|
|
|
|
? t('trade:rises-to')
|
|
|
|
: t('trade:falls-to')
|
|
|
|
: shouldFlip
|
|
|
|
? t('trade:falls-to')
|
|
|
|
: t('trade:rises-to')
|
|
|
|
|
2023-09-07 19:39:39 -07:00
|
|
|
return borrowToReduceShort
|
|
|
|
? t('trade:trigger-order-desc-with-borrow', {
|
|
|
|
action: action,
|
|
|
|
amount: floorToDecimal(amountInFormValue, inputBankDecimals),
|
|
|
|
borrowAmount: borrowToReduceShort,
|
|
|
|
orderType: orderTypeString,
|
|
|
|
priceUnit: quoteString,
|
|
|
|
quoteSymbol: formattedOutputTokenName,
|
|
|
|
symbol: formattedInputTokenName,
|
|
|
|
triggerPrice: priceToDisplayString(triggerPrice),
|
|
|
|
})
|
|
|
|
: t('trade:trigger-order-desc', {
|
|
|
|
action: action,
|
|
|
|
amount: floorToDecimal(amountInFormValue, inputBankDecimals),
|
|
|
|
orderType: orderTypeString,
|
|
|
|
priceUnit: quoteString,
|
|
|
|
symbol: formattedInputTokenName,
|
|
|
|
triggerPrice: priceToDisplayString(triggerPrice),
|
|
|
|
})
|
2023-08-02 06:20:09 -07:00
|
|
|
}, [
|
|
|
|
amountInFormValue,
|
|
|
|
amountOutFormValue,
|
|
|
|
flipPrices,
|
2023-08-04 06:48:17 -07:00
|
|
|
inputBankDecimals,
|
|
|
|
inputBankName,
|
2023-09-07 19:39:39 -07:00
|
|
|
mangoAccountAddress,
|
2023-08-03 19:06:09 -07:00
|
|
|
orderType,
|
2023-08-04 06:48:17 -07:00
|
|
|
outputBankDecimals,
|
|
|
|
outputBankName,
|
2023-08-02 06:20:09 -07:00
|
|
|
triggerPrice,
|
2023-09-07 02:43:29 -07:00
|
|
|
isReducingShort,
|
2023-08-02 06:20:09 -07:00
|
|
|
])
|
|
|
|
|
|
|
|
const triggerPriceSuffix = useMemo(() => {
|
2023-08-04 06:48:17 -07:00
|
|
|
if (!inputBankName || !outputBankName) return
|
2023-08-08 06:26:12 -07:00
|
|
|
if (flipPrices) {
|
2023-08-04 06:48:17 -07:00
|
|
|
return `${inputBankName} per ${outputBankName}`
|
2023-08-03 03:34:57 -07:00
|
|
|
} else {
|
2023-08-04 06:48:17 -07:00
|
|
|
return `${outputBankName} per ${inputBankName}`
|
2023-08-02 06:20:09 -07:00
|
|
|
}
|
2023-08-04 06:48:17 -07:00
|
|
|
}, [flipPrices, inputBankName, outputBankName])
|
2023-08-01 22:32:20 -07:00
|
|
|
|
2023-08-03 03:34:57 -07:00
|
|
|
const toggleFlipPrices = useCallback(
|
2023-08-01 22:32:20 -07:00
|
|
|
(flip: boolean) => {
|
2023-08-04 06:48:17 -07:00
|
|
|
if (!inputBankName || !outputBankName) return
|
2023-08-03 23:14:41 -07:00
|
|
|
setFormErrors({})
|
2023-08-16 19:58:35 -07:00
|
|
|
set((state) => {
|
|
|
|
state.swap.flipPrices = flip
|
|
|
|
})
|
2023-08-01 22:32:20 -07:00
|
|
|
},
|
2023-08-16 19:58:35 -07:00
|
|
|
[inputBankName, outputBankName, setFormErrors],
|
2023-08-01 22:32:20 -07:00
|
|
|
)
|
2023-06-16 05:23:50 -07:00
|
|
|
|
2023-08-03 06:44:23 -07:00
|
|
|
const handleOrderTypeChange = useCallback(
|
|
|
|
(type: string) => {
|
2023-08-03 23:14:41 -07:00
|
|
|
setFormErrors({})
|
2023-08-03 06:44:23 -07:00
|
|
|
const newType = type as OrderTypes
|
|
|
|
setOrderType(newType)
|
2023-09-07 01:22:31 -07:00
|
|
|
const triggerMultiplier = getOrderTypeMultiplier(
|
|
|
|
newType,
|
|
|
|
flipPrices,
|
2023-09-07 02:43:29 -07:00
|
|
|
isReducingShort,
|
2023-09-07 01:22:31 -07:00
|
|
|
)
|
2023-09-07 02:43:29 -07:00
|
|
|
const trigger = priceToDisplayString(
|
|
|
|
quotePrice * triggerMultiplier,
|
|
|
|
).toString()
|
2023-09-07 20:35:30 -07:00
|
|
|
set((state) => {
|
|
|
|
state.swap.triggerPrice = trigger
|
|
|
|
})
|
2023-08-03 19:06:09 -07:00
|
|
|
if (amountInAsDecimal.gt(0)) {
|
|
|
|
const amountOut = getAmountOut(
|
|
|
|
amountInAsDecimal.toString(),
|
2023-08-08 06:26:12 -07:00
|
|
|
flipPrices,
|
2023-08-03 19:06:09 -07:00
|
|
|
trigger,
|
|
|
|
).toString()
|
|
|
|
setAmountOutFormValue(amountOut)
|
|
|
|
}
|
2023-08-03 06:44:23 -07:00
|
|
|
},
|
2023-09-07 02:43:29 -07:00
|
|
|
[flipPrices, quotePrice, setFormErrors, isReducingShort],
|
2023-08-03 06:44:23 -07:00
|
|
|
)
|
|
|
|
|
2023-09-07 06:26:55 -07:00
|
|
|
const onClick = !connected ? connect : handlePlaceStopLoss
|
2023-08-10 17:52:42 -07:00
|
|
|
|
2023-06-16 05:23:50 -07:00
|
|
|
return (
|
|
|
|
<>
|
2023-09-09 06:00:45 -07:00
|
|
|
<div className="mb-3">
|
|
|
|
<InlineNotification
|
|
|
|
desc={
|
2023-09-18 18:22:10 -07:00
|
|
|
<div className="flex items-center">
|
2023-09-09 06:00:45 -07:00
|
|
|
<span className="mr-1">{t('swap:trigger-beta')}</span>
|
|
|
|
<Tooltip
|
|
|
|
content={
|
|
|
|
<ul className="ml-4 list-disc space-y-2">
|
|
|
|
<li>
|
|
|
|
Trigger orders on long-tail assets could be susceptible to
|
|
|
|
oracle manipulation.
|
|
|
|
</li>
|
|
|
|
<li>
|
|
|
|
Trigger orders rely on a sufficient amount of well
|
|
|
|
collateralized liquidators.
|
|
|
|
</li>
|
|
|
|
<li>
|
|
|
|
The slippage on existing orders could be higher/lower than
|
|
|
|
what's estimated.
|
|
|
|
</li>
|
|
|
|
<li>
|
|
|
|
The amount of tokens used to fill your order can vary and
|
|
|
|
depends on the final execution price.
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
}
|
|
|
|
>
|
2023-09-18 18:22:10 -07:00
|
|
|
<span className="tooltip-underline whitespace-nowrap">
|
2023-09-09 06:00:45 -07:00
|
|
|
{t('swap:important-info')}
|
|
|
|
</span>
|
|
|
|
</Tooltip>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
type="info"
|
|
|
|
/>
|
|
|
|
</div>
|
2023-09-06 06:06:57 -07:00
|
|
|
<ReduceInputTokenInput
|
2023-07-25 19:48:13 -07:00
|
|
|
className="rounded-b-none"
|
2023-08-01 22:32:20 -07:00
|
|
|
error={formErrors.amountIn}
|
2023-07-25 19:48:13 -07:00
|
|
|
handleAmountInChange={handleAmountInChange}
|
2023-09-06 06:06:57 -07:00
|
|
|
setShowTokenSelect={() => handleTokenSelect('reduce-input')}
|
2023-08-01 05:21:19 -07:00
|
|
|
handleMax={handleMax}
|
2023-08-03 06:44:23 -07:00
|
|
|
isTriggerOrder
|
2023-07-25 19:48:13 -07:00
|
|
|
/>
|
2023-08-23 15:17:04 -07:00
|
|
|
<div className="bg-th-bkg-2 p-3 pt-0">
|
|
|
|
{swapFormSizeUi === 'slider' ? (
|
|
|
|
<SwapSlider
|
|
|
|
useMargin={false}
|
|
|
|
amount={amountInAsDecimal.toNumber()}
|
|
|
|
onChange={(v) => handleAmountInUi(v)}
|
|
|
|
step={1 / 10 ** (inputBankDecimals || 6)}
|
2023-09-07 00:59:04 -07:00
|
|
|
maxAmount={useAbsInputPosition}
|
2023-08-23 15:17:04 -07:00
|
|
|
/>
|
|
|
|
) : (
|
|
|
|
<div className="-mt-2">
|
|
|
|
<PercentageSelectButtons
|
|
|
|
amountIn={amountInAsDecimal.toString()}
|
|
|
|
setAmountIn={(v) => handleAmountInUi(v)}
|
|
|
|
useMargin={false}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
2023-06-16 05:23:50 -07:00
|
|
|
<div
|
2023-09-06 20:51:57 -07:00
|
|
|
className={`grid grid-cols-2 gap-2 bg-th-bkg-2 p-3 pt-1`}
|
2023-06-16 05:23:50 -07:00
|
|
|
id="swap-step-two"
|
|
|
|
>
|
2023-08-03 06:44:23 -07:00
|
|
|
<div className="col-span-1">
|
|
|
|
<p className="mb-2 text-th-fgd-2">{t('trade:order-type')}</p>
|
|
|
|
<Select
|
|
|
|
value={t(orderType)}
|
|
|
|
onChange={(type) => handleOrderTypeChange(type)}
|
|
|
|
className="w-full"
|
|
|
|
buttonClassName="ring-transparent rounded-t-lg rounded-b-lg focus:outline-none md:hover:bg-th-bkg-1 md:hover:ring-transparent focus-visible:bg-th-bkg-3 whitespace-nowrap"
|
|
|
|
>
|
|
|
|
{ORDER_TYPES.map((type) => (
|
|
|
|
<Select.Option key={type} value={type}>
|
|
|
|
{t(type)}
|
|
|
|
</Select.Option>
|
|
|
|
))}
|
|
|
|
</Select>
|
|
|
|
</div>
|
|
|
|
<div className="col-span-1">
|
2023-08-04 05:03:19 -07:00
|
|
|
<div className="mb-2 flex items-end justify-between">
|
|
|
|
<p className="text-th-fgd-2">{t('trade:trigger-price')}</p>
|
|
|
|
<p
|
|
|
|
className={`font-mono text-xs ${
|
|
|
|
triggerPriceDifference >= 0 ? 'text-th-up' : 'text-th-down'
|
|
|
|
}`}
|
|
|
|
>
|
|
|
|
{triggerPriceDifference
|
2023-09-07 01:22:31 -07:00
|
|
|
? (triggerPriceDifference > 0 ? '+' : '') +
|
|
|
|
triggerPriceDifference.toFixed(2)
|
2023-08-04 05:03:19 -07:00
|
|
|
: '0.00'}
|
|
|
|
%
|
|
|
|
</p>
|
2023-08-02 06:20:09 -07:00
|
|
|
</div>
|
|
|
|
<div className="flex items-center">
|
|
|
|
<div className="relative w-full">
|
|
|
|
<NumberFormat
|
|
|
|
inputMode="decimal"
|
|
|
|
thousandSeparator=","
|
|
|
|
allowNegative={false}
|
|
|
|
isNumericString={true}
|
|
|
|
name="triggerPrice"
|
|
|
|
id="triggerPrice"
|
2023-08-03 06:44:23 -07:00
|
|
|
className="h-10 w-full rounded-lg bg-th-input-bkg p-3 pl-8 font-mono text-sm text-th-fgd-1 focus:outline-none md:hover:bg-th-bkg-1"
|
2023-08-02 06:20:09 -07:00
|
|
|
placeholder="0.00"
|
|
|
|
value={triggerPrice}
|
|
|
|
onValueChange={handleTriggerPrice}
|
|
|
|
isAllowed={withValueLimit}
|
|
|
|
/>
|
2023-08-17 16:47:11 -07:00
|
|
|
<div className="absolute left-2 top-1/2 -translate-y-1/2">
|
2023-08-02 06:20:09 -07:00
|
|
|
<TokenLogo
|
2023-08-08 06:26:12 -07:00
|
|
|
bank={flipPrices ? inputBank : outputBank}
|
2023-08-02 06:20:09 -07:00
|
|
|
size={16}
|
|
|
|
/>
|
|
|
|
</div>
|
2023-08-01 22:32:20 -07:00
|
|
|
</div>
|
2023-08-03 06:44:23 -07:00
|
|
|
</div>
|
2023-08-04 05:03:19 -07:00
|
|
|
<div className="flex justify-end">
|
|
|
|
<LinkButton
|
2023-08-17 16:47:11 -07:00
|
|
|
className="flex items-center text-xxs font-normal text-th-fgd-3"
|
2023-08-04 05:03:19 -07:00
|
|
|
onClick={() => toggleFlipPrices(!flipPrices)}
|
2023-08-03 06:44:23 -07:00
|
|
|
>
|
2023-08-04 05:03:19 -07:00
|
|
|
<span className="mr-1">{triggerPriceSuffix}</span>
|
|
|
|
<ArrowsRightLeftIcon className="h-3.5 w-3.5" />
|
|
|
|
</LinkButton>
|
2023-06-16 05:23:50 -07:00
|
|
|
</div>
|
2023-08-01 22:32:20 -07:00
|
|
|
</div>
|
2023-08-03 23:14:41 -07:00
|
|
|
{formErrors.triggerPrice ? (
|
|
|
|
<div className="col-span-2 flex justify-center">
|
|
|
|
<InlineNotification
|
|
|
|
type="error"
|
|
|
|
desc={formErrors.triggerPrice}
|
|
|
|
hideBorder
|
|
|
|
hidePadding
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
) : null}
|
2023-06-16 05:23:50 -07:00
|
|
|
</div>
|
2023-09-06 06:06:57 -07:00
|
|
|
<ReduceOutputTokenInput
|
2023-07-25 19:48:13 -07:00
|
|
|
handleAmountOutChange={handleAmountOutChange}
|
2023-09-06 06:06:57 -07:00
|
|
|
setShowTokenSelect={() => handleTokenSelect('reduce-output')}
|
2023-07-25 19:48:13 -07:00
|
|
|
/>
|
2023-09-07 05:37:33 -07:00
|
|
|
{orderDescription ? (
|
|
|
|
<div className="mt-4">
|
|
|
|
<InlineNotification desc={orderDescription} type="info" />
|
|
|
|
</div>
|
|
|
|
) : null}
|
2023-08-03 19:06:09 -07:00
|
|
|
{ipAllowed ? (
|
|
|
|
<Button
|
2023-09-10 06:26:52 -07:00
|
|
|
disabled={borrowExceedsLimitInPeriod || tokenPositionsFull}
|
2023-08-10 17:52:42 -07:00
|
|
|
onClick={onClick}
|
2023-08-17 16:47:11 -07:00
|
|
|
className="mb-4 mt-6 flex w-full items-center justify-center text-base"
|
2023-08-03 19:06:09 -07:00
|
|
|
size="large"
|
|
|
|
>
|
2023-08-10 17:52:42 -07:00
|
|
|
{connected ? (
|
2023-09-07 06:26:55 -07:00
|
|
|
submitting ? (
|
2023-08-10 17:52:42 -07:00
|
|
|
<Loading />
|
|
|
|
) : (
|
|
|
|
<span>{t('swap:place-limit-order')}</span>
|
|
|
|
)
|
|
|
|
) : (
|
|
|
|
<div className="flex items-center">
|
|
|
|
<LinkIcon className="mr-2 h-5 w-5" />
|
|
|
|
{t('connect')}
|
|
|
|
</div>
|
|
|
|
)}
|
2023-08-03 19:06:09 -07:00
|
|
|
</Button>
|
|
|
|
) : (
|
|
|
|
<Button
|
|
|
|
disabled
|
2023-08-17 16:47:11 -07:00
|
|
|
className="mb-4 mt-6 w-full leading-tight"
|
2023-08-03 19:06:09 -07:00
|
|
|
size="large"
|
|
|
|
>
|
|
|
|
{t('country-not-allowed', {
|
|
|
|
country: ipCountry ? `(${ipCountry})` : '',
|
|
|
|
})}
|
|
|
|
</Button>
|
|
|
|
)}
|
2023-09-10 06:26:52 -07:00
|
|
|
{tokenPositionsFull ? (
|
|
|
|
<div className="pb-4">
|
|
|
|
<InlineNotification
|
|
|
|
type="error"
|
|
|
|
desc={
|
|
|
|
<>
|
|
|
|
{t('error-token-positions-full')}{' '}
|
2023-10-20 08:11:19 -07:00
|
|
|
<Link href={''} onClick={() => setShowSettingsModal(true)}>
|
2023-09-10 06:26:52 -07:00
|
|
|
{t('manage')}
|
|
|
|
</Link>
|
|
|
|
</>
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
) : null}
|
2023-08-28 04:59:36 -07:00
|
|
|
{borrowExceedsLimitInPeriod &&
|
|
|
|
remainingBorrowsInPeriod &&
|
|
|
|
timeToNextPeriod ? (
|
2023-08-27 23:06:16 -07:00
|
|
|
<div className="mb-4">
|
|
|
|
<InlineNotification
|
|
|
|
type="error"
|
|
|
|
desc={t('error-borrow-exceeds-limit', {
|
2023-08-28 04:59:36 -07:00
|
|
|
remaining: formatCurrencyValue(remainingBorrowsInPeriod),
|
2023-08-27 23:06:16 -07:00
|
|
|
resetTime: dayjs().to(dayjs().add(timeToNextPeriod, 'second')),
|
|
|
|
})}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
) : null}
|
2023-06-16 05:23:50 -07:00
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-09-10 06:26:52 -07:00
|
|
|
export default TriggerSwapForm
|