Alert users when the network is busy (#12268)

When a lot of transactions are occurring on the network, such as during
an NFT drop, it drives gas fees up. When this happens, we want to not
only inform the user about this, but also dissuade them from using a
higher gas fee (as we have proved in testing that high gas fees can
cause bidding wars and exacerbate the situation).

The method for determining whether the network is "busy" is already
handled by GasFeeController, which exposes a `networkCongestion`
property within the gas fee estimate data. If this number exceeds 0.66 —
meaning that the current base fee is above the 66th percentile among the
base fees over the last several days — then we determine that the
network is "busy".
feature/default_network_editable
Elliot Winkler 3 years ago committed by GitHub
parent 0bada3abf1
commit 7b963cabd7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      app/_locales/en/messages.json
  2. 12
      shared/constants/gas.js
  3. 2
      ui/components/app/advanced-gas-controls/advanced-gas-controls.component.js
  4. 24
      ui/components/app/edit-gas-display/edit-gas-display.component.js
  5. 38
      ui/components/app/edit-gas-display/edit-gas-display.test.js
  6. 17
      ui/components/app/edit-gas-fee-popover/network-statistics/status-slider/status-slider.js
  7. 26
      ui/components/app/edit-gas-fee-popover/network-statistics/status-slider/status-slider.test.js
  8. 5
      ui/components/app/edit-gas-popover/edit-gas-popover.component.js
  9. 13
      ui/ducks/metamask/metamask.js
  10. 27
      ui/ducks/metamask/metamask.test.js
  11. 2
      ui/hooks/gasFeeInput/useGasFeeInputs.js
  12. 3
      ui/hooks/useGasFeeEstimates.js
  13. 8
      ui/pages/confirm-transaction-base/transaction-alerts/transaction-alerts.js
  14. 429
      ui/pages/confirm-transaction-base/transaction-alerts/transaction-alerts.test.js
  15. 7
      ui/selectors/transactions.js

@ -1785,6 +1785,9 @@
"networkDetails": {
"message": "Network Details"
},
"networkIsBusy": {
"message": "Network is busy. Gas prices are high and estimates are less accurate."
},
"networkName": {
"message": "Network Name"
},

@ -56,3 +56,15 @@ export const EDIT_GAS_MODES = {
MODIFY_IN_PLACE: 'modify-in-place',
SWAPS: 'swaps',
};
/**
* Represents levels for `networkCongestion` (calculated along with gas fee
* estimates; represents a number between 0 and 1) that we use to render the
* network status slider on the send transaction screen and inform users when
* gas fees are high
*/
export const NETWORK_CONGESTION_THRESHOLDS = {
NOT_BUSY: 0,
STABLE: 0.33,
BUSY: 0.66,
};

@ -7,7 +7,7 @@ import FormField from '../../ui/form-field';
import { GAS_ESTIMATE_TYPES } from '../../../../shared/constants/gas';
import { getGasFormErrorText } from '../../../helpers/constants/gas';
import { getIsGasEstimatesLoading } from '../../../ducks/metamask/metamask';
import { getNetworkSupportsSettingGasPrice } from '../../../selectors/selectors';
import { getNetworkSupportsSettingGasPrice } from '../../../selectors';
export default function AdvancedGasControls({
gasEstimateType,

@ -63,7 +63,6 @@ export default function EditGasDisplay({
estimatedMaximumFiat,
dappSuggestedGasFeeAcknowledged,
setDappSuggestedGasFeeAcknowledged,
warning,
gasErrors,
gasWarnings,
onManualChange,
@ -72,6 +71,7 @@ export default function EditGasDisplay({
estimatesUnavailableWarning,
hasGasErrors,
txParamsHaveBeenCustomized,
isNetworkBusy,
}) {
const t = useContext(I18nContext);
const scrollRef = useRef(null);
@ -93,7 +93,7 @@ export default function EditGasDisplay({
useLayoutEffect(() => {
if (showAdvancedForm && scrollRef.current) {
scrollRef.current.scrollIntoView();
scrollRef.current.scrollIntoView?.();
}
}, [showAdvancedForm]);
@ -133,14 +133,6 @@ export default function EditGasDisplay({
return (
<div className="edit-gas-display">
<div className="edit-gas-display__content">
{warning && !isGasEstimatesLoading && (
<div className="edit-gas-display__warning">
<ActionableMessage
className="actionable-message--warning"
message={warning}
/>
</div>
)}
{showTopError && (
<div className="edit-gas-display__warning">
<ErrorMessage errorKey={errorKey} />
@ -156,6 +148,16 @@ export default function EditGasDisplay({
/>
</div>
)}
{isNetworkBusy ? (
<div className="edit-gas-display__warning">
<ActionableMessage
className="actionable-message--warning"
message={t('networkIsBusy')}
iconFillColor="#f8c000"
useIcon
/>
</div>
) : null}
{mode === EDIT_GAS_MODES.SPEED_UP && (
<div className="edit-gas-display__top-tooltip">
<Typography
@ -336,7 +338,6 @@ EditGasDisplay.propTypes = {
estimatedMaximumFiat: PropTypes.string,
dappSuggestedGasFeeAcknowledged: PropTypes.bool,
setDappSuggestedGasFeeAcknowledged: PropTypes.func,
warning: PropTypes.string,
transaction: PropTypes.object,
gasErrors: PropTypes.object,
gasWarnings: PropTypes.object,
@ -346,4 +347,5 @@ EditGasDisplay.propTypes = {
estimatesUnavailableWarning: PropTypes.bool,
hasGasErrors: PropTypes.bool,
txParamsHaveBeenCustomized: PropTypes.bool,
isNetworkBusy: PropTypes.bool,
};

@ -0,0 +1,38 @@
import React from 'react';
import { screen } from '@testing-library/react';
import { renderWithProvider } from '../../../../test/jest';
import configureStore from '../../../store/store';
import EditGasDisplay from '.';
jest.mock('../../../selectors');
jest.mock('../../../helpers/utils/confirm-tx.util');
jest.mock('../../../helpers/utils/transactions.util');
function render({ componentProps = {} } = {}) {
const store = configureStore({});
return renderWithProvider(<EditGasDisplay {...componentProps} />, store);
}
describe('EditGasDisplay', () => {
describe('if getIsNetworkBusy returns a truthy value', () => {
it('informs the user', () => {
render({ componentProps: { isNetworkBusy: true } });
expect(
screen.getByText(
'Network is busy. Gas prices are high and estimates are less accurate.',
),
).toBeInTheDocument();
});
});
describe('if getIsNetworkBusy does not return a truthy value', () => {
it('does not inform the user', () => {
render({ componentProps: { isNetworkBusy: false } });
expect(
screen.queryByText(
'Network is busy. Gas prices are high and estimates are less accurate.',
),
).not.toBeInTheDocument();
});
});
});

@ -1,5 +1,6 @@
import React from 'react';
import { NETWORK_CONGESTION_THRESHOLDS } from '../../../../../../shared/constants/gas';
import { useGasFeeContext } from '../../../../../contexts/gasFee';
import I18nValue from '../../../../ui/i18n-value';
import { NetworkStabilityTooltip } from '../tooltips';
@ -24,24 +25,24 @@ const determineStatusInfo = (givenNetworkCongestion) => {
const color = GRADIENT_COLORS[colorIndex];
const sliderTickValue = colorIndex * 10;
if (networkCongestion <= 0.33) {
if (networkCongestion >= NETWORK_CONGESTION_THRESHOLDS.BUSY) {
return {
statusLabel: 'notBusy',
tooltipLabel: 'lowLowercase',
statusLabel: 'busy',
tooltipLabel: 'highLowercase',
color,
sliderTickValue,
};
} else if (networkCongestion > 0.66) {
} else if (networkCongestion >= NETWORK_CONGESTION_THRESHOLDS.STABLE) {
return {
statusLabel: 'busy',
tooltipLabel: 'highLowercase',
statusLabel: 'stable',
tooltipLabel: 'stableLowercase',
color,
sliderTickValue,
};
}
return {
statusLabel: 'stable',
tooltipLabel: 'stableLowercase',
statusLabel: 'notBusy',
tooltipLabel: 'lowLowercase',
color,
sliderTickValue,
};

@ -20,31 +20,31 @@ const renderComponent = ({ networkCongestion }) => {
describe('StatusSlider', () => {
it('should show "Not busy" when networkCongestion is less than 0.33', () => {
const { queryByText } = renderComponent({ networkCongestion: 0.32 });
expect(queryByText('Not busy')).toBeInTheDocument();
const { getByText } = renderComponent({ networkCongestion: 0.32 });
expect(getByText('Not busy')).toBeInTheDocument();
});
it('should show "Not busy" when networkCongestion is 0.33', () => {
const { queryByText } = renderComponent({ networkCongestion: 0.33 });
expect(queryByText('Not busy')).toBeInTheDocument();
it('should show "Stable" when networkCongestion is 0.33', () => {
const { getByText } = renderComponent({ networkCongestion: 0.33 });
expect(getByText('Stable')).toBeInTheDocument();
});
it('should show "Stable" when networkCongestion is between 0.33 and 0.66', () => {
const { queryByText } = renderComponent({ networkCongestion: 0.5 });
expect(queryByText('Stable')).toBeInTheDocument();
const { getByText } = renderComponent({ networkCongestion: 0.5 });
expect(getByText('Stable')).toBeInTheDocument();
});
it('should show "Stable" when networkCongestion is 0.66', () => {
const { queryByText } = renderComponent({ networkCongestion: 0.66 });
expect(queryByText('Stable')).toBeInTheDocument();
it('should show "Busy" when networkCongestion is 0.66', () => {
const { getByText } = renderComponent({ networkCongestion: 0.66 });
expect(getByText('Busy')).toBeInTheDocument();
});
it('should show "Busy" when networkCongestion is greater than 0.66', () => {
const { queryByText } = renderComponent({ networkCongestion: 0.67 });
expect(queryByText('Busy')).toBeInTheDocument();
const { getByText } = renderComponent({ networkCongestion: 0.67 });
expect(getByText('Busy')).toBeInTheDocument();
});
it('should show "Stable" if networkCongestion has not been set yet', () => {
it('should show "Stable" if networkCongestion is not available yet', () => {
const { getByText } = renderComponent({});
expect(getByText('Stable')).toBeInTheDocument();
});

@ -61,8 +61,6 @@ export default function EditGasPopover({
supportsEIP1559;
const [showEducationContent, setShowEducationContent] = useState(false);
const [warning] = useState(null);
const [
dappSuggestedGasFeeAcknowledged,
setDappSuggestedGasFeeAcknowledged,
@ -109,6 +107,7 @@ export default function EditGasPopover({
balanceError,
estimatesUnavailableWarning,
estimatedBaseFee,
isNetworkBusy,
} = useGasFeeInputs(
defaultEstimateToUse,
updatedTransaction,
@ -264,7 +263,6 @@ export default function EditGasPopover({
{process.env.IN_TEST ? null : <LoadingHeartBeat />}
<EditGasDisplay
showEducationButton={showEducationButton}
warning={warning}
dappSuggestedGasFeeAcknowledged={dappSuggestedGasFeeAcknowledged}
setDappSuggestedGasFeeAcknowledged={
setDappSuggestedGasFeeAcknowledged
@ -298,6 +296,7 @@ export default function EditGasPopover({
estimatesUnavailableWarning={estimatesUnavailableWarning}
hasGasErrors={hasGasErrors}
txParamsHaveBeenCustomized={txParamsHaveBeenCustomized}
isNetworkBusy={isNetworkBusy}
{...editGasDisplayProps}
/>
</>

@ -1,6 +1,10 @@
import { addHexPrefix, isHexString, stripHexPrefix } from 'ethereumjs-util';
import * as actionConstants from '../../store/actionConstants';
import { ALERT_TYPES } from '../../../shared/constants/alerts';
import {
GAS_ESTIMATE_TYPES,
NETWORK_CONGESTION_THRESHOLDS,
} from '../../../shared/constants/gas';
import { NETWORK_TYPE_RPC } from '../../../shared/constants/network';
import {
accountsWithSendEtherInfoSelector,
@ -11,7 +15,7 @@ import { updateTransaction } from '../../store/actions';
import { setCustomGasLimit, setCustomGasPrice } from '../gas/gas.duck';
import { decGWEIToHexWEI } from '../../helpers/utils/conversions.util';
import { isEqualCaseInsensitive } from '../../helpers/utils/util';
import { GAS_ESTIMATE_TYPES } from '../../../shared/constants/gas';
import { KEYRING_TYPES } from '../../../shared/constants/hardware-wallets';
export default function reduceMetamask(state = {}, action) {
@ -361,6 +365,13 @@ export function getIsGasEstimatesLoading(state) {
return isGasEstimatesLoading;
}
export function getIsNetworkBusy(state) {
const gasFeeEstimates = getGasFeeEstimates(state);
return (
gasFeeEstimates?.networkCongestion >= NETWORK_CONGESTION_THRESHOLDS.BUSY
);
}
export function getCompletedOnboarding(state) {
return state.metamask.completedOnboarding;
}

@ -3,6 +3,7 @@ import * as actionConstants from '../../store/actionConstants';
import reduceMetamask, {
getBlockGasLimit,
getConversionRate,
getIsNetworkBusy,
getNativeCurrency,
getSendHexDataFeatureFlagState,
getSendToAccounts,
@ -414,4 +415,30 @@ describe('MetaMask Reducers', () => {
).toStrictEqual(false);
});
});
describe('getIsNetworkBusy', () => {
it('should return true if state.metamask.gasFeeEstimates.networkCongestion is over the "busy" threshold', () => {
expect(
getIsNetworkBusy({
metamask: { gasFeeEstimates: { networkCongestion: 0.67 } },
}),
).toBe(true);
});
it('should return true if state.metamask.gasFeeEstimates.networkCongestion is right at the "busy" threshold', () => {
expect(
getIsNetworkBusy({
metamask: { gasFeeEstimates: { networkCongestion: 0.66 } },
}),
).toBe(true);
});
it('should return false if state.metamask.gasFeeEstimates.networkCongestion is not over the "busy" threshold', () => {
expect(
getIsNetworkBusy({
metamask: { gasFeeEstimates: { networkCongestion: 0.65 } },
}),
).toBe(false);
});
});
});

@ -111,6 +111,7 @@ export function useGasFeeInputs(
gasFeeEstimates,
isGasEstimatesLoading,
estimatedGasFeeTimeBounds,
isNetworkBusy,
} = useGasFeeEstimates();
const userPrefersAdvancedGas = useSelector(getAdvancedInlineGasShown);
@ -342,6 +343,7 @@ export function useGasFeeInputs(
gasFeeEstimates,
gasEstimateType,
estimatedGasFeeTimeBounds,
isNetworkBusy,
onManualChange,
estimatedBaseFee,
// error and warnings

@ -5,6 +5,7 @@ import {
getGasEstimateType,
getGasFeeEstimates,
getIsGasEstimatesLoading,
getIsNetworkBusy,
} from '../ducks/metamask/metamask';
import { useSafeGasEstimatePolling } from './useSafeGasEstimatePolling';
@ -38,6 +39,7 @@ export function useGasFeeEstimates() {
shallowEqual,
);
const isGasEstimatesLoading = useSelector(getIsGasEstimatesLoading);
const isNetworkBusy = useSelector(getIsNetworkBusy);
useSafeGasEstimatePolling();
return {
@ -45,5 +47,6 @@ export function useGasFeeEstimates() {
gasEstimateType,
estimatedGasFeeTimeBounds,
isGasEstimatesLoading,
isNetworkBusy,
};
}

@ -22,6 +22,7 @@ const TransactionAlerts = ({
estimateUsed,
hasSimulationError,
supportsEIP1559V2,
isNetworkBusy,
} = useGasFeeContext();
const pendingTransactions = useSelector(submittedPendingTransactionsSelector);
const t = useI18nContext();
@ -107,6 +108,13 @@ const TransactionAlerts = ({
type="warning"
/>
)}
{isNetworkBusy ? (
<ActionableMessage
message={<I18nValue messageKey="networkIsBusy" />}
iconFillColor="#f8c000"
type="warning"
/>
) : null}
</div>
);
};

@ -1,172 +1,307 @@
import React from 'react';
import { fireEvent, screen } from '@testing-library/react';
import { GAS_ESTIMATE_TYPES } from '../../../../shared/constants/gas';
import {
TRANSACTION_ENVELOPE_TYPES,
TRANSACTION_STATUSES,
} from '../../../../shared/constants/transaction';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import mockEstimates from '../../../../test/data/mock-estimates.json';
import mockState from '../../../../test/data/mock-state.json';
import { GasFeeContextProvider } from '../../../contexts/gasFee';
import { fireEvent } from '@testing-library/react';
import { renderWithProvider } from '../../../../test/jest';
import { submittedPendingTransactionsSelector } from '../../../selectors/transactions';
import { useGasFeeContext } from '../../../contexts/gasFee';
import configureStore from '../../../store/store';
import TransactionAlerts from './transaction-alerts';
jest.mock('../../../store/actions', () => ({
disconnectGasFeeEstimatePoller: jest.fn(),
getGasFeeEstimatesAndStartPolling: jest
.fn()
.mockImplementation(() => Promise.resolve()),
addPollingTokenToAppState: jest.fn(),
}));
const render = ({ componentProps, transactionProps, state }) => {
const store = configureStore({
metamask: {
...mockState.metamask,
accounts: {
[mockState.metamask.selectedAddress]: {
address: mockState.metamask.selectedAddress,
balance: '0x1F4',
},
},
gasFeeEstimates: mockEstimates[GAS_ESTIMATE_TYPES.FEE_MARKET],
...state,
},
});
jest.mock('../../../selectors/transactions', () => {
return {
...jest.requireActual('../../../selectors/transactions'),
submittedPendingTransactionsSelector: jest.fn(),
};
});
return renderWithProvider(
<GasFeeContextProvider
transaction={{
txParams: {
type: TRANSACTION_ENVELOPE_TYPES.FEE_MARKET,
},
...transactionProps,
}}
>
<TransactionAlerts {...componentProps} />
</GasFeeContextProvider>,
store,
jest.mock('../../../contexts/gasFee');
function render({
componentProps = {},
useGasFeeContextValue = {},
submittedPendingTransactionsSelectorValue = null,
}) {
useGasFeeContext.mockReturnValue(useGasFeeContextValue);
submittedPendingTransactionsSelector.mockReturnValue(
submittedPendingTransactionsSelectorValue,
);
};
const store = configureStore({});
return renderWithProvider(<TransactionAlerts {...componentProps} />, store);
}
describe('TransactionAlerts', () => {
beforeEach(() => {
process.env.EIP_1559_V2 = true;
});
describe('when supportsEIP1559V2 from useGasFeeContext is truthy', () => {
describe('if hasSimulationError from useGasFeeContext is true', () => {
it('informs the user that a simulation of the transaction failed', () => {
const { getByText } = render({
useGasFeeContextValue: {
supportsEIP1559V2: true,
hasSimulationError: true,
},
});
afterEach(() => {
process.env.EIP_1559_V2 = false;
});
expect(
getByText(
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.',
),
).toBeInTheDocument();
});
it('should returning warning message for low gas estimate', () => {
render({ transactionProps: { userFeeLevel: 'low' } });
expect(
document.getElementsByClassName('actionable-message--warning'),
).toHaveLength(1);
});
describe('if the user has not acknowledged the failure', () => {
it('offers the user an option to bypass the warning', () => {
const { getByText } = render({
useGasFeeContextValue: {
supportsEIP1559V2: true,
hasSimulationError: true,
},
});
expect(getByText('I want to proceed anyway')).toBeInTheDocument();
});
it('should return null for gas estimate other than low', () => {
render({ transactionProps: { userFeeLevel: 'high' } });
expect(
document.getElementsByClassName('actionable-message--warning'),
).toHaveLength(0);
});
it('calls setUserAcknowledgedGasMissing if the user bypasses the warning', () => {
const setUserAcknowledgedGasMissing = jest.fn();
const { getByText } = render({
useGasFeeContextValue: {
supportsEIP1559V2: true,
hasSimulationError: true,
},
componentProps: { setUserAcknowledgedGasMissing },
});
fireEvent.click(getByText('I want to proceed anyway'));
expect(setUserAcknowledgedGasMissing).toHaveBeenCalled();
});
});
it('should not show insufficient balance message if transaction value is less than balance', () => {
render({
transactionProps: {
userFeeLevel: 'high',
txParams: { value: '0x64' },
},
describe('if the user has already acknowledged the failure', () => {
it('does not offer the user an option to bypass the warning', () => {
const { queryByText } = render({
useGasFeeContextValue: {
supportsEIP1559V2: true,
hasSimulationError: true,
},
componentProps: { userAcknowledgedGasMissing: true },
});
expect(
queryByText('I want to proceed anyway'),
).not.toBeInTheDocument();
});
});
});
expect(screen.queryByText('Insufficient funds.')).not.toBeInTheDocument();
});
it('should show insufficient balance message if transaction value is more than balance', () => {
render({
transactionProps: {
userFeeLevel: 'high',
txParams: { value: '0x5208' },
},
describe('if hasSimulationError from useGasFeeContext is falsy', () => {
it('does not inform the user that a simulation of the transaction failed', () => {
const { queryByText } = render({
useGasFeeContextValue: {
supportsEIP1559V2: true,
},
});
expect(
queryByText(
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.',
),
).not.toBeInTheDocument();
});
});
expect(screen.queryByText('Insufficient funds.')).toBeInTheDocument();
});
it('should show pending transaction message if there are >= 1 pending transactions', () => {
render({
state: {
currentNetworkTxList: [
{
id: 0,
time: 0,
txParams: {
from: mockState.metamask.selectedAddress,
to: '0xRecipient',
},
status: TRANSACTION_STATUSES.SUBMITTED,
describe('if the length of pendingTransactions is 1', () => {
it('informs the user that they have a pending transaction', () => {
const { getByText } = render({
useGasFeeContextValue: { supportsEIP1559V2: true },
submittedPendingTransactionsSelectorValue: [{ some: 'transaction' }],
});
expect(
getByText('You have (1) pending transaction.'),
).toBeInTheDocument();
});
});
describe('if the length of pendingTransactions is more than 1', () => {
it('informs the user that they have pending transactions', () => {
const { getByText } = render({
useGasFeeContextValue: { supportsEIP1559V2: true },
submittedPendingTransactionsSelectorValue: [
{ some: 'transaction' },
{ some: 'transaction' },
],
});
expect(
getByText('You have (2) pending transactions.'),
).toBeInTheDocument();
});
});
describe('if the length of pendingTransactions is 0', () => {
it('does not inform the user that they have pending transactions', () => {
const { queryByText } = render({
useGasFeeContextValue: { supportsEIP1559V2: true },
submittedPendingTransactionsSelectorValue: [],
});
expect(
queryByText('You have (0) pending transactions.'),
).not.toBeInTheDocument();
});
});
describe('if balanceError from useGasFeeContext is true', () => {
it('informs the user that they have insufficient funds', () => {
const { getByText } = render({
useGasFeeContextValue: {
supportsEIP1559V2: true,
balanceError: true,
},
});
expect(getByText('Insufficient funds.')).toBeInTheDocument();
});
});
describe('if balanceError from useGasFeeContext is falsy', () => {
it('does not inform the user that they have insufficient funds', () => {
const { queryByText } = render({
useGasFeeContextValue: {
supportsEIP1559V2: true,
balanceError: false,
},
],
},
});
expect(queryByText('Insufficient funds.')).not.toBeInTheDocument();
});
});
describe('if estimateUsed from useGasFeeContext is "low"', () => {
it('informs the user that the current transaction is queued', () => {
const { getByText } = render({
useGasFeeContextValue: {
supportsEIP1559V2: true,
estimateUsed: 'low',
},
});
expect(
getByText(
'Future transactions will queue after this one. This price was last seen was some time ago.',
),
).toBeInTheDocument();
});
});
describe('if estimateUsed from useGasFeeContext is not "low"', () => {
it('does not inform the user that the current transaction is queued', () => {
const { queryByText } = render({
useGasFeeContextValue: {
supportsEIP1559V2: true,
estimateUsed: 'something_else',
},
});
expect(
queryByText(
'Future transactions will queue after this one. This price was last seen was some time ago.',
),
).not.toBeInTheDocument();
});
});
describe('if isNetworkBusy from useGasFeeContext is truthy', () => {
it('informs the user that the network is busy', () => {
const { getByText } = render({
useGasFeeContextValue: {
supportsEIP1559V2: true,
isNetworkBusy: true,
},
});
expect(
getByText(
'Network is busy. Gas prices are high and estimates are less accurate.',
),
).toBeInTheDocument();
});
});
describe('if isNetworkBusy from useGasFeeContext is falsy', () => {
it('does not inform the user that the network is busy', () => {
const { queryByText } = render({
useGasFeeContextValue: {
supportsEIP1559V2: true,
isNetworkBusy: false,
},
});
expect(
queryByText(
'Network is busy. Gas prices are high and estimates are less accurate.',
),
).not.toBeInTheDocument();
});
});
expect(
screen.queryByText('You have (1) pending transaction.'),
).toBeInTheDocument();
});
describe('SimulationError Message', () => {
it('should show simulation error message along with option to proceed anyway if transaction.simulationFails is true', () => {
render({ transactionProps: { simulationFails: true } });
expect(
screen.queryByText(
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.',
),
).toBeInTheDocument();
expect(
screen.queryByText('I want to proceed anyway'),
).toBeInTheDocument();
});
it('should not show options to acknowledge gas-missing warning if component prop userAcknowledgedGasMissing is already true', () => {
render({
componentProps: {
userAcknowledgedGasMissing: true,
},
transactionProps: { simulationFails: true },
});
expect(
screen.queryByText(
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.',
),
).toBeInTheDocument();
expect(
screen.queryByText('I want to proceed anyway'),
).not.toBeInTheDocument();
});
it('should call prop setUserAcknowledgedGasMissing if option to acknowledge gas-missing warning is clicked', () => {
const setUserAcknowledgedGasMissing = jest.fn();
render({
componentProps: {
setUserAcknowledgedGasMissing,
},
transactionProps: { simulationFails: true },
});
fireEvent.click(screen.queryByText('I want to proceed anyway'));
expect(setUserAcknowledgedGasMissing).toHaveBeenCalledTimes(1);
});
it('should return null for legacy transactions', () => {
const { container } = render({
transactionProps: {
txParams: {
type: TRANSACTION_ENVELOPE_TYPES.LEGACY,
describe('when supportsEIP1559V2 from useGasFeeContext is falsy', () => {
describe('if hasSimulationError from useGasFeeContext is true', () => {
it('does not inform the user that a simulation of the transaction failed', () => {
const { queryByText } = render({
useGasFeeContextValue: {
supportsEIP1559V2: false,
hasSimulationError: true,
},
});
expect(
queryByText(
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.',
),
).not.toBeInTheDocument();
});
});
describe('if the length of pendingTransactions is at least 1', () => {
it('informs the user that they have a pending transaction', () => {
const { queryByText } = render({
useGasFeeContextValue: { supportsEIP1559V2: false },
submittedPendingTransactionsSelectorValue: [{ some: 'transaction' }],
});
expect(
queryByText('You have (1) pending transaction.'),
).not.toBeInTheDocument();
});
});
describe('if balanceError from useGasFeeContext is true', () => {
it('informs the user that they have insufficient funds', () => {
const { queryByText } = render({
useGasFeeContextValue: {
supportsEIP1559V2: false,
balanceError: true,
},
});
expect(queryByText('Insufficient funds.')).not.toBeInTheDocument();
});
});
describe('if estimateUsed from useGasFeeContext is "low"', () => {
it('informs the user that the current transaction is queued', () => {
const { queryByText } = render({
useGasFeeContextValue: {
supportsEIP1559V2: false,
estimateUsed: 'low',
},
});
expect(
queryByText(
'Future transactions will queue after this one. This price was last seen was some time ago.',
),
).not.toBeInTheDocument();
});
});
describe('if isNetworkBusy from useGasFeeContext is truthy', () => {
it('does not inform the user that the network is busy', () => {
const { queryByText } = render({
useGasFeeContextValue: {
supportsEIP1559V2: false,
isNetworkBusy: true,
},
},
});
expect(
queryByText(
'Network is busy. Gas prices are high and estimates are less accurate.',
),
).not.toBeInTheDocument();
});
expect(container.firstChild).toBeNull();
});
});
});

@ -10,8 +10,11 @@ import {
TRANSACTION_TYPES,
} from '../../shared/constants/transaction';
import { transactionMatchesNetwork } from '../../shared/modules/transaction.utils';
import { getCurrentChainId, deprecatedGetCurrentNetworkId } from './selectors';
import { getSelectedAddress } from '.';
import {
getCurrentChainId,
deprecatedGetCurrentNetworkId,
getSelectedAddress,
} from './selectors';
export const incomingTxListSelector = (state) => {
const { showIncomingTransactions } = state.metamask.featureFlags;

Loading…
Cancel
Save