import React, { useContext, useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import BigNumber from 'bignumber.js'; import { GAS_ESTIMATE_TYPES } from '../../../../shared/constants/gas'; import { usePrevious } from '../../../hooks/usePrevious'; import { I18nContext } from '../../../contexts/i18n'; import { getGasEstimateType, getGasFeeEstimates, getIsGasEstimatesLoading, } from '../../../ducks/metamask/metamask'; import Typography from '../../ui/typography/typography'; import { TYPOGRAPHY, FONT_WEIGHT, } from '../../../helpers/constants/design-system'; import InfoTooltip from '../../ui/info-tooltip/info-tooltip'; import { getGasFeeTimeEstimate } from '../../../store/actions'; import { GAS_FORM_ERRORS } from '../../../helpers/constants/gas'; import { useGasFeeContext } from '../../../contexts/gasFee'; // Once we reach this second threshold, we switch to minutes as a unit const SECOND_CUTOFF = 90; // eslint-disable-next-line prefer-destructuring const EIP_1559_V2 = process.env.EIP_1559_V2; // Shows "seconds" as unit of time if under SECOND_CUTOFF, otherwise "minutes" const toHumanReadableTime = (milliseconds = 1, t) => { const seconds = Math.ceil(milliseconds / 1000); if (seconds <= SECOND_CUTOFF) { return t('gasTimingSeconds', [seconds]); } return t('gasTimingMinutes', [Math.ceil(seconds / 60)]); }; export default function GasTiming({ maxFeePerGas = 0, maxPriorityFeePerGas = 0, gasWarnings, }) { const gasEstimateType = useSelector(getGasEstimateType); const gasFeeEstimates = useSelector(getGasFeeEstimates); const isGasEstimatesLoading = useSelector(getIsGasEstimatesLoading); const [customEstimatedTime, setCustomEstimatedTime] = useState(null); const t = useContext(I18nContext); const { estimateToUse } = useGasFeeContext(); // If the user has chosen a value lower than the low gas fee estimate, // We'll need to use the useEffect hook below to make a call to calculate // the time to show const isUnknownLow = gasFeeEstimates?.low && Number(maxPriorityFeePerGas) < Number(gasFeeEstimates.low.suggestedMaxPriorityFeePerGas); const previousMaxFeePerGas = usePrevious(maxFeePerGas); const previousMaxPriorityFeePerGas = usePrevious(maxPriorityFeePerGas); const previousIsUnknownLow = usePrevious(isUnknownLow); useEffect(() => { const priority = maxPriorityFeePerGas; const fee = maxFeePerGas; if ( isUnknownLow || (priority && priority !== previousMaxPriorityFeePerGas) || (fee && fee !== previousMaxFeePerGas) ) { // getGasFeeTimeEstimate requires parameters in string format getGasFeeTimeEstimate( new BigNumber(priority, 10).toString(10), new BigNumber(fee, 10).toString(10), ).then((result) => { if (maxFeePerGas === fee && maxPriorityFeePerGas === priority) { setCustomEstimatedTime(result); } }); } if (isUnknownLow !== false && previousIsUnknownLow === true) { setCustomEstimatedTime(null); } }, [ maxPriorityFeePerGas, maxFeePerGas, isUnknownLow, previousMaxFeePerGas, previousMaxPriorityFeePerGas, previousIsUnknownLow, ]); let unknownProcessingTimeText; if (EIP_1559_V2) { unknownProcessingTimeText = t('editGasTooLow'); } else { unknownProcessingTimeText = ( <> {t('editGasTooLow')}{' '} ); } if ( gasWarnings?.maxPriorityFee === GAS_FORM_ERRORS.MAX_PRIORITY_FEE_TOO_LOW || gasWarnings?.maxFee === GAS_FORM_ERRORS.MAX_FEE_TOO_LOW ) { return ( {unknownProcessingTimeText} ); } // Don't show anything if we don't have enough information if ( isGasEstimatesLoading || gasEstimateType !== GAS_ESTIMATE_TYPES.FEE_MARKET ) { return null; } const { low = {}, medium = {}, high = {} } = gasFeeEstimates; let text = ''; let attitude = 'positive'; // Anything medium or faster is positive if ( Number(maxPriorityFeePerGas) >= Number(medium.suggestedMaxPriorityFeePerGas) ) { // High+ is very likely, medium is likely if ( Number(maxPriorityFeePerGas) < Number(high.suggestedMaxPriorityFeePerGas) ) { // Medium text = t('gasTimingPositive', [ toHumanReadableTime(low.maxWaitTimeEstimate, t), ]); } else { // High text = t('gasTimingVeryPositive', [ toHumanReadableTime(high.minWaitTimeEstimate, t), ]); } } else { if (!EIP_1559_V2 || estimateToUse === 'low') { attitude = 'negative'; } // If the user has chosen a value less than our low estimate, // calculate a potential wait time if (isUnknownLow) { // If we didn't get any useful information, show the // "unknown processing time" message if ( !customEstimatedTime || customEstimatedTime === 'unknown' || customEstimatedTime?.upperTimeBound === 'unknown' ) { text = unknownProcessingTimeText; } else { text = t('gasTimingNegative', [ toHumanReadableTime(Number(customEstimatedTime?.upperTimeBound), t), ]); } } // code below needs to cleaned-up once EIP_1559_V2 flag is removed else if (EIP_1559_V2) { text = t('gasTimingNegative', [ toHumanReadableTime(low.maxWaitTimeEstimate, t), ]); } else { text = ( <> {t('gasTimingNegative', [ toHumanReadableTime(low.maxWaitTimeEstimate, t), ])} ); } } return ( {text} ); } GasTiming.propTypes = { maxPriorityFeePerGas: PropTypes.string, maxFeePerGas: PropTypes.string, gasWarnings: PropTypes.object, };