use jupiter on trade page
This commit is contained in:
parent
d2a6b1e616
commit
e478d6405a
|
@ -14,18 +14,18 @@ import {
|
|||
} from 'utils/constants'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
|
||||
export const TRITON_DEDICATED_URL = process.env.NEXT_PUBLIC_TRITON_TOKEN
|
||||
? `https://mango.rpcpool.com/${process.env.NEXT_PUBLIC_TRITON_TOKEN}`
|
||||
: 'https://mango.rpcpool.com/946ef7337da3f5b8d3e4a34e7f88'
|
||||
|
||||
const RPC_URLS = [
|
||||
{
|
||||
label: 'Triton Shared',
|
||||
value:
|
||||
process.env.NEXT_PUBLIC_ENDPOINT ||
|
||||
'https://mango.rpcpool.com/946ef7337da3f5b8d3e4a34e7f88',
|
||||
value: process.env.NEXT_PUBLIC_ENDPOINT || TRITON_DEDICATED_URL,
|
||||
},
|
||||
{
|
||||
label: 'Triton Dedicated',
|
||||
value: process.env.NEXT_PUBLIC_TRITON_TOKEN
|
||||
? `https://mango.rpcpool.com/${process.env.NEXT_PUBLIC_TRITON_TOKEN}`
|
||||
: 'https://mango.rpcpool.com/946ef7337da3f5b8d3e4a34e7f88',
|
||||
value: TRITON_DEDICATED_URL,
|
||||
},
|
||||
// {
|
||||
// label: 'Genesys Go',
|
||||
|
|
|
@ -15,7 +15,6 @@ import mangoStore from '@store/mangoStore'
|
|||
import ContentBox from '../shared/ContentBox'
|
||||
import SwapReviewRouteInfo from './SwapReviewRouteInfo'
|
||||
import TokenSelect from './TokenSelect'
|
||||
import useDebounce from '../shared/useDebounce'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import SwapFormTokenList from './SwapFormTokenList'
|
||||
import { Transition } from '@headlessui/react'
|
||||
|
@ -85,27 +84,25 @@ const SwapForm = () => {
|
|||
amountOut: amountOutFormValue,
|
||||
swapMode,
|
||||
} = mangoStore((s) => s.swap)
|
||||
const [debouncedAmountIn] = useDebounce(amountInFormValue, 300)
|
||||
const [debouncedAmountOut] = useDebounce(amountOutFormValue, 300)
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const { connected, publicKey } = useWallet()
|
||||
|
||||
const amountInAsDecimal: Decimal | null = useMemo(() => {
|
||||
return Number(debouncedAmountIn)
|
||||
? new Decimal(debouncedAmountIn)
|
||||
return Number(amountInFormValue)
|
||||
? new Decimal(amountInFormValue)
|
||||
: new Decimal(0)
|
||||
}, [debouncedAmountIn])
|
||||
}, [amountInFormValue])
|
||||
|
||||
const amountOutAsDecimal: Decimal | null = useMemo(() => {
|
||||
return Number(debouncedAmountOut)
|
||||
? new Decimal(debouncedAmountOut)
|
||||
return Number(amountOutFormValue)
|
||||
? new Decimal(amountOutFormValue)
|
||||
: new Decimal(0)
|
||||
}, [debouncedAmountOut])
|
||||
}, [amountOutFormValue])
|
||||
|
||||
const { bestRoute, routes } = useQuoteRoutes({
|
||||
inputMint: inputBank?.mint.toString() || USDC_MINT,
|
||||
outputMint: outputBank?.mint.toString() || MANGO_MINT,
|
||||
amount: swapMode === 'ExactIn' ? debouncedAmountIn : debouncedAmountOut,
|
||||
amount: swapMode === 'ExactIn' ? amountInFormValue : amountOutFormValue,
|
||||
slippage,
|
||||
swapMode,
|
||||
wallet: publicKey?.toBase58(),
|
||||
|
@ -127,6 +124,13 @@ const SwapForm = () => {
|
|||
[]
|
||||
)
|
||||
|
||||
const setAmountFromSlider = useCallback(
|
||||
(amount: string) => {
|
||||
setAmountInFormValue(amount, true)
|
||||
},
|
||||
[setAmountInFormValue]
|
||||
)
|
||||
|
||||
const setAmountOutFormValue = useCallback((amountOut: string) => {
|
||||
set((s) => {
|
||||
s.swap.amountOut = amountOut
|
||||
|
@ -214,20 +218,23 @@ const SwapForm = () => {
|
|||
setShowTokenSelect(undefined)
|
||||
}, [])
|
||||
|
||||
const handleSwitchTokens = useCallback(() => {
|
||||
if (amountInAsDecimal?.gt(0) && amountOutAsDecimal.gte(0)) {
|
||||
setAmountInFormValue(amountOutAsDecimal.toString())
|
||||
}
|
||||
const inputBank = mangoStore.getState().swap.inputBank
|
||||
const outputBank = mangoStore.getState().swap.outputBank
|
||||
set((s) => {
|
||||
s.swap.inputBank = outputBank
|
||||
s.swap.outputBank = inputBank
|
||||
})
|
||||
setAnimateSwitchArrow(
|
||||
(prevanimateSwitchArrow) => prevanimateSwitchArrow + 1
|
||||
)
|
||||
}, [setAmountInFormValue, amountOutAsDecimal, amountInAsDecimal])
|
||||
const handleSwitchTokens = useCallback(
|
||||
(amountIn: Decimal, amountOut: Decimal) => {
|
||||
if (amountIn?.gt(0) && amountOut.gte(0)) {
|
||||
setAmountInFormValue(amountOut.toString())
|
||||
}
|
||||
const inputBank = mangoStore.getState().swap.inputBank
|
||||
const outputBank = mangoStore.getState().swap.outputBank
|
||||
set((s) => {
|
||||
s.swap.inputBank = outputBank
|
||||
s.swap.outputBank = inputBank
|
||||
})
|
||||
setAnimateSwitchArrow(
|
||||
(prevanimateSwitchArrow) => prevanimateSwitchArrow + 1
|
||||
)
|
||||
},
|
||||
[setAmountInFormValue]
|
||||
)
|
||||
|
||||
const maintProjectedHealth = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
|
@ -385,7 +392,9 @@ const SwapForm = () => {
|
|||
<div className="-mb-2 flex justify-center">
|
||||
<button
|
||||
className="rounded-full border border-th-bkg-4 p-1.5 text-th-fgd-3 focus-visible:border-th-fgd-4 md:hover:text-th-active"
|
||||
onClick={handleSwitchTokens}
|
||||
onClick={() =>
|
||||
handleSwitchTokens(amountInAsDecimal, amountOutAsDecimal)
|
||||
}
|
||||
>
|
||||
<ArrowDownIcon
|
||||
className="h-5 w-5"
|
||||
|
@ -444,13 +453,13 @@ const SwapForm = () => {
|
|||
<SwapSlider
|
||||
useMargin={useMargin}
|
||||
amount={amountInAsDecimal.toNumber()}
|
||||
onChange={(v) => setAmountInFormValue(v, true)}
|
||||
onChange={setAmountFromSlider}
|
||||
step={1 / 10 ** (inputBank?.mintDecimals || 6)}
|
||||
/>
|
||||
) : (
|
||||
<PercentageSelectButtons
|
||||
amountIn={amountInAsDecimal.toString()}
|
||||
setAmountIn={(v) => setAmountInFormValue(v, true)}
|
||||
setAmountIn={setAmountFromSlider}
|
||||
useMargin={useMargin}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -117,7 +117,7 @@ const prepareMangoRouterInstructions = async (
|
|||
return [instructions, []]
|
||||
}
|
||||
|
||||
const fetchJupiterTransaction = async (
|
||||
export const fetchJupiterTransaction = async (
|
||||
connection: Connection,
|
||||
selectedRoute: RouteInfo,
|
||||
userPublicKey: PublicKey,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import { useCallback } from 'react'
|
||||
import LeverageSlider from '../shared/LeverageSlider'
|
||||
import { useTokenMax } from './useTokenMax'
|
||||
|
||||
|
@ -17,10 +16,6 @@ const SwapSlider = ({
|
|||
const { mangoAccount } = useMangoAccount()
|
||||
const { amount: tokenMax, amountWithBorrow } = useTokenMax(useMargin)
|
||||
|
||||
const handleChange = useCallback((x: string) => {
|
||||
onChange(x)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
{!mangoAccount ? (
|
||||
|
@ -36,7 +31,7 @@ const SwapSlider = ({
|
|||
leverageMax={
|
||||
useMargin ? amountWithBorrow.toNumber() : tokenMax.toNumber()
|
||||
}
|
||||
onChange={handleChange}
|
||||
onChange={onChange}
|
||||
step={step}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -5,6 +5,8 @@ import Decimal from 'decimal.js'
|
|||
import { RouteInfo } from 'types/jupiter'
|
||||
import { MANGO_ROUTER_API_URL } from 'utils/constants'
|
||||
import useJupiterSwapData from './useJupiterSwapData'
|
||||
import useDebounce from '@components/shared/useDebounce'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
type SwapModes = 'ALL' | 'JUPITER' | 'MANGO'
|
||||
|
||||
|
@ -16,6 +18,7 @@ type useQuoteRoutesPropTypes = {
|
|||
swapMode: string
|
||||
wallet: string | undefined | null
|
||||
mode?: SwapModes
|
||||
enabled?: () => boolean
|
||||
}
|
||||
|
||||
const fetchJupiterRoutes = async (
|
||||
|
@ -185,24 +188,36 @@ const useQuoteRoutes = ({
|
|||
swapMode,
|
||||
wallet,
|
||||
mode = 'ALL',
|
||||
enabled,
|
||||
}: useQuoteRoutesPropTypes) => {
|
||||
const [debouncedAmount] = useDebounce(amount, 250)
|
||||
const { inputTokenInfo, outputTokenInfo } = useJupiterSwapData()
|
||||
|
||||
const decimals =
|
||||
swapMode === 'ExactIn'
|
||||
const decimals = useMemo(() => {
|
||||
return swapMode === 'ExactIn'
|
||||
? inputTokenInfo?.decimals || 6
|
||||
: outputTokenInfo?.decimals || 6
|
||||
}, [swapMode, inputTokenInfo?.decimals, outputTokenInfo?.decimals])
|
||||
|
||||
const nativeAmount =
|
||||
amount && !Number.isNaN(+amount)
|
||||
? new Decimal(amount).mul(10 ** decimals)
|
||||
const nativeAmount = useMemo(() => {
|
||||
return debouncedAmount && !Number.isNaN(+debouncedAmount)
|
||||
? new Decimal(debouncedAmount).mul(10 ** decimals)
|
||||
: new Decimal(0)
|
||||
}, [debouncedAmount, decimals])
|
||||
|
||||
const res = useQuery<
|
||||
{ routes: RouteInfo[]; bestRoute: RouteInfo | null },
|
||||
Error
|
||||
>(
|
||||
['swap-routes', inputMint, outputMint, amount, slippage, swapMode, wallet],
|
||||
[
|
||||
'swap-routes',
|
||||
inputMint,
|
||||
outputMint,
|
||||
debouncedAmount,
|
||||
slippage,
|
||||
swapMode,
|
||||
wallet,
|
||||
],
|
||||
async () =>
|
||||
handleGetRoutes(
|
||||
inputMint,
|
||||
|
@ -216,8 +231,8 @@ const useQuoteRoutes = ({
|
|||
),
|
||||
{
|
||||
cacheTime: 1000 * 60,
|
||||
staleTime: 1000 * 30,
|
||||
enabled: amount ? true : false,
|
||||
staleTime: 1000 * 3,
|
||||
enabled: enabled ? enabled() : amount ? true : false,
|
||||
retry: 3,
|
||||
}
|
||||
)
|
||||
|
|
|
@ -58,6 +58,7 @@ import { useWallet } from '@solana/wallet-adapter-react'
|
|||
import { useEnhancedWallet } from '@components/wallet/EnhancedWalletProvider'
|
||||
import { isMangoError } from 'types'
|
||||
import InlineNotification from '@components/shared/InlineNotification'
|
||||
import SpotMarketOrderSwapForm from './SpotMarketOrderSwapForm'
|
||||
|
||||
const set = mangoStore.getState().set
|
||||
|
||||
|
@ -66,10 +67,10 @@ export const successSound = new Howl({
|
|||
volume: 0.5,
|
||||
})
|
||||
|
||||
const INPUT_SUFFIX_CLASSNAMES =
|
||||
export const INPUT_SUFFIX_CLASSNAMES =
|
||||
'absolute right-[1px] top-1/2 flex h-[calc(100%-2px)] -translate-y-1/2 items-center rounded-r-md bg-th-input-bkg px-2 text-xs font-normal text-th-fgd-4'
|
||||
|
||||
const INPUT_PREFIX_CLASSNAMES =
|
||||
export const INPUT_PREFIX_CLASSNAMES =
|
||||
'absolute left-2 top-1/2 h-5 w-5 flex-shrink-0 -translate-y-1/2'
|
||||
|
||||
export const DEFAULT_CHECKBOX_SETTINGS = {
|
||||
|
@ -508,281 +509,293 @@ const AdvancedTradeForm = () => {
|
|||
values={['Limit', 'Market']}
|
||||
/>
|
||||
</div>
|
||||
<form onSubmit={(e) => handleSubmit(e)}>
|
||||
<div className="mt-3 px-3 md:px-4">
|
||||
{tradeForm.tradeType === 'Limit' ? (
|
||||
<>
|
||||
<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="relative">
|
||||
{quoteLogoURI ? (
|
||||
<div className={INPUT_PREFIX_CLASSNAMES}>
|
||||
<Image alt="" width="20" height="20" src={quoteLogoURI} />
|
||||
{tradeForm.tradeType === 'Market' &&
|
||||
selectedMarket instanceof Serum3Market ? (
|
||||
<>
|
||||
<SpotMarketOrderSwapForm />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<form onSubmit={(e) => handleSubmit(e)}>
|
||||
<div className="mt-3 px-3 md:px-4">
|
||||
{tradeForm.tradeType === 'Limit' ? (
|
||||
<>
|
||||
<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="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={tickDecimals}
|
||||
name="price"
|
||||
id="price"
|
||||
className="flex w-full items-center rounded-md 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-visible:border-th-fgd-4 lg:text-base"
|
||||
placeholder="0.00"
|
||||
value={tradeForm.price}
|
||||
onValueChange={handlePriceChange}
|
||||
/>
|
||||
<div className={INPUT_SUFFIX_CLASSNAMES}>{quoteSymbol}</div>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
<MaxSizeButton
|
||||
minOrderDecimals={minOrderDecimals}
|
||||
tickDecimals={tickDecimals}
|
||||
useMargin={savedCheckboxSettings.margin}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<div className="relative">
|
||||
<NumberFormat
|
||||
inputMode="decimal"
|
||||
thousandSeparator=","
|
||||
allowNegative={false}
|
||||
isNumericString={true}
|
||||
decimalScale={minOrderDecimals}
|
||||
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={tradeForm.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={tickDecimals}
|
||||
name="quote"
|
||||
id="quote"
|
||||
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"
|
||||
placeholder="0.00"
|
||||
value={tradeForm.quoteSize}
|
||||
onValueChange={handleQuoteSizeChange}
|
||||
/>
|
||||
<div className={INPUT_SUFFIX_CLASSNAMES}>{quoteSymbol}</div>
|
||||
</div>
|
||||
{minOrderSize &&
|
||||
tradeForm.baseSize &&
|
||||
parseFloat(tradeForm.baseSize) < minOrderSize ? (
|
||||
<div className="mt-1">
|
||||
<InlineNotification
|
||||
type="error"
|
||||
desc={t('trade:min-order-size-error', {
|
||||
minSize: minOrderSize,
|
||||
symbol: baseSymbol,
|
||||
})}
|
||||
hideBorder
|
||||
hidePadding
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 flex">
|
||||
{selectedMarket instanceof Serum3Market ? (
|
||||
tradeFormSizeUi === 'slider' ? (
|
||||
<SpotSlider
|
||||
minOrderDecimals={minOrderDecimals}
|
||||
tickDecimals={tickDecimals}
|
||||
step={tradeForm.side === 'buy' ? tickSize : minOrderSize}
|
||||
useMargin={savedCheckboxSettings.margin}
|
||||
/>
|
||||
) : (
|
||||
<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={tickDecimals}
|
||||
name="price"
|
||||
id="price"
|
||||
className="flex w-full items-center rounded-md 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-visible:border-th-fgd-4 lg:text-base"
|
||||
placeholder="0.00"
|
||||
value={tradeForm.price}
|
||||
onValueChange={handlePriceChange}
|
||||
<SpotButtonGroup
|
||||
minOrderDecimals={minOrderDecimals}
|
||||
tickDecimals={tickDecimals}
|
||||
useMargin={savedCheckboxSettings.margin}
|
||||
/>
|
||||
)
|
||||
) : tradeFormSizeUi === 'slider' ? (
|
||||
<PerpSlider
|
||||
minOrderDecimals={minOrderDecimals}
|
||||
tickDecimals={tickDecimals}
|
||||
/>
|
||||
<div className={INPUT_SUFFIX_CLASSNAMES}>{quoteSymbol}</div>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
<MaxSizeButton
|
||||
minOrderDecimals={minOrderDecimals}
|
||||
tickDecimals={tickDecimals}
|
||||
) : (
|
||||
<PerpButtonGroup
|
||||
minOrderDecimals={minOrderDecimals}
|
||||
tickDecimals={tickDecimals}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-wrap px-5 md:flex-nowrap">
|
||||
{tradeForm.tradeType === 'Limit' ? (
|
||||
<div className="flex">
|
||||
<div className="mr-3 mt-4" id="trade-step-six">
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
delay={100}
|
||||
placement="left"
|
||||
content={t('trade:tooltip-post')}
|
||||
>
|
||||
<Checkbox
|
||||
checked={tradeForm.postOnly}
|
||||
onChange={(e) => handlePostOnlyChange(e.target.checked)}
|
||||
>
|
||||
{t('trade:post')}
|
||||
</Checkbox>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="mr-3 mt-4" id="trade-step-seven">
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
delay={100}
|
||||
placement="left"
|
||||
content={t('trade:tooltip-ioc')}
|
||||
>
|
||||
<div className="flex items-center text-xs text-th-fgd-3">
|
||||
<Checkbox
|
||||
checked={tradeForm.ioc}
|
||||
onChange={(e) => handleIocChange(e.target.checked)}
|
||||
>
|
||||
IOC
|
||||
</Checkbox>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{selectedMarket instanceof Serum3Market ? (
|
||||
<div className="mt-4" id="trade-step-eight">
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
delay={100}
|
||||
placement="left"
|
||||
content={t('trade:tooltip-enable-margin')}
|
||||
>
|
||||
<Checkbox
|
||||
checked={savedCheckboxSettings.margin}
|
||||
onChange={handleSetMargin}
|
||||
>
|
||||
{t('trade:margin')}
|
||||
</Checkbox>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mr-3 mt-4">
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
delay={100}
|
||||
placement="left"
|
||||
content={
|
||||
'Reduce will only decrease the size of an open position. This is often used for closing a position.'
|
||||
}
|
||||
>
|
||||
<div className="flex items-center text-xs text-th-fgd-3">
|
||||
<Checkbox
|
||||
checked={tradeForm.reduceOnly}
|
||||
onChange={(e) =>
|
||||
handleReduceOnlyChange(e.target.checked)
|
||||
}
|
||||
>
|
||||
{t('trade:reduce-only')}
|
||||
</Checkbox>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-6 mb-4 flex px-3 md:px-4">
|
||||
{ipAllowed ? (
|
||||
<Button
|
||||
className={`flex w-full items-center justify-center ${
|
||||
!connected
|
||||
? ''
|
||||
: tradeForm.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"
|
||||
>
|
||||
{!connected ? (
|
||||
<div className="flex items-center">
|
||||
<LinkIcon className="mr-2 h-5 w-5" />
|
||||
{t('connect')}
|
||||
</div>
|
||||
) : !placingOrder ? (
|
||||
<span>
|
||||
{t('trade:place-order', {
|
||||
side:
|
||||
tradeForm.side === 'buy'
|
||||
? sideNames[0]
|
||||
: sideNames[1],
|
||||
})}
|
||||
</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})` : '',
|
||||
})}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
<TradeSummary
|
||||
mangoAccount={mangoAccount}
|
||||
useMargin={savedCheckboxSettings.margin}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<div className="relative">
|
||||
<NumberFormat
|
||||
inputMode="decimal"
|
||||
thousandSeparator=","
|
||||
allowNegative={false}
|
||||
isNumericString={true}
|
||||
decimalScale={minOrderDecimals}
|
||||
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={tradeForm.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={tickDecimals}
|
||||
name="quote"
|
||||
id="quote"
|
||||
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"
|
||||
placeholder="0.00"
|
||||
value={tradeForm.quoteSize}
|
||||
onValueChange={handleQuoteSizeChange}
|
||||
/>
|
||||
<div className={INPUT_SUFFIX_CLASSNAMES}>{quoteSymbol}</div>
|
||||
</div>
|
||||
{minOrderSize &&
|
||||
tradeForm.baseSize &&
|
||||
parseFloat(tradeForm.baseSize) < minOrderSize ? (
|
||||
<div className="mt-1">
|
||||
<InlineNotification
|
||||
type="error"
|
||||
desc={t('trade:min-order-size-error', {
|
||||
minSize: minOrderSize,
|
||||
symbol: baseSymbol,
|
||||
})}
|
||||
hideBorder
|
||||
hidePadding
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 flex">
|
||||
{selectedMarket instanceof Serum3Market ? (
|
||||
tradeFormSizeUi === 'slider' ? (
|
||||
<SpotSlider
|
||||
minOrderDecimals={minOrderDecimals}
|
||||
tickDecimals={tickDecimals}
|
||||
step={tradeForm.side === 'buy' ? tickSize : minOrderSize}
|
||||
useMargin={savedCheckboxSettings.margin}
|
||||
/>
|
||||
) : (
|
||||
<SpotButtonGroup
|
||||
minOrderDecimals={minOrderDecimals}
|
||||
tickDecimals={tickDecimals}
|
||||
useMargin={savedCheckboxSettings.margin}
|
||||
/>
|
||||
)
|
||||
) : tradeFormSizeUi === 'slider' ? (
|
||||
<PerpSlider
|
||||
minOrderDecimals={minOrderDecimals}
|
||||
tickDecimals={tickDecimals}
|
||||
/>
|
||||
) : (
|
||||
<PerpButtonGroup
|
||||
minOrderDecimals={minOrderDecimals}
|
||||
tickDecimals={tickDecimals}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-wrap px-5 md:flex-nowrap">
|
||||
{tradeForm.tradeType === 'Limit' ? (
|
||||
<div className="flex">
|
||||
<div className="mr-3 mt-4" id="trade-step-six">
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
delay={100}
|
||||
placement="left"
|
||||
content={t('trade:tooltip-post')}
|
||||
>
|
||||
<Checkbox
|
||||
checked={tradeForm.postOnly}
|
||||
onChange={(e) => handlePostOnlyChange(e.target.checked)}
|
||||
>
|
||||
{t('trade:post')}
|
||||
</Checkbox>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="mr-3 mt-4" id="trade-step-seven">
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
delay={100}
|
||||
placement="left"
|
||||
content={t('trade:tooltip-ioc')}
|
||||
>
|
||||
<div className="flex items-center text-xs text-th-fgd-3">
|
||||
<Checkbox
|
||||
checked={tradeForm.ioc}
|
||||
onChange={(e) => handleIocChange(e.target.checked)}
|
||||
>
|
||||
IOC
|
||||
</Checkbox>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{selectedMarket instanceof Serum3Market ? (
|
||||
<div className="mt-4" id="trade-step-eight">
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
delay={100}
|
||||
placement="left"
|
||||
content={t('trade:tooltip-enable-margin')}
|
||||
>
|
||||
<Checkbox
|
||||
checked={savedCheckboxSettings.margin}
|
||||
onChange={handleSetMargin}
|
||||
>
|
||||
{t('trade:margin')}
|
||||
</Checkbox>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mr-3 mt-4">
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
delay={100}
|
||||
placement="left"
|
||||
content={
|
||||
'Reduce will only decrease the size of an open position. This is often used for closing a position.'
|
||||
}
|
||||
>
|
||||
<div className="flex items-center text-xs text-th-fgd-3">
|
||||
<Checkbox
|
||||
checked={tradeForm.reduceOnly}
|
||||
onChange={(e) => handleReduceOnlyChange(e.target.checked)}
|
||||
>
|
||||
{t('trade:reduce-only')}
|
||||
</Checkbox>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-6 mb-4 flex px-3 md:px-4">
|
||||
{ipAllowed ? (
|
||||
<Button
|
||||
className={`flex w-full items-center justify-center ${
|
||||
!connected
|
||||
? ''
|
||||
: tradeForm.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"
|
||||
>
|
||||
{!connected ? (
|
||||
<div className="flex items-center">
|
||||
<LinkIcon className="mr-2 h-5 w-5" />
|
||||
{t('connect')}
|
||||
</div>
|
||||
) : !placingOrder ? (
|
||||
<span>
|
||||
{t('trade:place-order', {
|
||||
side:
|
||||
tradeForm.side === 'buy' ? sideNames[0] : sideNames[1],
|
||||
})}
|
||||
</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})` : '',
|
||||
})}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
{tradeForm.tradeType === 'Market' ? (
|
||||
<div className="m-4">
|
||||
<InlineNotification
|
||||
type="warning"
|
||||
desc="Use caution with market orders. Liquidity may be low."
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<TradeSummary
|
||||
mangoAccount={mangoAccount}
|
||||
useMargin={savedCheckboxSettings.margin}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,409 @@
|
|||
import mangoStore from '@store/mangoStore'
|
||||
import NumberFormat, {
|
||||
NumberFormatValues,
|
||||
SourceInfo,
|
||||
} from 'react-number-format'
|
||||
import {
|
||||
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'
|
||||
import { FormEvent, useCallback, useMemo, useState } from 'react'
|
||||
import Loading from '@components/shared/Loading'
|
||||
import Button from '@components/shared/Button'
|
||||
import Image from 'next/image'
|
||||
import useQuoteRoutes from '@components/swap/useQuoteRoutes'
|
||||
import {
|
||||
Serum3Market,
|
||||
fetchJupiterTransaction,
|
||||
} from '@blockworks-foundation/mango-v4'
|
||||
import Decimal from 'decimal.js'
|
||||
import { useEnhancedWallet } from '@components/wallet/EnhancedWalletProvider'
|
||||
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 } from 'utils/constants'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
|
||||
const set = mangoStore.getState().set
|
||||
const slippage = 100
|
||||
|
||||
function stringToNumberOrZero(s: string): number {
|
||||
const n = parseFloat(s)
|
||||
if (isNaN(n)) {
|
||||
return 0
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
export default function SpotMarketOrderSwapForm() {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const { baseSize, price, quoteSize, side } = mangoStore((s) => s.tradeForm)
|
||||
const [placingOrder, setPlacingOrder] = useState(false)
|
||||
const { ipAllowed, ipCountry } = useIpAddress()
|
||||
const { connected, publicKey } = useWallet()
|
||||
const { handleConnect } = useEnhancedWallet()
|
||||
const [swapFormSizeUi] = useLocalStorageState(SIZE_INPUT_UI_KEY, 'slider')
|
||||
const {
|
||||
selectedMarket,
|
||||
price: oraclePrice,
|
||||
baseLogoURI,
|
||||
baseSymbol,
|
||||
quoteLogoURI,
|
||||
quoteSymbol,
|
||||
serumOrPerpMarket,
|
||||
} = useSelectedMarket()
|
||||
|
||||
const handleBaseSizeChange = useCallback(
|
||||
(e: NumberFormatValues, info: SourceInfo) => {
|
||||
if (info.source !== 'event') return
|
||||
console.log('base size change')
|
||||
|
||||
set((s) => {
|
||||
const price =
|
||||
s.tradeForm.tradeType === 'Market'
|
||||
? oraclePrice
|
||||
: Number(s.tradeForm.price)
|
||||
|
||||
s.tradeForm.baseSize = e.value
|
||||
if (price && e.value !== '' && !Number.isNaN(Number(e.value))) {
|
||||
s.tradeForm.quoteSize = new Decimal(price).mul(e.value).toFixed()
|
||||
} else {
|
||||
s.tradeForm.quoteSize = ''
|
||||
}
|
||||
})
|
||||
},
|
||||
[oraclePrice]
|
||||
)
|
||||
|
||||
const handleQuoteSizeChange = useCallback(
|
||||
(e: NumberFormatValues, info: SourceInfo) => {
|
||||
if (info.source !== 'event') return
|
||||
set((s) => {
|
||||
const price =
|
||||
s.tradeForm.tradeType === 'Market'
|
||||
? oraclePrice
|
||||
: Number(s.tradeForm.price)
|
||||
|
||||
s.tradeForm.quoteSize = e.value
|
||||
if (price && e.value !== '' && !Number.isNaN(Number(e.value))) {
|
||||
s.tradeForm.baseSize = new Decimal(e.value).div(price).toFixed()
|
||||
} else {
|
||||
s.tradeForm.baseSize = ''
|
||||
}
|
||||
})
|
||||
},
|
||||
[oraclePrice]
|
||||
)
|
||||
|
||||
console.log('side outer', side)
|
||||
|
||||
const setAmountFromSlider = useCallback(
|
||||
(amount: string) => {
|
||||
console.log('amount', amount)
|
||||
|
||||
if (side === 'buy') {
|
||||
handleQuoteSizeChange(
|
||||
{ value: amount } as NumberFormatValues,
|
||||
{ source: 'event' } as SourceInfo
|
||||
)
|
||||
} else {
|
||||
handleBaseSizeChange(
|
||||
{ value: amount } as NumberFormatValues,
|
||||
{ source: 'event' } as SourceInfo
|
||||
)
|
||||
}
|
||||
},
|
||||
[side]
|
||||
)
|
||||
|
||||
const [inputBank, outputBank] = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
if (!group || !(selectedMarket instanceof Serum3Market)) return []
|
||||
|
||||
const quoteBank = group?.getFirstBankByTokenIndex(
|
||||
selectedMarket.quoteTokenIndex
|
||||
)
|
||||
const baseBank = group.getFirstBankByTokenIndex(
|
||||
selectedMarket.baseTokenIndex
|
||||
)
|
||||
|
||||
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]
|
||||
}
|
||||
}, [selectedMarket, side])
|
||||
|
||||
const { bestRoute: selectedRoute, isLoading } = useQuoteRoutes({
|
||||
inputMint: inputBank?.mint.toString() || '',
|
||||
outputMint: outputBank?.mint.toString() || '',
|
||||
amount: side === 'buy' ? quoteSize : baseSize,
|
||||
slippage,
|
||||
swapMode: 'ExactIn',
|
||||
wallet: publicKey?.toBase58(),
|
||||
})
|
||||
|
||||
const handlePlaceOrder = useCallback(async () => {
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const { baseSize, quoteSize, side } = mangoStore.getState().tradeForm
|
||||
const actions = mangoStore.getState().actions
|
||||
const connection = mangoStore.getState().connection
|
||||
|
||||
if (!group || !mangoAccount) return
|
||||
console.log('placing order')
|
||||
|
||||
if (
|
||||
!mangoAccount ||
|
||||
!group ||
|
||||
!inputBank ||
|
||||
!outputBank ||
|
||||
!publicKey ||
|
||||
!selectedRoute
|
||||
) {
|
||||
console.log(
|
||||
mangoAccount,
|
||||
group,
|
||||
inputBank,
|
||||
outputBank,
|
||||
publicKey,
|
||||
selectedRoute
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
setPlacingOrder(true)
|
||||
console.log('placing order 2')
|
||||
console.log('selected route', selectedRoute)
|
||||
|
||||
const [ixs, alts] = await fetchJupiterTransaction(
|
||||
connection,
|
||||
selectedRoute,
|
||||
publicKey,
|
||||
slippage,
|
||||
inputBank.mint,
|
||||
outputBank.mint
|
||||
)
|
||||
|
||||
try {
|
||||
const tx = await client.marginTrade({
|
||||
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()
|
||||
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)
|
||||
}
|
||||
}, [inputBank, outputBank, publicKey, selectedRoute])
|
||||
|
||||
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
connected ? handlePlaceOrder() : handleConnect()
|
||||
}
|
||||
|
||||
const disabled =
|
||||
(connected && (!baseSize || !price)) ||
|
||||
!serumOrPerpMarket ||
|
||||
parseFloat(baseSize) < serumOrPerpMarket.minOrderSize ||
|
||||
isLoading
|
||||
|
||||
return (
|
||||
<>
|
||||
<form onSubmit={(e) => handleSubmit(e)}>
|
||||
<div className="mt-3 px-3 md:px-4">
|
||||
<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"
|
||||
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"
|
||||
placeholder="0.00"
|
||||
value={quoteSize}
|
||||
onValueChange={handleQuoteSizeChange}
|
||||
/>
|
||||
<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={true}
|
||||
amount={
|
||||
side === 'buy'
|
||||
? stringToNumberOrZero(quoteSize)
|
||||
: stringToNumberOrZero(baseSize)
|
||||
}
|
||||
onChange={(v) => setAmountFromSlider(v)}
|
||||
step={1 / 10 ** (inputBank?.mintDecimals || 6)}
|
||||
/>
|
||||
) : (
|
||||
<PercentageSelectButtons
|
||||
amountIn={side === 'buy' ? quoteSize : baseSize}
|
||||
setAmountIn={(v) => setAmountFromSlider(v)}
|
||||
useMargin={true}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-6 mb-4 flex px-3 md:px-4">
|
||||
{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"
|
||||
>
|
||||
{isLoading ? (
|
||||
<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>
|
||||
) : !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})` : '',
|
||||
})}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -76,6 +76,7 @@
|
|||
"explorer": "Explorer",
|
||||
"fee": "Fee",
|
||||
"fees": "Fees",
|
||||
"fetching-route": "Finding Route",
|
||||
"free-collateral": "Free Collateral",
|
||||
"get-started": "Get Started",
|
||||
"governance": "Governance",
|
||||
|
|
|
@ -65,7 +65,10 @@ import {
|
|||
import spotBalancesUpdater from './spotBalancesUpdater'
|
||||
import { PerpMarket } from '@blockworks-foundation/mango-v4/'
|
||||
import perpPositionsUpdater from './perpPositionsUpdater'
|
||||
import { DEFAULT_PRIORITY_FEE } from '@components/settings/RpcSettings'
|
||||
import {
|
||||
DEFAULT_PRIORITY_FEE,
|
||||
TRITON_DEDICATED_URL,
|
||||
} from '@components/settings/RpcSettings'
|
||||
import {
|
||||
IExecutionLineAdapter,
|
||||
IOrderLineAdapter,
|
||||
|
@ -76,12 +79,8 @@ const GROUP = new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX')
|
|||
const ENDPOINTS = [
|
||||
{
|
||||
name: 'mainnet-beta',
|
||||
url:
|
||||
process.env.NEXT_PUBLIC_ENDPOINT ||
|
||||
'https://mango.rpcpool.com/946ef7337da3f5b8d3e4a34e7f88',
|
||||
websocket:
|
||||
process.env.NEXT_PUBLIC_ENDPOINT ||
|
||||
'https://mango.rpcpool.com/946ef7337da3f5b8d3e4a34e7f88',
|
||||
url: process.env.NEXT_PUBLIC_ENDPOINT || TRITON_DEDICATED_URL,
|
||||
websocket: process.env.NEXT_PUBLIC_ENDPOINT || TRITON_DEDICATED_URL,
|
||||
custom: false,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -43,7 +43,7 @@ export const FAVORITE_MARKETS_KEY = 'favoriteMarkets-0.2'
|
|||
|
||||
export const THEME_KEY = 'theme-0.1'
|
||||
|
||||
export const RPC_PROVIDER_KEY = 'rpcProviderKey-0.6'
|
||||
export const RPC_PROVIDER_KEY = 'rpcProviderKey-0.7'
|
||||
|
||||
export const PRIORITY_FEE_KEY = 'priorityFeeKey-0.1'
|
||||
|
||||
|
|
|
@ -150,3 +150,11 @@ export const numberCompacter = Intl.NumberFormat('en', {
|
|||
maximumFractionDigits: 2,
|
||||
notation: 'compact',
|
||||
})
|
||||
|
||||
export function stringToNumber(s: string): number | undefined {
|
||||
const n = parseFloat(s)
|
||||
if (isNaN(n)) {
|
||||
return undefined
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue