allow setting the amount out in jupiter swap

This commit is contained in:
tjs 2022-12-07 16:33:35 -05:00
parent c5c88ce3c2
commit 41223e846c
13 changed files with 421 additions and 285 deletions

View File

@ -6,7 +6,7 @@ import MultiSelectDropdown from '@components/forms/MultiSelectDropdown'
import { EXPLORERS } from '@components/settings/PreferredExplorerSettings'
import Button, { IconButton } from '@components/shared/Button'
import Tooltip from '@components/shared/Tooltip'
import { Disclosure, Transition } from '@headlessui/react'
import { Disclosure } from '@headlessui/react'
import {
AdjustmentsVerticalIcon,
ArrowLeftIcon,
@ -244,37 +244,26 @@ const ActivityFilters = ({
</Disclosure.Button>
</div>
</div>
<Transition
appear={true}
show={showMobileFilters}
enter="transition-all ease-in duration-300"
enterFrom="opacity-100 max-h-0"
enterTo="opacity-100 max-h-full"
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}
<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}
/>
<Button
className="w-full md:w-auto"
size="large"
onClick={handleUpdateMobileResults}
>
{t('activity:update')}
</Button>
</Disclosure.Panel>
</Transition>
</div>
<AdvancedFiltersForm
advancedFilters={advancedFilters}
setAdvancedFilters={setAdvancedFilters}
/>
<Button
className="w-full md:w-auto"
size="large"
onClick={handleUpdateMobileResults}
>
{t('activity:update')}
</Button>
</Disclosure.Panel>
</Disclosure>
) : null
}

View File

@ -8,7 +8,7 @@ import { useViewport } from 'hooks/useViewport'
import { useTranslation } from 'next-i18next'
import Image from 'next/legacy/image'
import { useRouter } from 'next/router'
import { useMemo } from 'react'
import { useCallback, useMemo } from 'react'
import {
floorToDecimal,
formatDecimal,
@ -203,61 +203,87 @@ const Balance = ({ bank }: { bank: Bank }) => {
const { selectedMarket } = useSelectedMarket()
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 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)
if (balance >= 0) {
set((s) => {
s.tradeForm.baseSize = baseSize.toString()
s.tradeForm.quoteSize = quoteSize.toString()
s.swap.inputBank = bank
s.swap.amountIn = balance.toString()
s.swap.swapMode = 'ExactIn'
})
} else {
const baseSize = trimDecimals(balance, minOrderDecimals)
const quoteSize = trimDecimals(baseSize * price, tickSize)
console.log('else')
set((s) => {
s.tradeForm.baseSize = baseSize.toString()
s.tradeForm.quoteSize = quoteSize.toString()
s.swap.outputBank = bank
s.swap.amountOut = Math.abs(balance).toString()
s.swap.swapMode = 'ExactOut'
})
}
}
}, [])
const balance = useMemo(() => {
return mangoAccount ? mangoAccount.getTokenBalanceUi(bank) : 0
}, [mangoAccount])
}, [bank, mangoAccount])
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) {
return 'base'
} else if (bank.tokenIndex === selectedMarket.quoteTokenIndex) {
@ -266,18 +292,26 @@ const Balance = ({ bank }: { bank: Bank }) => {
}
}, [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 (
<p className="flex justify-end">
{balance ? (
isBaseOrQuote ? (
<LinkButton
className="font-normal"
onClick={() =>
handleBalanceClick(
parseFloat(formatDecimal(balance, bank.mintDecimals)),
isBaseOrQuote
)
}
onClick={() => handleClick(balance, isBaseOrQuote)}
>
{formatDecimal(balance, bank.mintDecimals)}
</LinkButton>

View File

@ -32,7 +32,7 @@ const HealthImpact = ({
</p>
</Tooltip>
<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}%
</p>
<ArrowRightIcon className="h-4 w-4 text-th-fgd-4" />

View File

@ -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

View File

@ -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

View File

@ -7,19 +7,20 @@ import {
ExclamationCircleIcon,
LinkIcon,
} 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 mangoStore from '@store/mangoStore'
import ContentBox from '../shared/ContentBox'
import JupiterRouteInfo from './JupiterRouteInfo'
import SwapReviewRouteInfo from './SwapReviewRouteInfo'
import TokenSelect from './TokenSelect'
import useDebounce from '../shared/useDebounce'
import { floorToDecimal, numberFormat } from '../../utils/numbers'
import { useTranslation } from 'next-i18next'
import SwapFormTokenList from './SwapFormTokenList'
import { Transition } from '@headlessui/react'
import Button, { IconButton } from '../shared/Button'
import ButtonGroup from '../forms/ButtonGroup'
import Loading from '../shared/Loading'
import { EnterBottomExitBottom } from '../shared/Transitions'
import useJupiterRoutes from './useJupiterRoutes'
@ -34,7 +35,6 @@ import {
USDC_MINT,
} from '../../utils/constants'
import { useTokenMax } from './useTokenMax'
import MaxAmountButton from '@components/shared/MaxAmountButton'
import HealthImpact from '@components/shared/HealthImpact'
import { useWallet } from '@solana/wallet-adapter-react'
import useMangoAccount from 'hooks/useMangoAccount'
@ -43,6 +43,8 @@ import useMangoGroup from 'hooks/useMangoGroup'
import useLocalStorageState from 'hooks/useLocalStorageState'
import SwapSlider from './SwapSlider'
import TokenVaultWarnings from '@components/shared/TokenVaultWarnings'
import MaxSwapAmount from './MaxSwapAmount'
import PercentageSelectButtons from './PercentageSelectButtons'
const MAX_DIGITS = 11
export const withValueLimit = (values: NumberFormatValues): boolean => {
@ -54,7 +56,6 @@ export const withValueLimit = (values: NumberFormatValues): boolean => {
const SwapForm = () => {
const { t } = useTranslation(['common', 'swap'])
const [selectedRoute, setSelectedRoute] = useState<RouteInfo>()
const [amountInFormValue, setAmountInFormValue] = useState('')
const [animateSwitchArrow, setAnimateSwitchArrow] = useState(0)
const [showTokenSelect, setShowTokenSelect] = useState('')
const [showSettings, setShowSettings] = useState(false)
@ -68,77 +69,131 @@ const SwapForm = () => {
slippage,
inputBank,
outputBank,
amountIn: amountInFormValue,
amountOut: amountOutFormValue,
swapMode,
} = mangoStore((s) => s.swap)
const [debouncedAmountIn] = useDebounce(amountInFormValue, 300)
const [debouncedAmountOut] = useDebounce(amountOutFormValue, 300)
const { mangoAccount } = useMangoAccount()
const { connected } = useWallet()
const amountIn: Decimal | null = useMemo(() => {
const amountInAsDecimal: Decimal | null = useMemo(() => {
return Number(debouncedAmountIn)
? new Decimal(debouncedAmountIn)
: new Decimal(0)
}, [debouncedAmountIn])
const amountOutAsDecimal: Decimal | null = useMemo(() => {
return Number(debouncedAmountOut)
? new Decimal(debouncedAmountOut)
: new Decimal(0)
}, [debouncedAmountOut])
const { bestRoute, routes } = useJupiterRoutes({
inputMint: inputBank?.mint.toString() || USDC_MINT,
outputMint: outputBank?.mint.toString() || MANGO_MINT,
inputAmount: debouncedAmountIn,
amount: swapMode === 'ExactIn' ? debouncedAmountIn : debouncedAmountOut,
slippage,
swapMode,
})
const outAmount: number = useMemo(() => {
return selectedRoute?.outAmount.toString()
? new Decimal(selectedRoute.outAmount.toString())
.div(10 ** outputBank!.mintDecimals)
.toNumber()
: 0
}, [selectedRoute, outputBank])
const setAmountInFormValue = useCallback((amountIn: string) => {
set((s) => {
s.swap.amountIn = amountIn
})
}, [])
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(() => {
if (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(() => {
setAmountInFormValue('')
setAmountOutFormValue('')
}, [useMargin])
const handleAmountInChange = useCallback((e: NumberFormatValues) => {
setAmountInFormValue(e.value)
const handleAmountInChange = useCallback(
(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(
(mintAddress: string) => {
const group = mangoStore.getState().group
if (group) {
const bank = group.getFirstBankByMint(new PublicKey(mintAddress))
set((s) => {
s.swap.inputBank = bank
})
}
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 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('')
}, [])
const handleSwitchTokens = useCallback(() => {
if (amountIn?.gt(0) && outAmount) {
setAmountInFormValue(outAmount.toString())
if (amountInAsDecimal?.gt(0) && amountOutAsDecimal.gte(0)) {
setAmountInFormValue(amountOutAsDecimal.toString())
set((s) => {
s.swap.swapMode = 'ExactIn'
})
}
const inputBank = mangoStore.getState().swap.inputBank
const outputBank = mangoStore.getState().swap.outputBank
@ -149,11 +204,17 @@ const SwapForm = () => {
setAnimateSwitchArrow(
(prevanimateSwitchArrow) => prevanimateSwitchArrow + 1
)
}, [set, outAmount, amountIn])
}, [set, amountOutAsDecimal, amountInAsDecimal])
const maintProjectedHealth = useMemo(() => {
const group = mangoStore.getState().group
if (!inputBank || !mangoAccount || !outputBank || !outAmount || !group)
if (
!inputBank ||
!mangoAccount ||
!outputBank ||
!amountOutAsDecimal ||
!group
)
return 0
const simulatedHealthRatio =
@ -162,25 +223,31 @@ const SwapForm = () => {
[
{
mintPk: inputBank.mint,
uiTokenAmount: amountIn.toNumber() * -1,
uiTokenAmount: amountInAsDecimal.toNumber() * -1,
},
{
mintPk: outputBank.mint,
uiTokenAmount: outAmount,
uiTokenAmount: amountInAsDecimal.toNumber(),
},
],
HealthType.maint
)
return simulatedHealthRatio! > 100
return simulatedHealthRatio > 100
? 100
: simulatedHealthRatio! < 0
: simulatedHealthRatio < 0
? 0
: Math.trunc(simulatedHealthRatio!)
}, [mangoAccount, inputBank, outputBank, amountIn, outAmount])
: Math.trunc(simulatedHealthRatio)
}, [
mangoAccount,
inputBank,
outputBank,
amountInAsDecimal,
amountOutAsDecimal,
])
const loadingSwapDetails: boolean = useMemo(() => {
return !!amountIn.toNumber() && connected && !selectedRoute
}, [amountIn, connected, selectedRoute])
return !!amountInAsDecimal.toNumber() && connected && !selectedRoute
}, [amountInAsDecimal, connected, selectedRoute])
return (
<ContentBox
@ -199,9 +266,9 @@ const SwapForm = () => {
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
<JupiterRouteInfo
<SwapReviewRouteInfo
onClose={() => setShowConfirm(false)}
amountIn={amountIn}
amountIn={amountInAsDecimal}
slippage={slippage}
routes={routes}
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">
<TokenSelect
bank={
inputBank || group?.banksMapByName.get(INPUT_TOKEN_DEFAULT)![0]
inputBank || group?.banksMapByName.get(INPUT_TOKEN_DEFAULT)?.[0]
}
showTokenList={setShowTokenSelect}
type="input"
@ -296,13 +363,13 @@ const SwapForm = () => {
<TokenSelect
bank={
outputBank ||
group?.banksMapByName.get(OUTPUT_TOKEN_DEFAULT)![0]
group?.banksMapByName.get(OUTPUT_TOKEN_DEFAULT)?.[0]
}
showTokenList={setShowTokenSelect}
type="output"
/>
</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 ? (
<div className="w-full">
<SheenLoader className="flex flex-1 rounded-l-none">
@ -318,10 +385,10 @@ const SwapForm = () => {
decimalScale={outputBank?.mintDecimals || 6}
name="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"
disabled
value={outAmount ? numberFormat.format(outAmount) : ''}
value={amountOutFormValue}
onValueChange={handleAmountOutChange}
/>
)}
</div>
@ -329,13 +396,13 @@ const SwapForm = () => {
{swapFormSizeUi === 'Slider' ? (
<SwapSlider
useMargin={useMargin}
amount={amountIn.toNumber()}
amount={amountInAsDecimal.toNumber()}
onChange={setAmountInFormValue}
step={1 / 10 ** (inputBank?.mintDecimals || 6)}
/>
) : (
<PercentageSelectButtons
amountIn={amountIn.toString()}
amountIn={amountInAsDecimal.toString()}
setAmountIn={setAmountInFormValue}
useMargin={useMargin}
/>
@ -344,25 +411,30 @@ const SwapForm = () => {
loadingSwapDetails={loadingSwapDetails}
useMargin={useMargin}
setShowConfirm={setShowConfirm}
amountIn={amountIn}
amountIn={amountInAsDecimal}
inputSymbol={inputBank?.name}
amountOut={selectedRoute ? outAmount : undefined}
amountOut={selectedRoute ? amountOutAsDecimal.toNumber() : undefined}
/>
{group ? (
{group && inputBank ? (
<div className="pt-4">
<TokenVaultWarnings
bank={
inputBank || group.banksMapByName.get(INPUT_TOKEN_DEFAULT)![0]
}
/>
<TokenVaultWarnings bank={inputBank} />
</div>
) : null}
</div>
<div
id="swap-step-four"
className={`border-t border-th-bkg-3 px-6 py-4 transition-all`}
>
<HealthImpact maintProjectedHealth={maintProjectedHealth} />
<div id="swap-step-four" className={`px-2 py-4`}>
<HealthImpact maintProjectedHealth={maintProjectedHealth} />
</div>
<div className={`px-2`}>
<div className="flex justify-between">
<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>
</ContentBox>
)
@ -382,7 +454,7 @@ const SwapFormSubmitButton = ({
amountOut: number | undefined
inputSymbol: string | undefined
loadingSwapDetails: boolean
setShowConfirm: (x: any) => any
setShowConfirm: (x: boolean) => void
useMargin: boolean
}) => {
const { t } = useTranslation('common')
@ -433,92 +505,3 @@ const SwapFormSubmitButton = ({
</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>
)
}

View File

@ -93,7 +93,7 @@ const EMPTY_COINGECKO_PRICES = {
outputCoingeckoPrice: 0,
}
const JupiterRouteInfo = ({
const SwapReviewRouteInfo = ({
amountIn,
onClose,
routes,
@ -570,4 +570,4 @@ const JupiterRouteInfo = ({
) : null
}
export default React.memo(JupiterRouteInfo)
export default React.memo(SwapReviewRouteInfo)

View File

@ -38,7 +38,7 @@ const SwapSettings = ({ onClose }: { onClose: () => void }) => {
</IconButton>
<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 ? (
<Input
type="text"

View File

@ -6,8 +6,9 @@ import useJupiterSwapData from './useJupiterSwapData'
type useJupiterPropTypes = {
inputMint: string
outputMint: string
inputAmount: string
amount: string
slippage: number
swapMode: string
}
const fetchJupiterRoutes = async (
@ -15,19 +16,20 @@ const fetchJupiterRoutes = async (
outputMint = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
amount = 0,
slippage = 50,
swapMode = 'ExactIn',
feeBps = 0
) => {
{
const params: any = {
const paramsString = new URLSearchParams({
inputMint: inputMint.toString(),
outputMint: outputMint.toString(),
amount,
slippageBps: Math.ceil(slippage * 100),
amount: amount.toString(),
slippageBps: Math.ceil(slippage * 100).toString(),
onlyDirectRoutes: 'true',
feeBps,
}
feeBps: feeBps.toString(),
swapMode,
}).toString()
const paramsString = new URLSearchParams(params).toString()
const response = await fetch(
`https://quote-api.jup.ag/v3/quote?${paramsString}`
)
@ -45,25 +47,39 @@ const fetchJupiterRoutes = async (
const useJupiterRoutes = ({
inputMint,
outputMint,
inputAmount,
amount,
slippage,
swapMode,
}: useJupiterPropTypes) => {
const { inputTokenInfo } = useJupiterSwapData()
console.log('amount: ', amount)
const amount = inputAmount
? new Decimal(inputAmount).mul(10 ** (inputTokenInfo?.decimals || 6))
const { inputTokenInfo, outputTokenInfo } = useJupiterSwapData()
const decimals =
swapMode === 'ExactIn'
? inputTokenInfo?.decimals || 6
: outputTokenInfo?.decimals || 6
const nativeAmount = amount
? new Decimal(amount).mul(10 ** decimals)
: new Decimal(0)
const res = useQuery<{ routes: RouteInfo[]; bestRoute: RouteInfo }, Error>(
['swap-routes', inputMint, outputMint, inputAmount, slippage],
['swap-routes', inputMint, outputMint, amount, slippage, swapMode],
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 ?? {
routes: [],

View File

@ -54,6 +54,13 @@ const PerpFundingRate = () => {
: '-'}
%
</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> */}
</>
)
}

View File

@ -12,7 +12,7 @@
"postinstall": "tar -xzC public -f vendor/charting_library.tgz;tar -xzC public -f vendor/datafeeds.tgz"
},
"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",
"@heroicons/react": "^2.0.10",
"@project-serum/anchor": "0.25.0",

View File

@ -37,7 +37,6 @@ import { Orderbook, SpotBalances } from 'types'
import spotBalancesUpdater from './spotBalancesUpdater'
import { PerpMarket } from '@blockworks-foundation/mango-v4/'
import perpPositionsUpdater from './perpPositionsUpdater'
import { token } from '@project-serum/anchor/dist/cjs/utils'
const GROUP = new PublicKey('DLdcpC6AsAJ9xeKMR3WhHrN5sM5o7GVVXQhQ5vwisTtz')
@ -232,6 +231,9 @@ export type MangoStore = {
margin: boolean
slippage: number
success: boolean
swapMode: 'ExactIn' | 'ExactOut'
amountIn: string
amountOut: string
}
set: (x: (x: MangoStore) => void) => void
tokenStats: {
@ -342,6 +344,18 @@ const mangoStore = create<MangoStore>()(
},
uiLocked: true,
},
swap: {
inputBank: undefined,
outputBank: undefined,
inputTokenInfo: undefined,
outputTokenInfo: undefined,
margin: true,
slippage: 0.5,
success: false,
swapMode: 'ExactIn',
amountIn: '',
amountOut: '',
},
tokenStats: {
loading: false,
data: [],
@ -355,15 +369,6 @@ const mangoStore = create<MangoStore>()(
postOnly: false,
ioc: false,
},
swap: {
inputBank: undefined,
outputBank: undefined,
inputTokenInfo: undefined,
outputTokenInfo: undefined,
margin: true,
slippage: 0.5,
success: false,
},
wallet: {
tokens: [],
nfts: {

View File

@ -50,9 +50,9 @@
dependencies:
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"
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:
"@project-serum/anchor" "^0.25.0"
"@project-serum/serum" "^0.13.65"