1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-22 19:26:13 +02:00
metamask-extension/ui/hooks/gasFeeInput/useGasFeeInputs.js
Elliot Winkler 7b963cabd7
Alert users when the network is busy (#12268)
When a lot of transactions are occurring on the network, such as during
an NFT drop, it drives gas fees up. When this happens, we want to not
only inform the user about this, but also dissuade them from using a
higher gas fee (as we have proved in testing that high gas fees can
cause bidding wars and exacerbate the situation).

The method for determining whether the network is "busy" is already
handled by GasFeeController, which exposes a `networkCongestion`
property within the gas fee estimate data. If this number exceeds 0.66 —
meaning that the current base fee is above the 66th percentile among the
base fees over the last several days — then we determine that the
network is "busy".
2022-01-07 12:18:02 -07:00

367 lines
11 KiB
JavaScript

import { useCallback, useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import {
CUSTOM_GAS_ESTIMATE,
GAS_RECOMMENDATIONS,
EDIT_GAS_MODES,
PRIORITY_LEVELS,
} from '../../../shared/constants/gas';
import { GAS_FORM_ERRORS } from '../../helpers/constants/gas';
import {
checkNetworkAndAccountSupports1559,
getAdvancedInlineGasShown,
} from '../../selectors';
import { hexToDecimal } from '../../helpers/utils/conversions.util';
import { isLegacyTransaction } from '../../helpers/utils/transactions.util';
import { useGasFeeEstimates } from '../useGasFeeEstimates';
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 {DecGweiString} [maxFeePerGas] - the maxFeePerGas input value.
* @property {string} [maxFeePerGasFiat] - the maxFeePerGas converted to the
* user's preferred currency.
* @property {(DecGweiString) => void} setMaxFeePerGas - state setter method to
* update the maxFeePerGas.
* @property {DecGweiString} [maxPriorityFeePerGas] - the maxPriorityFeePerGas
* input value.
* @property {string} [maxPriorityFeePerGasFiat] - the maxPriorityFeePerGas
* converted to the user's preferred currency.
* @property {(DecGweiString) => void} setMaxPriorityFeePerGas - state setter
* method to update the maxPriorityFeePerGas.
* @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 {EstimateLevel} [estimateToUse] - the estimate level currently
* selected. This will be null if the user has ejected from using the
* estimates.
* @property {([EstimateLevel]) => void} setEstimateToUse - Setter method for
* choosing which EstimateLevel to use.
* @property {string} [estimatedMinimumFiat] - The amount estimated to be paid
* based on current network conditions. Expressed in user's preferred
* currency.
* @property {string} [estimatedMaximumFiat] - the maximum amount estimated to be
* paid if current network transaction volume increases. Expressed in user's
* preferred currency.
* @property {string} [estimatedMaximumNative] - the maximum amount estimated to
* be paid if the current network transaction volume increases. Expressed in
* the network's native currency.
*/
/**
* 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 {EstimateLevel} [defaultEstimateToUse] - which estimate
* level to default the 'estimateToUse' state variable to.
* @param {object} [transaction]
* @param {string} [minimumGasLimit]
* @param {EDIT_GAS_MODES[keyof EDIT_GAS_MODES]} editGasMode
* @returns {GasFeeInputReturnType & import(
* './useGasFeeEstimates'
* ).GasEstimates} gas fee input state and the GasFeeEstimates object
*/
export function useGasFeeInputs(
defaultEstimateToUse = GAS_RECOMMENDATIONS.MEDIUM,
transaction,
minimumGasLimit = '0x5208',
editGasMode = EDIT_GAS_MODES.MODIFY_IN_PLACE,
) {
const EIP_1559_V2_ENABLED =
// This is a string in unit tests but is a boolean in the browser
process.env.EIP_1559_V2 === true || process.env.EIP_1559_V2 === 'true';
const supportsEIP1559 =
useSelector(checkNetworkAndAccountSupports1559) &&
!isLegacyTransaction(transaction?.txParams);
const supportsEIP1559V2 = supportsEIP1559 && EIP_1559_V2_ENABLED;
// 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,
estimatedGasFeeTimeBounds,
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 PRIORITY_LEVELS.CUSTOM;
});
const [gasLimit, setGasLimit] = useState(() =>
Number(hexToDecimal(transaction?.txParams?.gas ?? '0x0')),
);
/**
* 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 (supportsEIP1559V2) {
if (transaction?.userFeeLevel) {
setEstimateUsed(transaction?.userFeeLevel);
setInternalEstimateToUse(transaction?.userFeeLevel);
}
setGasLimit(Number(hexToDecimal(transaction?.txParams?.gas ?? '0x0')));
}
}, [
setEstimateUsed,
setGasLimit,
setInternalEstimateToUse,
supportsEIP1559V2,
transaction,
]);
const {
gasPrice,
setGasPrice,
setGasPriceHasBeenManuallySet,
} = useGasPriceInput({
estimateToUse,
gasEstimateType,
gasFeeEstimates,
transaction,
});
const {
maxFeePerGas,
maxFeePerGasFiat,
setMaxFeePerGas,
} = useMaxFeePerGasInput({
estimateToUse,
gasEstimateType,
gasFeeEstimates,
gasLimit,
gasPrice,
supportsEIP1559V2,
transaction,
});
const {
maxPriorityFeePerGas,
maxPriorityFeePerGasFiat,
setMaxPriorityFeePerGas,
} = useMaxPriorityFeePerGasInput({
estimateToUse,
gasEstimateType,
gasFeeEstimates,
gasLimit,
supportsEIP1559V2,
transaction,
});
const {
estimatedBaseFee,
estimatedMaximumFiat,
estimatedMinimumFiat,
estimatedMaximumNative,
estimatedMinimumNative,
maximumCostInHexWei,
minimumCostInHexWei,
} = useGasEstimates({
editGasMode,
gasEstimateType,
gasFeeEstimates,
gasLimit,
gasPrice,
maxFeePerGas,
maxPriorityFeePerGas,
minimumGasLimit,
transaction,
});
const {
balanceError,
estimatesUnavailableWarning,
gasErrors,
gasWarnings,
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,
});
// When a user selects an estimate level, it will wipe out what they have
// previously put in the inputs. This returns the inputs to the estimated
// values at the level specified.
const setEstimateToUse = useCallback(
(estimateLevel) => {
setInternalEstimateToUse(estimateLevel);
handleGasLimitOutOfBoundError();
setMaxFeePerGas(null);
setMaxPriorityFeePerGas(null);
setGasPrice(null);
setGasPriceHasBeenManuallySet(false);
setEstimateUsed(estimateLevel);
},
[
setInternalEstimateToUse,
handleGasLimitOutOfBoundError,
setMaxFeePerGas,
setMaxPriorityFeePerGas,
setGasPrice,
setGasPriceHasBeenManuallySet,
setEstimateUsed,
],
);
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,
maxFeePerGasFiat,
setMaxFeePerGas,
maxPriorityFeePerGas,
maxPriorityFeePerGasFiat,
setMaxPriorityFeePerGas,
gasPrice,
setGasPrice,
gasLimit,
setGasLimit,
editGasMode,
estimateToUse,
setEstimateToUse,
estimatedMinimumFiat,
estimatedMaximumFiat,
estimatedMaximumNative,
estimatedMinimumNative,
isGasEstimatesLoading,
maximumCostInHexWei,
minimumCostInHexWei,
estimateUsed,
gasFeeEstimates,
gasEstimateType,
estimatedGasFeeTimeBounds,
isNetworkBusy,
onManualChange,
estimatedBaseFee,
// error and warnings
balanceError,
estimatesUnavailableWarning,
gasErrors,
gasWarnings,
hasGasErrors,
hasSimulationError,
minimumGasLimitDec: hexToDecimal(minimumGasLimit),
supportsEIP1559,
supportsEIP1559V2,
cancelTransaction,
speedUpTransaction,
updateTransaction,
updateTransactionToTenPercentIncreasedGasFee,
updateTransactionUsingDAPPSuggestedValues,
updateTransactionUsingEstimate,
};
}