import { useDispatch, useSelector } from "react-redux"; import { ReactNode, useContext, useEffect, useState } from "react"; import { CustomizeContext } from "../../providers/CustomizeProvider"; import { UserTxType } from "../../consts/"; import { ChevronUp, Edit, Info } from "react-feather"; // components import { Button } from "../common/Button"; import { Modal } from "../common/Modal"; import { InnerCard } from "../common/InnerCard"; import { TxStepDetails } from "../TxModal/TxStepDetails"; import { TokenDetailsRow } from "../common/TokenDetailsRow"; import { Tooltip } from "../common/Tooltip"; // actions import { setIsSettingsModalOpen, setIsTxModalOpen } from "../../state/modals"; import { setSelectedRoute } from "../../state/selectedRouteSlice"; import { formatCurrencyAmount, timeInMinutes, truncateDecimalValue, } from "../../utils/"; import { useGetFees } from "../../hooks/useGetFees"; import { useGetGasLimitFromUserTxs } from "../../hooks/useGetGasLimitFromUserTxs"; export const ReviewModal = ({ closeModal, style, }: { closeModal: () => void; style?: any; }) => { const dispatch = useDispatch(); const bestRoute = useSelector((state: any) => state.quotes.bestRoute); const selectedRoute = useSelector((state: any) => state.routes.selectedRoute); const hideIntegratorFee = useSelector( (state: any) => state.customSettings.hideIntegratorFee ); const [showTxDetails, setShowTxDetails] = useState(false); const [quoteUpdated, setQuoteUpdated] = useState(false); const [isSameChainSwap, setIsSameChainSwap] = useState(false); const customSettings = useContext(CustomizeContext); const { borderRadius } = customSettings.customization; // Sets the selected route if updated. function updateSelectedRoute() { dispatch(setSelectedRoute(bestRoute)); } async function openTxModal() { dispatch(setIsTxModalOpen(true)); } useEffect(() => { if (bestRoute !== selectedRoute) { setQuoteUpdated(true); } else setQuoteUpdated(false); }, [selectedRoute, bestRoute]); const refuelSourceToken = { amount: selectedRoute?.refuel?.fromAmount, asset: selectedRoute?.refuel?.fromAsset, }; const refuelDestToken = { amount: selectedRoute?.refuel?.toAmount, asset: selectedRoute?.refuel?.toAsset, }; // Source Gas Fees const sourceGasLimit = useGetGasLimitFromUserTxs( selectedRoute?.route, selectedRoute?.path?.fromToken?.chainId ); const sourceNativeToken = selectedRoute?.route?.userTxs.filter( (tx) => tx.chainId === selectedRoute?.path?.fromToken?.chainId )[0]?.gasFees?.asset; const { feesInToken: sourceFeesInToken, feesInUsd: sourceFeesInUSD } = useGetFees( sourceGasLimit, sourceNativeToken?.chainId, sourceNativeToken?.decimals, selectedRoute?.route ); // Dest Gas Fees const destGasLimit = useGetGasLimitFromUserTxs( selectedRoute?.route, selectedRoute?.path?.fromToken?.chainId ); const destNativeToken = selectedRoute?.route?.userTxs.filter( (tx) => tx.chainId === selectedRoute?.path?.toToken?.chainId )[0]?.gasFees?.asset; const { feesInToken: destFeesInToken, feesInUsd: destFeesInUSD } = useGetFees( destGasLimit, destNativeToken?.chainId, destNativeToken?.decimals, selectedRoute?.route ); // Extracting Bridge Step from fundMove userTx const fundMovrData = selectedRoute?.route?.userTxs.filter( (item) => item.userTxType === UserTxType.FUND_MOVR )[0]; const bridgeData = fundMovrData?.steps && fundMovrData?.steps.filter((step) => step.type === "bridge")[0]; const swapStepInFundMovr = fundMovrData?.steps && fundMovrData?.steps.filter((step) => step.type === "middleware")[0]; // Extracting the Swap step from userTxs const swapData = selectedRoute?.route?.userTxs.filter( (item) => item.userTxType === UserTxType.DEX_SWAP )?.[0]; // Bridge Fee const bridgeFee = bridgeData?.protocolFees.feesInUsd; const bridgeFeeInToken = formatCurrencyAmount( bridgeData?.protocolFees.amount, bridgeData?.protocolFees.asset.decimals, 5 ); const bridgeFeeTokenSymbol = bridgeData?.protocolFees.asset.symbol; // Integrator Fee const integratorFee = selectedRoute?.route?.integratorFee; const integratorFeeToken = integratorFee?.asset; const integratorFeeInToken = integratorFee?.amount && formatCurrencyAmount( integratorFee?.amount, integratorFeeToken?.decimals, 4 ); // OP Rebates data const opRebateData = bridgeData?.extraData?.rewards?.[0]; const opToken = opRebateData?.asset; const opRebateAmountFormatted = opRebateData && formatCurrencyAmount(opRebateData.amount, opToken.decimals); const OpRebateLabel = ( ARB Rewards ); useEffect(() => { setIsSameChainSwap( selectedRoute?.path?.fromToken?.chainId === selectedRoute?.path?.toToken?.chainId ); }, [selectedRoute]); const openSettingsModal = () => { dispatch(setIsSettingsModalOpen(true)); }; // getting the minimum amount from the last user tx. // TODO: We definitely need the minimum output at the root. let minAmountInToken; if (!!selectedRoute?.route) { const _selectedRoute = selectedRoute.route; // taking the route object out const lastUserTx = _selectedRoute && _selectedRoute.userTxs[_selectedRoute.userTxs?.length - 1]; const stepsInLastUserTx = lastUserTx?.userTxType === "claim" ? _selectedRoute?.userTxs[_selectedRoute?.userTxs?.length - 2]?.steps : lastUserTx?.steps; const lastStep = stepsInLastUserTx?.length > 0 && stepsInLastUserTx[stepsInLastUserTx?.length - 1]; const minAmountOut = _selectedRoute?.userTxs[_selectedRoute?.userTxs?.length - 1] ?.minAmountOut ?? lastStep?.minAmountOut; // note that selectedRoute is used below and not _selectedRoute minAmountInToken = minAmountOut && selectedRoute?.path?.toToken && formatCurrencyAmount( minAmountOut, selectedRoute?.path?.toToken?.decimals, 4 ); } return ( setShowTxDetails(!showTxDetails) : closeModal } style={style} >
{!isSameChainSwap ? ( <>
{bridgeData?.protocol?.displayName} ~{" "} {timeInMinutes(bridgeData?.serviceTime)}
) : ( )} {!!destFeesInToken && !isSameChainSwap && ( <> )} {!hideIntegratorFee && integratorFee?.amount !== "0" && ( )} {(!!swapStepInFundMovr || !!swapData) && (
{swapData?.swapSlippage ?? swapStepInFundMovr?.swapSlippage}%{" "}
)} {bridgeData && bridgeData?.protocol?.displayName.toLowerCase() !== "hyphen" && ( )} {opRebateData && opRebateData?.amount != "0" && (
)}
{showTxDetails && (
)}
{quoteUpdated && ( {!bestRoute ? "Quote updating..." : "Quote updated"} )}
); }; const RouteDetailRow = ({ label, value, children, }: { label: ReactNode; value?: string; children?: ReactNode; }) => { return (
{label} {value} {children}
); }; interface FeeDisplayProps { feeInToken: string; tokenSymbol: string | undefined; feeInUsd: number; } const FeeDisplay = (props: FeeDisplayProps) => { const { feeInToken, tokenSymbol, feeInUsd } = props; if (!!feeInToken) { return ( {!!feeInToken && feeInToken !== "0" ? ( {truncateDecimalValue(feeInToken, 5)}{" "} {tokenSymbol}{" "} ) : ( 0 )} {feeInUsd !== 0 && ( (${feeInUsd?.toFixed(2)}) )} ); } else return null; };