import type { RouteExtended } from '@lifi/sdk' import { isGaslessStep } from '@lifi/sdk' import ExpandLess from '@mui/icons-material/ExpandLess' import ExpandMore from '@mui/icons-material/ExpandMore' import LocalGasStationRounded from '@mui/icons-material/LocalGasStationRounded' import type { CardProps } from '@mui/material' import { Box, Collapse, Tooltip, Typography } from '@mui/material' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { useWidgetConfig } from '../providers/WidgetProvider/WidgetProvider.js' import { isRouteDone } from '../stores/routes/utils.js' import { getAccumulatedFeeCostsBreakdown } from '../utils/fees.js' import { formatTokenAmount, formatTokenPrice } from '../utils/format.js' import { getPriceImpact } from '../utils/getPriceImpact.js' import { Card } from './Card/Card.js' import { CardIconButton } from './Card/CardIconButton.js' import { FeeBreakdownTooltip } from './FeeBreakdownTooltip.js' import { IconTypography } from './IconTypography.js' import { TokenRate } from './TokenRate/TokenRate.js' interface TransactionDetailsProps extends CardProps { route: RouteExtended } export const TransactionDetails: React.FC = ({ route, ...props }) => { const { t } = useTranslation() const { feeConfig, defaultUI } = useWidgetConfig() const [cardExpanded, setCardExpanded] = useState( defaultUI?.transactionDetailsExpanded ?? false ) const toggleCard = () => { setCardExpanded((cardExpanded) => !cardExpanded) } const { gasCosts, feeCosts, gasCostUSD, feeCostUSD, combinedFeesUSD } = getAccumulatedFeeCostsBreakdown(route) const priceImpact = getPriceImpact({ fromAmount: BigInt(route.fromAmount), toAmount: BigInt(route.toAmount), fromToken: route.fromToken, toToken: route.toToken, }) const feeCollectionStep = route.steps[0].includedSteps.find( (includedStep) => includedStep.tool === 'feeCollection' ) let feeAmountUSD = 0 let feePercentage = 0 if (feeCollectionStep) { const estimatedFromAmount = BigInt(feeCollectionStep.estimate.fromAmount) - BigInt(feeCollectionStep.estimate.toAmount) feeAmountUSD = formatTokenPrice( estimatedFromAmount, feeCollectionStep.action.fromToken.priceUSD, feeCollectionStep.action.fromToken.decimals ) feePercentage = feeCollectionStep.estimate.feeCosts?.reduce( (percentage, feeCost) => percentage + Number.parseFloat(feeCost.percentage || '0'), 0 ) ?? 0 } const hasGaslessSupport = route.steps.every((step) => isGaslessStep(step)) const showIntegratorFeeCollectionDetails = (feeAmountUSD || Number.isFinite(feeConfig?.fee)) && !hasGaslessSupport return ( {!combinedFeesUSD ? t('main.fees.free') : t('format.currency', { value: combinedFeesUSD })} {cardExpanded ? ( ) : ( )} {t('main.fees.network')} {!gasCostUSD ? t('main.fees.free') : t('format.currency', { value: gasCostUSD, })} {feeCosts.length ? ( {t('main.fees.provider')} {t('format.currency', { value: feeCostUSD, })} ) : null} {showIntegratorFeeCollectionDetails ? ( {feeConfig?.name || t('main.fees.defaultIntegrator')} {feeConfig?.showFeePercentage && ( <> ({t('format.percent', { value: feePercentage })}) )} {feeConfig?.showFeeTooltip && (feeConfig?.name || feeConfig?.feeTooltipComponent) ? ( {t('format.currency', { value: feeAmountUSD, })} ) : ( {t('format.currency', { value: feeAmountUSD, })} )} ) : null} {t('main.priceImpact')} {t('format.percent', { value: priceImpact, usePlusSign: true, })} {!isRouteDone(route) ? ( <> {t('main.maxSlippage')} {route.steps[0].action.slippage ? t('format.percent', { value: route.steps[0].action.slippage, }) : t('button.auto')} {t('main.minReceived')} {t('format.tokenAmount', { value: formatTokenAmount( BigInt(route.toAmountMin), route.toToken.decimals ), })}{' '} {route.toToken.symbol} ) : null} ) }