import { useState, useCallback, useEffect, useMemo } from 'react' import { TransactionInstruction } 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/state' import ContentBox from '../shared/ContentBox' import { notify } from '../../utils/notifications' 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 SelectToken from './SelectToken' 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' const MAX_DIGITS = 11 const withValueLimit = (values: NumberFormatValues): boolean => { return values.floatValue ? values.floatValue.toFixed(0).length <= MAX_DIGITS : true } const Swap = () => { const { t } = useTranslation('common') const [selectedRoute, setSelectedRoute] = useState() const [amountInFormValue, setAmountInFormValue] = useState('') const [submitting, setSubmitting] = useState(false) 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 inputToken = mangoStore((s) => s.swap.inputToken) const outputToken = mangoStore((s) => s.swap.outputToken) const jupiterTokens = mangoStore((s) => s.jupiterTokens) const connected = mangoStore((s) => s.connected) const [debouncedAmountIn, setDebouncedValue] = useDebounce( amountInFormValue, 300 ) const { amountOut, jupiter, outputTokenInfo, routes } = useJupiter({ inputTokenSymbol: inputToken, outputTokenSymbol: outputToken, inputAmount: debouncedAmountIn, slippage, }) useEffect(() => { setSelectedRoute(routes[0]) }, [routes]) const handleAmountInChange = useCallback((e: NumberFormatValues) => { setAmountInFormValue(e.value) }, []) const inputBank = useMemo(() => { const group = mangoStore.getState().group return group?.banksMap.get(inputToken) }, [inputToken]) const handleTokenInSelect = useCallback( (mintAddress: string) => { const inputTokenInfo = jupiterTokens.find( (t: any) => t.address === mintAddress ) const group = mangoStore.getState().group if (group) { const banks = Array.from(group.banksMap.values()) const bank = banks.find((b) => b.mint.toString() === mintAddress) set((s) => { s.swap.inputToken = bank!.name 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 banks = Array.from(group.banksMap.values()) const bank = banks.find((b) => b.mint.toString() === mintAddress) set((s) => { s.swap.outputToken = bank!.name s.swap.outputTokenInfo = outputTokenInfo }) } setShowTokenSelect('') }, [jupiterTokens, set] ) const handleSwitchTokens = useCallback(() => { const inputTokenInfo = jupiterTokens.find( (t: any) => t.symbol === inputToken ) const outputTokenInfo = jupiterTokens.find( (t: any) => t.symbol === outputToken ) setAmountInFormValue(amountOut.toString()) set((s) => { s.swap.inputToken = outputToken s.swap.outputToken = inputToken s.swap.inputTokenInfo = outputTokenInfo s.swap.outputTokenInfo = inputTokenInfo }) setAnimateSwitchArrow( (prevanimateSwitchArrow) => prevanimateSwitchArrow + 1 ) }, [jupiterTokens, inputToken, outputToken, set, amountOut]) const handleSwap = useCallback( async (userDefinedInstructions: TransactionInstruction[]) => { const client = mangoStore.getState().client const group = mangoStore.getState().group const actions = mangoStore.getState().actions const mangoAccount = mangoStore.getState().mangoAccount.current if (!mangoAccount || !group) return try { setSubmitting(true) const tx = await client.marginTrade({ group, mangoAccount, inputToken, amountIn: parseFloat(debouncedAmountIn), outputToken, userDefinedInstructions, }) console.log('Success swapping:', tx) notify({ title: 'Transaction confirmed', type: 'success', txid: tx, }) await actions.reloadAccount() } catch (e: any) { console.log('Error swapping:', e) notify({ title: 'Transaction failed', description: e.message, txid: e?.signature, type: 'error', }) } finally { setSubmitting(false) } }, [debouncedAmountIn, inputToken, outputToken] ) 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)} outputToken={outputToken} amountIn={amountIn} slippage={slippage} handleSwap={handleSwap} submitting={submitting} 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) : 0} )}
{useMargin ? ( <>

{t('leverage')}

0.00x

) : null} ) } export default Swap export const useTokenMax = (inputToken: string, outputToken: string) => { const mangoAccount = mangoStore((s) => s.mangoAccount.current) const tokenInMax = useMemo(() => { const group = mangoStore.getState().group const bank = group?.banksMap.get(inputToken) if (!group || !bank || !mangoAccount) return { amount: 0.0, decimals: 6, amountWithBorrow: 0.0 } const amount = mangoAccount.getUi(bank) const amountWithBorrow = mangoAccount ?.getMaxSourceForTokenSwap(group, inputToken, outputToken, 0.94) .toNumber() return { amount: amount > 0 ? floorToDecimal(amount, bank.mintDecimals) : 0, amountWithBorrow: amountWithBorrow > 0 ? floorToDecimal( toUiDecimals(amountWithBorrow, bank.mintDecimals), bank.mintDecimals ) : 0, decimals: bank.mintDecimals, } }, [inputToken, mangoAccount, outputToken]) return tokenInMax } const MaxSwapAmount = ({ inputToken, outputToken, setAmountIn, useMargin, }: { inputToken: string outputToken: string setAmountIn: (x: any) => void useMargin: boolean }) => { const { t } = useTranslation('common') const { amount: tokenMax, amountWithBorrow } = useTokenMax( inputToken, outputToken ) const setMaxInputAmount = () => { const amountIn = useMargin ? amountWithBorrow : tokenMax setAmountIn(amountIn) } return ( {t('max')}: {useMargin ? amountWithBorrow : tokenMax} ) } const PercentageSelectButtons = ({ inputToken, outputToken, setAmountIn, }: { inputToken: string outputToken: string setAmountIn: (x: any) => any }) => { const [sizePercentage, setSizePercentage] = useState('') const { amount: tokenMax, decimals } = useTokenMax(inputToken, outputToken) 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="%" />
) }