import { getEstimatedGasPrices, getEstimatedGasTimes, getFeatureFlags, getIsMainnet } from '../selectors' import { hexWEIToDecGWEI } from '../helpers/utils/conversions.util' import { useSelector } from 'react-redux' import { useRef, useEffect, useState, useMemo } from 'react' import { isEqual } from 'lodash' import { captureException } from '@sentry/browser' import { getRawTimeEstimateData } from '../helpers/utils/gas-time-estimates.util' import { getCurrentLocale } from '../ducks/metamask/metamask' /** * Calculate the number of minutes remaining until the transaction completes. * @param {number} initialTimeEstimate - timestamp for the projected completion time * @param {number} submittedTime - timestamp of when the tx was submitted * @return {number} minutes remaining */ function calcTransactionTimeRemaining (initialTimeEstimate, submittedTime) { const currentTime = (new Date()).getTime() const timeElapsedSinceSubmission = (currentTime - submittedTime) / 1000 const timeRemainingOnEstimate = initialTimeEstimate - timeElapsedSinceSubmission const renderingTimeRemainingEstimate = Math.round(timeRemainingOnEstimate / 60) return renderingTimeRemainingEstimate } /** * returns a string representing the number of minutes predicted for the transaction to be * completed. Only returns this prediction if the transaction is the earliest pending * transaction, and the feature flag for showing timing is enabled. * @param {bool} isPending - is the transaction currently pending * @param {bool} isEarliestNonce - is this transaction the earliest nonce in list * @param {number} submittedTime - the timestamp for when the transaction was submitted * @param {number} currentGasPrice - gas price to use for calculation of time * @returns {string | undefined} i18n formatted string if applicable */ export function useTransactionTimeRemaining ( isPending, isEarliestNonce, submittedTime, currentGasPrice, ) { // the following two selectors return the result of mapping over an array, as such they // will always be new objects and trigger effects. To avoid this, we use isEqual as the // equalityFn to only update when the data is new. const gasPrices = useSelector(getEstimatedGasPrices, isEqual) const estimatedTimes = useSelector(getEstimatedGasTimes, isEqual) const locale = useSelector(getCurrentLocale) const isMainNet = useSelector(getIsMainnet) const interval = useRef() const [timeRemaining, setTimeRemaining] = useState(null) const featureFlags = useSelector(getFeatureFlags) const transactionTimeFeatureActive = featureFlags?.transactionTime const rtf = new Intl.RelativeTimeFormat(locale.replace('_', '-'), { numeric: 'auto', style: 'narrow' }) // Memoize this value so it can be used as a dependency in the effect below const initialTimeEstimate = useMemo(() => { const customGasPrice = Number(hexWEIToDecGWEI(currentGasPrice)) try { const { newTimeEstimate, } = getRawTimeEstimateData(customGasPrice, gasPrices, estimatedTimes) return newTimeEstimate } catch (error) { captureException(error) return NaN } }, [ currentGasPrice, gasPrices, estimatedTimes ]) useEffect(() => { if ( isMainNet && transactionTimeFeatureActive && isPending && isEarliestNonce && !isNaN(initialTimeEstimate) ) { clearInterval(interval.current) setTimeRemaining( calcTransactionTimeRemaining(initialTimeEstimate, submittedTime), ) interval.current = setInterval(() => { setTimeRemaining( calcTransactionTimeRemaining(initialTimeEstimate, submittedTime), ) }, 10000) return () => clearInterval(interval.current) } }, [ isMainNet, transactionTimeFeatureActive, isEarliestNonce, isPending, submittedTime, initialTimeEstimate, ]) // there are numerous checks to determine if time should be displayed. // if any of the following are true, the timeRemaining will be null // User is currently not on the mainnet // User does not have the transactionTime feature flag enabled // The transaction is not pending, or isn't the earliest nonce return timeRemaining ? rtf.format(timeRemaining, 'minute') : undefined }