1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-26 04:20:53 +01:00

Extracting out error and warning code from useGasFeeInputs hook (#12283)

Extracting out error and warning code from useGasFeeInputs hook
This commit is contained in:
Jyoti Puri 2021-10-18 17:25:35 +05:30 committed by GitHub
parent 71f91568db
commit 707ae7d652
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 655 additions and 119 deletions

267
ui/hooks/useGasFeeErrors.js Normal file
View File

@ -0,0 +1,267 @@
import { useMemo } from 'react';
import { useSelector } from 'react-redux';
import { GAS_ESTIMATE_TYPES, GAS_LIMITS } from '../../shared/constants/gas';
import {
conversionLessThan,
conversionGreaterThan,
} from '../../shared/modules/conversion.utils';
import {
checkNetworkAndAccountSupports1559,
getSelectedAccount,
} from '../selectors';
import { addHexes } from '../helpers/utils/conversions.util';
import { isLegacyTransaction } from '../helpers/utils/transactions.util';
import {
bnGreaterThan,
bnLessThan,
bnLessThanEqualTo,
} from '../helpers/utils/util';
import { GAS_FORM_ERRORS } from '../helpers/constants/gas';
const HIGH_FEE_WARNING_MULTIPLIER = 1.5;
const validateGasLimit = (gasLimit, minimumGasLimit) => {
const gasLimitTooLow = conversionLessThan(
{ value: gasLimit, fromNumericBase: 'dec' },
{ value: minimumGasLimit || GAS_LIMITS.SIMPLE, fromNumericBase: 'hex' },
);
if (gasLimitTooLow) return GAS_FORM_ERRORS.GAS_LIMIT_OUT_OF_BOUNDS;
return undefined;
};
const validateMaxPriorityFee = (maxPriorityFeePerGasToUse, supportsEIP1559) => {
if (supportsEIP1559 && bnLessThanEqualTo(maxPriorityFeePerGasToUse, 0)) {
return GAS_FORM_ERRORS.MAX_PRIORITY_FEE_BELOW_MINIMUM;
}
return undefined;
};
const validateMaxFee = (
maxFeePerGasToUse,
maxPriorityFeeError,
maxPriorityFeePerGasToUse,
supportsEIP1559,
) => {
if (maxPriorityFeeError) return undefined;
if (
supportsEIP1559 &&
bnGreaterThan(maxPriorityFeePerGasToUse, maxFeePerGasToUse)
) {
return GAS_FORM_ERRORS.MAX_FEE_IMBALANCE;
}
return undefined;
};
const validateGasPrice = (
isFeeMarketGasEstimate,
gasPriceToUse,
supportsEIP1559,
transaction,
) => {
if (
(!supportsEIP1559 || transaction?.txParams?.gasPrice) &&
!isFeeMarketGasEstimate &&
bnLessThanEqualTo(gasPriceToUse, 0)
) {
return GAS_FORM_ERRORS.GAS_PRICE_TOO_LOW;
}
return undefined;
};
const getMaxPriorityFeeWarning = (
gasFeeEstimates,
isFeeMarketGasEstimate,
isGasEstimatesLoading,
maxPriorityFeePerGasToUse,
supportsEIP1559,
) => {
if (!supportsEIP1559 || !isFeeMarketGasEstimate || isGasEstimatesLoading)
return undefined;
if (
bnLessThan(
maxPriorityFeePerGasToUse,
gasFeeEstimates?.low?.suggestedMaxPriorityFeePerGas,
)
) {
return GAS_FORM_ERRORS.MAX_PRIORITY_FEE_TOO_LOW;
}
if (
gasFeeEstimates?.high &&
bnGreaterThan(
maxPriorityFeePerGasToUse,
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,
maxFeePerGasToUse,
supportsEIP1559,
) => {
if (
maxPriorityFeeError ||
maxFeeError ||
!isFeeMarketGasEstimate ||
!supportsEIP1559
) {
return undefined;
}
if (
!isGasEstimatesLoading &&
bnLessThan(maxFeePerGasToUse, gasFeeEstimates?.low?.suggestedMaxFeePerGas)
) {
return GAS_FORM_ERRORS.MAX_FEE_TOO_LOW;
}
if (
gasFeeEstimates?.high &&
bnGreaterThan(
maxFeePerGasToUse,
gasFeeEstimates.high.suggestedMaxFeePerGas * HIGH_FEE_WARNING_MULTIPLIER,
)
) {
return GAS_FORM_ERRORS.MAX_FEE_HIGH_WARNING;
}
return undefined;
};
const getBalanceError = (minimumCostInHexWei, transaction, ethBalance) => {
const minimumTxCostInHexWei = addHexes(
minimumCostInHexWei,
transaction?.txParams?.value || '0x0',
);
return conversionGreaterThan(
{ value: minimumTxCostInHexWei, fromNumericBase: 'hex' },
{ value: ethBalance, fromNumericBase: 'hex' },
);
};
/**
* @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 {Object} gasWarnings - map of gas warnings for EIP-1559 fields.
* @property {boolean} [balanceError] - true if user balance is less than transaction value.
* @property {boolean} [estimatesUnavailableWarning] - true if supportsEIP1559 is true and
* estimate is not of type fee-market.
*/
export function useGasFeeErrors({
transaction,
gasEstimateType,
gasFeeEstimates,
gasLimit,
gasPriceToUse,
isGasEstimatesLoading,
maxPriorityFeePerGasToUse,
maxFeePerGasToUse,
minimumCostInHexWei,
minimumGasLimit,
}) {
const supportsEIP1559 =
useSelector(checkNetworkAndAccountSupports1559) &&
!isLegacyTransaction(transaction?.txParams);
const isFeeMarketGasEstimate =
gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET;
// Get all errors
const gasLimitError = validateGasLimit(gasLimit, minimumGasLimit);
const maxPriorityFeeError = validateMaxPriorityFee(
maxPriorityFeePerGasToUse,
supportsEIP1559,
);
const maxFeeError = validateMaxFee(
maxFeePerGasToUse,
maxPriorityFeeError,
maxPriorityFeePerGasToUse,
supportsEIP1559,
);
const gasPriceError = validateGasPrice(
isFeeMarketGasEstimate,
gasPriceToUse,
supportsEIP1559,
transaction,
);
// Get all warnings
const maxPriorityFeeWarning = getMaxPriorityFeeWarning(
gasFeeEstimates,
isFeeMarketGasEstimate,
isGasEstimatesLoading,
maxPriorityFeePerGasToUse,
supportsEIP1559,
);
const maxFeeWarning = getMaxFeeWarning(
gasFeeEstimates,
isGasEstimatesLoading,
isFeeMarketGasEstimate,
maxFeeError,
maxPriorityFeeError,
maxFeePerGasToUse,
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]);
const estimatesUnavailableWarning =
supportsEIP1559 && !isFeeMarketGasEstimate;
// 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);
const balanceError = getBalanceError(
minimumCostInHexWei,
transaction,
ethBalance,
);
return {
gasErrors: errorsAndWarnings,
hasGasErrors,
gasWarnings,
balanceError,
estimatesUnavailableWarning,
};
}

View File

@ -0,0 +1,364 @@
import { renderHook } from '@testing-library/react-hooks';
import { useSelector } from 'react-redux';
import { GAS_ESTIMATE_TYPES } from '../../shared/constants/gas';
import { GAS_FORM_ERRORS } from '../helpers/constants/gas';
import {
checkNetworkAndAccountSupports1559,
getSelectedAccount,
} from '../selectors';
import { useGasFeeErrors } from './useGasFeeErrors';
jest.mock('./useGasFeeEstimates', () => ({
useGasFeeEstimates: jest.fn(),
}));
jest.mock('react-redux', () => {
const actual = jest.requireActual('react-redux');
return {
...actual,
useSelector: jest.fn(),
};
});
const LEGACY_GAS_ESTIMATE_RETURN_VALUE = {
gasEstimateType: GAS_ESTIMATE_TYPES.LEGACY,
gasFeeEstimates: {
low: '10',
medium: '20',
high: '30',
},
estimatedGasFeeTimeBounds: {},
};
const FEE_MARKET_ESTIMATE_RETURN_VALUE = {
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
gasFeeEstimates: {
low: {
minWaitTimeEstimate: 180000,
maxWaitTimeEstimate: 300000,
suggestedMaxPriorityFeePerGas: '3',
suggestedMaxFeePerGas: '53',
},
medium: {
minWaitTimeEstimate: 15000,
maxWaitTimeEstimate: 60000,
suggestedMaxPriorityFeePerGas: '7',
suggestedMaxFeePerGas: '70',
},
high: {
minWaitTimeEstimate: 0,
maxWaitTimeEstimate: 15000,
suggestedMaxPriorityFeePerGas: '10',
suggestedMaxFeePerGas: '100',
},
estimatedBaseFee: '50',
},
estimatedGasFeeTimeBounds: {},
};
const generateUseSelectorRouter = ({
checkNetworkAndAccountSupports1559Response,
} = {}) => (selector) => {
if (selector === getSelectedAccount) {
return {
balance: '0x440aa47cc2556',
};
}
if (selector === checkNetworkAndAccountSupports1559) {
return checkNetworkAndAccountSupports1559Response;
}
return undefined;
};
const configureEIP1559 = () => {
useSelector.mockImplementation(
generateUseSelectorRouter({
checkNetworkAndAccountSupports1559Response: true,
}),
);
};
const configureLegacy = () => {
useSelector.mockImplementation(
generateUseSelectorRouter({
checkNetworkAndAccountSupports1559Response: false,
}),
);
};
const renderUseGasFeeErrorsHook = (props) => {
return renderHook(() =>
useGasFeeErrors({
transaction: { txParams: { type: '0x2', value: '100' } },
gasLimit: '21000',
gasPriceToUse: '10',
maxPriorityFeePerGasToUse: '10',
maxFeePerGasToUse: '100',
minimumCostInHexWei: '0x5208',
minimumGasLimit: '0x5208',
...FEE_MARKET_ESTIMATE_RETURN_VALUE,
...props,
}),
);
};
describe('useGasFeeErrors', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('gasLimit validation', () => {
beforeEach(() => {
configureLegacy();
});
it('does not returns gasLimitError if gasLimit is not below minimum', () => {
const { result } = renderUseGasFeeErrorsHook(
LEGACY_GAS_ESTIMATE_RETURN_VALUE,
);
expect(result.current.gasErrors.gasLimit).toBeUndefined();
expect(result.current.hasGasErrors).toBe(false);
});
it('returns gasLimitError if gasLimit is below minimum', () => {
const { result } = renderUseGasFeeErrorsHook({
gasLimit: '100',
...LEGACY_GAS_ESTIMATE_RETURN_VALUE,
});
expect(result.current.gasErrors.gasLimit).toBe(
GAS_FORM_ERRORS.GAS_LIMIT_OUT_OF_BOUNDS,
);
expect(result.current.hasGasErrors).toBe(true);
});
});
describe('maxPriorityFee validation', () => {
describe('EIP1559 compliant estimates', () => {
beforeEach(() => {
configureEIP1559();
});
it('does not return maxPriorityFeeError if maxPriorityFee is not 0', () => {
const { result } = renderUseGasFeeErrorsHook();
expect(result.current.gasErrors.maxPriorityFee).toBeUndefined();
expect(result.current.hasGasErrors).toBe(false);
});
it('return maxPriorityFeeError if maxPriorityFee is 0', () => {
const { result } = renderUseGasFeeErrorsHook({
maxPriorityFeePerGasToUse: '0',
});
expect(result.current.gasErrors.maxPriorityFee).toBe(
GAS_FORM_ERRORS.MAX_PRIORITY_FEE_BELOW_MINIMUM,
);
expect(result.current.hasGasErrors).toBe(true);
});
});
describe('Legacy estimates', () => {
beforeEach(() => {
configureLegacy();
});
it('does not return maxPriorityFeeError if maxPriorityFee is 0', () => {
const { result } = renderUseGasFeeErrorsHook(
LEGACY_GAS_ESTIMATE_RETURN_VALUE,
);
expect(result.current.gasErrors.maxPriorityFee).toBeUndefined();
expect(result.current.hasGasErrors).toBe(false);
});
});
});
describe('maxFee validation', () => {
describe('EIP1559 compliant estimates', () => {
beforeEach(() => {
configureEIP1559();
});
it('does not return maxFeeError if maxFee is greater than maxPriorityFee', () => {
const { result } = renderUseGasFeeErrorsHook();
expect(result.current.gasErrors.maxFee).toBeUndefined();
expect(result.current.hasGasErrors).toBe(false);
});
it('return maxFeeError if maxFee is less than maxPriorityFee', () => {
const { result } = renderUseGasFeeErrorsHook({
maxFeePerGasToUse: '1',
maxPriorityFeePerGasToUse: '10',
});
expect(result.current.gasErrors.maxFee).toBe(
GAS_FORM_ERRORS.MAX_FEE_IMBALANCE,
);
expect(result.current.hasGasErrors).toBe(true);
});
it('does not return MAX_FEE_IMBALANCE error if maxPriorityFeePerGasToUse is 0', () => {
const { result } = renderUseGasFeeErrorsHook({
maxFeePerGasToUse: '1',
maxPriorityFeePerGasToUse: '0',
});
expect(result.current.gasErrors.maxFee).toBeUndefined();
});
});
describe('Legacy estimates', () => {
beforeEach(() => {
configureLegacy();
});
it('does not return maxFeeError if maxFee is less than maxPriorityFee', () => {
const { result } = renderUseGasFeeErrorsHook({
maxFeePerGasToUse: '1',
maxPriorityFeePerGasToUse: '10',
...LEGACY_GAS_ESTIMATE_RETURN_VALUE,
});
expect(result.current.gasErrors.maxFee).toBeUndefined();
expect(result.current.hasGasErrors).toBe(false);
});
});
});
describe('gasPrice validation', () => {
describe('EIP1559 compliant estimates', () => {
beforeEach(() => {
configureEIP1559();
});
it('does not return gasPriceError if gasPrice is 0', () => {
const { result } = renderUseGasFeeErrorsHook({ gasPriceToUse: '0' });
expect(result.current.gasErrors.gasPrice).toBeUndefined();
expect(result.current.hasGasErrors).toBe(false);
});
});
describe('Legacy estimates', () => {
beforeEach(() => {
configureLegacy();
});
it('returns gasPriceError if gasPrice is 0', () => {
const { result } = renderUseGasFeeErrorsHook({
gasPriceToUse: '0',
...LEGACY_GAS_ESTIMATE_RETURN_VALUE,
});
expect(result.current.gasErrors.gasPrice).toBe(
GAS_FORM_ERRORS.GAS_PRICE_TOO_LOW,
);
expect(result.current.hasGasErrors).toBe(true);
});
it('does not return gasPriceError if gasPrice is > 0', () => {
const { result } = renderUseGasFeeErrorsHook(
LEGACY_GAS_ESTIMATE_RETURN_VALUE,
);
expect(result.current.gasErrors.gasPrice).toBeUndefined();
expect(result.current.hasGasErrors).toBe(false);
});
});
});
describe('maxPriorityFee warning', () => {
describe('EIP1559 compliant estimates', () => {
beforeEach(() => {
configureEIP1559();
});
it('does not return maxPriorityFeeWarning if maxPriorityFee is > suggestedMaxPriorityFeePerGas', () => {
const { result } = renderUseGasFeeErrorsHook();
expect(result.current.gasWarnings.maxPriorityFee).toBeUndefined();
});
it('return maxPriorityFeeWarning if maxPriorityFee is < suggestedMaxPriorityFeePerGas', () => {
const { result } = renderUseGasFeeErrorsHook({
maxPriorityFeePerGasToUse: '1',
});
expect(result.current.gasWarnings.maxPriorityFee).toBe(
GAS_FORM_ERRORS.MAX_PRIORITY_FEE_TOO_LOW,
);
});
it('return maxPriorityFeeWarning if maxPriorityFee is > gasFeeEstimates.high.suggestedMaxPriorityFeePerGas', () => {
const { result } = renderUseGasFeeErrorsHook({
maxPriorityFeePerGasToUse: '100',
});
expect(result.current.gasWarnings.maxPriorityFee).toBe(
GAS_FORM_ERRORS.MAX_PRIORITY_FEE_HIGH_WARNING,
);
});
});
describe('Legacy estimates', () => {
beforeEach(() => {
configureLegacy();
});
it('does not return maxPriorityFeeWarning if maxPriorityFee is < gasFeeEstimates.low.suggestedMaxPriorityFeePerGas', () => {
const { result } = renderUseGasFeeErrorsHook({
maxPriorityFeePerGasToUse: '1',
...LEGACY_GAS_ESTIMATE_RETURN_VALUE,
});
expect(result.current.gasWarnings.maxPriorityFee).toBeUndefined();
expect(result.current.hasGasErrors).toBe(false);
});
});
});
describe('maxFee warning', () => {
describe('EIP1559 compliant estimates', () => {
beforeEach(() => {
configureEIP1559();
});
it('does not return maxFeeWarning if maxFee is > suggestedMaxFeePerGas', () => {
const { result } = renderUseGasFeeErrorsHook();
expect(result.current.gasWarnings.maxFee).toBeUndefined();
});
it('return maxFeeWarning if maxFee is < suggestedMaxFeePerGas', () => {
const { result } = renderUseGasFeeErrorsHook({
maxFeePerGasToUse: '20',
});
expect(result.current.gasWarnings.maxFee).toBe(
GAS_FORM_ERRORS.MAX_FEE_TOO_LOW,
);
});
it('return maxFeeWarning if gasFeeEstimates are high and maxFee is > suggestedMaxFeePerGas', () => {
const { result } = renderUseGasFeeErrorsHook({
maxFeePerGasToUse: '1000',
});
expect(result.current.gasWarnings.maxFee).toBe(
GAS_FORM_ERRORS.MAX_FEE_HIGH_WARNING,
);
});
});
describe('Legacy estimates', () => {
beforeEach(() => {
configureLegacy();
});
it('does not return maxFeeWarning if maxFee is < suggestedMaxFeePerGas', () => {
const { result } = renderUseGasFeeErrorsHook({
maxFeePerGasToUse: '1',
...LEGACY_GAS_ESTIMATE_RETURN_VALUE,
});
expect(result.current.gasWarnings.maxFee).toBeUndefined();
});
});
});
describe('Balance Error', () => {
it('is false if balance is greater than transaction value', () => {
configureEIP1559();
const { result } = renderUseGasFeeErrorsHook();
expect(result.current.balanceError).toBe(false);
});
it('is true if balance is less than transaction value', () => {
configureLegacy();
const { result } = renderUseGasFeeErrorsHook({
transaction: { txParams: { type: '0x2', value: '0x440aa47cc2556' } },
...LEGACY_GAS_ESTIMATE_RETURN_VALUE,
});
expect(result.current.balanceError).toBe(true);
});
});
describe('estimatesUnavailableWarning', () => {
it('is false if supportsEIP1559 and gasEstimateType is fee-market', () => {
configureEIP1559();
const { result } = renderUseGasFeeErrorsHook();
expect(result.current.estimatesUnavailableWarning).toBe(false);
});
it('is true if supportsEIP1559 and gasEstimateType is not fee-market', () => {
useSelector.mockImplementation(
generateUseSelectorRouter({
checkNetworkAndAccountSupports1559Response: true,
}),
);
const { result } = renderUseGasFeeErrorsHook(
LEGACY_GAS_ESTIMATE_RETURN_VALUE,
);
expect(result.current.estimatesUnavailableWarning).toBe(true);
});
});
});

View File

@ -2,16 +2,8 @@ import { addHexPrefix } from 'ethereumjs-util';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { import { GAS_ESTIMATE_TYPES, EDIT_GAS_MODES } from '../../shared/constants/gas';
GAS_ESTIMATE_TYPES, import { multiplyCurrencies } from '../../shared/modules/conversion.utils';
EDIT_GAS_MODES,
GAS_LIMITS,
} from '../../shared/constants/gas';
import {
multiplyCurrencies,
conversionLessThan,
conversionGreaterThan,
} from '../../shared/modules/conversion.utils';
import { import {
getMaximumGasTotalInHexWei, getMaximumGasTotalInHexWei,
getMinimumGasTotalInHexWei, getMinimumGasTotalInHexWei,
@ -20,7 +12,6 @@ import { PRIMARY, SECONDARY } from '../helpers/constants/common';
import { import {
checkNetworkAndAccountSupports1559, checkNetworkAndAccountSupports1559,
getShouldShowFiat, getShouldShowFiat,
getSelectedAccount,
getAdvancedInlineGasShown, getAdvancedInlineGasShown,
} from '../selectors'; } from '../selectors';
@ -29,21 +20,14 @@ import {
decGWEIToHexWEI, decGWEIToHexWEI,
decimalToHex, decimalToHex,
hexToDecimal, hexToDecimal,
addHexes,
} from '../helpers/utils/conversions.util'; } from '../helpers/utils/conversions.util';
import {
bnGreaterThan,
bnLessThan,
bnLessThanEqualTo,
} from '../helpers/utils/util';
import { GAS_FORM_ERRORS } from '../helpers/constants/gas'; import { GAS_FORM_ERRORS } from '../helpers/constants/gas';
import { isLegacyTransaction } from '../helpers/utils/transactions.util'; import { isLegacyTransaction } from '../helpers/utils/transactions.util';
import { useCurrencyDisplay } from './useCurrencyDisplay'; import { useCurrencyDisplay } from './useCurrencyDisplay';
import { useGasFeeEstimates } from './useGasFeeEstimates'; import { useGasFeeEstimates } from './useGasFeeEstimates';
import { useUserPreferencedCurrency } from './useUserPreferencedCurrency'; import { useUserPreferencedCurrency } from './useUserPreferencedCurrency';
import { useGasFeeErrors } from './useGasFeeErrors';
const HIGH_FEE_WARNING_MULTIPLIER = 1.5;
/** /**
* Opaque string type representing a decimal (base 10) number in GWEI * Opaque string type representing a decimal (base 10) number in GWEI
@ -155,7 +139,6 @@ export function useGasFeeInputs(
minimumGasLimit = '0x5208', minimumGasLimit = '0x5208',
editGasMode, editGasMode,
) { ) {
const { balance: ethBalance } = useSelector(getSelectedAccount);
const supportsEIP1559 = const supportsEIP1559 =
useSelector(checkNetworkAndAccountSupports1559) && useSelector(checkNetworkAndAccountSupports1559) &&
!isLegacyTransaction(transaction?.txParams); !isLegacyTransaction(transaction?.txParams);
@ -380,102 +363,24 @@ export function useGasFeeInputs(
}, },
); );
let estimatesUnavailableWarning = null; const {
gasErrors,
// Separating errors from warnings so we can know which value problems hasGasErrors,
// are blocking or simply useful information for the users gasWarnings,
const gasErrors = {}; balanceError,
const gasWarnings = {}; estimatesUnavailableWarning,
} = useGasFeeErrors({
const gasLimitTooLow = conversionLessThan( transaction,
{ value: gasLimit, fromNumericBase: 'dec' }, gasEstimateType,
{ value: minimumGasLimit || GAS_LIMITS.SIMPLE, fromNumericBase: 'hex' }, gasFeeEstimates,
); gasLimit,
gasPriceToUse,
if (gasLimitTooLow) { isGasEstimatesLoading,
gasErrors.gasLimit = GAS_FORM_ERRORS.GAS_LIMIT_OUT_OF_BOUNDS; maxPriorityFeePerGasToUse,
} maxFeePerGasToUse,
// This ensures these are applied when the api fails to return a fee market type
// It is okay if these errors get overwritten below, as those overwrites can only
// happen when the estimate api is live.
if (supportsEIP1559) {
if (bnLessThanEqualTo(maxPriorityFeePerGasToUse, 0)) {
gasErrors.maxPriorityFee = GAS_FORM_ERRORS.MAX_PRIORITY_FEE_BELOW_MINIMUM;
} else if (bnGreaterThan(maxPriorityFeePerGasToUse, maxFeePerGasToUse)) {
gasErrors.maxFee = GAS_FORM_ERRORS.MAX_FEE_IMBALANCE;
}
}
if (supportsEIP1559 && gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET) {
if (bnLessThanEqualTo(maxPriorityFeePerGasToUse, 0)) {
gasErrors.maxPriorityFee = GAS_FORM_ERRORS.MAX_PRIORITY_FEE_BELOW_MINIMUM;
} else if (
!isGasEstimatesLoading &&
bnLessThan(
maxPriorityFeePerGasToUse,
gasFeeEstimates?.low?.suggestedMaxPriorityFeePerGas,
)
) {
gasWarnings.maxPriorityFee = GAS_FORM_ERRORS.MAX_PRIORITY_FEE_TOO_LOW;
} else if (bnGreaterThan(maxPriorityFeePerGasToUse, maxFeePerGasToUse)) {
gasErrors.maxFee = GAS_FORM_ERRORS.MAX_FEE_IMBALANCE;
} else if (
gasFeeEstimates?.high &&
bnGreaterThan(
maxPriorityFeePerGasToUse,
gasFeeEstimates.high.suggestedMaxPriorityFeePerGas *
HIGH_FEE_WARNING_MULTIPLIER,
)
) {
gasWarnings.maxPriorityFee =
GAS_FORM_ERRORS.MAX_PRIORITY_FEE_HIGH_WARNING;
}
if (
!isGasEstimatesLoading &&
bnLessThan(maxFeePerGasToUse, gasFeeEstimates?.low?.suggestedMaxFeePerGas)
) {
gasWarnings.maxFee = GAS_FORM_ERRORS.MAX_FEE_TOO_LOW;
} else if (
gasFeeEstimates?.high &&
bnGreaterThan(
maxFeePerGasToUse,
gasFeeEstimates.high.suggestedMaxFeePerGas *
HIGH_FEE_WARNING_MULTIPLIER,
)
) {
gasWarnings.maxFee = GAS_FORM_ERRORS.MAX_FEE_HIGH_WARNING;
}
} else if (supportsEIP1559) {
estimatesUnavailableWarning = true;
} else if (
(!supportsEIP1559 || transaction?.txParams?.gasPrice) &&
bnLessThanEqualTo(gasPriceToUse, 0)
) {
gasErrors.gasPrice = GAS_FORM_ERRORS.GAS_PRICE_TOO_LOW;
}
// Determine if we have any errors which should block submission
const hasBlockingGasErrors = Boolean(Object.keys(gasErrors).length);
// Now that we've determined errors that block submission, we can pool 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 = {
...gasWarnings,
...gasErrors,
};
const minimumTxCostInHexWei = addHexes(
minimumCostInHexWei, minimumCostInHexWei,
transaction?.txParams?.value || '0x0', minimumGasLimit,
); });
const balanceError = conversionGreaterThan(
{ value: minimumTxCostInHexWei, fromNumericBase: 'hex' },
{ value: ethBalance, fromNumericBase: 'hex' },
);
const handleGasLimitOutOfBoundError = useCallback(() => { const handleGasLimitOutOfBoundError = useCallback(() => {
if (gasErrors.gasLimit === GAS_FORM_ERRORS.GAS_LIMIT_OUT_OF_BOUNDS) { if (gasErrors.gasLimit === GAS_FORM_ERRORS.GAS_LIMIT_OUT_OF_BOUNDS) {
@ -521,9 +426,6 @@ export function useGasFeeInputs(
gasFeeEstimates, gasFeeEstimates,
gasEstimateType, gasEstimateType,
estimatedGasFeeTimeBounds, estimatedGasFeeTimeBounds,
gasErrors: errorsAndWarnings,
hasGasErrors: hasBlockingGasErrors,
gasWarnings,
onManualChange: () => { onManualChange: () => {
setInternalEstimateToUse('custom'); setInternalEstimateToUse('custom');
handleGasLimitOutOfBoundError(); handleGasLimitOutOfBoundError();
@ -534,8 +436,11 @@ export function useGasFeeInputs(
setMaxPriorityFeePerGas(maxPriorityFeePerGasToUse); setMaxPriorityFeePerGas(maxPriorityFeePerGasToUse);
setGasPriceHasBeenManuallySet(true); setGasPriceHasBeenManuallySet(true);
}, },
estimatedBaseFee: gasSettings.baseFeePerGas,
gasErrors,
hasGasErrors,
gasWarnings,
balanceError, balanceError,
estimatesUnavailableWarning, estimatesUnavailableWarning,
estimatedBaseFee: gasSettings.baseFeePerGas,
}; };
} }