mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +01:00
Adding validations for advance gas fee input fields (#12879)
This commit is contained in:
parent
d78cfebd43
commit
2af45a656f
@ -810,12 +810,25 @@
|
|||||||
"editGasLimitOutOfBounds": {
|
"editGasLimitOutOfBounds": {
|
||||||
"message": "Gas limit must be at least $1"
|
"message": "Gas limit must be at least $1"
|
||||||
},
|
},
|
||||||
|
"editGasLimitOutOfBoundsV2": {
|
||||||
|
"message": "Gas limit must be greater than $1 and less than $2",
|
||||||
|
"description": "$1 is the minimum limit for gas and $2 is the maximum limit"
|
||||||
|
},
|
||||||
"editGasLimitTooltip": {
|
"editGasLimitTooltip": {
|
||||||
"message": "Gas limit is the maximum units of gas you are willing to use. Units of gas are a multiplier to “Max priority fee” and “Max fee”."
|
"message": "Gas limit is the maximum units of gas you are willing to use. Units of gas are a multiplier to “Max priority fee” and “Max fee”."
|
||||||
},
|
},
|
||||||
"editGasLow": {
|
"editGasLow": {
|
||||||
"message": "Low"
|
"message": "Low"
|
||||||
},
|
},
|
||||||
|
"editGasMaxBaseFeeHigh": {
|
||||||
|
"message": "Max base fee is higher than necessary"
|
||||||
|
},
|
||||||
|
"editGasMaxBaseFeeImbalance": {
|
||||||
|
"message": "Max base fee cannot be lower than priority fee"
|
||||||
|
},
|
||||||
|
"editGasMaxBaseFeeLow": {
|
||||||
|
"message": "Max base fee is low for current network conditions"
|
||||||
|
},
|
||||||
"editGasMaxFeeHigh": {
|
"editGasMaxFeeHigh": {
|
||||||
"message": "Max fee is higher than necessary"
|
"message": "Max fee is higher than necessary"
|
||||||
},
|
},
|
||||||
@ -831,12 +844,21 @@
|
|||||||
"editGasMaxPriorityFeeBelowMinimum": {
|
"editGasMaxPriorityFeeBelowMinimum": {
|
||||||
"message": "Max priority fee must be greater than 0 GWEI"
|
"message": "Max priority fee must be greater than 0 GWEI"
|
||||||
},
|
},
|
||||||
|
"editGasMaxPriorityFeeBelowMinimumV2": {
|
||||||
|
"message": "Priority fee must be at least 1 GWEI"
|
||||||
|
},
|
||||||
"editGasMaxPriorityFeeHigh": {
|
"editGasMaxPriorityFeeHigh": {
|
||||||
"message": "Max priority fee is higher than necessary. You may pay more than needed."
|
"message": "Max priority fee is higher than necessary. You may pay more than needed."
|
||||||
},
|
},
|
||||||
|
"editGasMaxPriorityFeeHighV2": {
|
||||||
|
"message": "Priority fee is higher than necessary. You may pay more than needed"
|
||||||
|
},
|
||||||
"editGasMaxPriorityFeeLow": {
|
"editGasMaxPriorityFeeLow": {
|
||||||
"message": "Max priority fee is low for current network conditions"
|
"message": "Max priority fee is low for current network conditions"
|
||||||
},
|
},
|
||||||
|
"editGasMaxPriorityFeeLowV2": {
|
||||||
|
"message": "Priority fee is low for current network conditions"
|
||||||
|
},
|
||||||
"editGasMaxPriorityFeeTooltip": {
|
"editGasMaxPriorityFeeTooltip": {
|
||||||
"message": "Max priority fee (aka “miner tip”) goes directly to miners and incentivizes them to prioritize your transaction. You’ll most often pay your max setting"
|
"message": "Max priority fee (aka “miner tip”) goes directly to miners and incentivizes them to prioritize your transaction. You’ll most often pay your max setting"
|
||||||
},
|
},
|
||||||
|
@ -1,25 +1,38 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { useGasFeeContext } from '../../../../contexts/gasFee';
|
import { useGasFeeContext } from '../../../../contexts/gasFee';
|
||||||
|
import { bnGreaterThan, bnLessThan } from '../../../../helpers/utils/util';
|
||||||
import { TYPOGRAPHY } from '../../../../helpers/constants/design-system';
|
import { TYPOGRAPHY } from '../../../../helpers/constants/design-system';
|
||||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||||
import Box from '../../../ui/box';
|
import { MAX_GAS_LIMIT_DEC } from '../../../../pages/send/send.constants';
|
||||||
import Button from '../../../ui/button';
|
import Button from '../../../ui/button';
|
||||||
import FormField from '../../../ui/form-field';
|
import FormField from '../../../ui/form-field';
|
||||||
import I18nValue from '../../../ui/i18n-value';
|
import I18nValue from '../../../ui/i18n-value';
|
||||||
import Typography from '../../../ui/typography';
|
import Typography from '../../../ui/typography';
|
||||||
|
|
||||||
import { useAdvanceGasFeePopoverContext } from '../context';
|
import { useAdvancedGasFeePopoverContext } from '../context';
|
||||||
|
|
||||||
|
const validateGasLimit = (gasLimit, minimumGasLimitDec) => {
|
||||||
|
return bnLessThan(gasLimit, minimumGasLimitDec) ||
|
||||||
|
bnGreaterThan(gasLimit, MAX_GAS_LIMIT_DEC)
|
||||||
|
? 'editGasLimitOutOfBoundsV2'
|
||||||
|
: null;
|
||||||
|
};
|
||||||
|
|
||||||
const AdvancedGasFeeGasLimit = () => {
|
const AdvancedGasFeeGasLimit = () => {
|
||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
const {
|
const {
|
||||||
setDirty,
|
setDirty,
|
||||||
setGasLimit: setGasLimitInContext,
|
setGasLimit: setGasLimitInContext,
|
||||||
} = useAdvanceGasFeePopoverContext();
|
setHasError,
|
||||||
const { gasLimit: gasLimitInTransaction } = useGasFeeContext();
|
} = useAdvancedGasFeePopoverContext();
|
||||||
|
const {
|
||||||
|
gasLimit: gasLimitInTransaction,
|
||||||
|
minimumGasLimitDec,
|
||||||
|
} = useGasFeeContext();
|
||||||
const [isEditing, setEditing] = useState(false);
|
const [isEditing, setEditing] = useState(false);
|
||||||
const [gasLimit, setGasLimit] = useState(gasLimitInTransaction);
|
const [gasLimit, setGasLimit] = useState(gasLimitInTransaction);
|
||||||
|
const [gasLimitError, setGasLimitError] = useState();
|
||||||
|
|
||||||
const updateGasLimit = (value) => {
|
const updateGasLimit = (value) => {
|
||||||
setGasLimit(value);
|
setGasLimit(value);
|
||||||
@ -28,11 +41,19 @@ const AdvancedGasFeeGasLimit = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setGasLimitInContext(gasLimit);
|
setGasLimitInContext(gasLimit);
|
||||||
}, [gasLimit, setGasLimitInContext]);
|
const error = validateGasLimit(gasLimit, minimumGasLimitDec);
|
||||||
|
setGasLimitError(error);
|
||||||
|
setHasError(Boolean(error));
|
||||||
|
}, [gasLimit, minimumGasLimitDec, setGasLimitInContext, setHasError]);
|
||||||
|
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
return (
|
return (
|
||||||
<FormField
|
<FormField
|
||||||
|
error={
|
||||||
|
gasLimitError
|
||||||
|
? t(gasLimitError, [minimumGasLimitDec - 1, MAX_GAS_LIMIT_DEC])
|
||||||
|
: ''
|
||||||
|
}
|
||||||
onChange={updateGasLimit}
|
onChange={updateGasLimit}
|
||||||
titleText={t('gasLimitV2')}
|
titleText={t('gasLimitV2')}
|
||||||
value={gasLimit}
|
value={gasLimit}
|
||||||
@ -42,24 +63,22 @@ const AdvancedGasFeeGasLimit = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Typography tag={TYPOGRAPHY.Paragraph} variant={TYPOGRAPHY.H7}>
|
<Typography
|
||||||
<Box
|
tag={TYPOGRAPHY.Paragraph}
|
||||||
display="flex"
|
variant={TYPOGRAPHY.H7}
|
||||||
alignItems="center"
|
className="advanced-gas-fee-gas-limit"
|
||||||
className="advanced-gas-fee-gas-limit"
|
>
|
||||||
|
<strong>
|
||||||
|
<I18nValue messageKey="gasLimitV2" />
|
||||||
|
</strong>
|
||||||
|
<span>{gasLimit}</span>
|
||||||
|
<Button
|
||||||
|
className="advanced-gas-fee-gas-limit__edit-link"
|
||||||
|
onClick={() => setEditing(true)}
|
||||||
|
type="link"
|
||||||
>
|
>
|
||||||
<strong>
|
<I18nValue messageKey="edit" />
|
||||||
<I18nValue messageKey="gasLimitV2" />
|
</Button>
|
||||||
</strong>
|
|
||||||
<span>{gasLimit}</span>
|
|
||||||
<Button
|
|
||||||
className="advanced-gas-fee-gas-limit__edit-link"
|
|
||||||
onClick={() => setEditing(true)}
|
|
||||||
type="link"
|
|
||||||
>
|
|
||||||
<I18nValue messageKey="edit" />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Typography>
|
</Typography>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,7 @@ import mockState from '../../../../../test/data/mock-state.json';
|
|||||||
import { GasFeeContextProvider } from '../../../../contexts/gasFee';
|
import { GasFeeContextProvider } from '../../../../contexts/gasFee';
|
||||||
import configureStore from '../../../../store/store';
|
import configureStore from '../../../../store/store';
|
||||||
|
|
||||||
import { AdvanceGasFeePopoverContextProvider } from '../context';
|
import { AdvancedGasFeePopoverContextProvider } from '../context';
|
||||||
import AdvancedGasFeeGasLimit from './advanced-gas-fee-gas-limit';
|
import AdvancedGasFeeGasLimit from './advanced-gas-fee-gas-limit';
|
||||||
|
|
||||||
jest.mock('../../../../store/actions', () => ({
|
jest.mock('../../../../store/actions', () => ({
|
||||||
@ -20,7 +20,7 @@ jest.mock('../../../../store/actions', () => ({
|
|||||||
removePollingTokenFromAppState: jest.fn(),
|
removePollingTokenFromAppState: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const render = (txProps) => {
|
const render = (contextProps) => {
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
metamask: {
|
metamask: {
|
||||||
...mockState.metamask,
|
...mockState.metamask,
|
||||||
@ -42,12 +42,12 @@ const render = (txProps) => {
|
|||||||
transaction={{
|
transaction={{
|
||||||
userFeeLevel: 'custom',
|
userFeeLevel: 'custom',
|
||||||
txParams: { gas: '0x5208' },
|
txParams: { gas: '0x5208' },
|
||||||
...txProps,
|
|
||||||
}}
|
}}
|
||||||
|
{...contextProps}
|
||||||
>
|
>
|
||||||
<AdvanceGasFeePopoverContextProvider>
|
<AdvancedGasFeePopoverContextProvider>
|
||||||
<AdvancedGasFeeGasLimit />
|
<AdvancedGasFeeGasLimit />
|
||||||
</AdvanceGasFeePopoverContextProvider>
|
</AdvancedGasFeePopoverContextProvider>
|
||||||
</GasFeeContextProvider>,
|
</GasFeeContextProvider>,
|
||||||
store,
|
store,
|
||||||
);
|
);
|
||||||
@ -65,4 +65,46 @@ describe('AdvancedGasFeeGasLimit', () => {
|
|||||||
fireEvent.click(screen.queryByText('Edit'));
|
fireEvent.click(screen.queryByText('Edit'));
|
||||||
expect(document.getElementsByTagName('input')[0]).toHaveValue(21000);
|
expect(document.getElementsByTagName('input')[0]).toHaveValue(21000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should show error if gas limit is not in range', () => {
|
||||||
|
render();
|
||||||
|
fireEvent.click(screen.queryByText('Edit'));
|
||||||
|
fireEvent.change(document.getElementsByTagName('input')[0], {
|
||||||
|
target: { value: 20000 },
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
screen.queryByText(
|
||||||
|
'Gas limit must be greater than 20999 and less than 7920027',
|
||||||
|
),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
fireEvent.change(document.getElementsByTagName('input')[0], {
|
||||||
|
target: { value: 8000000 },
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
screen.queryByText(
|
||||||
|
'Gas limit must be greater than 20999 and less than 7920027',
|
||||||
|
),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
fireEvent.change(document.getElementsByTagName('input')[0], {
|
||||||
|
target: { value: 7000000 },
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
screen.queryByText(
|
||||||
|
'Gas limit must be greater than 20999 and less than 7920027',
|
||||||
|
),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should validate gas limit against minimumGasLimit it is passed to context', () => {
|
||||||
|
render({ minimumGasLimit: '0x7530' });
|
||||||
|
fireEvent.click(screen.queryByText('Edit'));
|
||||||
|
fireEvent.change(document.getElementsByTagName('input')[0], {
|
||||||
|
target: { value: 25000 },
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
screen.queryByText(
|
||||||
|
'Gas limit must be greater than 29999 and less than 7920027',
|
||||||
|
),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
.advanced-gas-fee-gas-limit {
|
.advanced-gas-fee-gas-limit {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
|
@ -6,7 +6,7 @@ import PriorityFeeInput from './priority-fee-input';
|
|||||||
|
|
||||||
const AdvancedGasFeeInputs = () => {
|
const AdvancedGasFeeInputs = () => {
|
||||||
return (
|
return (
|
||||||
<Box className="advanced-gas-fee-inputs" margin={4}>
|
<Box className="advanced-gas-fee-inputs" margin={[4, 0]}>
|
||||||
<BaseFeeInput />
|
<BaseFeeInput />
|
||||||
<div className="advanced-gas-fee-inputs__separator" />
|
<div className="advanced-gas-fee-inputs__separator" />
|
||||||
<PriorityFeeInput />
|
<PriorityFeeInput />
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
import { HIGH_FEE_WARNING_MULTIPLIER } from '../../../../../pages/send/send.constants';
|
||||||
import { PRIORITY_LEVELS } from '../../../../../../shared/constants/gas';
|
import { PRIORITY_LEVELS } from '../../../../../../shared/constants/gas';
|
||||||
import {
|
import {
|
||||||
divideCurrencies,
|
divideCurrencies,
|
||||||
multiplyCurrencies,
|
multiplyCurrencies,
|
||||||
} from '../../../../../../shared/modules/conversion.utils';
|
} from '../../../../../../shared/modules/conversion.utils';
|
||||||
import { PRIMARY, SECONDARY } from '../../../../../helpers/constants/common';
|
import { PRIMARY, SECONDARY } from '../../../../../helpers/constants/common';
|
||||||
|
import { bnGreaterThan, bnLessThan } from '../../../../../helpers/utils/util';
|
||||||
import { decGWEIToHexWEI } from '../../../../../helpers/utils/conversions.util';
|
import { decGWEIToHexWEI } from '../../../../../helpers/utils/conversions.util';
|
||||||
import { getAdvancedGasFeeValues } from '../../../../../selectors';
|
import { getAdvancedGasFeeValues } from '../../../../../selectors';
|
||||||
import { useGasFeeContext } from '../../../../../contexts/gasFee';
|
import { useGasFeeContext } from '../../../../../contexts/gasFee';
|
||||||
@ -18,7 +20,7 @@ import Box from '../../../../ui/box';
|
|||||||
import FormField from '../../../../ui/form-field';
|
import FormField from '../../../../ui/form-field';
|
||||||
import I18nValue from '../../../../ui/i18n-value';
|
import I18nValue from '../../../../ui/i18n-value';
|
||||||
|
|
||||||
import { useAdvanceGasFeePopoverContext } from '../../context';
|
import { useAdvancedGasFeePopoverContext } from '../../context';
|
||||||
import AdvancedGasFeeInputSubtext from '../../advanced-gas-fee-input-subtext';
|
import AdvancedGasFeeInputSubtext from '../../advanced-gas-fee-input-subtext';
|
||||||
|
|
||||||
const divideCurrencyValues = (value, baseFee) => {
|
const divideCurrencyValues = (value, baseFee) => {
|
||||||
@ -39,11 +41,40 @@ const multiplyCurrencyValues = (baseFee, value, numberOfDecimals) =>
|
|||||||
multiplierBase: 10,
|
multiplierBase: 10,
|
||||||
}).toNumber();
|
}).toNumber();
|
||||||
|
|
||||||
|
const validateBaseFee = (value, gasFeeEstimates, maxPriorityFeePerGas) => {
|
||||||
|
if (bnGreaterThan(maxPriorityFeePerGas, value)) {
|
||||||
|
return 'editGasMaxBaseFeeImbalance';
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
gasFeeEstimates?.low &&
|
||||||
|
bnLessThan(value, gasFeeEstimates.low.suggestedMaxFeePerGas)
|
||||||
|
) {
|
||||||
|
return 'editGasMaxBaseFeeLow';
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
gasFeeEstimates?.high &&
|
||||||
|
bnGreaterThan(
|
||||||
|
value,
|
||||||
|
gasFeeEstimates.high.suggestedMaxFeePerGas * HIGH_FEE_WARNING_MULTIPLIER,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return 'editGasMaxBaseFeeHigh';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
const BaseFeeInput = () => {
|
const BaseFeeInput = () => {
|
||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
const { gasFeeEstimates, estimateUsed, maxFeePerGas } = useGasFeeContext();
|
const { gasFeeEstimates, estimateUsed, maxFeePerGas } = useGasFeeContext();
|
||||||
const { setDirty, setMaxFeePerGas } = useAdvanceGasFeePopoverContext();
|
const {
|
||||||
|
setDirty,
|
||||||
|
setHasError,
|
||||||
|
setMaxFeePerGas,
|
||||||
|
maxPriorityFeePerGas,
|
||||||
|
} = useAdvancedGasFeePopoverContext();
|
||||||
|
|
||||||
const { estimatedBaseFee } = gasFeeEstimates;
|
const { estimatedBaseFee } = gasFeeEstimates;
|
||||||
|
const [baseFeeError, setBaseFeeError] = useState();
|
||||||
const {
|
const {
|
||||||
numberOfDecimals: numberOfDecimalsPrimary,
|
numberOfDecimals: numberOfDecimalsPrimary,
|
||||||
} = useUserPreferencedCurrency(PRIMARY);
|
} = useUserPreferencedCurrency(PRIMARY);
|
||||||
@ -116,11 +147,26 @@ const BaseFeeInput = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMaxFeePerGas(maxBaseFeeGWEI);
|
setMaxFeePerGas(maxBaseFeeGWEI);
|
||||||
}, [maxBaseFeeGWEI, setMaxFeePerGas]);
|
const error = validateBaseFee(
|
||||||
|
maxBaseFeeGWEI,
|
||||||
|
gasFeeEstimates,
|
||||||
|
maxPriorityFeePerGas,
|
||||||
|
);
|
||||||
|
setBaseFeeError(error);
|
||||||
|
setHasError(Boolean(error));
|
||||||
|
}, [
|
||||||
|
gasFeeEstimates,
|
||||||
|
maxBaseFeeGWEI,
|
||||||
|
maxPriorityFeePerGas,
|
||||||
|
setHasError,
|
||||||
|
setBaseFeeError,
|
||||||
|
setMaxFeePerGas,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className="base-fee-input">
|
<Box className="base-fee-input">
|
||||||
<FormField
|
<FormField
|
||||||
|
error={baseFeeError ? t(baseFeeError) : ''}
|
||||||
onChange={updateBaseFee}
|
onChange={updateBaseFee}
|
||||||
titleText={t('maxBaseFee')}
|
titleText={t('maxBaseFee')}
|
||||||
titleUnit={editingInGwei ? 'GWEI' : `(${t('multiplier')})`}
|
titleUnit={editingInGwei ? 'GWEI' : `(${t('multiplier')})`}
|
||||||
|
@ -8,7 +8,7 @@ import mockState from '../../../../../../test/data/mock-state.json';
|
|||||||
import { GasFeeContextProvider } from '../../../../../contexts/gasFee';
|
import { GasFeeContextProvider } from '../../../../../contexts/gasFee';
|
||||||
import configureStore from '../../../../../store/store';
|
import configureStore from '../../../../../store/store';
|
||||||
|
|
||||||
import { AdvanceGasFeePopoverContextProvider } from '../../context';
|
import { AdvancedGasFeePopoverContextProvider } from '../../context';
|
||||||
import BaseFeeInput from './base-fee-input';
|
import BaseFeeInput from './base-fee-input';
|
||||||
|
|
||||||
jest.mock('../../../../../store/actions', () => ({
|
jest.mock('../../../../../store/actions', () => ({
|
||||||
@ -44,9 +44,9 @@ const render = (txProps) => {
|
|||||||
...txProps,
|
...txProps,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AdvanceGasFeePopoverContextProvider>
|
<AdvancedGasFeePopoverContextProvider>
|
||||||
<BaseFeeInput />
|
<BaseFeeInput />
|
||||||
</AdvanceGasFeePopoverContextProvider>
|
</AdvancedGasFeePopoverContextProvider>
|
||||||
</GasFeeContextProvider>,
|
</GasFeeContextProvider>,
|
||||||
store,
|
store,
|
||||||
);
|
);
|
||||||
@ -115,4 +115,58 @@ describe('BaseFeeInput', () => {
|
|||||||
});
|
});
|
||||||
expect(screen.queryByText('50')).toBeInTheDocument();
|
expect(screen.queryByText('50')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should show error if base fee is less than suggested low value', () => {
|
||||||
|
render({
|
||||||
|
txParams: {
|
||||||
|
maxFeePerGas: '0x174876E800',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
fireEvent.change(document.getElementsByTagName('input')[0], {
|
||||||
|
target: { value: 3 },
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
screen.queryByText('Max base fee is low for current network conditions'),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
fireEvent.change(document.getElementsByTagName('input')[0], {
|
||||||
|
target: { value: 0.01 },
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
screen.queryByText('Max base fee is low for current network conditions'),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
fireEvent.click(screen.queryByText('Edit in GWEI'));
|
||||||
|
fireEvent.change(document.getElementsByTagName('input')[0], {
|
||||||
|
target: { value: 10 },
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
screen.queryByText('Max base fee is low for current network conditions'),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show error if base if is more than suggested high value', () => {
|
||||||
|
render({
|
||||||
|
txParams: {
|
||||||
|
maxFeePerGas: '0x174876E800',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
fireEvent.change(document.getElementsByTagName('input')[0], {
|
||||||
|
target: { value: 3 },
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
screen.queryByText('Max base fee is higher than necessary'),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
fireEvent.change(document.getElementsByTagName('input')[0], {
|
||||||
|
target: { value: 10 },
|
||||||
|
});
|
||||||
|
fireEvent.click(screen.queryByText('Edit in GWEI'));
|
||||||
|
expect(
|
||||||
|
screen.queryByText('Max base fee is higher than necessary'),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
fireEvent.change(document.getElementsByTagName('input')[0], {
|
||||||
|
target: { value: 500 },
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
screen.queryByText('Max base fee is higher than necessary'),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
import { HIGH_FEE_WARNING_MULTIPLIER } from '../../../../../pages/send/send.constants';
|
||||||
import { PRIORITY_LEVELS } from '../../../../../../shared/constants/gas';
|
import { PRIORITY_LEVELS } from '../../../../../../shared/constants/gas';
|
||||||
import { SECONDARY } from '../../../../../helpers/constants/common';
|
import { SECONDARY } from '../../../../../helpers/constants/common';
|
||||||
import { decGWEIToHexWEI } from '../../../../../helpers/utils/conversions.util';
|
import { decGWEIToHexWEI } from '../../../../../helpers/utils/conversions.util';
|
||||||
@ -10,18 +11,48 @@ import { useGasFeeContext } from '../../../../../contexts/gasFee';
|
|||||||
import { useI18nContext } from '../../../../../hooks/useI18nContext';
|
import { useI18nContext } from '../../../../../hooks/useI18nContext';
|
||||||
import { useUserPreferencedCurrency } from '../../../../../hooks/useUserPreferencedCurrency';
|
import { useUserPreferencedCurrency } from '../../../../../hooks/useUserPreferencedCurrency';
|
||||||
import FormField from '../../../../ui/form-field';
|
import FormField from '../../../../ui/form-field';
|
||||||
|
import { bnGreaterThan, bnLessThan } from '../../../../../helpers/utils/util';
|
||||||
|
|
||||||
import { useAdvanceGasFeePopoverContext } from '../../context';
|
import { useAdvancedGasFeePopoverContext } from '../../context';
|
||||||
import AdvancedGasFeeInputSubtext from '../../advanced-gas-fee-input-subtext';
|
import AdvancedGasFeeInputSubtext from '../../advanced-gas-fee-input-subtext';
|
||||||
|
|
||||||
|
const validatePriorityFee = (value, gasFeeEstimates) => {
|
||||||
|
if (value < 1) {
|
||||||
|
return 'editGasMaxPriorityFeeBelowMinimumV2';
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
gasFeeEstimates?.low &&
|
||||||
|
bnLessThan(value, gasFeeEstimates.low.suggestedMaxPriorityFeePerGas)
|
||||||
|
) {
|
||||||
|
return 'editGasMaxPriorityFeeLowV2';
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
gasFeeEstimates?.high &&
|
||||||
|
bnGreaterThan(
|
||||||
|
value,
|
||||||
|
gasFeeEstimates.high.suggestedMaxPriorityFeePerGas *
|
||||||
|
HIGH_FEE_WARNING_MULTIPLIER,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return 'editGasMaxPriorityFeeHighV2';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
const PriorityFeeInput = () => {
|
const PriorityFeeInput = () => {
|
||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues);
|
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues);
|
||||||
const {
|
const {
|
||||||
setDirty,
|
setDirty,
|
||||||
|
setHasError,
|
||||||
setMaxPriorityFeePerGas,
|
setMaxPriorityFeePerGas,
|
||||||
} = useAdvanceGasFeePopoverContext();
|
} = useAdvancedGasFeePopoverContext();
|
||||||
const { estimateUsed, maxPriorityFeePerGas } = useGasFeeContext();
|
const {
|
||||||
|
estimateUsed,
|
||||||
|
gasFeeEstimates,
|
||||||
|
maxPriorityFeePerGas,
|
||||||
|
} = useGasFeeContext();
|
||||||
|
const [priorityFeeError, setPriorityFeeError] = useState();
|
||||||
|
|
||||||
const [priorityFee, setPriorityFee] = useState(() => {
|
const [priorityFee, setPriorityFee] = useState(() => {
|
||||||
if (
|
if (
|
||||||
@ -46,11 +77,21 @@ const PriorityFeeInput = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMaxPriorityFeePerGas(priorityFee);
|
setMaxPriorityFeePerGas(priorityFee);
|
||||||
}, [priorityFee, setMaxPriorityFeePerGas]);
|
const error = validatePriorityFee(priorityFee, gasFeeEstimates);
|
||||||
|
setPriorityFeeError(error);
|
||||||
|
setHasError(Boolean(error));
|
||||||
|
}, [
|
||||||
|
gasFeeEstimates,
|
||||||
|
priorityFee,
|
||||||
|
setHasError,
|
||||||
|
setMaxPriorityFeePerGas,
|
||||||
|
setPriorityFeeError,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormField
|
<FormField
|
||||||
|
error={priorityFeeError ? t(priorityFeeError) : ''}
|
||||||
onChange={updatePriorityFee}
|
onChange={updatePriorityFee}
|
||||||
titleText={t('priorityFeeProperCase')}
|
titleText={t('priorityFeeProperCase')}
|
||||||
titleUnit="(GWEI)"
|
titleUnit="(GWEI)"
|
||||||
|
@ -7,7 +7,7 @@ import mockState from '../../../../../../test/data/mock-state.json';
|
|||||||
import { GasFeeContextProvider } from '../../../../../contexts/gasFee';
|
import { GasFeeContextProvider } from '../../../../../contexts/gasFee';
|
||||||
import configureStore from '../../../../../store/store';
|
import configureStore from '../../../../../store/store';
|
||||||
|
|
||||||
import { AdvanceGasFeePopoverContextProvider } from '../../context';
|
import { AdvancedGasFeePopoverContextProvider } from '../../context';
|
||||||
import PriorityfeeInput from './priority-fee-input';
|
import PriorityfeeInput from './priority-fee-input';
|
||||||
|
|
||||||
jest.mock('../../../../../store/actions', () => ({
|
jest.mock('../../../../../store/actions', () => ({
|
||||||
@ -43,9 +43,9 @@ const render = (txProps) => {
|
|||||||
...txProps,
|
...txProps,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AdvanceGasFeePopoverContextProvider>
|
<AdvancedGasFeePopoverContextProvider>
|
||||||
<PriorityfeeInput />
|
<PriorityfeeInput />
|
||||||
</AdvanceGasFeePopoverContextProvider>
|
</AdvancedGasFeePopoverContextProvider>
|
||||||
</GasFeeContextProvider>,
|
</GasFeeContextProvider>,
|
||||||
store,
|
store,
|
||||||
);
|
);
|
||||||
|
@ -5,7 +5,7 @@ import { useTransactionModalContext } from '../../../contexts/transaction-modal'
|
|||||||
import Box from '../../ui/box';
|
import Box from '../../ui/box';
|
||||||
import Popover from '../../ui/popover';
|
import Popover from '../../ui/popover';
|
||||||
|
|
||||||
import { AdvanceGasFeePopoverContextProvider } from './context';
|
import { AdvancedGasFeePopoverContextProvider } from './context';
|
||||||
import AdvancedGasFeeInputs from './advanced-gas-fee-inputs';
|
import AdvancedGasFeeInputs from './advanced-gas-fee-inputs';
|
||||||
import AdvancedGasFeeGasLimit from './advanced-gas-fee-gas-limit';
|
import AdvancedGasFeeGasLimit from './advanced-gas-fee-gas-limit';
|
||||||
import AdvancedGasFeeSaveButton from './advanced-gas-fee-save';
|
import AdvancedGasFeeSaveButton from './advanced-gas-fee-save';
|
||||||
@ -21,7 +21,7 @@ const AdvancedGasFeePopover = () => {
|
|||||||
if (currentModal !== 'advancedGasFee') return null;
|
if (currentModal !== 'advancedGasFee') return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdvanceGasFeePopoverContextProvider>
|
<AdvancedGasFeePopoverContextProvider>
|
||||||
<Popover
|
<Popover
|
||||||
className="advanced-gas-fee-popover"
|
className="advanced-gas-fee-popover"
|
||||||
title={t('advancedGasFeeModalTitle')}
|
title={t('advancedGasFeeModalTitle')}
|
||||||
@ -35,7 +35,7 @@ const AdvancedGasFeePopover = () => {
|
|||||||
<AdvancedGasFeeGasLimit />
|
<AdvancedGasFeeGasLimit />
|
||||||
</Box>
|
</Box>
|
||||||
</Popover>
|
</Popover>
|
||||||
</AdvanceGasFeePopoverContextProvider>
|
</AdvancedGasFeePopoverContextProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ describe('AdvancedGasFeePopover', () => {
|
|||||||
it('should enable save button as input value is changed', () => {
|
it('should enable save button as input value is changed', () => {
|
||||||
render();
|
render();
|
||||||
fireEvent.change(document.getElementsByTagName('input')[0], {
|
fireEvent.change(document.getElementsByTagName('input')[0], {
|
||||||
target: { value: 4 },
|
target: { value: 3 },
|
||||||
});
|
});
|
||||||
expect(screen.queryByRole('button', { name: 'Save' })).not.toBeDisabled();
|
expect(screen.queryByRole('button', { name: 'Save' })).not.toBeDisabled();
|
||||||
});
|
});
|
||||||
|
@ -6,7 +6,7 @@ import { useGasFeeContext } from '../../../../contexts/gasFee';
|
|||||||
import Button from '../../../ui/button';
|
import Button from '../../../ui/button';
|
||||||
import I18nValue from '../../../ui/i18n-value';
|
import I18nValue from '../../../ui/i18n-value';
|
||||||
|
|
||||||
import { useAdvanceGasFeePopoverContext } from '../context';
|
import { useAdvancedGasFeePopoverContext } from '../context';
|
||||||
import { decGWEIToHexWEI } from '../../../../../shared/modules/conversion.utils';
|
import { decGWEIToHexWEI } from '../../../../../shared/modules/conversion.utils';
|
||||||
|
|
||||||
const AdvancedGasFeeSaveButton = () => {
|
const AdvancedGasFeeSaveButton = () => {
|
||||||
@ -15,9 +15,10 @@ const AdvancedGasFeeSaveButton = () => {
|
|||||||
const {
|
const {
|
||||||
isDirty,
|
isDirty,
|
||||||
gasLimit,
|
gasLimit,
|
||||||
|
hasError,
|
||||||
maxFeePerGas,
|
maxFeePerGas,
|
||||||
maxPriorityFeePerGas,
|
maxPriorityFeePerGas,
|
||||||
} = useAdvanceGasFeePopoverContext();
|
} = useAdvancedGasFeePopoverContext();
|
||||||
|
|
||||||
const onSave = () => {
|
const onSave = () => {
|
||||||
updateTransaction({
|
updateTransaction({
|
||||||
@ -30,7 +31,7 @@ const AdvancedGasFeeSaveButton = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button type="primary" disabled={!isDirty} onClick={onSave}>
|
<Button type="primary" disabled={!isDirty || hasError} onClick={onSave}>
|
||||||
<I18nValue messageKey="save" />
|
<I18nValue messageKey="save" />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
@ -1,36 +1,39 @@
|
|||||||
import React, { createContext, useContext, useState } from 'react';
|
import React, { createContext, useContext, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
export const AdvanceGasFeePopoverContext = createContext({});
|
export const AdvancedGasFeePopoverContext = createContext({});
|
||||||
|
|
||||||
export const AdvanceGasFeePopoverContextProvider = ({ children }) => {
|
export const AdvancedGasFeePopoverContextProvider = ({ children }) => {
|
||||||
const [gasLimit, setGasLimit] = useState();
|
const [gasLimit, setGasLimit] = useState();
|
||||||
const [maxFeePerGas, setMaxFeePerGas] = useState();
|
const [maxFeePerGas, setMaxFeePerGas] = useState();
|
||||||
const [maxPriorityFeePerGas, setMaxPriorityFeePerGas] = useState();
|
const [maxPriorityFeePerGas, setMaxPriorityFeePerGas] = useState();
|
||||||
const [isDirty, setDirty] = useState();
|
const [isDirty, setDirty] = useState();
|
||||||
|
const [hasError, setHasError] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdvanceGasFeePopoverContext.Provider
|
<AdvancedGasFeePopoverContext.Provider
|
||||||
value={{
|
value={{
|
||||||
gasLimit,
|
gasLimit,
|
||||||
|
hasError,
|
||||||
isDirty,
|
isDirty,
|
||||||
maxFeePerGas,
|
maxFeePerGas,
|
||||||
maxPriorityFeePerGas,
|
maxPriorityFeePerGas,
|
||||||
setDirty,
|
setDirty,
|
||||||
setGasLimit,
|
setGasLimit,
|
||||||
|
setHasError,
|
||||||
setMaxPriorityFeePerGas,
|
setMaxPriorityFeePerGas,
|
||||||
setMaxFeePerGas,
|
setMaxFeePerGas,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</AdvanceGasFeePopoverContext.Provider>
|
</AdvancedGasFeePopoverContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useAdvanceGasFeePopoverContext() {
|
export function useAdvancedGasFeePopoverContext() {
|
||||||
return useContext(AdvanceGasFeePopoverContext);
|
return useContext(AdvancedGasFeePopoverContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
AdvanceGasFeePopoverContextProvider.propTypes = {
|
AdvancedGasFeePopoverContextProvider.propTypes = {
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
};
|
};
|
@ -1 +1 @@
|
|||||||
export * from './advanceGasFeePopover';
|
export * from './advancedGasFeePopover';
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
background-color: $primary-2;
|
background-color: $primary-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-selected {
|
&--selected {
|
||||||
background-color: $ui-1;
|
background-color: $ui-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,21 @@ import { useMaxPriorityFeePerGasInput } from './useMaxPriorityFeePerGasInput';
|
|||||||
import { useGasEstimates } from './useGasEstimates';
|
import { useGasEstimates } from './useGasEstimates';
|
||||||
import { useTransactionFunctions } from './useTransactionFunctions';
|
import { useTransactionFunctions } from './useTransactionFunctions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In EIP_1559_V2 implementation as used by useGasfeeInputContext() the use of this hook is evolved.
|
||||||
|
* It is no longer used to keep transient state of advance gas fee inputs.
|
||||||
|
* Transient state of inputs is maintained locally in /ui/components/app/advance-gas-fee-popover component.
|
||||||
|
*
|
||||||
|
* This hook is used now as source of shared data about transaction, it shares details of gas fee in transaction,
|
||||||
|
* estimate used, is EIP-1559 supported and other details. It also have methods to update transaction.
|
||||||
|
*
|
||||||
|
* Transaction is used as single source of truth and as transaction is updated the fields shared by hook are
|
||||||
|
* also updated using useEffect hook.
|
||||||
|
*
|
||||||
|
* It will be useful to plan a task to create a new hook of this shared information from this hook.
|
||||||
|
* Methods like setEstimateToUse, onManualChange are deprecated in context of EIP_1559_V2 implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} GasFeeInputReturnType
|
* @typedef {Object} GasFeeInputReturnType
|
||||||
* @property {DecGweiString} [maxFeePerGas] - the maxFeePerGas input value.
|
* @property {DecGweiString} [maxFeePerGas] - the maxFeePerGas input value.
|
||||||
@ -318,24 +333,10 @@ export function useGasFeeInputs(
|
|||||||
gasWarnings,
|
gasWarnings,
|
||||||
hasGasErrors,
|
hasGasErrors,
|
||||||
hasSimulationError,
|
hasSimulationError,
|
||||||
|
minimumGasLimitDec: hexToDecimal(minimumGasLimit),
|
||||||
supportsEIP1559,
|
supportsEIP1559,
|
||||||
supportsEIP1559V2,
|
supportsEIP1559V2,
|
||||||
updateTransaction,
|
updateTransaction,
|
||||||
updateTransactionUsingGasFeeEstimates,
|
updateTransactionUsingGasFeeEstimates,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* In EIP_1559_V2 implementation as used by useGasfeeInputContext() the use of this hook is evolved.
|
|
||||||
* It is no longer used to keep transient state of advance gas fee inputs.
|
|
||||||
* Transient state of inputs is maintained locally in /ui/components/app/advance-gas-fee-popover component.
|
|
||||||
*
|
|
||||||
* This hook is used now as source of shared data about transaction, it shares details of gas fee in transaction,
|
|
||||||
* estimate used, is EIP-1559 supported and other details. It also have methods to update transaction.
|
|
||||||
*
|
|
||||||
* Transaction is used as single source of truth and as transaction is updated the fields shared by hook are
|
|
||||||
* also updated using useEffect hook.
|
|
||||||
*
|
|
||||||
* It will be useful to plan a task to create a new hook of this shared information from this hook.
|
|
||||||
* Methods like setEstimateToUse, onManualChange are deprecated in context of EIP_1559_V2 implementation.
|
|
||||||
*/
|
|
||||||
|
@ -7,8 +7,10 @@ import { addHexPrefix } from '../../../app/scripts/lib/util';
|
|||||||
const MIN_GAS_PRICE_DEC = '0';
|
const MIN_GAS_PRICE_DEC = '0';
|
||||||
const MIN_GAS_PRICE_HEX = parseInt(MIN_GAS_PRICE_DEC, 10).toString(16);
|
const MIN_GAS_PRICE_HEX = parseInt(MIN_GAS_PRICE_DEC, 10).toString(16);
|
||||||
const MIN_GAS_LIMIT_DEC = '21000';
|
const MIN_GAS_LIMIT_DEC = '21000';
|
||||||
|
const MAX_GAS_LIMIT_DEC = '7920027';
|
||||||
const MIN_GAS_LIMIT_HEX = parseInt(MIN_GAS_LIMIT_DEC, 10).toString(16);
|
const MIN_GAS_LIMIT_HEX = parseInt(MIN_GAS_LIMIT_DEC, 10).toString(16);
|
||||||
|
|
||||||
|
const HIGH_FEE_WARNING_MULTIPLIER = 1.5;
|
||||||
const MIN_GAS_PRICE_GWEI = addHexPrefix(
|
const MIN_GAS_PRICE_GWEI = addHexPrefix(
|
||||||
conversionUtil(MIN_GAS_PRICE_HEX, {
|
conversionUtil(MIN_GAS_PRICE_HEX, {
|
||||||
fromDenomination: 'WEI',
|
fromDenomination: 'WEI',
|
||||||
@ -45,6 +47,8 @@ const ENS_UNKNOWN_ERROR = 'ensUnknownError';
|
|||||||
const ENS_REGISTRATION_ERROR = 'ensRegistrationError';
|
const ENS_REGISTRATION_ERROR = 'ensRegistrationError';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
MAX_GAS_LIMIT_DEC,
|
||||||
|
HIGH_FEE_WARNING_MULTIPLIER,
|
||||||
INSUFFICIENT_FUNDS_ERROR,
|
INSUFFICIENT_FUNDS_ERROR,
|
||||||
INSUFFICIENT_TOKENS_ERROR,
|
INSUFFICIENT_TOKENS_ERROR,
|
||||||
INVALID_RECIPIENT_ADDRESS_ERROR,
|
INVALID_RECIPIENT_ADDRESS_ERROR,
|
||||||
|
Loading…
Reference in New Issue
Block a user