1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-05 07:24:56 +01:00
metamask-extension/ui/hooks/gasFeeInput/useGasFeeInputs.js
Dan J Miller 78f68b3dab Fix speed-up/cancel: don't update existing transaction data (#14355)
* Fix speed-up/cancel: don't update existing transaction data

* Move retryTxMeta state management to useGasFeeInputs.js

* Handle initial retryTxMeta set if no transaction is passed to useGasFeeInputs

* Ensure previousGas is use on retry transaction if it is available in useGasFeeInputs

* Remove update transaction mock and correctly test gas fee increase scenarios now that updateTransaction used in cancel-speedup is defined on the front end
2022-04-14 08:53:33 -07:00

396 lines
12 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,
getEIP1559V2Enabled,
} from '../../selectors';
import { hexToDecimal } from '../../helpers/utils/conversions.util';
import { isLegacyTransaction } from '../../helpers/utils/transactions.util';
import { useGasFeeEstimates } from '../useGasFeeEstimates';
import { editGasModeIsSpeedUpOrCancel } from '../../helpers/utils/gas';
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 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 eip1559V2Enabled = useSelector(getEIP1559V2Enabled);
const supportsEIP1559 =
useSelector(checkNetworkAndAccountSupports1559) &&
!isLegacyTransaction(transaction?.txParams);
const supportsEIP1559V2 = supportsEIP1559 && eip1559V2Enabled;
// 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')),
);
const properGasLimit = Number(hexToDecimal(transaction?.originalGasEstimate));
const [userEditedGasLimit, setUserEditedGasLimit] = useState(() =>
Boolean(transaction?.userEditedGasLimit),
);
/**
* 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,
setRetryTxMeta,
});
// 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);
setUserEditedGasLimit(true);
setMaxFeePerGas(maxFeePerGas);
setMaxPriorityFeePerGas(maxPriorityFeePerGas);
setGasPriceHasBeenManuallySet(true);
setEstimateUsed('custom');
}, [
setInternalEstimateToUse,
handleGasLimitOutOfBoundError,
setGasPrice,
gasPrice,
setGasLimit,
gasLimit,
setUserEditedGasLimit,
setMaxFeePerGas,
maxFeePerGas,
setMaxPriorityFeePerGas,
maxPriorityFeePerGas,
setGasPriceHasBeenManuallySet,
]);
return {
transaction,
maxFeePerGas,
maxFeePerGasFiat,
setMaxFeePerGas,
maxPriorityFeePerGas,
maxPriorityFeePerGasFiat,
setMaxPriorityFeePerGas,
gasPrice,
setGasPrice,
gasLimit,
setGasLimit,
properGasLimit,
userEditedGasLimit,
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,
};
}