mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-30 16:18:07 +01:00
284 lines
7.2 KiB
JavaScript
284 lines
7.2 KiB
JavaScript
import { useMemo } from 'react';
|
|
import { shallowEqual, useSelector } from 'react-redux';
|
|
import { GasEstimateTypes, GAS_LIMITS } from '../../../shared/constants/gas';
|
|
import {
|
|
checkNetworkAndAccountSupports1559,
|
|
getSelectedAccount,
|
|
} from '../../selectors';
|
|
import { isLegacyTransaction } from '../../helpers/utils/transactions.util';
|
|
import { bnGreaterThan, bnLessThan } from '../../helpers/utils/util';
|
|
import { GAS_FORM_ERRORS } from '../../helpers/constants/gas';
|
|
import { Numeric } from '../../../shared/modules/Numeric';
|
|
|
|
const HIGH_FEE_WARNING_MULTIPLIER = 1.5;
|
|
|
|
const validateGasLimit = (gasLimit, minimumGasLimit) => {
|
|
const gasLimitTooLow = new Numeric(gasLimit, 10).lessThan(
|
|
new Numeric(minimumGasLimit || GAS_LIMITS.SIMPLE, 16),
|
|
);
|
|
|
|
if (gasLimitTooLow) {
|
|
return GAS_FORM_ERRORS.GAS_LIMIT_OUT_OF_BOUNDS;
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
const validateMaxPriorityFee = (maxPriorityFeePerGas, supportsEIP1559) => {
|
|
if (!supportsEIP1559) {
|
|
return undefined;
|
|
}
|
|
if (bnLessThan(maxPriorityFeePerGas, 0)) {
|
|
return GAS_FORM_ERRORS.MAX_PRIORITY_FEE_BELOW_MINIMUM;
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
const validateMaxFee = (
|
|
maxFeePerGas,
|
|
maxPriorityFeeError,
|
|
maxPriorityFeePerGas,
|
|
supportsEIP1559,
|
|
) => {
|
|
if (maxPriorityFeeError || !supportsEIP1559) {
|
|
return undefined;
|
|
}
|
|
if (bnGreaterThan(maxPriorityFeePerGas, maxFeePerGas)) {
|
|
return GAS_FORM_ERRORS.MAX_FEE_IMBALANCE;
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
const validateGasPrice = (
|
|
isFeeMarketGasEstimate,
|
|
gasPrice,
|
|
supportsEIP1559,
|
|
transaction,
|
|
) => {
|
|
if (supportsEIP1559 && isFeeMarketGasEstimate) {
|
|
return undefined;
|
|
}
|
|
if (
|
|
(!supportsEIP1559 || transaction?.txParams?.gasPrice) &&
|
|
bnLessThan(gasPrice, 0)
|
|
) {
|
|
return GAS_FORM_ERRORS.GAS_PRICE_TOO_LOW;
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
const getMaxPriorityFeeWarning = (
|
|
gasFeeEstimates,
|
|
isFeeMarketGasEstimate,
|
|
isGasEstimatesLoading,
|
|
maxPriorityFeePerGas,
|
|
supportsEIP1559,
|
|
) => {
|
|
if (!supportsEIP1559 || !isFeeMarketGasEstimate || isGasEstimatesLoading) {
|
|
return undefined;
|
|
}
|
|
if (
|
|
bnLessThan(
|
|
maxPriorityFeePerGas,
|
|
gasFeeEstimates?.low?.suggestedMaxPriorityFeePerGas,
|
|
)
|
|
) {
|
|
return GAS_FORM_ERRORS.MAX_PRIORITY_FEE_TOO_LOW;
|
|
}
|
|
if (
|
|
gasFeeEstimates?.high &&
|
|
bnGreaterThan(
|
|
maxPriorityFeePerGas,
|
|
gasFeeEstimates.high.suggestedMaxPriorityFeePerGas *
|
|
HIGH_FEE_WARNING_MULTIPLIER,
|
|
)
|
|
) {
|
|
return GAS_FORM_ERRORS.MAX_PRIORITY_FEE_HIGH_WARNING;
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
const getMaxFeeWarning = (
|
|
gasFeeEstimates,
|
|
isGasEstimatesLoading,
|
|
isFeeMarketGasEstimate,
|
|
maxFeeError,
|
|
maxPriorityFeeError,
|
|
maxFeePerGas,
|
|
supportsEIP1559,
|
|
) => {
|
|
if (
|
|
maxPriorityFeeError ||
|
|
maxFeeError ||
|
|
!isFeeMarketGasEstimate ||
|
|
!supportsEIP1559 ||
|
|
isGasEstimatesLoading
|
|
) {
|
|
return undefined;
|
|
}
|
|
if (bnLessThan(maxFeePerGas, gasFeeEstimates?.low?.suggestedMaxFeePerGas)) {
|
|
return GAS_FORM_ERRORS.MAX_FEE_TOO_LOW;
|
|
}
|
|
if (
|
|
gasFeeEstimates?.high &&
|
|
bnGreaterThan(
|
|
maxFeePerGas,
|
|
gasFeeEstimates.high.suggestedMaxFeePerGas * HIGH_FEE_WARNING_MULTIPLIER,
|
|
)
|
|
) {
|
|
return GAS_FORM_ERRORS.MAX_FEE_HIGH_WARNING;
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
const hasBalanceError = (minimumCostInHexWei, transaction, ethBalance) => {
|
|
if (minimumCostInHexWei === undefined || ethBalance === undefined) {
|
|
return false;
|
|
}
|
|
const minimumTxCostInHexWei = new Numeric(minimumCostInHexWei, 16).add(
|
|
new Numeric(transaction?.txParams?.value || '0x0', 16),
|
|
);
|
|
const ethBalanceInHexWei = new Numeric(ethBalance, 16);
|
|
|
|
return minimumTxCostInHexWei.greaterThan(ethBalanceInHexWei);
|
|
};
|
|
|
|
/**
|
|
* @typedef {object} GasFeeErrorsReturnType
|
|
* @property {object} [gasErrors] - combined map of errors and warnings.
|
|
* @property {boolean} [hasGasErrors] - true if there are errors that can block submission.
|
|
* @property {boolean} [balanceError] - true if user balance is less than transaction value.
|
|
* @property {boolean} [hasSimulationError] - true if simulation error exists.
|
|
*/
|
|
|
|
/**
|
|
* @param options
|
|
* @param options.gasEstimateType
|
|
* @param options.gasFeeEstimates
|
|
* @param options.isGasEstimatesLoading
|
|
* @param options.gasLimit
|
|
* @param options.gasPrice
|
|
* @param options.maxPriorityFeePerGas
|
|
* @param options.maxFeePerGas
|
|
* @param options.minimumCostInHexWei
|
|
* @param options.minimumGasLimit
|
|
* @param options.transaction
|
|
* @returns {GasFeeErrorsReturnType}
|
|
*/
|
|
export function useGasFeeErrors({
|
|
gasEstimateType,
|
|
gasFeeEstimates,
|
|
isGasEstimatesLoading,
|
|
gasLimit,
|
|
gasPrice,
|
|
maxPriorityFeePerGas,
|
|
maxFeePerGas,
|
|
minimumCostInHexWei,
|
|
minimumGasLimit,
|
|
transaction,
|
|
}) {
|
|
const supportsEIP1559 =
|
|
useSelector(checkNetworkAndAccountSupports1559) &&
|
|
!isLegacyTransaction(transaction?.txParams);
|
|
|
|
const isFeeMarketGasEstimate = gasEstimateType === GasEstimateTypes.feeMarket;
|
|
|
|
// Get all errors
|
|
const gasLimitError = validateGasLimit(gasLimit, minimumGasLimit);
|
|
|
|
const maxPriorityFeeError = validateMaxPriorityFee(
|
|
maxPriorityFeePerGas,
|
|
supportsEIP1559,
|
|
);
|
|
|
|
const maxFeeError = validateMaxFee(
|
|
maxFeePerGas,
|
|
maxPriorityFeeError,
|
|
maxPriorityFeePerGas,
|
|
supportsEIP1559,
|
|
);
|
|
|
|
const gasPriceError = validateGasPrice(
|
|
isFeeMarketGasEstimate,
|
|
gasPrice,
|
|
supportsEIP1559,
|
|
transaction,
|
|
);
|
|
|
|
// Get all warnings
|
|
const maxPriorityFeeWarning = getMaxPriorityFeeWarning(
|
|
gasFeeEstimates,
|
|
isFeeMarketGasEstimate,
|
|
isGasEstimatesLoading,
|
|
maxPriorityFeePerGas,
|
|
supportsEIP1559,
|
|
);
|
|
|
|
const maxFeeWarning = getMaxFeeWarning(
|
|
gasFeeEstimates,
|
|
isGasEstimatesLoading,
|
|
isFeeMarketGasEstimate,
|
|
maxFeeError,
|
|
maxPriorityFeeError,
|
|
maxFeePerGas,
|
|
supportsEIP1559,
|
|
);
|
|
|
|
// Separating errors from warnings so we can know which value problems
|
|
// are blocking or simply useful information for the users
|
|
|
|
const gasErrors = useMemo(() => {
|
|
const errors = {};
|
|
if (gasLimitError) {
|
|
errors.gasLimit = gasLimitError;
|
|
}
|
|
if (maxPriorityFeeError) {
|
|
errors.maxPriorityFee = maxPriorityFeeError;
|
|
}
|
|
if (maxFeeError) {
|
|
errors.maxFee = maxFeeError;
|
|
}
|
|
if (gasPriceError) {
|
|
errors.gasPrice = gasPriceError;
|
|
}
|
|
return errors;
|
|
}, [gasLimitError, maxPriorityFeeError, maxFeeError, gasPriceError]);
|
|
|
|
const gasWarnings = useMemo(() => {
|
|
const warnings = {};
|
|
if (maxPriorityFeeWarning) {
|
|
warnings.maxPriorityFee = maxPriorityFeeWarning;
|
|
}
|
|
if (maxFeeWarning) {
|
|
warnings.maxFee = maxFeeWarning;
|
|
}
|
|
return warnings;
|
|
}, [maxPriorityFeeWarning, maxFeeWarning]);
|
|
|
|
// Determine if we have any errors which should block submission
|
|
const hasGasErrors = Boolean(Object.keys(gasErrors).length);
|
|
|
|
// Combine the warnings and errors into one object for easier use within the UI.
|
|
// This object should have no effect on whether or not the user can submit the form
|
|
const errorsAndWarnings = useMemo(
|
|
() => ({
|
|
...gasWarnings,
|
|
...gasErrors,
|
|
}),
|
|
[gasErrors, gasWarnings],
|
|
);
|
|
|
|
const { balance: ethBalance } = useSelector(getSelectedAccount, shallowEqual);
|
|
const balanceError = hasBalanceError(
|
|
minimumCostInHexWei,
|
|
transaction,
|
|
ethBalance,
|
|
);
|
|
|
|
return {
|
|
gasErrors: errorsAndWarnings,
|
|
hasGasErrors,
|
|
balanceError,
|
|
hasSimulationError: Boolean(transaction?.simulationFails),
|
|
};
|
|
}
|