import { useState, useCallback, useEffect, useMemo } from 'react' import { PublicKey } from '@solana/web3.js' import { ArrowDownIcon, ArrowRightIcon, Cog8ToothIcon, MagnifyingGlassIcon, ExclamationCircleIcon, HeartIcon, } from '@heroicons/react/20/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 { HealthType } from '@blockworks-foundation/mango-v4' import { INPUT_TOKEN_DEFAULT, OUTPUT_TOKEN_DEFAULT, } from '../../utils/constants' import { getTokenInMax } from './useTokenMax' import WalletIcon from '../icons/WalletIcon' const MAX_DIGITS = 11 export const withValueLimit = (values: NumberFormatValues): boolean => { return values.floatValue ? values.floatValue.toFixed(0).length <= MAX_DIGITS : true } const SwapForm = () => { const { t } = useTranslation(['common', 'swap']) 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 mangoAccount = mangoStore((s) => s.mangoAccount.current) 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 { amount: tokenMax, amountWithBorrow, decimals, } = useMemo(() => { const group = mangoStore.getState().group if (inputTokenInfo && group) { return getTokenInMax(inputTokenInfo.address, group, useMargin) } return { amount: new Decimal(0), amountWithBorrow: new Decimal(0), decimals: 6, } }, [inputTokenInfo, useMargin, connected]) const amountIn: Decimal | null = useMemo(() => { return Number(debouncedAmountIn) ? new Decimal(debouncedAmountIn) : new Decimal(0) }, [debouncedAmountIn]) const { amountOut, jupiter, routes } = useJupiter({ inputTokenInfo, outputTokenInfo, inputAmount: debouncedAmountIn, slippage, }) useEffect(() => { setSelectedRoute(routes[0]) }, [routes]) useEffect(() => { setAmountInFormValue('') }, [useMargin]) 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(() => { if (amountIn?.gt(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 s.swap.inputTokenInfo = outputTokenInfo s.swap.outputTokenInfo = inputTokenInfo }) setAnimateSwitchArrow( (prevanimateSwitchArrow) => prevanimateSwitchArrow + 1 ) }, [inputTokenInfo, outputTokenInfo, set, amountOut, amountIn]) const currentMaintHealth = useMemo(() => { if (!mangoAccount) return 0 return mangoAccount.getHealthRatioUi(HealthType.maint) }, [mangoAccount]) const maintProjectedHealth = useMemo(() => { const group = mangoStore.getState().group if ( !inputTokenInfo || !mangoAccount || !outputTokenInfo || !amountOut || !group ) return 0 const simulatedHealthRatio = mangoAccount.simHealthRatioWithTokenPositionUiChanges( group, [ { mintPk: new PublicKey(inputTokenInfo.address), uiTokenAmount: amountIn.toNumber() * -1, }, { mintPk: new PublicKey(outputTokenInfo.address), uiTokenAmount: amountOut.toNumber(), }, ], HealthType.maint ) return simulatedHealthRatio! > 100 ? 100 : simulatedHealthRatio! < 0 ? 0 : Math.trunc(simulatedHealthRatio!) }, [mangoAccount, inputTokenInfo, outputTokenInfo, amountIn, amountOut]) const isLoadingTradeDetails = useMemo(() => { return ( amountIn.toNumber() && connected && (!selectedRoute || !outputTokenInfo) ) }, [amountIn, connected, selectedRoute, outputTokenInfo]) const showHealthImpact = !!inputTokenInfo && !!outputTokenInfo && !!amountOut const showInsufficientBalance = useMargin ? amountWithBorrow.lt(amountIn) : tokenMax.lt(amountIn) return (
setShowConfirm(false)} amountIn={amountIn} slippage={slippage} jupiter={jupiter} routes={routes} selectedRoute={selectedRoute} setSelectedRoute={setSelectedRoute} /> setShowTokenSelect('')} onTokenSelect={ showTokenSelect === 'input' ? handleTokenInSelect : handleTokenOutSelect } type={showTokenSelect} useMargin={useMargin} /> setShowSettings(false)} />

{t('swap')}

setShowSettings(true)} size="small" >

{t('swap:from')}

{!useMargin ? ( ) : null}

{t('swap:to')}

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

{t('leverage')}

{/*

0.00x

*/}
) : null}

{t('health-impact')}

{currentMaintHealth}%

15 ? 'text-th-orange' : maintProjectedHealth! <= 15 ? 'text-th-red' : 'text-th-green' } text-sm`} > {maintProjectedHealth!}%{' '} = currentMaintHealth! ? 'text-th-green' : 'text-th-red' }`} > ({maintProjectedHealth! >= currentMaintHealth! ? '+' : ''} {maintProjectedHealth! - currentMaintHealth!}%)

) } export default SwapForm const MaxSwapAmount = ({ amountWithBorrow, setAmountIn, tokenMax, useMargin, decimals, }: { amountWithBorrow: Decimal setAmountIn: (x: string) => void tokenMax: Decimal useMargin: boolean decimals: number }) => { const mangoAccountLoading = mangoStore((s) => s.mangoAccount.initialLoad) const { t } = useTranslation('common') const setMaxInputAmount = () => { const amountIn = useMargin ? amountWithBorrow : tokenMax setAmountIn(amountIn.toFixed(decimals)) } if (mangoAccountLoading) return null const maxAmount = useMargin ? amountWithBorrow : tokenMax return ( {t('max')}: {maxAmount.toFixed()} ) } const PercentageSelectButtons = ({ amountIn, decimals, setAmountIn, tokenMax, }: { amountIn: string decimals: number setAmountIn: (x: string) => any tokenMax: Decimal }) => { const [sizePercentage, setSizePercentage] = useState('') useEffect(() => { if (tokenMax.gt(0) && amountIn && tokenMax.eq(amountIn)) { setSizePercentage('100') } }, [amountIn, tokenMax]) const handleSizePercentage = (percentage: string) => { setSizePercentage(percentage) if (tokenMax.gt(0)) { let amount = tokenMax.mul(percentage).div(100) if (percentage !== '100') { amount = floorToDecimal(amount, decimals) } setAmountIn(amount.toFixed()) } else { setAmountIn('0') } } return (
handleSizePercentage(p)} values={['10', '25', '50', '75', '100']} unit="%" />
) }