import { useState, useCallback, useEffect, useMemo } from 'react' import { PublicKey } from '@solana/web3.js' import { ArrowDownIcon, CogIcon } from '@heroicons/react/solid' import { RouteInfo } from '@jup-ag/core' import NumberFormat, { NumberFormatValues } from 'react-number-format' import Decimal from 'decimal.js' import mangoStore from '../../store/mangoStore' import ContentBox from '../shared/ContentBox' import JupiterRouteInfo from './JupiterRouteInfo' import TokenSelect from '../TokenSelect' import useDebounce from '../shared/useDebounce' import { floorToDecimal, numberFormat } from '../../utils/numbers' import { SwapLeverageSlider } from './LeverageSlider' import { useTranslation } from 'next-i18next' import SwapFormTokenList from './SwapFormTokenList' import { Transition } from '@headlessui/react' import Button, { IconButton, LinkButton } from '../shared/Button' import ButtonGroup from '../forms/ButtonGroup' import Loading from '../shared/Loading' import { EnterBottomExitBottom } from '../shared/Transitions' import useJupiter from './useJupiter' import SwapSettings from './SwapSettings' import SheenLoader from '../shared/SheenLoader' import { toUiDecimals } from '@blockworks-foundation/mango-v4' import { INPUT_TOKEN_DEFAULT, OUTPUT_TOKEN_DEFAULT, } from '../../utils/constants' const MAX_DIGITS = 11 const withValueLimit = (values: NumberFormatValues): boolean => { return values.floatValue ? values.floatValue.toFixed(0).length <= MAX_DIGITS : true } const SwapForm = () => { const { t } = useTranslation('common') const [selectedRoute, setSelectedRoute] = useState() const [amountInFormValue, setAmountInFormValue] = useState('') const [animateSwitchArrow, setAnimateSwitchArrow] = useState(0) const [showTokenSelect, setShowTokenSelect] = useState('') const [showSettings, setShowSettings] = useState(false) const [showConfirm, setShowConfirm] = useState(false) const set = mangoStore.getState().set const useMargin = mangoStore((s) => s.swap.margin) const slippage = mangoStore((s) => s.swap.slippage) const inputTokenInfo = mangoStore((s) => s.swap.inputTokenInfo) const outputTokenInfo = mangoStore((s) => s.swap.outputTokenInfo) const jupiterTokens = mangoStore((s) => s.jupiterTokens) const connected = mangoStore((s) => s.connected) const [debouncedAmountIn] = useDebounce(amountInFormValue, 300) const { amountOut, jupiter, routes } = useJupiter({ inputTokenInfo, outputTokenInfo, inputAmount: debouncedAmountIn, slippage, }) useEffect(() => { setSelectedRoute(routes[0]) }, [routes]) const handleAmountInChange = useCallback((e: NumberFormatValues) => { setAmountInFormValue(e.value) }, []) const handleTokenInSelect = useCallback( (mintAddress: string) => { const inputTokenInfo = jupiterTokens.find( (t: any) => t.address === mintAddress ) const group = mangoStore.getState().group if (group) { const bank = group.getFirstBankByMint(new PublicKey(mintAddress)) set((s) => { s.swap.inputBank = bank s.swap.inputTokenInfo = inputTokenInfo }) } setShowTokenSelect('') }, [jupiterTokens, set] ) const handleTokenOutSelect = useCallback( (mintAddress: string) => { const outputTokenInfo = jupiterTokens.find( (t: any) => t.address === mintAddress ) const group = mangoStore.getState().group if (group) { const bank = group.getFirstBankByMint(new PublicKey(mintAddress)) set((s) => { s.swap.outputBank = bank s.swap.outputTokenInfo = outputTokenInfo }) } setShowTokenSelect('') }, [jupiterTokens, set] ) const handleSwitchTokens = useCallback(() => { 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 s.swap.inputTokenInfo = outputTokenInfo s.swap.outputTokenInfo = inputTokenInfo }) setAnimateSwitchArrow( (prevanimateSwitchArrow) => prevanimateSwitchArrow + 1 ) }, [inputTokenInfo, outputTokenInfo, set, amountOut]) const amountIn: Decimal | null = useMemo(() => { return Number(debouncedAmountIn) ? new Decimal(debouncedAmountIn) : new Decimal(0) }, [debouncedAmountIn]) const isLoadingTradeDetails = useMemo(() => { return ( amountIn.toNumber() && connected && (!selectedRoute || !outputTokenInfo) ) }, [amountIn, connected, selectedRoute, outputTokenInfo]) return ( setShowConfirm(false)} amountIn={amountIn} slippage={slippage} outputTokenInfo={outputTokenInfo} jupiter={jupiter} routes={routes} selectedRoute={selectedRoute} setSelectedRoute={setSelectedRoute} /> setShowTokenSelect('')} onTokenSelect={ showTokenSelect === 'input' ? handleTokenInSelect : handleTokenOutSelect } type={showTokenSelect} />

{t('trade')}

setShowSettings(true)} size="small" >
setShowSettings(false)} />

{t('sell')}

{!useMargin ? ( ) : null}

{t('buy')}

{isLoadingTradeDetails ? (
) : ( {amountOut ? numberFormat.format(amountOut) : ''} )}
{useMargin ? ( <>

{t('leverage')}

{/*

0.00x

*/}
) : null} ) } export default SwapForm export const useTokenMax = () => { const mangoAccount = mangoStore((s) => s.mangoAccount.current) const inputBank = mangoStore((s) => s.swap.inputBank) const outputBank = mangoStore((s) => s.swap.outputBank) const slippage = mangoStore((s) => s.swap.slippage) const tokenInMax = useMemo(() => { const group = mangoStore.getState().group if (!group || !inputBank || !mangoAccount || !outputBank) return { amount: 0.0, decimals: 6, amountWithBorrow: 0.0 } const inputBankLiquidity = floorToDecimal( inputBank.uiDeposits() - inputBank.uiBorrows(), inputBank.mintDecimals ) const maxWithdrawableAmount = Math.min( mangoAccount.getTokenBalanceUi(inputBank), inputBankLiquidity ) const maxNativeAmountWithBorrow = mangoAccount ?.getMaxSourceForTokenSwap( group, inputBank.mint, outputBank.mint, 0.98 - slippage / 10 ) .toNumber() const maxUiAmountWithBorrow = toUiDecimals( maxNativeAmountWithBorrow, inputBank.mintDecimals ) return { amount: maxWithdrawableAmount > 0 ? floorToDecimal(maxWithdrawableAmount, inputBank.mintDecimals) : 0, amountWithBorrow: maxUiAmountWithBorrow > 0 ? Math.min( floorToDecimal(maxUiAmountWithBorrow, inputBank.mintDecimals), inputBankLiquidity ) : 0, decimals: inputBank.mintDecimals, } }, [inputBank, mangoAccount, outputBank, slippage]) return tokenInMax } const MaxSwapAmount = ({ setAmountIn, useMargin, }: { setAmountIn: (x: any) => void useMargin: boolean }) => { const mangoAccountLoading = mangoStore((s) => s.mangoAccount.initialLoad) const { t } = useTranslation('common') const { amount: tokenMax, amountWithBorrow } = useTokenMax() const setMaxInputAmount = () => { const amountIn = useMargin ? amountWithBorrow : tokenMax setAmountIn(amountIn) } if (mangoAccountLoading) return null return ( {t('max')}: {useMargin ? amountWithBorrow : tokenMax} ) } const PercentageSelectButtons = ({ setAmountIn, }: { setAmountIn: (x: any) => any }) => { const [sizePercentage, setSizePercentage] = useState('') const { amount: tokenMax, decimals } = useTokenMax() const handleSizePercentage = (percentage: string) => { setSizePercentage(percentage) if (tokenMax > 0) { let amount = (Number(percentage) / 100) * tokenMax if (percentage !== '100') { amount = floorToDecimal(amount, decimals) } setAmountIn(amount.toString()) } else { setAmountIn('0') } } return (
handleSizePercentage(p)} values={['10', '25', '50', '75', '100']} unit="%" />
) }