mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 01:39:44 +01:00
Refactoring useGasFeeInputs hook (#12327)
Refactoring useGasFeeInputs hook
This commit is contained in:
parent
3068324ae0
commit
79b08fb803
@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useContext, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useGasFeeInputs } from '../../../hooks/useGasFeeInputs';
|
||||
import { useGasFeeInputs } from '../../../hooks/gasFeeInput/useGasFeeInputs';
|
||||
import { getGasLoadingAnimationIsShowing } from '../../../ducks/app/app';
|
||||
import { txParamsAreDappSuggested } from '../../../../shared/modules/transaction.utils';
|
||||
import { EDIT_GAS_MODES, GAS_LIMITS } from '../../../../shared/constants/gas';
|
||||
|
178
ui/hooks/gasFeeInput/test-utils.js
Normal file
178
ui/hooks/gasFeeInput/test-utils.js
Normal file
@ -0,0 +1,178 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../shared/constants/gas';
|
||||
import {
|
||||
conversionUtil,
|
||||
multiplyCurrencies,
|
||||
} from '../../../shared/modules/conversion.utils';
|
||||
import {
|
||||
getConversionRate,
|
||||
getNativeCurrency,
|
||||
} from '../../ducks/metamask/metamask';
|
||||
import {
|
||||
checkNetworkAndAccountSupports1559,
|
||||
getCurrentCurrency,
|
||||
getSelectedAccount,
|
||||
getShouldShowFiat,
|
||||
getPreferences,
|
||||
txDataSelector,
|
||||
} from '../../selectors';
|
||||
import { ETH } from '../../helpers/constants/common';
|
||||
|
||||
import { useGasFeeEstimates } from '../useGasFeeEstimates';
|
||||
|
||||
// Why this number?
|
||||
// 20 gwei * 21000 gasLimit = 420,000 gwei
|
||||
// 420,000 gwei is 0.00042 ETH
|
||||
// 0.00042 ETH * 100000 = $42
|
||||
export const MOCK_ETH_USD_CONVERSION_RATE = 100000;
|
||||
|
||||
export const LEGACY_GAS_ESTIMATE_RETURN_VALUE = {
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.LEGACY,
|
||||
gasFeeEstimates: {
|
||||
low: '10',
|
||||
medium: '20',
|
||||
high: '30',
|
||||
},
|
||||
estimatedGasFeeTimeBounds: {},
|
||||
};
|
||||
|
||||
export 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: {},
|
||||
};
|
||||
|
||||
export const HIGH_FEE_MARKET_ESTIMATE_RETURN_VALUE = {
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
|
||||
gasFeeEstimates: {
|
||||
low: {
|
||||
minWaitTimeEstimate: 180000,
|
||||
maxWaitTimeEstimate: 300000,
|
||||
suggestedMaxPriorityFeePerGas: '3',
|
||||
suggestedMaxFeePerGas: '53000',
|
||||
},
|
||||
medium: {
|
||||
minWaitTimeEstimate: 15000,
|
||||
maxWaitTimeEstimate: 60000,
|
||||
suggestedMaxPriorityFeePerGas: '7',
|
||||
suggestedMaxFeePerGas: '70000',
|
||||
},
|
||||
high: {
|
||||
minWaitTimeEstimate: 0,
|
||||
maxWaitTimeEstimate: 15000,
|
||||
suggestedMaxPriorityFeePerGas: '10',
|
||||
suggestedMaxFeePerGas: '100000',
|
||||
},
|
||||
estimatedBaseFee: '50000',
|
||||
},
|
||||
estimatedGasFeeTimeBounds: {},
|
||||
};
|
||||
|
||||
export const generateUseSelectorRouter = ({
|
||||
checkNetworkAndAccountSupports1559Response,
|
||||
shouldShowFiat = true,
|
||||
} = {}) => (selector) => {
|
||||
if (selector === getConversionRate) {
|
||||
return MOCK_ETH_USD_CONVERSION_RATE;
|
||||
}
|
||||
if (selector === getNativeCurrency) {
|
||||
return ETH;
|
||||
}
|
||||
if (selector === getPreferences) {
|
||||
return {
|
||||
useNativeCurrencyAsPrimaryCurrency: true,
|
||||
};
|
||||
}
|
||||
if (selector === getCurrentCurrency) {
|
||||
return 'USD';
|
||||
}
|
||||
if (selector === getShouldShowFiat) {
|
||||
return shouldShowFiat;
|
||||
}
|
||||
if (selector === txDataSelector) {
|
||||
return {
|
||||
txParams: {
|
||||
value: '0x5555',
|
||||
},
|
||||
};
|
||||
}
|
||||
if (selector === getSelectedAccount) {
|
||||
return {
|
||||
balance: '0x440aa47cc2556',
|
||||
};
|
||||
}
|
||||
if (selector === checkNetworkAndAccountSupports1559) {
|
||||
return checkNetworkAndAccountSupports1559Response;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export function getTotalCostInETH(gwei, gasLimit) {
|
||||
return multiplyCurrencies(gwei, gasLimit, {
|
||||
fromDenomination: 'GWEI',
|
||||
toDenomination: 'ETH',
|
||||
multiplicandBase: 10,
|
||||
multiplierBase: 10,
|
||||
});
|
||||
}
|
||||
|
||||
export function convertFromHexToFiat(value) {
|
||||
const val = conversionUtil(value, {
|
||||
fromNumericBase: 'hex',
|
||||
toNumericBase: 'dec',
|
||||
fromDenomination: 'WEI',
|
||||
});
|
||||
return `$${(val * MOCK_ETH_USD_CONVERSION_RATE).toFixed(2)}`;
|
||||
}
|
||||
|
||||
export function convertFromHexToETH(value) {
|
||||
const val = conversionUtil(value, {
|
||||
fromNumericBase: 'hex',
|
||||
toNumericBase: 'dec',
|
||||
fromDenomination: 'WEI',
|
||||
});
|
||||
return `${val} ETH`;
|
||||
}
|
||||
|
||||
export const configureEIP1559 = () => {
|
||||
useGasFeeEstimates.mockImplementation(() => FEE_MARKET_ESTIMATE_RETURN_VALUE);
|
||||
useSelector.mockImplementation(
|
||||
generateUseSelectorRouter({
|
||||
checkNetworkAndAccountSupports1559Response: true,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
export const configureLegacy = () => {
|
||||
useGasFeeEstimates.mockImplementation(() => LEGACY_GAS_ESTIMATE_RETURN_VALUE);
|
||||
useSelector.mockImplementation(
|
||||
generateUseSelectorRouter({
|
||||
checkNetworkAndAccountSupports1559Response: false,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
export const configure = () => {
|
||||
useSelector.mockImplementation(generateUseSelectorRouter());
|
||||
};
|
148
ui/hooks/gasFeeInput/useGasEstimates.js
Normal file
148
ui/hooks/gasFeeInput/useGasEstimates.js
Normal file
@ -0,0 +1,148 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import {
|
||||
EDIT_GAS_MODES,
|
||||
GAS_ESTIMATE_TYPES,
|
||||
} from '../../../shared/constants/gas';
|
||||
import {
|
||||
getMaximumGasTotalInHexWei,
|
||||
getMinimumGasTotalInHexWei,
|
||||
} from '../../../shared/modules/gas.utils';
|
||||
|
||||
import { PRIMARY, SECONDARY } from '../../helpers/constants/common';
|
||||
import {
|
||||
checkNetworkAndAccountSupports1559,
|
||||
getShouldShowFiat,
|
||||
} from '../../selectors';
|
||||
import {
|
||||
decGWEIToHexWEI,
|
||||
decimalToHex,
|
||||
} from '../../helpers/utils/conversions.util';
|
||||
import { isLegacyTransaction } from '../../helpers/utils/transactions.util';
|
||||
|
||||
import { useCurrencyDisplay } from '../useCurrencyDisplay';
|
||||
import { useUserPreferencedCurrency } from '../useUserPreferencedCurrency';
|
||||
|
||||
/**
|
||||
* @typedef {Object} GasEstimatesReturnType
|
||||
* @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.
|
||||
* @property {string} [estimatedMinimumNative] - the maximum amount estimated to be paid if the
|
||||
* current network transaction volume increases. Expressed in the network's native currency.
|
||||
* @property {string} [estimatedMinimumNative] - the maximum amount estimated to be paid if the
|
||||
* current network transaction volume increases. Expressed in the network's native currency.
|
||||
* @property {HexWeiString} [estimatedBaseFee] - estimatedBaseFee from fee-market gasFeeEstimates
|
||||
* in HexWei.
|
||||
* @property {HexWeiString} [minimumCostInHexWei] - the minimum amount this transaction will cost.
|
||||
*/
|
||||
export function useGasEstimates({
|
||||
editGasMode,
|
||||
gasEstimateType,
|
||||
gasFeeEstimates,
|
||||
gasLimit,
|
||||
gasPrice,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
minimumGasLimit,
|
||||
transaction,
|
||||
}) {
|
||||
const supportsEIP1559 =
|
||||
useSelector(checkNetworkAndAccountSupports1559) &&
|
||||
!isLegacyTransaction(transaction?.txParams);
|
||||
|
||||
const {
|
||||
currency: fiatCurrency,
|
||||
numberOfDecimals: fiatNumberOfDecimals,
|
||||
} = useUserPreferencedCurrency(SECONDARY);
|
||||
|
||||
const showFiat = useSelector(getShouldShowFiat);
|
||||
|
||||
const {
|
||||
currency: primaryCurrency,
|
||||
numberOfDecimals: primaryNumberOfDecimals,
|
||||
} = useUserPreferencedCurrency(PRIMARY);
|
||||
|
||||
// We have two helper methods that take an object that can have either
|
||||
// gasPrice OR the EIP-1559 fields on it, plus gasLimit. This object is
|
||||
// conditionally set to the appropriate fields to compute the minimum
|
||||
// and maximum cost of a transaction given the current estimates or selected
|
||||
// gas fees.
|
||||
let gasSettings = {
|
||||
gasLimit: decimalToHex(gasLimit),
|
||||
};
|
||||
if (supportsEIP1559) {
|
||||
gasSettings = {
|
||||
...gasSettings,
|
||||
maxFeePerGas: decGWEIToHexWEI(maxFeePerGas || gasPrice || '0'),
|
||||
maxPriorityFeePerGas: decGWEIToHexWEI(
|
||||
maxPriorityFeePerGas || maxFeePerGas || gasPrice || '0',
|
||||
),
|
||||
baseFeePerGas: decGWEIToHexWEI(gasFeeEstimates.estimatedBaseFee ?? '0'),
|
||||
};
|
||||
} else if (gasEstimateType === GAS_ESTIMATE_TYPES.NONE) {
|
||||
gasSettings = {
|
||||
...gasSettings,
|
||||
gasPrice: '0x0',
|
||||
};
|
||||
} else {
|
||||
gasSettings = {
|
||||
...gasSettings,
|
||||
gasPrice: decGWEIToHexWEI(gasPrice),
|
||||
};
|
||||
}
|
||||
|
||||
// The maximum amount this transaction will cost
|
||||
const maximumCostInHexWei = getMaximumGasTotalInHexWei(gasSettings);
|
||||
|
||||
if (editGasMode === EDIT_GAS_MODES.SWAPS) {
|
||||
gasSettings = { ...gasSettings, gasLimit: decimalToHex(minimumGasLimit) };
|
||||
}
|
||||
|
||||
// The minimum amount this transaction will cost
|
||||
const minimumCostInHexWei = getMinimumGasTotalInHexWei(gasSettings);
|
||||
|
||||
// The estimated total amount of native currency that will be expended
|
||||
// given the selected gas fees.
|
||||
const [estimatedMaximumNative] = useCurrencyDisplay(maximumCostInHexWei, {
|
||||
numberOfDecimals: primaryNumberOfDecimals,
|
||||
currency: primaryCurrency,
|
||||
});
|
||||
|
||||
const [, { value: estimatedMaximumFiat }] = useCurrencyDisplay(
|
||||
maximumCostInHexWei,
|
||||
{
|
||||
numberOfDecimals: fiatNumberOfDecimals,
|
||||
currency: fiatCurrency,
|
||||
},
|
||||
);
|
||||
|
||||
const [estimatedMinimumNative] = useCurrencyDisplay(minimumCostInHexWei, {
|
||||
numberOfDecimals: primaryNumberOfDecimals,
|
||||
currency: primaryCurrency,
|
||||
});
|
||||
|
||||
// We also need to display our closest estimate of the low end of estimation
|
||||
// in fiat.
|
||||
const [, { value: estimatedMinimumFiat }] = useCurrencyDisplay(
|
||||
minimumCostInHexWei,
|
||||
{
|
||||
numberOfDecimals: fiatNumberOfDecimals,
|
||||
currency: fiatCurrency,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
estimatedMaximumFiat: showFiat ? estimatedMaximumFiat : '',
|
||||
estimatedMinimumFiat: showFiat ? estimatedMinimumFiat : '',
|
||||
estimatedMaximumNative,
|
||||
estimatedMinimumNative,
|
||||
estimatedBaseFee: supportsEIP1559
|
||||
? decGWEIToHexWEI(gasFeeEstimates.estimatedBaseFee ?? '0')
|
||||
: undefined,
|
||||
minimumCostInHexWei,
|
||||
};
|
||||
}
|
192
ui/hooks/gasFeeInput/useGasEstimates.test.js
Normal file
192
ui/hooks/gasFeeInput/useGasEstimates.test.js
Normal file
@ -0,0 +1,192 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import {
|
||||
getMaximumGasTotalInHexWei,
|
||||
getMinimumGasTotalInHexWei,
|
||||
} from '../../../shared/modules/gas.utils';
|
||||
import {
|
||||
decGWEIToHexWEI,
|
||||
decimalToHex,
|
||||
} from '../../helpers/utils/conversions.util';
|
||||
|
||||
import {
|
||||
FEE_MARKET_ESTIMATE_RETURN_VALUE,
|
||||
LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||
configureEIP1559,
|
||||
configureLegacy,
|
||||
convertFromHexToETH,
|
||||
convertFromHexToFiat,
|
||||
generateUseSelectorRouter,
|
||||
} from './test-utils';
|
||||
import { useGasEstimates } from './useGasEstimates';
|
||||
|
||||
jest.mock('../useGasFeeEstimates', () => ({
|
||||
useGasFeeEstimates: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('react-redux', () => {
|
||||
const actual = jest.requireActual('react-redux');
|
||||
|
||||
return {
|
||||
...actual,
|
||||
useSelector: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const useGasEstimatesHook = (props) =>
|
||||
useGasEstimates({
|
||||
transaction: { txParams: { type: '0x2', value: '100' } },
|
||||
gasLimit: '21000',
|
||||
gasPrice: '10',
|
||||
maxPriorityFeePerGas: '10',
|
||||
maxFeePerGas: '100',
|
||||
minimumCostInHexWei: '0x5208',
|
||||
minimumGasLimit: '0x5208',
|
||||
supportsEIP1559: true,
|
||||
...FEE_MARKET_ESTIMATE_RETURN_VALUE,
|
||||
...props,
|
||||
});
|
||||
|
||||
describe('useGasEstimates', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('EIP-1559 scenario', () => {
|
||||
beforeEach(() => {
|
||||
configureEIP1559();
|
||||
});
|
||||
|
||||
it('uses new EIP-1559 gas fields to calculate minimum values', () => {
|
||||
const gasLimit = '21000';
|
||||
const maxFeePerGas = '100';
|
||||
const maxPriorityFeePerGas = '10';
|
||||
const {
|
||||
estimatedBaseFee,
|
||||
} = FEE_MARKET_ESTIMATE_RETURN_VALUE.gasFeeEstimates;
|
||||
const { result } = renderHook(() =>
|
||||
useGasEstimatesHook({ gasLimit, maxFeePerGas, maxPriorityFeePerGas }),
|
||||
);
|
||||
const minimumHexValue = getMinimumGasTotalInHexWei({
|
||||
baseFeePerGas: decGWEIToHexWEI(estimatedBaseFee),
|
||||
gasLimit: decimalToHex(gasLimit),
|
||||
maxFeePerGas: decGWEIToHexWEI(maxFeePerGas),
|
||||
maxPriorityFeePerGas: decGWEIToHexWEI(maxPriorityFeePerGas),
|
||||
});
|
||||
|
||||
expect(result.current.minimumCostInHexWei).toBe(minimumHexValue);
|
||||
expect(result.current.estimatedMinimumFiat).toBe(
|
||||
convertFromHexToFiat(minimumHexValue),
|
||||
);
|
||||
expect(result.current.estimatedMinimumNative).toBe(
|
||||
convertFromHexToETH(minimumHexValue),
|
||||
);
|
||||
});
|
||||
|
||||
it('uses new EIP-1559 gas fields to calculate maximum values', () => {
|
||||
const gasLimit = '21000';
|
||||
const maxFeePerGas = '100';
|
||||
const { result } = renderHook(() =>
|
||||
useGasEstimatesHook({ gasLimit, maxFeePerGas }),
|
||||
);
|
||||
const maximumHexValue = getMaximumGasTotalInHexWei({
|
||||
gasLimit: decimalToHex(gasLimit),
|
||||
maxFeePerGas: decGWEIToHexWEI(maxFeePerGas),
|
||||
});
|
||||
expect(result.current.estimatedMaximumFiat).toBe(
|
||||
convertFromHexToFiat(maximumHexValue),
|
||||
);
|
||||
expect(result.current.estimatedMaximumNative).toBe(
|
||||
convertFromHexToETH(maximumHexValue),
|
||||
);
|
||||
});
|
||||
|
||||
it('does not return fiat values if showFiat is false', () => {
|
||||
const gasLimit = '21000';
|
||||
const maxFeePerGas = '100';
|
||||
useSelector.mockImplementation(
|
||||
generateUseSelectorRouter({
|
||||
checkNetworkAndAccountSupports1559Response: true,
|
||||
shouldShowFiat: false,
|
||||
}),
|
||||
);
|
||||
const { result } = renderHook(() =>
|
||||
useGasEstimatesHook({ gasLimit, maxFeePerGas }),
|
||||
);
|
||||
|
||||
expect(result.current.estimatedMaximumFiat).toBe('');
|
||||
expect(result.current.estimatedMinimumFiat).toBe('');
|
||||
});
|
||||
|
||||
it('uses gasFeeEstimates.estimatedBaseFee prop to calculate estimatedBaseFee', () => {
|
||||
const {
|
||||
estimatedBaseFee,
|
||||
} = FEE_MARKET_ESTIMATE_RETURN_VALUE.gasFeeEstimates;
|
||||
const { result } = renderHook(() => useGasEstimatesHook());
|
||||
expect(result.current.estimatedBaseFee).toBe(
|
||||
decGWEIToHexWEI(estimatedBaseFee),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacy scenario', () => {
|
||||
beforeEach(() => {
|
||||
configureLegacy();
|
||||
});
|
||||
|
||||
it('uses legacy gas fields to calculate minimum values', () => {
|
||||
const gasLimit = '21000';
|
||||
const gasPrice = '10';
|
||||
const { result } = renderHook(() =>
|
||||
useGasEstimatesHook({
|
||||
gasLimit,
|
||||
gasPrice,
|
||||
supportsEIP1559: false,
|
||||
...LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||
}),
|
||||
);
|
||||
const minimumHexValue = getMinimumGasTotalInHexWei({
|
||||
gasLimit: decimalToHex(gasLimit),
|
||||
gasPrice: decGWEIToHexWEI(gasPrice),
|
||||
});
|
||||
|
||||
expect(result.current.minimumCostInHexWei).toBe(minimumHexValue);
|
||||
expect(result.current.estimatedMinimumFiat).toBe(
|
||||
convertFromHexToFiat(minimumHexValue),
|
||||
);
|
||||
expect(result.current.estimatedMinimumNative).toBe(
|
||||
convertFromHexToETH(minimumHexValue),
|
||||
);
|
||||
});
|
||||
|
||||
it('uses legacy gas fields to calculate maximum values', () => {
|
||||
const gasLimit = '21000';
|
||||
const gasPrice = '10';
|
||||
const { result } = renderHook(() =>
|
||||
useGasEstimatesHook({
|
||||
gasLimit,
|
||||
gasPrice,
|
||||
supportsEIP1559: false,
|
||||
...LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||
}),
|
||||
);
|
||||
const maximumHexValue = getMaximumGasTotalInHexWei({
|
||||
gasLimit: decimalToHex(gasLimit),
|
||||
gasPrice: decGWEIToHexWEI(gasPrice),
|
||||
});
|
||||
expect(result.current.estimatedMaximumFiat).toBe(
|
||||
convertFromHexToFiat(maximumHexValue),
|
||||
);
|
||||
expect(result.current.estimatedMaximumNative).toBe(
|
||||
convertFromHexToETH(maximumHexValue),
|
||||
);
|
||||
});
|
||||
|
||||
it('estimatedBaseFee is undefined', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useGasEstimatesHook({ supportsEIP1559: false }),
|
||||
);
|
||||
expect(result.current.estimatedBaseFee).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
263
ui/hooks/gasFeeInput/useGasFeeErrors.js
Normal file
263
ui/hooks/gasFeeInput/useGasFeeErrors.js
Normal file
@ -0,0 +1,263 @@
|
||||
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 = (maxPriorityFeePerGas, supportsEIP1559) => {
|
||||
if (!supportsEIP1559) return undefined;
|
||||
if (bnLessThanEqualTo(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) &&
|
||||
bnLessThanEqualTo(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 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({
|
||||
gasEstimateType,
|
||||
gasFeeEstimates,
|
||||
isGasEstimatesLoading,
|
||||
gasLimit,
|
||||
gasPrice,
|
||||
maxPriorityFeePerGas,
|
||||
maxFeePerGas,
|
||||
minimumCostInHexWei,
|
||||
minimumGasLimit,
|
||||
transaction,
|
||||
}) {
|
||||
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(
|
||||
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]);
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
301
ui/hooks/gasFeeInput/useGasFeeErrors.test.js
Normal file
301
ui/hooks/gasFeeInput/useGasFeeErrors.test.js
Normal file
@ -0,0 +1,301 @@
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { GAS_FORM_ERRORS } from '../../helpers/constants/gas';
|
||||
|
||||
import { useGasFeeErrors } from './useGasFeeErrors';
|
||||
|
||||
import {
|
||||
FEE_MARKET_ESTIMATE_RETURN_VALUE,
|
||||
LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||
configureEIP1559,
|
||||
configureLegacy,
|
||||
generateUseSelectorRouter,
|
||||
} from './test-utils';
|
||||
|
||||
jest.mock('../useGasFeeEstimates', () => ({
|
||||
useGasFeeEstimates: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('react-redux', () => {
|
||||
const actual = jest.requireActual('react-redux');
|
||||
|
||||
return {
|
||||
...actual,
|
||||
useSelector: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const renderUseGasFeeErrorsHook = (props) => {
|
||||
return renderHook(() =>
|
||||
useGasFeeErrors({
|
||||
transaction: { txParams: { type: '0x2', value: '100' } },
|
||||
gasLimit: '21000',
|
||||
gasPrice: '10',
|
||||
maxPriorityFeePerGas: '10',
|
||||
maxFeePerGas: '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({
|
||||
maxPriorityFeePerGas: '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({
|
||||
maxFeePerGas: '1',
|
||||
maxPriorityFeePerGas: '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 maxPriorityFeePerGas is 0', () => {
|
||||
const { result } = renderUseGasFeeErrorsHook({
|
||||
maxFeePerGas: '1',
|
||||
maxPriorityFeePerGas: '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({
|
||||
maxFeePerGas: '1',
|
||||
maxPriorityFeePerGas: '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({ gasPrice: '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({
|
||||
gasPrice: '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({
|
||||
maxPriorityFeePerGas: '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({
|
||||
maxPriorityFeePerGas: '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({
|
||||
maxPriorityFeePerGas: '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({
|
||||
maxFeePerGas: '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({
|
||||
maxFeePerGas: '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({
|
||||
maxFeePerGas: '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);
|
||||
});
|
||||
});
|
||||
});
|
254
ui/hooks/gasFeeInput/useGasFeeInputs.js
Normal file
254
ui/hooks/gasFeeInput/useGasFeeInputs.js
Normal file
@ -0,0 +1,254 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { getAdvancedInlineGasShown } from '../../selectors';
|
||||
import { hexToDecimal } from '../../helpers/utils/conversions.util';
|
||||
import { GAS_FORM_ERRORS } from '../../helpers/constants/gas';
|
||||
import { useGasFeeEstimates } from '../useGasFeeEstimates';
|
||||
|
||||
import { useGasFeeErrors } from './useGasFeeErrors';
|
||||
import { useGasPriceInput } from './useGasPriceInput';
|
||||
import { useMaxFeePerGasInput } from './useMaxFeePerGasInput';
|
||||
import { useMaxPriorityFeePerGasInput } from './useMaxPriorityFeePerGasInput';
|
||||
import { useGasEstimates } from './useGasEstimates';
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* @returns {GasFeeInputReturnType & import(
|
||||
* './useGasFeeEstimates'
|
||||
* ).GasEstimates} - gas fee input state and the GasFeeEstimates object
|
||||
*/
|
||||
export function useGasFeeInputs(
|
||||
defaultEstimateToUse = 'medium',
|
||||
transaction,
|
||||
minimumGasLimit = '0x5208',
|
||||
editGasMode,
|
||||
) {
|
||||
// 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,
|
||||
} = 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 [gasLimit, setGasLimit] = useState(
|
||||
Number(hexToDecimal(transaction?.txParams?.gas ?? '0x0')),
|
||||
);
|
||||
|
||||
const {
|
||||
gasPrice,
|
||||
setGasPrice,
|
||||
setGasPriceHasBeenManuallySet,
|
||||
} = useGasPriceInput({
|
||||
estimateToUse,
|
||||
gasEstimateType,
|
||||
gasFeeEstimates,
|
||||
transaction,
|
||||
});
|
||||
|
||||
const {
|
||||
maxFeePerGas,
|
||||
maxFeePerGasFiat,
|
||||
setMaxFeePerGas,
|
||||
} = useMaxFeePerGasInput({
|
||||
estimateToUse,
|
||||
gasEstimateType,
|
||||
gasFeeEstimates,
|
||||
gasLimit,
|
||||
gasPrice,
|
||||
transaction,
|
||||
});
|
||||
|
||||
const {
|
||||
maxPriorityFeePerGas,
|
||||
maxPriorityFeePerGasFiat,
|
||||
setMaxPriorityFeePerGas,
|
||||
} = useMaxPriorityFeePerGasInput({
|
||||
estimateToUse,
|
||||
gasEstimateType,
|
||||
gasFeeEstimates,
|
||||
gasLimit,
|
||||
transaction,
|
||||
});
|
||||
|
||||
const {
|
||||
estimatedBaseFee,
|
||||
estimatedMaximumFiat,
|
||||
estimatedMinimumFiat,
|
||||
estimatedMaximumNative,
|
||||
estimatedMinimumNative,
|
||||
minimumCostInHexWei,
|
||||
} = useGasEstimates({
|
||||
editGasMode,
|
||||
gasEstimateType,
|
||||
gasFeeEstimates,
|
||||
gasLimit,
|
||||
gasPrice,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
minimumGasLimit,
|
||||
transaction,
|
||||
});
|
||||
|
||||
const {
|
||||
balanceError,
|
||||
estimatesUnavailableWarning,
|
||||
gasErrors,
|
||||
gasWarnings,
|
||||
hasGasErrors,
|
||||
} = 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]);
|
||||
|
||||
// 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);
|
||||
},
|
||||
[
|
||||
setInternalEstimateToUse,
|
||||
handleGasLimitOutOfBoundError,
|
||||
setMaxFeePerGas,
|
||||
setMaxPriorityFeePerGas,
|
||||
setGasPrice,
|
||||
setGasPriceHasBeenManuallySet,
|
||||
],
|
||||
);
|
||||
|
||||
const onManualChange = useCallback(() => {
|
||||
setInternalEstimateToUse('custom');
|
||||
handleGasLimitOutOfBoundError();
|
||||
// Restore existing values
|
||||
setGasPrice(gasPrice);
|
||||
setGasLimit(gasLimit);
|
||||
setMaxFeePerGas(maxFeePerGas);
|
||||
setMaxPriorityFeePerGas(maxPriorityFeePerGas);
|
||||
setGasPriceHasBeenManuallySet(true);
|
||||
}, [
|
||||
setInternalEstimateToUse,
|
||||
handleGasLimitOutOfBoundError,
|
||||
setGasPrice,
|
||||
gasPrice,
|
||||
setGasLimit,
|
||||
gasLimit,
|
||||
setMaxFeePerGas,
|
||||
maxFeePerGas,
|
||||
setMaxPriorityFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
setGasPriceHasBeenManuallySet,
|
||||
]);
|
||||
|
||||
return {
|
||||
maxFeePerGas,
|
||||
maxFeePerGasFiat,
|
||||
setMaxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
maxPriorityFeePerGasFiat,
|
||||
setMaxPriorityFeePerGas,
|
||||
gasPrice,
|
||||
setGasPrice,
|
||||
gasLimit,
|
||||
setGasLimit,
|
||||
estimateToUse,
|
||||
setEstimateToUse,
|
||||
estimatedMinimumFiat,
|
||||
estimatedMaximumFiat,
|
||||
estimatedMaximumNative,
|
||||
estimatedMinimumNative,
|
||||
isGasEstimatesLoading,
|
||||
gasFeeEstimates,
|
||||
gasEstimateType,
|
||||
estimatedGasFeeTimeBounds,
|
||||
onManualChange,
|
||||
estimatedBaseFee,
|
||||
// error and warnings
|
||||
balanceError,
|
||||
estimatesUnavailableWarning,
|
||||
gasErrors,
|
||||
gasWarnings,
|
||||
hasGasErrors,
|
||||
};
|
||||
}
|
@ -1,31 +1,29 @@
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { GAS_ESTIMATE_TYPES } from '../../shared/constants/gas';
|
||||
import { multiplyCurrencies } from '../../shared/modules/conversion.utils';
|
||||
import { TRANSACTION_ENVELOPE_TYPES } from '../../shared/constants/transaction';
|
||||
import {
|
||||
getConversionRate,
|
||||
getNativeCurrency,
|
||||
} from '../ducks/metamask/metamask';
|
||||
import {
|
||||
checkNetworkAndAccountSupports1559,
|
||||
getCurrentCurrency,
|
||||
getShouldShowFiat,
|
||||
txDataSelector,
|
||||
getSelectedAccount,
|
||||
} from '../selectors';
|
||||
import { TRANSACTION_ENVELOPE_TYPES } from '../../../shared/constants/transaction';
|
||||
|
||||
import { ETH, PRIMARY } from '../helpers/constants/common';
|
||||
import { ETH, PRIMARY } from '../../helpers/constants/common';
|
||||
|
||||
import { useGasFeeEstimates } from './useGasFeeEstimates';
|
||||
import { useUserPreferencedCurrency } from '../useUserPreferencedCurrency';
|
||||
import { useGasFeeEstimates } from '../useGasFeeEstimates';
|
||||
import { useGasFeeInputs } from './useGasFeeInputs';
|
||||
import { useUserPreferencedCurrency } from './useUserPreferencedCurrency';
|
||||
|
||||
jest.mock('./useUserPreferencedCurrency', () => ({
|
||||
import {
|
||||
MOCK_ETH_USD_CONVERSION_RATE,
|
||||
LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||
FEE_MARKET_ESTIMATE_RETURN_VALUE,
|
||||
HIGH_FEE_MARKET_ESTIMATE_RETURN_VALUE,
|
||||
configureEIP1559,
|
||||
configureLegacy,
|
||||
generateUseSelectorRouter,
|
||||
getTotalCostInETH,
|
||||
} from './test-utils';
|
||||
|
||||
jest.mock('../useUserPreferencedCurrency', () => ({
|
||||
useUserPreferencedCurrency: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('./useGasFeeEstimates', () => ({
|
||||
jest.mock('../useGasFeeEstimates', () => ({
|
||||
useGasFeeEstimates: jest.fn(),
|
||||
}));
|
||||
|
||||
@ -38,116 +36,6 @@ jest.mock('react-redux', () => {
|
||||
};
|
||||
});
|
||||
|
||||
// Why this number?
|
||||
// 20 gwei * 21000 gasLimit = 420,000 gwei
|
||||
// 420,000 gwei is 0.00042 ETH
|
||||
// 0.00042 ETH * 100000 = $42
|
||||
const MOCK_ETH_USD_CONVERSION_RATE = 100000;
|
||||
|
||||
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 HIGH_FEE_MARKET_ESTIMATE_RETURN_VALUE = {
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
|
||||
gasFeeEstimates: {
|
||||
low: {
|
||||
minWaitTimeEstimate: 180000,
|
||||
maxWaitTimeEstimate: 300000,
|
||||
suggestedMaxPriorityFeePerGas: '3',
|
||||
suggestedMaxFeePerGas: '53000',
|
||||
},
|
||||
medium: {
|
||||
minWaitTimeEstimate: 15000,
|
||||
maxWaitTimeEstimate: 60000,
|
||||
suggestedMaxPriorityFeePerGas: '7',
|
||||
suggestedMaxFeePerGas: '70000',
|
||||
},
|
||||
high: {
|
||||
minWaitTimeEstimate: 0,
|
||||
maxWaitTimeEstimate: 15000,
|
||||
suggestedMaxPriorityFeePerGas: '10',
|
||||
suggestedMaxFeePerGas: '100000',
|
||||
},
|
||||
estimatedBaseFee: '50000',
|
||||
},
|
||||
estimatedGasFeeTimeBounds: {},
|
||||
};
|
||||
|
||||
const generateUseSelectorRouter = ({
|
||||
checkNetworkAndAccountSupports1559Response,
|
||||
} = {}) => (selector) => {
|
||||
if (selector === getConversionRate) {
|
||||
return MOCK_ETH_USD_CONVERSION_RATE;
|
||||
}
|
||||
if (selector === getNativeCurrency) {
|
||||
return ETH;
|
||||
}
|
||||
if (selector === getCurrentCurrency) {
|
||||
return 'USD';
|
||||
}
|
||||
if (selector === getShouldShowFiat) {
|
||||
return true;
|
||||
}
|
||||
if (selector === txDataSelector) {
|
||||
return {
|
||||
txParams: {
|
||||
value: '0x5555',
|
||||
},
|
||||
};
|
||||
}
|
||||
if (selector === getSelectedAccount) {
|
||||
return {
|
||||
balance: '0x440aa47cc2556',
|
||||
};
|
||||
}
|
||||
if (selector === checkNetworkAndAccountSupports1559) {
|
||||
return checkNetworkAndAccountSupports1559Response;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
function getTotalCostInETH(gwei, gasLimit) {
|
||||
return multiplyCurrencies(gwei, gasLimit, {
|
||||
fromDenomination: 'GWEI',
|
||||
toDenomination: 'ETH',
|
||||
multiplicandBase: 10,
|
||||
multiplierBase: 10,
|
||||
});
|
||||
}
|
||||
|
||||
describe('useGasFeeInputs', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
@ -161,10 +49,7 @@ describe('useGasFeeInputs', () => {
|
||||
|
||||
describe('when using gasPrice API for estimation', () => {
|
||||
beforeEach(() => {
|
||||
useGasFeeEstimates.mockImplementation(
|
||||
() => LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||
);
|
||||
useSelector.mockImplementation(generateUseSelectorRouter());
|
||||
configureLegacy();
|
||||
});
|
||||
it('passes through the raw estimate values from useGasFeeEstimates', () => {
|
||||
const { result } = renderHook(() => useGasFeeInputs());
|
||||
@ -226,11 +111,9 @@ describe('useGasFeeInputs', () => {
|
||||
|
||||
describe('when transaction is type-0', () => {
|
||||
beforeEach(() => {
|
||||
useGasFeeEstimates.mockImplementation(
|
||||
() => FEE_MARKET_ESTIMATE_RETURN_VALUE,
|
||||
);
|
||||
useSelector.mockImplementation(generateUseSelectorRouter());
|
||||
configureEIP1559();
|
||||
});
|
||||
|
||||
it('returns gasPrice appropriately, and "0" for EIP1559 fields', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useGasFeeInputs('medium', {
|
||||
@ -251,10 +134,7 @@ describe('useGasFeeInputs', () => {
|
||||
|
||||
describe('when using EIP 1559 API for estimation', () => {
|
||||
beforeEach(() => {
|
||||
useGasFeeEstimates.mockImplementation(
|
||||
() => FEE_MARKET_ESTIMATE_RETURN_VALUE,
|
||||
);
|
||||
useSelector.mockImplementation(generateUseSelectorRouter());
|
||||
configureEIP1559();
|
||||
});
|
||||
it('passes through the raw estimate values from useGasFeeEstimates', () => {
|
||||
const { result } = renderHook(() => useGasFeeInputs());
|
||||
@ -326,10 +206,7 @@ describe('useGasFeeInputs', () => {
|
||||
|
||||
describe('when balance is sufficient for minimum transaction cost', () => {
|
||||
beforeEach(() => {
|
||||
useGasFeeEstimates.mockImplementation(
|
||||
() => FEE_MARKET_ESTIMATE_RETURN_VALUE,
|
||||
);
|
||||
useSelector.mockImplementation(generateUseSelectorRouter());
|
||||
configureEIP1559();
|
||||
});
|
||||
|
||||
it('should return false', () => {
|
||||
@ -340,14 +217,10 @@ describe('useGasFeeInputs', () => {
|
||||
|
||||
describe('when balance is insufficient for minimum transaction cost', () => {
|
||||
beforeEach(() => {
|
||||
configureEIP1559();
|
||||
useGasFeeEstimates.mockImplementation(
|
||||
() => HIGH_FEE_MARKET_ESTIMATE_RETURN_VALUE,
|
||||
);
|
||||
useSelector.mockImplementation(
|
||||
generateUseSelectorRouter({
|
||||
checkNetworkAndAccountSupports1559Response: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return true', () => {
|
||||
@ -360,4 +233,79 @@ describe('useGasFeeInputs', () => {
|
||||
expect(result.current.balanceError).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('callback setEstimateToUse', () => {
|
||||
beforeEach(() => {
|
||||
configureEIP1559();
|
||||
});
|
||||
|
||||
it('should change estimateToUse value', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useGasFeeInputs(null, {
|
||||
userFeeLevel: 'medium',
|
||||
txParams: { gas: '0x5208' },
|
||||
}),
|
||||
);
|
||||
act(() => {
|
||||
result.current.setEstimateToUse('high');
|
||||
});
|
||||
expect(result.current.estimateToUse).toBe('high');
|
||||
expect(result.current.maxFeePerGas).toBe(
|
||||
FEE_MARKET_ESTIMATE_RETURN_VALUE.gasFeeEstimates.high
|
||||
.suggestedMaxFeePerGas,
|
||||
);
|
||||
expect(result.current.maxPriorityFeePerGas).toBe(
|
||||
FEE_MARKET_ESTIMATE_RETURN_VALUE.gasFeeEstimates.high
|
||||
.suggestedMaxPriorityFeePerGas,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('callback onManualChange', () => {
|
||||
beforeEach(() => {
|
||||
configureEIP1559();
|
||||
});
|
||||
|
||||
it('should change estimateToUse value to custom', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useGasFeeInputs(null, {
|
||||
userFeeLevel: 'medium',
|
||||
txParams: { gas: '0x5208' },
|
||||
}),
|
||||
);
|
||||
act(() => {
|
||||
result.current.onManualChange();
|
||||
result.current.setMaxFeePerGas('100');
|
||||
result.current.setMaxPriorityFeePerGas('10');
|
||||
});
|
||||
expect(result.current.estimateToUse).toBe('custom');
|
||||
expect(result.current.maxFeePerGas).toBe('100');
|
||||
expect(result.current.maxPriorityFeePerGas).toBe('10');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when showFiat is false', () => {
|
||||
beforeEach(() => {
|
||||
configureEIP1559();
|
||||
useSelector.mockImplementation(
|
||||
generateUseSelectorRouter({
|
||||
checkNetworkAndAccountSupports1559Response: true,
|
||||
shouldShowFiat: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('does not return fiat values', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useGasFeeInputs(null, {
|
||||
userFeeLevel: 'medium',
|
||||
txParams: { gas: '0x5208' },
|
||||
}),
|
||||
);
|
||||
expect(result.current.maxFeePerGasFiat).toBe('');
|
||||
expect(result.current.maxPriorityFeePerGasFiat).toBe('');
|
||||
expect(result.current.estimatedMaximumFiat).toBe('');
|
||||
expect(result.current.estimatedMinimumFiat).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
62
ui/hooks/gasFeeInput/useGasPriceInput.js
Normal file
62
ui/hooks/gasFeeInput/useGasPriceInput.js
Normal file
@ -0,0 +1,62 @@
|
||||
import { useState } from 'react';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../shared/constants/gas';
|
||||
import { hexWEIToDecGWEI } from '../../helpers/utils/conversions.util';
|
||||
import { isLegacyTransaction } from '../../helpers/utils/transactions.util';
|
||||
|
||||
import { feeParamsAreCustom } from './utils';
|
||||
|
||||
function getGasPriceEstimate(gasFeeEstimates, gasEstimateType, estimateToUse) {
|
||||
if (gasEstimateType === GAS_ESTIMATE_TYPES.LEGACY) {
|
||||
return gasFeeEstimates?.[estimateToUse] ?? '0';
|
||||
} else if (gasEstimateType === GAS_ESTIMATE_TYPES.ETH_GASPRICE) {
|
||||
return gasFeeEstimates?.gasPrice ?? '0';
|
||||
}
|
||||
return '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} GasPriceInputsReturnType
|
||||
* @property {DecGweiString} [gasPrice] - the gasPrice input value.
|
||||
* @property {(DecGweiString) => void} setGasPrice - state setter method to update the gasPrice.
|
||||
* @property {(boolean) => true} setGasPriceHasBeenManuallySet - state setter method to update gasPriceHasBeenManuallySet
|
||||
* field gasPriceHasBeenManuallySet is used in gasPrice calculations.
|
||||
*/
|
||||
export function useGasPriceInput({
|
||||
estimateToUse,
|
||||
gasEstimateType,
|
||||
gasFeeEstimates,
|
||||
transaction,
|
||||
}) {
|
||||
const [gasPriceHasBeenManuallySet, setGasPriceHasBeenManuallySet] = useState(
|
||||
transaction?.userFeeLevel === 'custom',
|
||||
);
|
||||
|
||||
const [gasPrice, setGasPrice] = useState(() => {
|
||||
const { gasPrice: txGasPrice } = transaction?.txParams || {};
|
||||
return txGasPrice && feeParamsAreCustom(transaction)
|
||||
? Number(hexWEIToDecGWEI(txGasPrice))
|
||||
: null;
|
||||
});
|
||||
|
||||
const [initialGasPriceEstimates] = useState(gasFeeEstimates);
|
||||
const gasPriceEstimatesHaveNotChanged = isEqual(
|
||||
initialGasPriceEstimates,
|
||||
gasFeeEstimates,
|
||||
);
|
||||
|
||||
const gasPriceToUse =
|
||||
gasPrice !== null &&
|
||||
(gasPriceHasBeenManuallySet ||
|
||||
gasPriceEstimatesHaveNotChanged ||
|
||||
isLegacyTransaction(transaction?.txParams))
|
||||
? gasPrice
|
||||
: getGasPriceEstimate(gasFeeEstimates, gasEstimateType, estimateToUse);
|
||||
|
||||
return {
|
||||
gasPrice: gasPriceToUse,
|
||||
setGasPrice,
|
||||
setGasPriceHasBeenManuallySet,
|
||||
};
|
||||
}
|
96
ui/hooks/gasFeeInput/useGasPriceInput.test.js
Normal file
96
ui/hooks/gasFeeInput/useGasPriceInput.test.js
Normal file
@ -0,0 +1,96 @@
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
|
||||
import {
|
||||
FEE_MARKET_ESTIMATE_RETURN_VALUE,
|
||||
LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||
configure,
|
||||
} from './test-utils';
|
||||
import { useGasPriceInput } from './useGasPriceInput';
|
||||
|
||||
jest.mock('../useGasFeeEstimates', () => ({
|
||||
useGasFeeEstimates: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('react-redux', () => {
|
||||
const actual = jest.requireActual('react-redux');
|
||||
|
||||
return {
|
||||
...actual,
|
||||
useSelector: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('useGasPriceInput', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
configure();
|
||||
});
|
||||
|
||||
it('returns gasPrice values from transaction if transaction.userFeeLevel is custom', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useGasPriceInput({
|
||||
transaction: {
|
||||
userFeeLevel: 'custom',
|
||||
txParams: { gasPrice: '0x5028' },
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(result.current.gasPrice).toBe(0.00002052);
|
||||
});
|
||||
|
||||
it('does not return gasPrice values from transaction if transaction.userFeeLevel is not custom', () => {
|
||||
configure();
|
||||
const { result } = renderHook(() =>
|
||||
useGasPriceInput({
|
||||
estimateToUse: 'high',
|
||||
transaction: {
|
||||
userFeeLevel: 'high',
|
||||
txParams: { gasPrice: '0x5028' },
|
||||
},
|
||||
...LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||
}),
|
||||
);
|
||||
expect(result.current.gasPrice).toBe('30');
|
||||
});
|
||||
|
||||
it('if no gasPrice is provided returns default estimate for legacy transaction', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useGasPriceInput({
|
||||
estimateToUse: 'medium',
|
||||
...LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||
}),
|
||||
);
|
||||
expect(result.current.gasPrice).toBe('20');
|
||||
});
|
||||
|
||||
it('for legacy transaction if estimateToUse is high and no gasPrice is provided returns high estimate value', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useGasPriceInput({
|
||||
estimateToUse: 'high',
|
||||
...LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||
}),
|
||||
);
|
||||
expect(result.current.gasPrice).toBe('30');
|
||||
});
|
||||
|
||||
it('returns 0 if gasPrice is not present in transaction and estimates are also not legacy', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useGasPriceInput({
|
||||
estimateToUse: 'medium',
|
||||
...FEE_MARKET_ESTIMATE_RETURN_VALUE,
|
||||
}),
|
||||
);
|
||||
expect(result.current.gasPrice).toBe('0');
|
||||
});
|
||||
|
||||
it('returns gasPrice set by user if gasPriceHasBeenManuallySet is true', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useGasPriceInput({ estimateToUse: 'medium' }),
|
||||
);
|
||||
act(() => {
|
||||
result.current.setGasPriceHasBeenManuallySet(true);
|
||||
result.current.setGasPrice(100);
|
||||
});
|
||||
expect(result.current.gasPrice).toBe(100);
|
||||
});
|
||||
});
|
121
ui/hooks/gasFeeInput/useMaxFeePerGasInput.js
Normal file
121
ui/hooks/gasFeeInput/useMaxFeePerGasInput.js
Normal file
@ -0,0 +1,121 @@
|
||||
import { useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../shared/constants/gas';
|
||||
import { SECONDARY } from '../../helpers/constants/common';
|
||||
import { getMaximumGasTotalInHexWei } from '../../../shared/modules/gas.utils';
|
||||
import {
|
||||
decGWEIToHexWEI,
|
||||
decimalToHex,
|
||||
hexWEIToDecGWEI,
|
||||
} from '../../helpers/utils/conversions.util';
|
||||
import {
|
||||
checkNetworkAndAccountSupports1559,
|
||||
getShouldShowFiat,
|
||||
} from '../../selectors';
|
||||
import { isLegacyTransaction } from '../../helpers/utils/transactions.util';
|
||||
|
||||
import { useCurrencyDisplay } from '../useCurrencyDisplay';
|
||||
import { useUserPreferencedCurrency } from '../useUserPreferencedCurrency';
|
||||
import { feeParamsAreCustom, getGasFeeEstimate } from './utils';
|
||||
|
||||
const getMaxFeePerGasFromTransaction = (transaction) => {
|
||||
const { maxFeePerGas, gasPrice } = transaction?.txParams || {};
|
||||
return Number(hexWEIToDecGWEI(maxFeePerGas || gasPrice));
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {Object} MaxFeePerGasInputReturnType
|
||||
* @property {(DecGweiString) => void} setMaxFeePerGas - state setter method to
|
||||
* update the maxFeePerGas.
|
||||
* @property {string} [maxFeePerGasFiat] - the maxFeePerGas converted to the
|
||||
* user's preferred currency.
|
||||
* @property {(DecGweiString) => void} setMaxFeePerGas - state setter
|
||||
* method to update the setMaxFeePerGas.
|
||||
*/
|
||||
export function useMaxFeePerGasInput({
|
||||
estimateToUse,
|
||||
gasEstimateType,
|
||||
gasFeeEstimates,
|
||||
gasLimit,
|
||||
gasPrice,
|
||||
transaction,
|
||||
}) {
|
||||
const supportsEIP1559 =
|
||||
useSelector(checkNetworkAndAccountSupports1559) &&
|
||||
!isLegacyTransaction(transaction?.txParams);
|
||||
|
||||
const {
|
||||
currency: fiatCurrency,
|
||||
numberOfDecimals: fiatNumberOfDecimals,
|
||||
} = useUserPreferencedCurrency(SECONDARY);
|
||||
|
||||
const showFiat = useSelector(getShouldShowFiat);
|
||||
|
||||
const maxFeePerGasFromTransaction = supportsEIP1559
|
||||
? getMaxFeePerGasFromTransaction(transaction)
|
||||
: 0;
|
||||
|
||||
// This hook keeps track of a few pieces of transitional state. It is
|
||||
// transitional because it is only used to modify a transaction in the
|
||||
// metamask (background) state tree.
|
||||
const [maxFeePerGas, setMaxFeePerGas] = useState(() => {
|
||||
if (maxFeePerGasFromTransaction && feeParamsAreCustom(transaction))
|
||||
return maxFeePerGasFromTransaction;
|
||||
return null;
|
||||
});
|
||||
|
||||
let gasSettings = {
|
||||
gasLimit: decimalToHex(gasLimit),
|
||||
};
|
||||
if (supportsEIP1559) {
|
||||
gasSettings = {
|
||||
...gasSettings,
|
||||
maxFeePerGas: decGWEIToHexWEI(maxFeePerGas || gasPrice || '0'),
|
||||
};
|
||||
} else if (gasEstimateType === GAS_ESTIMATE_TYPES.NONE) {
|
||||
gasSettings = {
|
||||
...gasSettings,
|
||||
gasPrice: '0x0',
|
||||
};
|
||||
} else {
|
||||
gasSettings = {
|
||||
...gasSettings,
|
||||
gasPrice: decGWEIToHexWEI(gasPrice),
|
||||
};
|
||||
}
|
||||
|
||||
const maximumCostInHexWei = getMaximumGasTotalInHexWei(gasSettings);
|
||||
|
||||
// We need to display thee estimated fiat currency impact of the maxFeePerGas
|
||||
// field to the user. This hook calculates that amount. This also works for
|
||||
// the gasPrice amount because in legacy transactions cost is always gasPrice
|
||||
// * gasLimit.
|
||||
const [, { value: maxFeePerGasFiat }] = useCurrencyDisplay(
|
||||
maximumCostInHexWei,
|
||||
{
|
||||
numberOfDecimals: fiatNumberOfDecimals,
|
||||
currency: fiatCurrency,
|
||||
},
|
||||
);
|
||||
|
||||
// We specify whether to use the estimate value by checking if the state
|
||||
// value has been set. The state value is only set by user input and is wiped
|
||||
// when the user selects an estimate. Default here is '0' to avoid bignumber
|
||||
// errors in later calculations for nullish values.
|
||||
const maxFeePerGasToUse =
|
||||
maxFeePerGas ??
|
||||
getGasFeeEstimate(
|
||||
'suggestedMaxFeePerGas',
|
||||
gasFeeEstimates,
|
||||
gasEstimateType,
|
||||
estimateToUse,
|
||||
maxFeePerGasFromTransaction,
|
||||
);
|
||||
|
||||
return {
|
||||
maxFeePerGas: maxFeePerGasToUse,
|
||||
maxFeePerGasFiat: showFiat ? maxFeePerGasFiat : '',
|
||||
setMaxFeePerGas,
|
||||
};
|
||||
}
|
129
ui/hooks/gasFeeInput/useMaxFeePerGasInput.test.js
Normal file
129
ui/hooks/gasFeeInput/useMaxFeePerGasInput.test.js
Normal file
@ -0,0 +1,129 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
|
||||
import { getMaximumGasTotalInHexWei } from '../../../shared/modules/gas.utils';
|
||||
import { decimalToHex } from '../../helpers/utils/conversions.util';
|
||||
|
||||
import {
|
||||
FEE_MARKET_ESTIMATE_RETURN_VALUE,
|
||||
LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||
configureEIP1559,
|
||||
configureLegacy,
|
||||
convertFromHexToFiat,
|
||||
generateUseSelectorRouter,
|
||||
} from './test-utils';
|
||||
import { useMaxFeePerGasInput } from './useMaxFeePerGasInput';
|
||||
|
||||
jest.mock('../useGasFeeEstimates', () => ({
|
||||
useGasFeeEstimates: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('react-redux', () => {
|
||||
const actual = jest.requireActual('react-redux');
|
||||
|
||||
return {
|
||||
...actual,
|
||||
useSelector: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const renderUseMaxFeePerGasInputHook = (props) =>
|
||||
renderHook(() =>
|
||||
useMaxFeePerGasInput({
|
||||
gasLimit: '21000',
|
||||
estimateToUse: 'medium',
|
||||
transaction: {
|
||||
userFeeLevel: 'custom',
|
||||
txParams: { maxFeePerGas: '0x5028' },
|
||||
},
|
||||
...FEE_MARKET_ESTIMATE_RETURN_VALUE,
|
||||
...props,
|
||||
}),
|
||||
);
|
||||
|
||||
describe('useMaxFeePerGasInput', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
configureEIP1559();
|
||||
});
|
||||
|
||||
it('returns maxFeePerGas values from transaction if transaction.userFeeLevel is custom', () => {
|
||||
const { result } = renderUseMaxFeePerGasInputHook();
|
||||
expect(result.current.maxFeePerGas).toBe(0.00002052);
|
||||
});
|
||||
|
||||
it('returns gasPrice values from transaction if transaction.userFeeLevel is custom and maxFeePerGas is not provided', () => {
|
||||
const { result } = renderUseMaxFeePerGasInputHook({
|
||||
transaction: {
|
||||
userFeeLevel: 'custom',
|
||||
txParams: { gasPrice: '0x5028' },
|
||||
},
|
||||
});
|
||||
expect(result.current.maxFeePerGas).toBe(0.00002052);
|
||||
});
|
||||
|
||||
it('does not returns maxFeePerGas values from transaction if transaction.userFeeLevel is not custom', () => {
|
||||
const { result } = renderUseMaxFeePerGasInputHook({
|
||||
estimateToUse: 'high',
|
||||
transaction: {
|
||||
userFeeLevel: 'high',
|
||||
txParams: { maxFeePerGas: '0x5028' },
|
||||
},
|
||||
});
|
||||
expect(result.current.maxFeePerGas).toBe(
|
||||
FEE_MARKET_ESTIMATE_RETURN_VALUE.gasFeeEstimates.high
|
||||
.suggestedMaxFeePerGas,
|
||||
);
|
||||
});
|
||||
|
||||
it('if no maxFeePerGas is provided by user or in transaction it returns value from fee market estimate', () => {
|
||||
const { result } = renderUseMaxFeePerGasInputHook({
|
||||
transaction: {
|
||||
userFeeLevel: 'high',
|
||||
txParams: {},
|
||||
},
|
||||
});
|
||||
expect(result.current.maxFeePerGas).toBe(
|
||||
FEE_MARKET_ESTIMATE_RETURN_VALUE.gasFeeEstimates.medium
|
||||
.suggestedMaxFeePerGas,
|
||||
);
|
||||
});
|
||||
|
||||
it('maxFeePerGasFiat is maximum amount that the transaction can cost', () => {
|
||||
const { result } = renderUseMaxFeePerGasInputHook();
|
||||
const maximumHexValue = getMaximumGasTotalInHexWei({
|
||||
gasLimit: decimalToHex('21000'),
|
||||
maxFeePerGas: '0x5028',
|
||||
});
|
||||
expect(result.current.maxFeePerGasFiat).toBe(
|
||||
convertFromHexToFiat(maximumHexValue),
|
||||
);
|
||||
});
|
||||
|
||||
it('does not return fiat values if showFiat is false', () => {
|
||||
useSelector.mockImplementation(
|
||||
generateUseSelectorRouter({
|
||||
checkNetworkAndAccountSupports1559Response: true,
|
||||
shouldShowFiat: false,
|
||||
}),
|
||||
);
|
||||
const { result } = renderUseMaxFeePerGasInputHook();
|
||||
expect(result.current.maxFeePerGasFiat).toBe('');
|
||||
});
|
||||
|
||||
it('returns 0 if EIP1559 is not supported and legacy gas estimates have been provided', () => {
|
||||
configureLegacy();
|
||||
const { result } = renderUseMaxFeePerGasInputHook({
|
||||
...LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||
});
|
||||
expect(result.current.maxFeePerGas).toBe('0');
|
||||
});
|
||||
|
||||
it('returns maxFeePerGas set by user if setMaxFeePerGas is called', () => {
|
||||
const { result } = renderUseMaxFeePerGasInputHook();
|
||||
act(() => {
|
||||
result.current.setMaxFeePerGas(100);
|
||||
});
|
||||
expect(result.current.maxFeePerGas).toBe(100);
|
||||
});
|
||||
});
|
97
ui/hooks/gasFeeInput/useMaxPriorityFeePerGasInput.js
Normal file
97
ui/hooks/gasFeeInput/useMaxPriorityFeePerGasInput.js
Normal file
@ -0,0 +1,97 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { addHexPrefix } from 'ethereumjs-util';
|
||||
|
||||
import { SECONDARY } from '../../helpers/constants/common';
|
||||
import { hexWEIToDecGWEI } from '../../helpers/utils/conversions.util';
|
||||
import {
|
||||
checkNetworkAndAccountSupports1559,
|
||||
getShouldShowFiat,
|
||||
} from '../../selectors';
|
||||
import { multiplyCurrencies } from '../../../shared/modules/conversion.utils';
|
||||
import { isLegacyTransaction } from '../../helpers/utils/transactions.util';
|
||||
|
||||
import { useCurrencyDisplay } from '../useCurrencyDisplay';
|
||||
import { useUserPreferencedCurrency } from '../useUserPreferencedCurrency';
|
||||
import { feeParamsAreCustom, getGasFeeEstimate } from './utils';
|
||||
|
||||
const getMaxPriorityFeePerGasFromTransaction = (transaction) => {
|
||||
const { maxPriorityFeePerGas, maxFeePerGas, gasPrice } =
|
||||
transaction?.txParams || {};
|
||||
return Number(
|
||||
hexWEIToDecGWEI(maxPriorityFeePerGas || maxFeePerGas || gasPrice),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {Object} MaxPriorityFeePerGasInputReturnType
|
||||
* @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.
|
||||
*/
|
||||
export function useMaxPriorityFeePerGasInput({
|
||||
estimateToUse,
|
||||
gasEstimateType,
|
||||
gasFeeEstimates,
|
||||
gasLimit,
|
||||
transaction,
|
||||
}) {
|
||||
const supportsEIP1559 =
|
||||
useSelector(checkNetworkAndAccountSupports1559) &&
|
||||
!isLegacyTransaction(transaction?.txParams);
|
||||
|
||||
const {
|
||||
currency: fiatCurrency,
|
||||
numberOfDecimals: fiatNumberOfDecimals,
|
||||
} = useUserPreferencedCurrency(SECONDARY);
|
||||
|
||||
const showFiat = useSelector(getShouldShowFiat);
|
||||
|
||||
const maxPriorityFeePerGasFromTransaction = supportsEIP1559
|
||||
? getMaxPriorityFeePerGasFromTransaction(transaction)
|
||||
: 0;
|
||||
|
||||
const [maxPriorityFeePerGas, setMaxPriorityFeePerGas] = useState(() => {
|
||||
if (maxPriorityFeePerGasFromTransaction && feeParamsAreCustom(transaction))
|
||||
return maxPriorityFeePerGasFromTransaction;
|
||||
return null;
|
||||
});
|
||||
|
||||
const maxPriorityFeePerGasToUse =
|
||||
maxPriorityFeePerGas ??
|
||||
getGasFeeEstimate(
|
||||
'suggestedMaxPriorityFeePerGas',
|
||||
gasFeeEstimates,
|
||||
gasEstimateType,
|
||||
estimateToUse,
|
||||
maxPriorityFeePerGasFromTransaction,
|
||||
);
|
||||
|
||||
// We need to display the estimated fiat currency impact of the
|
||||
// maxPriorityFeePerGas field to the user. This hook calculates that amount.
|
||||
const [, { value: maxPriorityFeePerGasFiat }] = useCurrencyDisplay(
|
||||
addHexPrefix(
|
||||
multiplyCurrencies(maxPriorityFeePerGasToUse, gasLimit, {
|
||||
toNumericBase: 'hex',
|
||||
fromDenomination: 'GWEI',
|
||||
toDenomination: 'WEI',
|
||||
multiplicandBase: 10,
|
||||
multiplierBase: 10,
|
||||
}),
|
||||
),
|
||||
{
|
||||
numberOfDecimals: fiatNumberOfDecimals,
|
||||
currency: fiatCurrency,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGasToUse,
|
||||
maxPriorityFeePerGasFiat: showFiat ? maxPriorityFeePerGasFiat : '',
|
||||
setMaxPriorityFeePerGas,
|
||||
};
|
||||
}
|
118
ui/hooks/gasFeeInput/useMaxPriorityFeePerGasInput.test.js
Normal file
118
ui/hooks/gasFeeInput/useMaxPriorityFeePerGasInput.test.js
Normal file
@ -0,0 +1,118 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
|
||||
import {
|
||||
FEE_MARKET_ESTIMATE_RETURN_VALUE,
|
||||
LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||
configureEIP1559,
|
||||
configureLegacy,
|
||||
convertFromHexToFiat,
|
||||
generateUseSelectorRouter,
|
||||
} from './test-utils';
|
||||
import { useMaxPriorityFeePerGasInput } from './useMaxPriorityFeePerGasInput';
|
||||
|
||||
jest.mock('../useGasFeeEstimates', () => ({
|
||||
useGasFeeEstimates: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('react-redux', () => {
|
||||
const actual = jest.requireActual('react-redux');
|
||||
|
||||
return {
|
||||
...actual,
|
||||
useSelector: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const renderUseMaxPriorityFeePerGasInputHook = (props) => {
|
||||
return renderHook(() =>
|
||||
useMaxPriorityFeePerGasInput({
|
||||
gasLimit: '21000',
|
||||
estimateToUse: 'medium',
|
||||
transaction: {
|
||||
userFeeLevel: 'custom',
|
||||
txParams: { maxPriorityFeePerGas: '0x5028' },
|
||||
},
|
||||
...FEE_MARKET_ESTIMATE_RETURN_VALUE,
|
||||
...props,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
describe('useMaxPriorityFeePerGasInput', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
configureEIP1559();
|
||||
});
|
||||
|
||||
it('returns maxPriorityFeePerGas values from transaction if transaction.userFeeLevel is custom', () => {
|
||||
const { result } = renderUseMaxPriorityFeePerGasInputHook();
|
||||
expect(result.current.maxPriorityFeePerGas).toBe(0.00002052);
|
||||
expect(result.current.maxPriorityFeePerGasFiat).toBe(
|
||||
convertFromHexToFiat('0x5028'),
|
||||
);
|
||||
});
|
||||
|
||||
it('returns maxFeePerGas values from transaction if transaction.userFeeLevel is custom and maxPriorityFeePerGas is not provided', () => {
|
||||
const { result } = renderUseMaxPriorityFeePerGasInputHook({
|
||||
transaction: {
|
||||
userFeeLevel: 'custom',
|
||||
txParams: { maxFeePerGas: '0x5028' },
|
||||
},
|
||||
});
|
||||
expect(result.current.maxPriorityFeePerGas).toBe(0.00002052);
|
||||
});
|
||||
|
||||
it('does not returns maxPriorityFeePerGas values from transaction if transaction.userFeeLevel is not custom', () => {
|
||||
const { result } = renderUseMaxPriorityFeePerGasInputHook({
|
||||
estimateToUse: 'high',
|
||||
transaction: {
|
||||
userFeeLevel: 'high',
|
||||
txParams: { maxPriorityFeePerGas: '0x5028' },
|
||||
},
|
||||
});
|
||||
expect(result.current.maxPriorityFeePerGas).toBe(
|
||||
FEE_MARKET_ESTIMATE_RETURN_VALUE.gasFeeEstimates.high
|
||||
.suggestedMaxPriorityFeePerGas,
|
||||
);
|
||||
});
|
||||
|
||||
it('if no maxPriorityFeePerGas is provided by user or in transaction it returns value from fee market estimate', () => {
|
||||
const { result } = renderUseMaxPriorityFeePerGasInputHook({
|
||||
transaction: {
|
||||
txParams: {},
|
||||
},
|
||||
});
|
||||
expect(result.current.maxPriorityFeePerGas).toBe(
|
||||
FEE_MARKET_ESTIMATE_RETURN_VALUE.gasFeeEstimates.medium
|
||||
.suggestedMaxPriorityFeePerGas,
|
||||
);
|
||||
});
|
||||
|
||||
it('does not return fiat values if showFiat is false', () => {
|
||||
useSelector.mockImplementation(
|
||||
generateUseSelectorRouter({
|
||||
checkNetworkAndAccountSupports1559Response: true,
|
||||
shouldShowFiat: false,
|
||||
}),
|
||||
);
|
||||
const { result } = renderUseMaxPriorityFeePerGasInputHook();
|
||||
expect(result.current.maxPriorityFeePerGasFiat).toBe('');
|
||||
});
|
||||
|
||||
it('returns 0 if EIP1559 is not supported and gas estimates are legacy', () => {
|
||||
configureLegacy();
|
||||
const { result } = renderUseMaxPriorityFeePerGasInputHook({
|
||||
...LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||
});
|
||||
expect(result.current.maxPriorityFeePerGas).toBe('0');
|
||||
});
|
||||
|
||||
it('returns maxPriorityFeePerGas set by user if setMaxPriorityFeePerGas is called', () => {
|
||||
const { result } = renderUseMaxPriorityFeePerGasInputHook();
|
||||
act(() => {
|
||||
result.current.setMaxPriorityFeePerGas(100);
|
||||
});
|
||||
expect(result.current.maxPriorityFeePerGas).toBe(100);
|
||||
});
|
||||
});
|
17
ui/hooks/gasFeeInput/utils.js
Normal file
17
ui/hooks/gasFeeInput/utils.js
Normal file
@ -0,0 +1,17 @@
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../shared/constants/gas';
|
||||
|
||||
export function getGasFeeEstimate(
|
||||
field,
|
||||
gasFeeEstimates,
|
||||
gasEstimateType,
|
||||
estimateToUse,
|
||||
fallback = '0',
|
||||
) {
|
||||
if (gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET) {
|
||||
return gasFeeEstimates?.[estimateToUse]?.[field] ?? String(fallback);
|
||||
}
|
||||
return String(fallback);
|
||||
}
|
||||
|
||||
export const feeParamsAreCustom = (transaction) =>
|
||||
!transaction?.userFeeLevel || transaction?.userFeeLevel === 'custom';
|
@ -10,7 +10,7 @@ import { useEthFiatAmount } from '../../../hooks/useEthFiatAmount';
|
||||
import { useEqualityCheck } from '../../../hooks/useEqualityCheck';
|
||||
import { useNewMetricEvent } from '../../../hooks/useMetricEvent';
|
||||
import { usePrevious } from '../../../hooks/usePrevious';
|
||||
import { useGasFeeInputs } from '../../../hooks/useGasFeeInputs';
|
||||
import { useGasFeeInputs } from '../../../hooks/gasFeeInput/useGasFeeInputs';
|
||||
import { MetaMetricsContext } from '../../../contexts/metametrics.new';
|
||||
import FeeCard from '../fee-card';
|
||||
import EditGasPopover from '../../../components/app/edit-gas-popover/edit-gas-popover.component';
|
||||
|
@ -14,7 +14,7 @@ jest.mock('../../../components/ui/info-tooltip/info-tooltip-icon', () => () =>
|
||||
'<InfoTooltipIcon />',
|
||||
);
|
||||
|
||||
jest.mock('../../../hooks/useGasFeeInputs', () => {
|
||||
jest.mock('../../../hooks/gasFeeInput/useGasFeeInputs', () => {
|
||||
return {
|
||||
useGasFeeInputs: () => {
|
||||
return {
|
||||
|
Loading…
Reference in New Issue
Block a user