allow setting the amount out in jupiter swap
This commit is contained in:
parent
c5c88ce3c2
commit
41223e846c
|
@ -6,7 +6,7 @@ import MultiSelectDropdown from '@components/forms/MultiSelectDropdown'
|
||||||
import { EXPLORERS } from '@components/settings/PreferredExplorerSettings'
|
import { EXPLORERS } from '@components/settings/PreferredExplorerSettings'
|
||||||
import Button, { IconButton } from '@components/shared/Button'
|
import Button, { IconButton } from '@components/shared/Button'
|
||||||
import Tooltip from '@components/shared/Tooltip'
|
import Tooltip from '@components/shared/Tooltip'
|
||||||
import { Disclosure, Transition } from '@headlessui/react'
|
import { Disclosure } from '@headlessui/react'
|
||||||
import {
|
import {
|
||||||
AdjustmentsVerticalIcon,
|
AdjustmentsVerticalIcon,
|
||||||
ArrowLeftIcon,
|
ArrowLeftIcon,
|
||||||
|
@ -244,37 +244,26 @@ const ActivityFilters = ({
|
||||||
</Disclosure.Button>
|
</Disclosure.Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Transition
|
<Disclosure.Panel className="bg-th-bkg-2 px-6 pb-6">
|
||||||
appear={true}
|
<div className="py-4">
|
||||||
show={showMobileFilters}
|
<Label text={t('activity:activity-type')} />
|
||||||
enter="transition-all ease-in duration-300"
|
<ActivityTypeFiltersForm
|
||||||
enterFrom="opacity-100 max-h-0"
|
filters={filters}
|
||||||
enterTo="opacity-100 max-h-full"
|
updateFilters={updateFilters}
|
||||||
leave="transition-all ease-out duration-300"
|
|
||||||
leaveFrom="opacity-100 max-h-full"
|
|
||||||
leaveTo="opacity-0 max-h-0"
|
|
||||||
>
|
|
||||||
<Disclosure.Panel className="bg-th-bkg-2 px-6 pb-6">
|
|
||||||
<div className="py-4">
|
|
||||||
<Label text={t('activity:activity-type')} />
|
|
||||||
<ActivityTypeFiltersForm
|
|
||||||
filters={filters}
|
|
||||||
updateFilters={updateFilters}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<AdvancedFiltersForm
|
|
||||||
advancedFilters={advancedFilters}
|
|
||||||
setAdvancedFilters={setAdvancedFilters}
|
|
||||||
/>
|
/>
|
||||||
<Button
|
</div>
|
||||||
className="w-full md:w-auto"
|
<AdvancedFiltersForm
|
||||||
size="large"
|
advancedFilters={advancedFilters}
|
||||||
onClick={handleUpdateMobileResults}
|
setAdvancedFilters={setAdvancedFilters}
|
||||||
>
|
/>
|
||||||
{t('activity:update')}
|
<Button
|
||||||
</Button>
|
className="w-full md:w-auto"
|
||||||
</Disclosure.Panel>
|
size="large"
|
||||||
</Transition>
|
onClick={handleUpdateMobileResults}
|
||||||
|
>
|
||||||
|
{t('activity:update')}
|
||||||
|
</Button>
|
||||||
|
</Disclosure.Panel>
|
||||||
</Disclosure>
|
</Disclosure>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { useViewport } from 'hooks/useViewport'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
import Image from 'next/legacy/image'
|
import Image from 'next/legacy/image'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useMemo } from 'react'
|
import { useCallback, useMemo } from 'react'
|
||||||
import {
|
import {
|
||||||
floorToDecimal,
|
floorToDecimal,
|
||||||
formatDecimal,
|
formatDecimal,
|
||||||
|
@ -203,61 +203,87 @@ const Balance = ({ bank }: { bank: Bank }) => {
|
||||||
const { selectedMarket } = useSelectedMarket()
|
const { selectedMarket } = useSelectedMarket()
|
||||||
const { asPath } = useRouter()
|
const { asPath } = useRouter()
|
||||||
|
|
||||||
const handleBalanceClick = (balance: number, type: 'base' | 'quote') => {
|
const handleTradeFormBalanceClick = useCallback(
|
||||||
|
(balance: number, type: 'base' | 'quote') => {
|
||||||
|
const set = mangoStore.getState().set
|
||||||
|
const group = mangoStore.getState().group
|
||||||
|
const tradeForm = mangoStore.getState().tradeForm
|
||||||
|
|
||||||
|
if (!group || !selectedMarket) return
|
||||||
|
|
||||||
|
let price: number
|
||||||
|
if (tradeForm.tradeType === 'Market') {
|
||||||
|
const orderbook = mangoStore.getState().selectedMarket.orderbook
|
||||||
|
const side =
|
||||||
|
(balance > 0 && type === 'quote') || (balance < 0 && type === 'base')
|
||||||
|
? 'buy'
|
||||||
|
: 'sell'
|
||||||
|
price = calculateMarketPrice(orderbook, balance, side)
|
||||||
|
} else {
|
||||||
|
price = new Decimal(tradeForm.price).toNumber()
|
||||||
|
}
|
||||||
|
|
||||||
|
let minOrderDecimals: number
|
||||||
|
let tickSize: number
|
||||||
|
if (selectedMarket instanceof Serum3Market) {
|
||||||
|
const market = group.getSerum3ExternalMarket(
|
||||||
|
selectedMarket.serumMarketExternal
|
||||||
|
)
|
||||||
|
minOrderDecimals = getDecimalCount(market.minOrderSize)
|
||||||
|
tickSize = getDecimalCount(market.tickSize)
|
||||||
|
} else {
|
||||||
|
minOrderDecimals = getDecimalCount(selectedMarket.minOrderSize)
|
||||||
|
tickSize = getDecimalCount(selectedMarket.tickSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'quote') {
|
||||||
|
const trimmedBalance = trimDecimals(balance, tickSize)
|
||||||
|
const baseSize = trimDecimals(trimmedBalance / price, minOrderDecimals)
|
||||||
|
const quoteSize = trimDecimals(baseSize * price, tickSize)
|
||||||
|
set((s) => {
|
||||||
|
s.tradeForm.baseSize = baseSize.toString()
|
||||||
|
s.tradeForm.quoteSize = quoteSize.toString()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const baseSize = trimDecimals(balance, minOrderDecimals)
|
||||||
|
const quoteSize = trimDecimals(baseSize * price, tickSize)
|
||||||
|
set((s) => {
|
||||||
|
s.tradeForm.baseSize = baseSize.toString()
|
||||||
|
s.tradeForm.quoteSize = quoteSize.toString()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[selectedMarket]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleSwapFormBalanceClick = useCallback((balance: number) => {
|
||||||
const set = mangoStore.getState().set
|
const set = mangoStore.getState().set
|
||||||
const group = mangoStore.getState().group
|
if (balance >= 0) {
|
||||||
const tradeForm = mangoStore.getState().tradeForm
|
|
||||||
|
|
||||||
if (!group || !selectedMarket) return
|
|
||||||
|
|
||||||
let price: number
|
|
||||||
if (tradeForm.tradeType === 'Market') {
|
|
||||||
const orderbook = mangoStore.getState().selectedMarket.orderbook
|
|
||||||
const side =
|
|
||||||
(balance > 0 && type === 'quote') || (balance < 0 && type === 'base')
|
|
||||||
? 'buy'
|
|
||||||
: 'sell'
|
|
||||||
price = calculateMarketPrice(orderbook, balance, side)
|
|
||||||
} else {
|
|
||||||
price = new Decimal(tradeForm.price).toNumber()
|
|
||||||
}
|
|
||||||
let minOrderDecimals: number
|
|
||||||
let tickSize: number
|
|
||||||
if (selectedMarket instanceof Serum3Market) {
|
|
||||||
const market = group.getSerum3ExternalMarket(
|
|
||||||
selectedMarket.serumMarketExternal
|
|
||||||
)
|
|
||||||
minOrderDecimals = getDecimalCount(market.minOrderSize)
|
|
||||||
tickSize = getDecimalCount(market.tickSize)
|
|
||||||
} else {
|
|
||||||
minOrderDecimals = getDecimalCount(selectedMarket.minOrderSize)
|
|
||||||
tickSize = getDecimalCount(selectedMarket.tickSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'quote') {
|
|
||||||
const trimmedBalance = trimDecimals(balance, tickSize)
|
|
||||||
const baseSize = trimDecimals(trimmedBalance / price, minOrderDecimals)
|
|
||||||
const quoteSize = trimDecimals(baseSize * price, tickSize)
|
|
||||||
set((s) => {
|
set((s) => {
|
||||||
s.tradeForm.baseSize = baseSize.toString()
|
s.swap.inputBank = bank
|
||||||
s.tradeForm.quoteSize = quoteSize.toString()
|
s.swap.amountIn = balance.toString()
|
||||||
|
s.swap.swapMode = 'ExactIn'
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const baseSize = trimDecimals(balance, minOrderDecimals)
|
console.log('else')
|
||||||
const quoteSize = trimDecimals(baseSize * price, tickSize)
|
|
||||||
set((s) => {
|
set((s) => {
|
||||||
s.tradeForm.baseSize = baseSize.toString()
|
s.swap.outputBank = bank
|
||||||
s.tradeForm.quoteSize = quoteSize.toString()
|
s.swap.amountOut = Math.abs(balance).toString()
|
||||||
|
s.swap.swapMode = 'ExactOut'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
const balance = useMemo(() => {
|
const balance = useMemo(() => {
|
||||||
return mangoAccount ? mangoAccount.getTokenBalanceUi(bank) : 0
|
return mangoAccount ? mangoAccount.getTokenBalanceUi(bank) : 0
|
||||||
}, [mangoAccount])
|
}, [bank, mangoAccount])
|
||||||
|
|
||||||
const isBaseOrQuote = useMemo(() => {
|
const isBaseOrQuote = useMemo(() => {
|
||||||
if (selectedMarket instanceof Serum3Market && asPath.includes('/trade')) {
|
if (
|
||||||
|
selectedMarket instanceof Serum3Market &&
|
||||||
|
(asPath.includes('/trade') || asPath.includes('/swap'))
|
||||||
|
) {
|
||||||
if (bank.tokenIndex === selectedMarket.baseTokenIndex) {
|
if (bank.tokenIndex === selectedMarket.baseTokenIndex) {
|
||||||
return 'base'
|
return 'base'
|
||||||
} else if (bank.tokenIndex === selectedMarket.quoteTokenIndex) {
|
} else if (bank.tokenIndex === selectedMarket.quoteTokenIndex) {
|
||||||
|
@ -266,18 +292,26 @@ const Balance = ({ bank }: { bank: Bank }) => {
|
||||||
}
|
}
|
||||||
}, [bank, selectedMarket])
|
}, [bank, selectedMarket])
|
||||||
|
|
||||||
|
const handleClick = (balance: number, type: 'base' | 'quote') => {
|
||||||
|
if (asPath.includes('/trade')) {
|
||||||
|
handleTradeFormBalanceClick(
|
||||||
|
parseFloat(formatDecimal(balance, bank.mintDecimals)),
|
||||||
|
type
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
handleSwapFormBalanceClick(
|
||||||
|
parseFloat(formatDecimal(balance, bank.mintDecimals))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p className="flex justify-end">
|
<p className="flex justify-end">
|
||||||
{balance ? (
|
{balance ? (
|
||||||
isBaseOrQuote ? (
|
isBaseOrQuote ? (
|
||||||
<LinkButton
|
<LinkButton
|
||||||
className="font-normal"
|
className="font-normal"
|
||||||
onClick={() =>
|
onClick={() => handleClick(balance, isBaseOrQuote)}
|
||||||
handleBalanceClick(
|
|
||||||
parseFloat(formatDecimal(balance, bank.mintDecimals)),
|
|
||||||
isBaseOrQuote
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{formatDecimal(balance, bank.mintDecimals)}
|
{formatDecimal(balance, bank.mintDecimals)}
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
|
|
|
@ -32,7 +32,7 @@ const HealthImpact = ({
|
||||||
</p>
|
</p>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<div className="flex items-center space-x-1.5 font-mono">
|
<div className="flex items-center space-x-1.5 font-mono">
|
||||||
<p className={`text-th-fgd-1 ${small ? 'text-xs' : 'text-sm'}`}>
|
<p className={`text-th-fgd-2 ${small ? 'text-xs' : 'text-sm'}`}>
|
||||||
{currentMaintHealth}%
|
{currentMaintHealth}%
|
||||||
</p>
|
</p>
|
||||||
<ArrowRightIcon className="h-4 w-4 text-th-fgd-4" />
|
<ArrowRightIcon className="h-4 w-4 text-th-fgd-4" />
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
import MaxAmountButton from '@components/shared/MaxAmountButton'
|
||||||
|
import mangoStore from '@store/mangoStore'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
import { useTokenMax } from './useTokenMax'
|
||||||
|
|
||||||
|
const MaxSwapAmount = ({
|
||||||
|
setAmountIn,
|
||||||
|
useMargin,
|
||||||
|
}: {
|
||||||
|
setAmountIn: (x: string) => void
|
||||||
|
useMargin: boolean
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation('common')
|
||||||
|
const mangoAccountLoading = mangoStore((s) => s.mangoAccount.initialLoad)
|
||||||
|
const {
|
||||||
|
amount: tokenMax,
|
||||||
|
amountWithBorrow,
|
||||||
|
decimals,
|
||||||
|
} = useTokenMax(useMargin)
|
||||||
|
|
||||||
|
if (mangoAccountLoading) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-wrap justify-end pl-6 text-xs">
|
||||||
|
<MaxAmountButton
|
||||||
|
className="mb-0.5"
|
||||||
|
label="Bal"
|
||||||
|
onClick={() => setAmountIn(tokenMax.toFixed(decimals))}
|
||||||
|
value={tokenMax.toFixed()}
|
||||||
|
/>
|
||||||
|
{useMargin ? (
|
||||||
|
<MaxAmountButton
|
||||||
|
className="mb-0.5 ml-2"
|
||||||
|
label={t('max')}
|
||||||
|
onClick={() => setAmountIn(amountWithBorrow.toFixed(decimals))}
|
||||||
|
value={amountWithBorrow.toFixed()}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MaxSwapAmount
|
|
@ -0,0 +1,59 @@
|
||||||
|
import ButtonGroup from '@components/forms/ButtonGroup'
|
||||||
|
import Decimal from 'decimal.js'
|
||||||
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
|
import { floorToDecimal } from 'utils/numbers'
|
||||||
|
import { useTokenMax } from './useTokenMax'
|
||||||
|
|
||||||
|
const PercentageSelectButtons = ({
|
||||||
|
amountIn,
|
||||||
|
setAmountIn,
|
||||||
|
useMargin,
|
||||||
|
}: {
|
||||||
|
amountIn: string
|
||||||
|
setAmountIn: (x: string) => void
|
||||||
|
useMargin: boolean
|
||||||
|
}) => {
|
||||||
|
const [sizePercentage, setSizePercentage] = useState('')
|
||||||
|
const {
|
||||||
|
amount: tokenMax,
|
||||||
|
amountWithBorrow,
|
||||||
|
decimals,
|
||||||
|
} = useTokenMax(useMargin)
|
||||||
|
|
||||||
|
const maxAmount = useMemo(() => {
|
||||||
|
if (!tokenMax && !amountWithBorrow) return new Decimal(0)
|
||||||
|
return useMargin ? amountWithBorrow : tokenMax
|
||||||
|
}, [tokenMax, amountWithBorrow, useMargin])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (maxAmount.gt(0) && amountIn && maxAmount.eq(amountIn)) {
|
||||||
|
setSizePercentage('100')
|
||||||
|
}
|
||||||
|
}, [amountIn, maxAmount])
|
||||||
|
|
||||||
|
const handleSizePercentage = (percentage: string) => {
|
||||||
|
setSizePercentage(percentage)
|
||||||
|
if (maxAmount.gt(0)) {
|
||||||
|
let amount = maxAmount.mul(percentage).div(100)
|
||||||
|
if (percentage !== '100') {
|
||||||
|
amount = floorToDecimal(amount, decimals)
|
||||||
|
}
|
||||||
|
setAmountIn(amount.toFixed())
|
||||||
|
} else {
|
||||||
|
setAmountIn('0')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="col-span-2 mt-2">
|
||||||
|
<ButtonGroup
|
||||||
|
activeValue={sizePercentage}
|
||||||
|
onChange={(p) => handleSizePercentage(p)}
|
||||||
|
values={['10', '25', '50', '75', '100']}
|
||||||
|
unit="%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PercentageSelectButtons
|
|
@ -7,19 +7,20 @@ import {
|
||||||
ExclamationCircleIcon,
|
ExclamationCircleIcon,
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
} from '@heroicons/react/20/solid'
|
} from '@heroicons/react/20/solid'
|
||||||
import NumberFormat, { NumberFormatValues } from 'react-number-format'
|
import NumberFormat, {
|
||||||
|
NumberFormatValues,
|
||||||
|
SourceInfo,
|
||||||
|
} from 'react-number-format'
|
||||||
import Decimal from 'decimal.js'
|
import Decimal from 'decimal.js'
|
||||||
import mangoStore from '@store/mangoStore'
|
import mangoStore from '@store/mangoStore'
|
||||||
import ContentBox from '../shared/ContentBox'
|
import ContentBox from '../shared/ContentBox'
|
||||||
import JupiterRouteInfo from './JupiterRouteInfo'
|
import SwapReviewRouteInfo from './SwapReviewRouteInfo'
|
||||||
import TokenSelect from './TokenSelect'
|
import TokenSelect from './TokenSelect'
|
||||||
import useDebounce from '../shared/useDebounce'
|
import useDebounce from '../shared/useDebounce'
|
||||||
import { floorToDecimal, numberFormat } from '../../utils/numbers'
|
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
import SwapFormTokenList from './SwapFormTokenList'
|
import SwapFormTokenList from './SwapFormTokenList'
|
||||||
import { Transition } from '@headlessui/react'
|
import { Transition } from '@headlessui/react'
|
||||||
import Button, { IconButton } from '../shared/Button'
|
import Button, { IconButton } from '../shared/Button'
|
||||||
import ButtonGroup from '../forms/ButtonGroup'
|
|
||||||
import Loading from '../shared/Loading'
|
import Loading from '../shared/Loading'
|
||||||
import { EnterBottomExitBottom } from '../shared/Transitions'
|
import { EnterBottomExitBottom } from '../shared/Transitions'
|
||||||
import useJupiterRoutes from './useJupiterRoutes'
|
import useJupiterRoutes from './useJupiterRoutes'
|
||||||
|
@ -34,7 +35,6 @@ import {
|
||||||
USDC_MINT,
|
USDC_MINT,
|
||||||
} from '../../utils/constants'
|
} from '../../utils/constants'
|
||||||
import { useTokenMax } from './useTokenMax'
|
import { useTokenMax } from './useTokenMax'
|
||||||
import MaxAmountButton from '@components/shared/MaxAmountButton'
|
|
||||||
import HealthImpact from '@components/shared/HealthImpact'
|
import HealthImpact from '@components/shared/HealthImpact'
|
||||||
import { useWallet } from '@solana/wallet-adapter-react'
|
import { useWallet } from '@solana/wallet-adapter-react'
|
||||||
import useMangoAccount from 'hooks/useMangoAccount'
|
import useMangoAccount from 'hooks/useMangoAccount'
|
||||||
|
@ -43,6 +43,8 @@ import useMangoGroup from 'hooks/useMangoGroup'
|
||||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||||
import SwapSlider from './SwapSlider'
|
import SwapSlider from './SwapSlider'
|
||||||
import TokenVaultWarnings from '@components/shared/TokenVaultWarnings'
|
import TokenVaultWarnings from '@components/shared/TokenVaultWarnings'
|
||||||
|
import MaxSwapAmount from './MaxSwapAmount'
|
||||||
|
import PercentageSelectButtons from './PercentageSelectButtons'
|
||||||
|
|
||||||
const MAX_DIGITS = 11
|
const MAX_DIGITS = 11
|
||||||
export const withValueLimit = (values: NumberFormatValues): boolean => {
|
export const withValueLimit = (values: NumberFormatValues): boolean => {
|
||||||
|
@ -54,7 +56,6 @@ export const withValueLimit = (values: NumberFormatValues): boolean => {
|
||||||
const SwapForm = () => {
|
const SwapForm = () => {
|
||||||
const { t } = useTranslation(['common', 'swap'])
|
const { t } = useTranslation(['common', 'swap'])
|
||||||
const [selectedRoute, setSelectedRoute] = useState<RouteInfo>()
|
const [selectedRoute, setSelectedRoute] = useState<RouteInfo>()
|
||||||
const [amountInFormValue, setAmountInFormValue] = useState('')
|
|
||||||
const [animateSwitchArrow, setAnimateSwitchArrow] = useState(0)
|
const [animateSwitchArrow, setAnimateSwitchArrow] = useState(0)
|
||||||
const [showTokenSelect, setShowTokenSelect] = useState('')
|
const [showTokenSelect, setShowTokenSelect] = useState('')
|
||||||
const [showSettings, setShowSettings] = useState(false)
|
const [showSettings, setShowSettings] = useState(false)
|
||||||
|
@ -68,77 +69,131 @@ const SwapForm = () => {
|
||||||
slippage,
|
slippage,
|
||||||
inputBank,
|
inputBank,
|
||||||
outputBank,
|
outputBank,
|
||||||
|
amountIn: amountInFormValue,
|
||||||
|
amountOut: amountOutFormValue,
|
||||||
|
swapMode,
|
||||||
} = mangoStore((s) => s.swap)
|
} = mangoStore((s) => s.swap)
|
||||||
const [debouncedAmountIn] = useDebounce(amountInFormValue, 300)
|
const [debouncedAmountIn] = useDebounce(amountInFormValue, 300)
|
||||||
|
const [debouncedAmountOut] = useDebounce(amountOutFormValue, 300)
|
||||||
const { mangoAccount } = useMangoAccount()
|
const { mangoAccount } = useMangoAccount()
|
||||||
const { connected } = useWallet()
|
const { connected } = useWallet()
|
||||||
|
|
||||||
const amountIn: Decimal | null = useMemo(() => {
|
const amountInAsDecimal: Decimal | null = useMemo(() => {
|
||||||
return Number(debouncedAmountIn)
|
return Number(debouncedAmountIn)
|
||||||
? new Decimal(debouncedAmountIn)
|
? new Decimal(debouncedAmountIn)
|
||||||
: new Decimal(0)
|
: new Decimal(0)
|
||||||
}, [debouncedAmountIn])
|
}, [debouncedAmountIn])
|
||||||
|
|
||||||
|
const amountOutAsDecimal: Decimal | null = useMemo(() => {
|
||||||
|
return Number(debouncedAmountOut)
|
||||||
|
? new Decimal(debouncedAmountOut)
|
||||||
|
: new Decimal(0)
|
||||||
|
}, [debouncedAmountOut])
|
||||||
|
|
||||||
const { bestRoute, routes } = useJupiterRoutes({
|
const { bestRoute, routes } = useJupiterRoutes({
|
||||||
inputMint: inputBank?.mint.toString() || USDC_MINT,
|
inputMint: inputBank?.mint.toString() || USDC_MINT,
|
||||||
outputMint: outputBank?.mint.toString() || MANGO_MINT,
|
outputMint: outputBank?.mint.toString() || MANGO_MINT,
|
||||||
inputAmount: debouncedAmountIn,
|
amount: swapMode === 'ExactIn' ? debouncedAmountIn : debouncedAmountOut,
|
||||||
slippage,
|
slippage,
|
||||||
|
swapMode,
|
||||||
})
|
})
|
||||||
|
|
||||||
const outAmount: number = useMemo(() => {
|
const setAmountInFormValue = useCallback((amountIn: string) => {
|
||||||
return selectedRoute?.outAmount.toString()
|
set((s) => {
|
||||||
? new Decimal(selectedRoute.outAmount.toString())
|
s.swap.amountIn = amountIn
|
||||||
.div(10 ** outputBank!.mintDecimals)
|
})
|
||||||
.toNumber()
|
}, [])
|
||||||
: 0
|
|
||||||
}, [selectedRoute, outputBank])
|
|
||||||
|
|
||||||
|
const setAmountOutFormValue = useCallback((amountOut: string) => {
|
||||||
|
set((s) => {
|
||||||
|
s.swap.amountOut = amountOut
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
/*
|
||||||
|
Once a route is returned from the Jupiter API, use the inAmount or outAmount
|
||||||
|
depending on the swapMode and set those values in state
|
||||||
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (bestRoute) {
|
if (bestRoute) {
|
||||||
setSelectedRoute(bestRoute)
|
setSelectedRoute(bestRoute)
|
||||||
}
|
|
||||||
}, [bestRoute])
|
|
||||||
|
|
||||||
|
if (inputBank && swapMode === 'ExactOut') {
|
||||||
|
const inAmount = new Decimal(bestRoute.inAmount)
|
||||||
|
.div(10 ** inputBank.mintDecimals)
|
||||||
|
.toString()
|
||||||
|
setAmountInFormValue(inAmount)
|
||||||
|
} else if (outputBank && swapMode === 'ExactIn') {
|
||||||
|
const outAmount = new Decimal(bestRoute.outAmount)
|
||||||
|
.div(10 ** outputBank.mintDecimals)
|
||||||
|
.toString()
|
||||||
|
setAmountOutFormValue(outAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [bestRoute, swapMode, inputBank, outputBank])
|
||||||
|
|
||||||
|
/*
|
||||||
|
If the use margin setting is toggled, clear the form values
|
||||||
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setAmountInFormValue('')
|
setAmountInFormValue('')
|
||||||
|
setAmountOutFormValue('')
|
||||||
}, [useMargin])
|
}, [useMargin])
|
||||||
|
|
||||||
const handleAmountInChange = useCallback((e: NumberFormatValues) => {
|
const handleAmountInChange = useCallback(
|
||||||
setAmountInFormValue(e.value)
|
(e: NumberFormatValues, info: SourceInfo) => {
|
||||||
|
if (info.source !== 'event') return
|
||||||
|
if (swapMode === 'ExactOut') {
|
||||||
|
set((s) => {
|
||||||
|
s.swap.swapMode = 'ExactIn'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setAmountInFormValue(e.value)
|
||||||
|
},
|
||||||
|
[swapMode]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleAmountOutChange = useCallback(
|
||||||
|
(e: NumberFormatValues, info: SourceInfo) => {
|
||||||
|
if (info.source !== 'event') return
|
||||||
|
if (swapMode === 'ExactIn') {
|
||||||
|
set((s) => {
|
||||||
|
s.swap.swapMode = 'ExactOut'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setAmountOutFormValue(e.value)
|
||||||
|
},
|
||||||
|
[swapMode]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleTokenInSelect = useCallback((mintAddress: string) => {
|
||||||
|
const group = mangoStore.getState().group
|
||||||
|
if (group) {
|
||||||
|
const bank = group.getFirstBankByMint(new PublicKey(mintAddress))
|
||||||
|
set((s) => {
|
||||||
|
s.swap.inputBank = bank
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setShowTokenSelect('')
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleTokenInSelect = useCallback(
|
const handleTokenOutSelect = useCallback((mintAddress: string) => {
|
||||||
(mintAddress: string) => {
|
const group = mangoStore.getState().group
|
||||||
const group = mangoStore.getState().group
|
if (group) {
|
||||||
if (group) {
|
const bank = group.getFirstBankByMint(new PublicKey(mintAddress))
|
||||||
const bank = group.getFirstBankByMint(new PublicKey(mintAddress))
|
set((s) => {
|
||||||
set((s) => {
|
s.swap.outputBank = bank
|
||||||
s.swap.inputBank = bank
|
})
|
||||||
})
|
}
|
||||||
}
|
setShowTokenSelect('')
|
||||||
setShowTokenSelect('')
|
}, [])
|
||||||
},
|
|
||||||
[set]
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleTokenOutSelect = useCallback(
|
|
||||||
(mintAddress: string) => {
|
|
||||||
const group = mangoStore.getState().group
|
|
||||||
if (group) {
|
|
||||||
const bank = group.getFirstBankByMint(new PublicKey(mintAddress))
|
|
||||||
set((s) => {
|
|
||||||
s.swap.outputBank = bank
|
|
||||||
})
|
|
||||||
}
|
|
||||||
setShowTokenSelect('')
|
|
||||||
},
|
|
||||||
[set]
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleSwitchTokens = useCallback(() => {
|
const handleSwitchTokens = useCallback(() => {
|
||||||
if (amountIn?.gt(0) && outAmount) {
|
if (amountInAsDecimal?.gt(0) && amountOutAsDecimal.gte(0)) {
|
||||||
setAmountInFormValue(outAmount.toString())
|
setAmountInFormValue(amountOutAsDecimal.toString())
|
||||||
|
set((s) => {
|
||||||
|
s.swap.swapMode = 'ExactIn'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
const inputBank = mangoStore.getState().swap.inputBank
|
const inputBank = mangoStore.getState().swap.inputBank
|
||||||
const outputBank = mangoStore.getState().swap.outputBank
|
const outputBank = mangoStore.getState().swap.outputBank
|
||||||
|
@ -149,11 +204,17 @@ const SwapForm = () => {
|
||||||
setAnimateSwitchArrow(
|
setAnimateSwitchArrow(
|
||||||
(prevanimateSwitchArrow) => prevanimateSwitchArrow + 1
|
(prevanimateSwitchArrow) => prevanimateSwitchArrow + 1
|
||||||
)
|
)
|
||||||
}, [set, outAmount, amountIn])
|
}, [set, amountOutAsDecimal, amountInAsDecimal])
|
||||||
|
|
||||||
const maintProjectedHealth = useMemo(() => {
|
const maintProjectedHealth = useMemo(() => {
|
||||||
const group = mangoStore.getState().group
|
const group = mangoStore.getState().group
|
||||||
if (!inputBank || !mangoAccount || !outputBank || !outAmount || !group)
|
if (
|
||||||
|
!inputBank ||
|
||||||
|
!mangoAccount ||
|
||||||
|
!outputBank ||
|
||||||
|
!amountOutAsDecimal ||
|
||||||
|
!group
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
const simulatedHealthRatio =
|
const simulatedHealthRatio =
|
||||||
|
@ -162,25 +223,31 @@ const SwapForm = () => {
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
mintPk: inputBank.mint,
|
mintPk: inputBank.mint,
|
||||||
uiTokenAmount: amountIn.toNumber() * -1,
|
uiTokenAmount: amountInAsDecimal.toNumber() * -1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
mintPk: outputBank.mint,
|
mintPk: outputBank.mint,
|
||||||
uiTokenAmount: outAmount,
|
uiTokenAmount: amountInAsDecimal.toNumber(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
HealthType.maint
|
HealthType.maint
|
||||||
)
|
)
|
||||||
return simulatedHealthRatio! > 100
|
return simulatedHealthRatio > 100
|
||||||
? 100
|
? 100
|
||||||
: simulatedHealthRatio! < 0
|
: simulatedHealthRatio < 0
|
||||||
? 0
|
? 0
|
||||||
: Math.trunc(simulatedHealthRatio!)
|
: Math.trunc(simulatedHealthRatio)
|
||||||
}, [mangoAccount, inputBank, outputBank, amountIn, outAmount])
|
}, [
|
||||||
|
mangoAccount,
|
||||||
|
inputBank,
|
||||||
|
outputBank,
|
||||||
|
amountInAsDecimal,
|
||||||
|
amountOutAsDecimal,
|
||||||
|
])
|
||||||
|
|
||||||
const loadingSwapDetails: boolean = useMemo(() => {
|
const loadingSwapDetails: boolean = useMemo(() => {
|
||||||
return !!amountIn.toNumber() && connected && !selectedRoute
|
return !!amountInAsDecimal.toNumber() && connected && !selectedRoute
|
||||||
}, [amountIn, connected, selectedRoute])
|
}, [amountInAsDecimal, connected, selectedRoute])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentBox
|
<ContentBox
|
||||||
|
@ -199,9 +266,9 @@ const SwapForm = () => {
|
||||||
leaveFrom="translate-x-0"
|
leaveFrom="translate-x-0"
|
||||||
leaveTo="translate-x-full"
|
leaveTo="translate-x-full"
|
||||||
>
|
>
|
||||||
<JupiterRouteInfo
|
<SwapReviewRouteInfo
|
||||||
onClose={() => setShowConfirm(false)}
|
onClose={() => setShowConfirm(false)}
|
||||||
amountIn={amountIn}
|
amountIn={amountInAsDecimal}
|
||||||
slippage={slippage}
|
slippage={slippage}
|
||||||
routes={routes}
|
routes={routes}
|
||||||
selectedRoute={selectedRoute}
|
selectedRoute={selectedRoute}
|
||||||
|
@ -252,7 +319,7 @@ const SwapForm = () => {
|
||||||
<div className="col-span-1 rounded-lg rounded-r-none border border-r-0 border-th-bkg-4 bg-th-bkg-1">
|
<div className="col-span-1 rounded-lg rounded-r-none border border-r-0 border-th-bkg-4 bg-th-bkg-1">
|
||||||
<TokenSelect
|
<TokenSelect
|
||||||
bank={
|
bank={
|
||||||
inputBank || group?.banksMapByName.get(INPUT_TOKEN_DEFAULT)![0]
|
inputBank || group?.banksMapByName.get(INPUT_TOKEN_DEFAULT)?.[0]
|
||||||
}
|
}
|
||||||
showTokenList={setShowTokenSelect}
|
showTokenList={setShowTokenSelect}
|
||||||
type="input"
|
type="input"
|
||||||
|
@ -296,13 +363,13 @@ const SwapForm = () => {
|
||||||
<TokenSelect
|
<TokenSelect
|
||||||
bank={
|
bank={
|
||||||
outputBank ||
|
outputBank ||
|
||||||
group?.banksMapByName.get(OUTPUT_TOKEN_DEFAULT)![0]
|
group?.banksMapByName.get(OUTPUT_TOKEN_DEFAULT)?.[0]
|
||||||
}
|
}
|
||||||
showTokenList={setShowTokenSelect}
|
showTokenList={setShowTokenSelect}
|
||||||
type="output"
|
type="output"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex h-[54px] w-full items-center justify-end rounded-r-lg border border-th-bkg-4 bg-th-bkg-3 text-right text-lg font-bold text-th-fgd-3 xl:text-xl">
|
<div className="flex h-[54px] w-full items-center justify-end rounded-r-lg border border-th-bkg-4 text-right text-lg font-bold text-th-fgd-3 xl:text-xl">
|
||||||
{loadingSwapDetails ? (
|
{loadingSwapDetails ? (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<SheenLoader className="flex flex-1 rounded-l-none">
|
<SheenLoader className="flex flex-1 rounded-l-none">
|
||||||
|
@ -318,10 +385,10 @@ const SwapForm = () => {
|
||||||
decimalScale={outputBank?.mintDecimals || 6}
|
decimalScale={outputBank?.mintDecimals || 6}
|
||||||
name="amountOut"
|
name="amountOut"
|
||||||
id="amountOut"
|
id="amountOut"
|
||||||
className="w-full rounded-r-lg bg-th-bkg-3 p-3 text-right font-mono text-base font-bold text-th-fgd-3 focus:outline-none lg:text-lg xl:text-xl"
|
className="w-full rounded-r-lg bg-th-bkg-1 p-3 text-right font-mono text-base font-bold text-th-fgd-3 focus:outline-none lg:text-lg xl:text-xl"
|
||||||
placeholder="0.00"
|
placeholder="0.00"
|
||||||
disabled
|
value={amountOutFormValue}
|
||||||
value={outAmount ? numberFormat.format(outAmount) : ''}
|
onValueChange={handleAmountOutChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -329,13 +396,13 @@ const SwapForm = () => {
|
||||||
{swapFormSizeUi === 'Slider' ? (
|
{swapFormSizeUi === 'Slider' ? (
|
||||||
<SwapSlider
|
<SwapSlider
|
||||||
useMargin={useMargin}
|
useMargin={useMargin}
|
||||||
amount={amountIn.toNumber()}
|
amount={amountInAsDecimal.toNumber()}
|
||||||
onChange={setAmountInFormValue}
|
onChange={setAmountInFormValue}
|
||||||
step={1 / 10 ** (inputBank?.mintDecimals || 6)}
|
step={1 / 10 ** (inputBank?.mintDecimals || 6)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<PercentageSelectButtons
|
<PercentageSelectButtons
|
||||||
amountIn={amountIn.toString()}
|
amountIn={amountInAsDecimal.toString()}
|
||||||
setAmountIn={setAmountInFormValue}
|
setAmountIn={setAmountInFormValue}
|
||||||
useMargin={useMargin}
|
useMargin={useMargin}
|
||||||
/>
|
/>
|
||||||
|
@ -344,25 +411,30 @@ const SwapForm = () => {
|
||||||
loadingSwapDetails={loadingSwapDetails}
|
loadingSwapDetails={loadingSwapDetails}
|
||||||
useMargin={useMargin}
|
useMargin={useMargin}
|
||||||
setShowConfirm={setShowConfirm}
|
setShowConfirm={setShowConfirm}
|
||||||
amountIn={amountIn}
|
amountIn={amountInAsDecimal}
|
||||||
inputSymbol={inputBank?.name}
|
inputSymbol={inputBank?.name}
|
||||||
amountOut={selectedRoute ? outAmount : undefined}
|
amountOut={selectedRoute ? amountOutAsDecimal.toNumber() : undefined}
|
||||||
/>
|
/>
|
||||||
{group ? (
|
{group && inputBank ? (
|
||||||
<div className="pt-4">
|
<div className="pt-4">
|
||||||
<TokenVaultWarnings
|
<TokenVaultWarnings bank={inputBank} />
|
||||||
bank={
|
|
||||||
inputBank || group.banksMapByName.get(INPUT_TOKEN_DEFAULT)![0]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
<div id="swap-step-four" className={`px-2 py-4`}>
|
||||||
<div
|
<HealthImpact maintProjectedHealth={maintProjectedHealth} />
|
||||||
id="swap-step-four"
|
</div>
|
||||||
className={`border-t border-th-bkg-3 px-6 py-4 transition-all`}
|
<div className={`px-2`}>
|
||||||
>
|
<div className="flex justify-between">
|
||||||
<HealthImpact maintProjectedHealth={maintProjectedHealth} />
|
<p className="text-sm text-th-fgd-3">Est. {t('swap:slippage')}</p>
|
||||||
|
<p className="text-right font-mono text-sm text-th-fgd-3">
|
||||||
|
{selectedRoute?.priceImpactPct
|
||||||
|
? selectedRoute?.priceImpactPct * 100 < 0.1
|
||||||
|
? '< 0.1%'
|
||||||
|
: `${(selectedRoute?.priceImpactPct * 100).toFixed(2)}%`
|
||||||
|
: '0.00%'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ContentBox>
|
</ContentBox>
|
||||||
)
|
)
|
||||||
|
@ -382,7 +454,7 @@ const SwapFormSubmitButton = ({
|
||||||
amountOut: number | undefined
|
amountOut: number | undefined
|
||||||
inputSymbol: string | undefined
|
inputSymbol: string | undefined
|
||||||
loadingSwapDetails: boolean
|
loadingSwapDetails: boolean
|
||||||
setShowConfirm: (x: any) => any
|
setShowConfirm: (x: boolean) => void
|
||||||
useMargin: boolean
|
useMargin: boolean
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
|
@ -433,92 +505,3 @@ const SwapFormSubmitButton = ({
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const MaxSwapAmount = ({
|
|
||||||
setAmountIn,
|
|
||||||
useMargin,
|
|
||||||
}: {
|
|
||||||
setAmountIn: (x: string) => void
|
|
||||||
useMargin: boolean
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation('common')
|
|
||||||
const mangoAccountLoading = mangoStore((s) => s.mangoAccount.initialLoad)
|
|
||||||
const {
|
|
||||||
amount: tokenMax,
|
|
||||||
amountWithBorrow,
|
|
||||||
decimals,
|
|
||||||
} = useTokenMax(useMargin)
|
|
||||||
|
|
||||||
if (mangoAccountLoading) return null
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-wrap justify-end pl-6 text-xs">
|
|
||||||
<MaxAmountButton
|
|
||||||
className="mb-0.5"
|
|
||||||
label="Bal"
|
|
||||||
onClick={() => setAmountIn(tokenMax.toFixed(decimals))}
|
|
||||||
value={tokenMax.toFixed()}
|
|
||||||
/>
|
|
||||||
{useMargin ? (
|
|
||||||
<MaxAmountButton
|
|
||||||
className="mb-0.5 ml-2"
|
|
||||||
label={t('max')}
|
|
||||||
onClick={() => setAmountIn(amountWithBorrow.toFixed(decimals))}
|
|
||||||
value={amountWithBorrow.toFixed()}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const PercentageSelectButtons = ({
|
|
||||||
amountIn,
|
|
||||||
setAmountIn,
|
|
||||||
useMargin,
|
|
||||||
}: {
|
|
||||||
amountIn: string
|
|
||||||
setAmountIn: (x: string) => any
|
|
||||||
useMargin: boolean
|
|
||||||
}) => {
|
|
||||||
const [sizePercentage, setSizePercentage] = useState('')
|
|
||||||
const {
|
|
||||||
amount: tokenMax,
|
|
||||||
amountWithBorrow,
|
|
||||||
decimals,
|
|
||||||
} = useTokenMax(useMargin)
|
|
||||||
|
|
||||||
const maxAmount = useMemo(() => {
|
|
||||||
if (!tokenMax && !amountWithBorrow) return new Decimal(0)
|
|
||||||
return useMargin ? amountWithBorrow : tokenMax
|
|
||||||
}, [tokenMax, amountWithBorrow, useMargin])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (maxAmount.gt(0) && amountIn && maxAmount.eq(amountIn)) {
|
|
||||||
setSizePercentage('100')
|
|
||||||
}
|
|
||||||
}, [amountIn, maxAmount])
|
|
||||||
|
|
||||||
const handleSizePercentage = (percentage: string) => {
|
|
||||||
setSizePercentage(percentage)
|
|
||||||
if (maxAmount.gt(0)) {
|
|
||||||
let amount = maxAmount.mul(percentage).div(100)
|
|
||||||
if (percentage !== '100') {
|
|
||||||
amount = floorToDecimal(amount, decimals)
|
|
||||||
}
|
|
||||||
setAmountIn(amount.toFixed())
|
|
||||||
} else {
|
|
||||||
setAmountIn('0')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="col-span-2 mt-2">
|
|
||||||
<ButtonGroup
|
|
||||||
activeValue={sizePercentage}
|
|
||||||
onChange={(p) => handleSizePercentage(p)}
|
|
||||||
values={['10', '25', '50', '75', '100']}
|
|
||||||
unit="%"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -93,7 +93,7 @@ const EMPTY_COINGECKO_PRICES = {
|
||||||
outputCoingeckoPrice: 0,
|
outputCoingeckoPrice: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
const JupiterRouteInfo = ({
|
const SwapReviewRouteInfo = ({
|
||||||
amountIn,
|
amountIn,
|
||||||
onClose,
|
onClose,
|
||||||
routes,
|
routes,
|
||||||
|
@ -570,4 +570,4 @@ const JupiterRouteInfo = ({
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
export default React.memo(JupiterRouteInfo)
|
export default React.memo(SwapReviewRouteInfo)
|
|
@ -38,7 +38,7 @@ const SwapSettings = ({ onClose }: { onClose: () => void }) => {
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<p className="mb-2 text-th-fgd-1">{t('swap:slippage')}</p>
|
<p className="mb-2 text-th-fgd-1">Max {t('swap:slippage')}</p>
|
||||||
{showCustomSlippageForm ? (
|
{showCustomSlippageForm ? (
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
@ -6,8 +6,9 @@ import useJupiterSwapData from './useJupiterSwapData'
|
||||||
type useJupiterPropTypes = {
|
type useJupiterPropTypes = {
|
||||||
inputMint: string
|
inputMint: string
|
||||||
outputMint: string
|
outputMint: string
|
||||||
inputAmount: string
|
amount: string
|
||||||
slippage: number
|
slippage: number
|
||||||
|
swapMode: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchJupiterRoutes = async (
|
const fetchJupiterRoutes = async (
|
||||||
|
@ -15,19 +16,20 @@ const fetchJupiterRoutes = async (
|
||||||
outputMint = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
outputMint = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
||||||
amount = 0,
|
amount = 0,
|
||||||
slippage = 50,
|
slippage = 50,
|
||||||
|
swapMode = 'ExactIn',
|
||||||
feeBps = 0
|
feeBps = 0
|
||||||
) => {
|
) => {
|
||||||
{
|
{
|
||||||
const params: any = {
|
const paramsString = new URLSearchParams({
|
||||||
inputMint: inputMint.toString(),
|
inputMint: inputMint.toString(),
|
||||||
outputMint: outputMint.toString(),
|
outputMint: outputMint.toString(),
|
||||||
amount,
|
amount: amount.toString(),
|
||||||
slippageBps: Math.ceil(slippage * 100),
|
slippageBps: Math.ceil(slippage * 100).toString(),
|
||||||
onlyDirectRoutes: 'true',
|
onlyDirectRoutes: 'true',
|
||||||
feeBps,
|
feeBps: feeBps.toString(),
|
||||||
}
|
swapMode,
|
||||||
|
}).toString()
|
||||||
|
|
||||||
const paramsString = new URLSearchParams(params).toString()
|
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`https://quote-api.jup.ag/v3/quote?${paramsString}`
|
`https://quote-api.jup.ag/v3/quote?${paramsString}`
|
||||||
)
|
)
|
||||||
|
@ -45,25 +47,39 @@ const fetchJupiterRoutes = async (
|
||||||
const useJupiterRoutes = ({
|
const useJupiterRoutes = ({
|
||||||
inputMint,
|
inputMint,
|
||||||
outputMint,
|
outputMint,
|
||||||
inputAmount,
|
amount,
|
||||||
slippage,
|
slippage,
|
||||||
|
swapMode,
|
||||||
}: useJupiterPropTypes) => {
|
}: useJupiterPropTypes) => {
|
||||||
const { inputTokenInfo } = useJupiterSwapData()
|
console.log('amount: ', amount)
|
||||||
|
|
||||||
const amount = inputAmount
|
const { inputTokenInfo, outputTokenInfo } = useJupiterSwapData()
|
||||||
? new Decimal(inputAmount).mul(10 ** (inputTokenInfo?.decimals || 6))
|
|
||||||
|
const decimals =
|
||||||
|
swapMode === 'ExactIn'
|
||||||
|
? inputTokenInfo?.decimals || 6
|
||||||
|
: outputTokenInfo?.decimals || 6
|
||||||
|
|
||||||
|
const nativeAmount = amount
|
||||||
|
? new Decimal(amount).mul(10 ** decimals)
|
||||||
: new Decimal(0)
|
: new Decimal(0)
|
||||||
|
|
||||||
const res = useQuery<{ routes: RouteInfo[]; bestRoute: RouteInfo }, Error>(
|
const res = useQuery<{ routes: RouteInfo[]; bestRoute: RouteInfo }, Error>(
|
||||||
['swap-routes', inputMint, outputMint, inputAmount, slippage],
|
['swap-routes', inputMint, outputMint, amount, slippage, swapMode],
|
||||||
async () =>
|
async () =>
|
||||||
fetchJupiterRoutes(inputMint, outputMint, amount.toNumber(), slippage),
|
fetchJupiterRoutes(
|
||||||
|
inputMint,
|
||||||
|
outputMint,
|
||||||
|
nativeAmount.toNumber(),
|
||||||
|
slippage,
|
||||||
|
swapMode
|
||||||
|
),
|
||||||
{
|
{
|
||||||
enabled: inputAmount ? true : false,
|
enabled: amount ? true : false,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return inputAmount
|
return amount
|
||||||
? {
|
? {
|
||||||
...(res.data ?? {
|
...(res.data ?? {
|
||||||
routes: [],
|
routes: [],
|
||||||
|
|
|
@ -54,6 +54,13 @@ const PerpFundingRate = () => {
|
||||||
: '-'}
|
: '-'}
|
||||||
%
|
%
|
||||||
</div>
|
</div>
|
||||||
|
{/* <div className="font-mono text-xs text-th-fgd-2">
|
||||||
|
{selectedMarket instanceof PerpMarket &&
|
||||||
|
bids instanceof BookSide &&
|
||||||
|
asks instanceof BookSide
|
||||||
|
? selectedMarket.getCurrentFundingRate(bids, asks)
|
||||||
|
: '-'}
|
||||||
|
</div> */}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"postinstall": "tar -xzC public -f vendor/charting_library.tgz;tar -xzC public -f vendor/datafeeds.tgz"
|
"postinstall": "tar -xzC public -f vendor/charting_library.tgz;tar -xzC public -f vendor/datafeeds.tgz"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blockworks-foundation/mango-v4": "https://tylersssss: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@github.com/blockworks-foundation/mango-v4.git#main",
|
"@blockworks-foundation/mango-v4": "https://tylersssss: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@github.com/blockworks-foundation/mango-v4.git#ts/test",
|
||||||
"@headlessui/react": "^1.6.6",
|
"@headlessui/react": "^1.6.6",
|
||||||
"@heroicons/react": "^2.0.10",
|
"@heroicons/react": "^2.0.10",
|
||||||
"@project-serum/anchor": "0.25.0",
|
"@project-serum/anchor": "0.25.0",
|
||||||
|
|
|
@ -37,7 +37,6 @@ import { Orderbook, SpotBalances } from 'types'
|
||||||
import spotBalancesUpdater from './spotBalancesUpdater'
|
import spotBalancesUpdater from './spotBalancesUpdater'
|
||||||
import { PerpMarket } from '@blockworks-foundation/mango-v4/'
|
import { PerpMarket } from '@blockworks-foundation/mango-v4/'
|
||||||
import perpPositionsUpdater from './perpPositionsUpdater'
|
import perpPositionsUpdater from './perpPositionsUpdater'
|
||||||
import { token } from '@project-serum/anchor/dist/cjs/utils'
|
|
||||||
|
|
||||||
const GROUP = new PublicKey('DLdcpC6AsAJ9xeKMR3WhHrN5sM5o7GVVXQhQ5vwisTtz')
|
const GROUP = new PublicKey('DLdcpC6AsAJ9xeKMR3WhHrN5sM5o7GVVXQhQ5vwisTtz')
|
||||||
|
|
||||||
|
@ -232,6 +231,9 @@ export type MangoStore = {
|
||||||
margin: boolean
|
margin: boolean
|
||||||
slippage: number
|
slippage: number
|
||||||
success: boolean
|
success: boolean
|
||||||
|
swapMode: 'ExactIn' | 'ExactOut'
|
||||||
|
amountIn: string
|
||||||
|
amountOut: string
|
||||||
}
|
}
|
||||||
set: (x: (x: MangoStore) => void) => void
|
set: (x: (x: MangoStore) => void) => void
|
||||||
tokenStats: {
|
tokenStats: {
|
||||||
|
@ -342,6 +344,18 @@ const mangoStore = create<MangoStore>()(
|
||||||
},
|
},
|
||||||
uiLocked: true,
|
uiLocked: true,
|
||||||
},
|
},
|
||||||
|
swap: {
|
||||||
|
inputBank: undefined,
|
||||||
|
outputBank: undefined,
|
||||||
|
inputTokenInfo: undefined,
|
||||||
|
outputTokenInfo: undefined,
|
||||||
|
margin: true,
|
||||||
|
slippage: 0.5,
|
||||||
|
success: false,
|
||||||
|
swapMode: 'ExactIn',
|
||||||
|
amountIn: '',
|
||||||
|
amountOut: '',
|
||||||
|
},
|
||||||
tokenStats: {
|
tokenStats: {
|
||||||
loading: false,
|
loading: false,
|
||||||
data: [],
|
data: [],
|
||||||
|
@ -355,15 +369,6 @@ const mangoStore = create<MangoStore>()(
|
||||||
postOnly: false,
|
postOnly: false,
|
||||||
ioc: false,
|
ioc: false,
|
||||||
},
|
},
|
||||||
swap: {
|
|
||||||
inputBank: undefined,
|
|
||||||
outputBank: undefined,
|
|
||||||
inputTokenInfo: undefined,
|
|
||||||
outputTokenInfo: undefined,
|
|
||||||
margin: true,
|
|
||||||
slippage: 0.5,
|
|
||||||
success: false,
|
|
||||||
},
|
|
||||||
wallet: {
|
wallet: {
|
||||||
tokens: [],
|
tokens: [],
|
||||||
nfts: {
|
nfts: {
|
||||||
|
|
|
@ -50,9 +50,9 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.4"
|
regenerator-runtime "^0.13.4"
|
||||||
|
|
||||||
"@blockworks-foundation/mango-v4@https://tylersssss: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@github.com/blockworks-foundation/mango-v4.git#main":
|
"@blockworks-foundation/mango-v4@https://tylersssss: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@github.com/blockworks-foundation/mango-v4.git#ts/test":
|
||||||
version "0.0.1-beta.6"
|
version "0.0.1-beta.6"
|
||||||
resolved "https://tylersssss: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@github.com/blockworks-foundation/mango-v4.git#0609adbe702ba68c11f02d317d79eae356dc47b9"
|
resolved "https://tylersssss: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@github.com/blockworks-foundation/mango-v4.git#c54fdbee5cc7be5e9d5367996b7986e370e609ff"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@project-serum/anchor" "^0.25.0"
|
"@project-serum/anchor" "^0.25.0"
|
||||||
"@project-serum/serum" "^0.13.65"
|
"@project-serum/serum" "^0.13.65"
|
||||||
|
|
Loading…
Reference in New Issue