import type { LiFiStep, RouteExtended, StepExtended } from '@lifi/sdk' import { useEthereumContext } from '@lifi/widget-provider' import ArrowForward from '@mui/icons-material/ArrowForward' import { Box, Divider, Tooltip } from '@mui/material' import { Fragment } from 'react' import { useTranslation } from 'react-i18next' import { useAvailableChains } from '../../hooks/useAvailableChains.js' import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js' import type { ModeOptions, WidgetFeeConfig, WidgetMode, } from '../../types/widget.js' import { formatTokenAmount, formatTokenPrice } from '../../utils/format.js' import { SmallAvatar } from '../Avatar/SmallAvatar.js' import { Card } from '../Card/Card.js' import { CardIconButton } from '../Card/CardIconButton.js' import { StepActionsHeader, StepActionsTitle, StepLabelTypography, } from './StepActions.style.js' interface StepDetailsLabelProps { step: StepExtended mode?: Extract modeOptions?: ModeOptions feeConfig?: WidgetFeeConfig relayerSupport?: boolean } export const StepActions: React.FC<{ route: RouteExtended }> = ({ route }) => { const { t } = useTranslation() const { mode, modeOptions, feeConfig, hiddenUI } = useWidgetConfig() const { isGaslessStep } = useEthereumContext() const headerIncludedSteps = route.steps.flatMap((step) => step.includedSteps) const flatSteps = route.steps.flatMap((routeStep) => { let steps = routeStep.includedSteps if (hiddenUI?.integratorStepDetails) { const feeCollectionStep = steps.find((s) => s.tool === 'feeCollection') if (feeCollectionStep) { steps = structuredClone(steps.filter((s) => s.tool !== 'feeCollection')) steps[0].estimate.fromAmount = feeCollectionStep.estimate.fromAmount } } const hasGaslessSupport = !!isGaslessStep?.(routeStep) return steps.map((includedStep) => ({ includedStep, hasGaslessSupport })) }) const tooltipContent = ( {flatSteps.map(({ includedStep: step, hasGaslessSupport }, i, arr) => { const showDivider = i !== arr.length - 1 const isFeeCollection = step.type === 'protocol' && step.tool === 'feeCollection' const toolName = isFeeCollection && feeConfig?.name ? feeConfig.name : step.toolDetails.name const toolLogoURI = isFeeCollection && feeConfig?.logoURI ? feeConfig.logoURI : step.toolDetails.logoURI return ( {toolLogoURI ? ( {toolName?.[0]} ) : null} {step.type === 'custom' && mode === 'custom' ? ( ) : step.type === 'cross' ? ( ) : step.type === 'protocol' ? ( ) : ( )} {showDivider && ( )} {showDivider && } ) })} ) return ( {t('main.route')} ({ borderRadius: theme.vars.shape.borderRadiusSecondary, })} > {headerIncludedSteps.map((includedStep, index) => ( {index > 0 ? ( ) : null} {includedStep.toolDetails.name[0]} ))} ) } const StepDetailsContent: React.FC<{ step: StepExtended }> = ({ step }) => { const { t } = useTranslation() const sameTokenProtocolStep = step.action.fromToken.chainId === step.action.toToken.chainId && step.action.fromToken.address === step.action.toToken.address let fromAmount: string | undefined if (sameTokenProtocolStep) { const estimatedFromAmount = BigInt(step.estimate.fromAmount) - BigInt(step.estimate.toAmount) fromAmount = estimatedFromAmount > 0n ? formatTokenAmount(estimatedFromAmount, step.action.fromToken.decimals) : formatTokenAmount( BigInt(step.estimate.fromAmount), step.action.fromToken.decimals ) } else { fromAmount = formatTokenAmount( BigInt(step.estimate.fromAmount), step.action.fromToken.decimals ) } const showToAmount = step.type !== 'custom' && step.tool !== 'custom' && !sameTokenProtocolStep return ( {!showToAmount ? ( <> {t('format.tokenAmount', { value: formatTokenAmount( BigInt(step.estimate.fromAmount), step.action.fromToken.decimals ), })}{' '} {step.action.fromToken.symbol} {' - '} ) : null} {t('format.tokenAmount', { value: fromAmount, })}{' '} {step.action.fromToken.symbol} {showToAmount ? ( <> {t('format.tokenAmount', { value: formatTokenAmount( BigInt(step.execution?.toAmount ?? step.estimate.toAmount), step.execution?.toToken?.decimals ?? step.action.toToken.decimals ), })}{' '} {step.execution?.toToken?.symbol ?? step.action.toToken.symbol} ) : ( ` (${t('format.currency', { value: formatTokenPrice( fromAmount, step.action.fromToken.priceUSD, step.action.fromToken.decimals ), })})` )} ) } const CustomStepDetailsLabel: React.FC = ({ step, mode, modeOptions, }) => { const { t } = useTranslation() if (!mode) { return null } // FIXME: step transaction request overrides step tool details, but not included step tool details const toolDetails = mode === 'custom' && (step as unknown as LiFiStep).includedSteps?.length > 0 ? (step as unknown as LiFiStep).includedSteps.find( (step) => step.tool === 'custom' && step.toolDetails.key !== 'custom' )?.toolDetails || step.toolDetails : step.toolDetails const stepDetailsKey = (mode === 'custom' && modeOptions?.custom?.type) || 'checkout' return ( {t(`main.${stepDetailsKey}StepDetails`, { tool: toolDetails.name, })} ) } const BridgeStepDetailsLabel: React.FC< Omit > = ({ step }) => { const { t } = useTranslation() const { getChainById } = useAvailableChains() return ( {t('main.bridgeStepDetails', { from: getChainById(step.action.fromChainId)?.name, to: getChainById(step.action.toChainId)?.name, tool: step.toolDetails.name, })} ) } const SwapStepDetailsLabel: React.FC< Omit > = ({ step }) => { const { t } = useTranslation() const { getChainById } = useAvailableChains() return ( {t('main.swapStepDetails', { chain: getChainById(step.action.fromChainId)?.name, tool: step.toolDetails.name, })} ) } const ProtocolStepDetailsLabel: React.FC< Omit > = ({ step, feeConfig, relayerSupport }) => { const { t } = useTranslation() return ( {step.toolDetails.key === 'feeCollection' ? feeConfig?.name || (relayerSupport ? t('main.fees.relayerService') : t('main.fees.defaultIntegrator')) : step.toolDetails.key === 'gasZip' ? t('main.refuelStepDetails', { tool: step.toolDetails.name, }) : step.toolDetails.name} ) }