import { useCallback, useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import { CUSTOM_GAS_ESTIMATE, GasRecommendations, EditGasModes, PriorityLevels, } from '../../../shared/constants/gas'; import { GAS_FORM_ERRORS } from '../../helpers/constants/gas'; import { checkNetworkAndAccountSupports1559, getAdvancedInlineGasShown, } from '../../selectors'; import { isLegacyTransaction } from '../../helpers/utils/transactions.util'; import { useGasFeeEstimates } from '../useGasFeeEstimates'; import { editGasModeIsSpeedUpOrCancel } from '../../helpers/utils/gas'; import { hexToDecimal } from '../../../shared/modules/conversion.utils'; import { useGasFeeErrors } from './useGasFeeErrors'; import { useGasPriceInput } from './useGasPriceInput'; import { useMaxFeePerGasInput } from './useMaxFeePerGasInput'; import { useMaxPriorityFeePerGasInput } from './useMaxPriorityFeePerGasInput'; import { useGasEstimates } from './useGasEstimates'; import { useTransactionFunctions } from './useTransactionFunctions'; /** * In EIP_1559_V2 implementation as used by useGasfeeInputContext() the use of this hook is evolved. * It is no longer used to keep transient state of advance gas fee inputs. * Transient state of inputs is maintained locally in /ui/components/app/advance-gas-fee-popover component. * * This hook is used now as source of shared data about transaction, it shares details of gas fee in transaction, * estimate used, is EIP-1559 supported and other details. It also have methods to update transaction. * * Transaction is used as single source of truth and as transaction is updated the fields shared by hook are * also updated using useEffect hook. * * It will be useful to plan a task to create a new hook of this shared information from this hook. * Methods like setEstimateToUse, onManualChange are deprecated in context of EIP_1559_V2 implementation. */ /** * @typedef {object} GasFeeInputReturnType * @property {object} [transaction] - . * @property {DecGweiString} [maxFeePerGas] - the maxFeePerGas input value. * @property {DecGweiString} [maxPriorityFeePerGas] - the maxPriorityFeePerGas * input value. * @property {DecGweiString} [gasPrice] - the gasPrice input value. * @property {(DecGweiString) => void} setGasPrice - state setter method to * update the gasPrice. * @property {DecGweiString} gasLimit - the gasLimit input value. * @property {(DecGweiString) => void} setGasLimit - state setter method to * update the gasLimit. * @property {DecGweiString} [properGasLimit] - proper gas limit. * @property {string} [editGasMode] - one of CANCEL, SPEED-UP, MODIFY_IN_PLACE, SWAPS. * @property {EstimateLevel} [estimateToUse] - the estimate level currently * selected. This will be null if the user has ejected from using the estimates. * @property {boolean} [isGasEstimatesLoading] - true if gas estimate is loading. * @property {DecGweiString} [maximumCostInHexWei] - maximum cost of transaction in HexWei. * @property {DecGweiString} [minimumCostInHexWei] - minimum cost of transaction in HexWei. * @property {string} [estimateUsed] - estimate used in the transaction. * @property {boolean} [isNetworkBusy] - true if network is busy. * @property {() => void} [onManualChange] - function to call when transaciton is manually changed. * @property {boolean} [balanceError] - true if user balance is less than transaction value. * @property {object} [gasErrors] - object of gas errors. * @property {boolean} [hasGasErrors] - true if there are gas errors. * @property {boolean} [hasSimulationError] - true if simulation error exists. * @property {number} [minimumGasLimitDec] - minimum gas limit in decimals. * @property {boolean} [supportsEIP1559] - true if EIP1559 is cupported. * @property {() => void} cancelTransaction - cancel the transaction. * @property {() => void} speedUpTransaction - speed up the transaction. * @property {(string, number, number, number, string) => void} updateTransaction - update the transaction. * @property {(boolean) => void} updateTransactionToTenPercentIncreasedGasFee - update the cancel / speed transaction to * gas fee which is equal to current gas fee +10 percent. * @property {(string) => void} updateTransactionUsingDAPPSuggestedValues - update the transaction to DAPP suggested gas value. * @property {(string) => void} updateTransactionUsingEstimate - update the transaction using the estimate passed. */ /** * Uses gasFeeEstimates and state to keep track of user gas fee inputs. * Will update the gas fee state when estimates update if the user has not yet * modified the fields. * * @param {GasRecommendations} [defaultEstimateToUse] - which estimate * level to default the 'estimateToUse' state variable to. * @param {object} [_transaction] * @param {string} [minimumGasLimit] * @param {EditGasModes[keyof EditGasModes]} editGasMode * @returns {GasFeeInputReturnType & import( * './useGasFeeEstimates' * ).GasEstimates} gas fee input state and the GasFeeEstimates object */ export function useGasFeeInputs( defaultEstimateToUse = GasRecommendations.medium, _transaction, minimumGasLimit = '0x5208', editGasMode = EditGasModes.modifyInPlace, ) { const initialRetryTxMeta = { txParams: _transaction?.txParams, id: _transaction?.id, userFeeLevel: _transaction?.userFeeLevel, originalGasEstimate: _transaction?.originalGasEstimate, userEditedGasLimit: _transaction?.userEditedGasLimit, }; if (_transaction?.previousGas) { initialRetryTxMeta.previousGas = _transaction?.previousGas; } const [retryTxMeta, setRetryTxMeta] = useState(initialRetryTxMeta); const transaction = editGasModeIsSpeedUpOrCancel(editGasMode) ? retryTxMeta : _transaction; const supportsEIP1559 = useSelector(checkNetworkAndAccountSupports1559) && !isLegacyTransaction(transaction?.txParams); // We need the gas estimates from the GasFeeController in the background. // Calling this hooks initiates polling for new gas estimates and returns the // current estimate. const { gasEstimateType, gasFeeEstimates, isGasEstimatesLoading, isNetworkBusy, } = useGasFeeEstimates(); const userPrefersAdvancedGas = useSelector(getAdvancedInlineGasShown); const [estimateToUse, setInternalEstimateToUse] = useState(() => { if ( userPrefersAdvancedGas && transaction?.txParams?.maxPriorityFeePerGas && transaction?.txParams?.maxFeePerGas ) { return null; } if (transaction) { return transaction?.userFeeLevel || null; } return defaultEstimateToUse; }); const [estimateUsed, setEstimateUsed] = useState(() => { if (estimateToUse) { return estimateToUse; } return PriorityLevels.custom; }); const [gasLimit, setGasLimit] = useState(() => Number(hexToDecimal(transaction?.txParams?.gas ?? '0x0')), ); const properGasLimit = Number(hexToDecimal(transaction?.originalGasEstimate)); /** * In EIP-1559 V2 designs change to gas estimate is always updated to transaction * Thus callback setEstimateToUse can be deprecate in favour of this useEffect * so that transaction is source of truth whenever possible. */ useEffect(() => { if (supportsEIP1559) { if (transaction?.userFeeLevel) { setEstimateUsed(transaction?.userFeeLevel); setInternalEstimateToUse(transaction?.userFeeLevel); } setGasLimit(Number(hexToDecimal(transaction?.txParams?.gas ?? '0x0'))); } }, [ setEstimateUsed, setGasLimit, setInternalEstimateToUse, supportsEIP1559, transaction, ]); const { gasPrice, setGasPrice, setGasPriceHasBeenManuallySet } = useGasPriceInput({ estimateToUse, gasEstimateType, gasFeeEstimates, transaction, }); const { maxFeePerGas, setMaxFeePerGas } = useMaxFeePerGasInput({ estimateToUse, gasEstimateType, gasFeeEstimates, transaction, }); const { maxPriorityFeePerGas, setMaxPriorityFeePerGas } = useMaxPriorityFeePerGasInput({ estimateToUse, gasEstimateType, gasFeeEstimates, transaction, }); const { estimatedMinimumNative, maximumCostInHexWei, minimumCostInHexWei } = useGasEstimates({ editGasMode, gasEstimateType, gasFeeEstimates, gasLimit, gasPrice, maxFeePerGas, maxPriorityFeePerGas, minimumGasLimit, transaction, }); const { balanceError, gasErrors, hasGasErrors, hasSimulationError } = useGasFeeErrors({ gasEstimateType, gasFeeEstimates, isGasEstimatesLoading, gasLimit, gasPrice, maxPriorityFeePerGas, maxFeePerGas, minimumCostInHexWei, minimumGasLimit, transaction, }); const handleGasLimitOutOfBoundError = useCallback(() => { if (gasErrors.gasLimit === GAS_FORM_ERRORS.GAS_LIMIT_OUT_OF_BOUNDS) { const transactionGasLimitDec = hexToDecimal(transaction?.txParams?.gas); const minimumGasLimitDec = hexToDecimal(minimumGasLimit); setGasLimit( transactionGasLimitDec > minimumGasLimitDec ? transactionGasLimitDec : minimumGasLimitDec, ); } }, [minimumGasLimit, gasErrors.gasLimit, transaction]); const { cancelTransaction, speedUpTransaction, updateTransaction, updateTransactionToTenPercentIncreasedGasFee, updateTransactionUsingDAPPSuggestedValues, updateTransactionUsingEstimate, } = useTransactionFunctions({ defaultEstimateToUse, editGasMode, gasFeeEstimates, gasLimit, maxPriorityFeePerGas, minimumGasLimit, transaction, setRetryTxMeta, }); const onManualChange = useCallback(() => { setInternalEstimateToUse(CUSTOM_GAS_ESTIMATE); handleGasLimitOutOfBoundError(); // Restore existing values setGasPrice(gasPrice); setGasLimit(gasLimit); setMaxFeePerGas(maxFeePerGas); setMaxPriorityFeePerGas(maxPriorityFeePerGas); setGasPriceHasBeenManuallySet(true); setEstimateUsed('custom'); }, [ setInternalEstimateToUse, handleGasLimitOutOfBoundError, setGasPrice, gasPrice, setGasLimit, gasLimit, setMaxFeePerGas, maxFeePerGas, setMaxPriorityFeePerGas, maxPriorityFeePerGas, setGasPriceHasBeenManuallySet, ]); return { transaction, maxFeePerGas, maxPriorityFeePerGas, gasPrice, setGasPrice, gasLimit, setGasLimit, properGasLimit, editGasMode, estimateToUse, estimatedMinimumNative, maximumCostInHexWei, minimumCostInHexWei, estimateUsed, gasFeeEstimates, isNetworkBusy, onManualChange, // error and warnings balanceError, gasErrors, hasGasErrors, hasSimulationError, minimumGasLimitDec: hexToDecimal(minimumGasLimit), supportsEIP1559, cancelTransaction, speedUpTransaction, updateTransaction, updateTransactionToTenPercentIncreasedGasFee, updateTransactionUsingDAPPSuggestedValues, updateTransactionUsingEstimate, }; }