import React, { Dispatch, SetStateAction, useEffect, useMemo, useState, } from 'react' import { TransactionInstruction, PublicKey } from '@solana/web3.js' import { toNativeDecimals, toUiDecimals } from '@blockworks-foundation/mango-v4' import { Jupiter, RouteInfo, TransactionFeeInfo } from '@jup-ag/core' import JSBI from 'jsbi' import Decimal from 'decimal.js' import mangoStore from '../../store/state' import RoutesModal from './RoutesModal' import Button, { IconButton } from '../shared/Button' import Loading from '../shared/Loading' import { ArrowLeftIcon, PencilIcon, SwitchHorizontalIcon, } from '@heroicons/react/solid' import { useTranslation } from 'next-i18next' import { Token } from '../../types/jupiter' import Image from 'next/image' import { formatDecimal } from '../../utils/numbers' type JupiterRouteInfoProps = { inputToken: string outputToken: string amountIn: number slippage: number submitting: boolean handleSwap: (x: TransactionInstruction[]) => void onClose: () => void jupiter: Jupiter | undefined routes: RouteInfo[] | undefined outputTokenInfo: Token | undefined selectedRoute: RouteInfo | undefined setSelectedRoute: Dispatch> } const parseJupiterRoute = async ( jupiter: Jupiter, selectedRoute: RouteInfo, userPublicKey: PublicKey ): Promise => { const { transactions } = await jupiter.exchange({ routeInfo: selectedRoute, userPublicKey, }) const { swapTransaction } = transactions const instructions = [] for (const ix of swapTransaction.instructions) { if ( ix.programId.toBase58() === 'JUP3c2Uh3WA4Ng34tw6kPd2G4C5BB21Xo36Je1s32Ph' ) { instructions.push(ix) } } return instructions } const JupiterRouteInfo = ({ inputToken, amountIn, handleSwap, submitting, onClose, jupiter, routes, outputTokenInfo, selectedRoute, setSelectedRoute, }: JupiterRouteInfoProps) => { const { t } = useTranslation('trade') const [showRoutesModal, setShowRoutesModal] = useState(false) const [swapRate, setSwapRate] = useState(false) const [depositAndFee, setDepositAndFee] = useState() const [feeValue, setFeeValue] = useState(null) const mangoAccount = mangoStore((s) => s.mangoAccount.current) const jupiterTokens = mangoStore((s) => s.jupiterTokens) const connected = mangoStore((s) => s.connected) const inputTokenIconUri = useMemo(() => { if (jupiterTokens.length) { const found = jupiterTokens.find((t) => t.symbol === inputToken) return found ? found.logoURI : '' } return '' }, [inputToken, jupiterTokens]) const amountOut = useMemo(() => { if (!selectedRoute || !outputTokenInfo) return return toUiDecimals( JSBI.toNumber(selectedRoute.outAmount), outputTokenInfo.decimals ) }, [selectedRoute, outputTokenInfo]) useEffect(() => { const getDepositAndFee = async () => { const fees = await selectedRoute?.getDepositAndFee() if (fees) { setDepositAndFee(fees) } } if (selectedRoute && connected) { getDepositAndFee() } }, [selectedRoute, connected]) const healthImpact = useMemo(() => { const group = mangoStore.getState().group if (!group || !mangoAccount || !outputTokenInfo || !amountOut) return 'Unknown' const bank = group.banksMap.get(inputToken)! const simulatedHealthRatio = mangoAccount .simHealthRatioWithTokenPositionChanges(group, [ { tokenName: inputToken, tokenAmount: toNativeDecimals(amountIn, bank.mintDecimals).toNumber() * -1, }, { tokenName: outputTokenInfo.symbol, tokenAmount: amountOut }, ]) .toNumber() // console.log('simulatedHealthRatio', simulatedHealthRatio) return simulatedHealthRatio }, [mangoAccount, inputToken, outputTokenInfo, amountIn, amountOut]) const onSwap = async () => { if (!jupiter || !selectedRoute) return const ixs = await parseJupiterRoute( jupiter, selectedRoute, mangoAccount!.owner ) await handleSwap(ixs) onClose() } return routes?.length && selectedRoute && outputTokenInfo && amountOut ? (

{`${amountIn} ${inputToken} for ${amountOut} ${outputTokenInfo.symbol}`}

{swapRate ? ( <> 1 {inputToken} ≈ {formatDecimal(amountOut / amountIn, 6)}{' '} {outputTokenInfo?.symbol} ) : ( <> 1 {outputTokenInfo?.symbol} ≈{' '} {formatDecimal(amountIn / amountOut, 6)} {inputToken} )}

setSwapRate(!swapRate)} />
{/* {tokenPrices?.outputTokenPrice && tokenPrices?.inputTokenPrice ? (
{Math.abs( ((amountIn / amountOut - tokenPrices?.outputTokenPrice / tokenPrices?.inputTokenPrice) / (amountIn / amountOut)) * 100 ).toFixed(1)} %{' '} {`${ ((amountIn / amountOut - tokenPrices?.outputTokenPrice / tokenPrices?.inputTokenPrice) / (amountIn / amountOut)) * 100 <= 0 ? 'Cheaper' : 'More expensive' } CoinGecko`}
) : null} */}

{t('trade:minimum-received')}

{outputTokenInfo?.decimals ? (

{formatDecimal( JSBI.toNumber(selectedRoute?.otherAmountThreshold) / 10 ** outputTokenInfo.decimals || 1, 6 )}{' '} {outputTokenInfo?.symbol}

) : null}

{t('trade:health-impact')}

{healthImpact}

{t('trade:est-liq-price')}

N/A

{t('trade:slippage')}

{selectedRoute?.priceImpactPct * 100 < 0.1 ? '< 0.1%' : `~ ${(selectedRoute?.priceImpactPct * 100).toFixed(4)}%`}

Swap Route

setShowRoutesModal(true)} > {selectedRoute?.marketInfos.map((info, index) => { let includeSeparator = false if ( selectedRoute?.marketInfos.length > 1 && index !== selectedRoute?.marketInfos.length - 1 ) { includeSeparator = true } return ( {`${info.amm.label} ${ includeSeparator ? 'x ' : '' }`} ) })}
{typeof feeValue === 'number' ? (

{t('fee')}

≈ ${feeValue?.toFixed(2)}

) : ( selectedRoute?.marketInfos.map((info, index) => { const feeToken = jupiterTokens.find( (item) => item?.address === info.lpFee?.mint ) return (

{t('trade:fees-paid-to', { route: info?.amm?.label, })}

{feeToken?.decimals && (

{( JSBI.toNumber(info.lpFee?.amount) / Math.pow(10, feeToken.decimals) ).toFixed(6)}{' '} {feeToken?.symbol} ({info.lpFee?.pct * 100}%)

)}
) }) )} {/* {connected ? ( <>
{t('swap:transaction-fee')}
{depositAndFee ? depositAndFee?.signatureFee / Math.pow(10, 9) : '-'}{' '} SOL
{depositAndFee?.ataDepositLength || depositAndFee?.openOrdersDeposits?.length ? (
{t('deposit')} {depositAndFee?.ataDepositLength ? (
{t('need-ata-account')}
) : null} {depositAndFee?.openOrdersDeposits?.length ? (
{t('swap:serum-requires-openorders')}{' '} {t('swap:heres-how')}
) : null} } placement={'left'} >
{depositAndFee?.ataDepositLength ? (
{depositAndFee?.ataDepositLength === 1 ? t('swap:ata-deposit-details', { cost: ( depositAndFee?.ataDeposit / Math.pow(10, 9) ).toFixed(5), count: depositAndFee?.ataDepositLength, }) : t('swap:ata-deposit-details_plural', { cost: ( depositAndFee?.ataDeposit / Math.pow(10, 9) ).toFixed(5), count: depositAndFee?.ataDepositLength, })}
) : null} {depositAndFee?.openOrdersDeposits?.length ? (
{depositAndFee?.openOrdersDeposits.length > 1 ? t('swap:serum-details_plural', { cost: ( sum(depositAndFee?.openOrdersDeposits) / Math.pow(10, 9) ).toFixed(5), count: depositAndFee?.openOrdersDeposits.length, }) : t('swap:serum-details', { cost: ( sum(depositAndFee?.openOrdersDeposits) / Math.pow(10, 9) ).toFixed(5), count: depositAndFee?.openOrdersDeposits.length, })}
) : null}
) : null} ) : null} */}
{showRoutesModal ? ( setShowRoutesModal(false)} setSelectedRoute={setSelectedRoute} selectedRoute={selectedRoute} routes={routes} inputTokenSymbol={inputToken} outputTokenInfo={outputTokenInfo} /> ) : null}
) : null } export default React.memo(JupiterRouteInfo)