mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +01:00
using 1559 V2 for cancel speed up flows (#13019)
This commit is contained in:
parent
dbfdf3b0eb
commit
f5dcd12293
@ -422,6 +422,14 @@
|
||||
"cancelSpeedUp": {
|
||||
"message": "cancel or speed up a tranaction."
|
||||
},
|
||||
"cancelSpeedUpLabel": {
|
||||
"message": "This gas fee will $1 the original.",
|
||||
"description": "$1 is text 'replace' in bold"
|
||||
},
|
||||
"cancelSpeedUpTransactionTooltip": {
|
||||
"message": "To $1 a transaction the gas fee must be increased by at least 10% for it to be recognized by the network.",
|
||||
"description": "$1 is string 'cancel' or 'speed up'"
|
||||
},
|
||||
"cancellationGasFee": {
|
||||
"message": "Cancellation Gas Fee"
|
||||
},
|
||||
@ -739,6 +747,10 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "If you already have some Ether, the quickest way to get Ether in your new wallet by direct deposit."
|
||||
},
|
||||
"disabledGasOptionToolTipMessage": {
|
||||
"message": "“$1” is disabled because it does not meet the minimum of a 10% increase from the original gas fee.",
|
||||
"description": "$1 is gas estimate type which can be market or aggressive"
|
||||
},
|
||||
"disconnect": {
|
||||
"message": "Disconnect"
|
||||
},
|
||||
@ -793,6 +805,9 @@
|
||||
"editAddressNickname": {
|
||||
"message": "Edit address nickname"
|
||||
},
|
||||
"editCancellationGasFeeModalTitle": {
|
||||
"message": "Edit cancellation gas fee"
|
||||
},
|
||||
"editContact": {
|
||||
"message": "Edit Contact"
|
||||
},
|
||||
@ -921,6 +936,9 @@
|
||||
"editPermission": {
|
||||
"message": "Edit Permission"
|
||||
},
|
||||
"editSpeedUpEditGasFeeModalTitle": {
|
||||
"message": "Edit speed up gas fee"
|
||||
},
|
||||
"enableAutoDetect": {
|
||||
"message": " Enable Autodetect"
|
||||
},
|
||||
@ -1157,6 +1175,9 @@
|
||||
"functionType": {
|
||||
"message": "Function Type"
|
||||
},
|
||||
"gas": {
|
||||
"message": "Gas"
|
||||
},
|
||||
"gasDisplayAcknowledgeDappButtonText": {
|
||||
"message": "Edit suggested gas fee"
|
||||
},
|
||||
@ -1709,6 +1730,15 @@
|
||||
"metametricsTitle": {
|
||||
"message": "Join 6M+ users to improve MetaMask"
|
||||
},
|
||||
"minimum": {
|
||||
"message": "minimum"
|
||||
},
|
||||
"minimumCancelSpeedupGasFee": {
|
||||
"message": "+10%"
|
||||
},
|
||||
"minimumEstimate": {
|
||||
"message": "10% Minimum"
|
||||
},
|
||||
"mismatchedChain": {
|
||||
"message": "The network details for this chain ID do not match our records. We recommend that you $1 before proceeding.",
|
||||
"description": "$1 is a clickable link with text defined by the 'mismatchedChainLinkText' key"
|
||||
@ -2289,6 +2319,9 @@
|
||||
"removeNFT": {
|
||||
"message": "Remove NFT"
|
||||
},
|
||||
"replace": {
|
||||
"message": "replace"
|
||||
},
|
||||
"requestsAwaitingAcknowledgement": {
|
||||
"message": "requests waiting to be acknowledged"
|
||||
},
|
||||
@ -3169,9 +3202,6 @@
|
||||
"transactionDetailGasHeading": {
|
||||
"message": "Estimated gas fee"
|
||||
},
|
||||
"transactionDetailGasHeadingV2": {
|
||||
"message": "Gas"
|
||||
},
|
||||
"transactionDetailGasInfoV2": {
|
||||
"message": "estimated"
|
||||
},
|
||||
|
@ -34,6 +34,7 @@ export const GAS_RECOMMENDATIONS = {
|
||||
* These represent types of gas estimation
|
||||
*/
|
||||
export const PRIORITY_LEVELS = {
|
||||
MINIMUM: 'minimum',
|
||||
LOW: 'low',
|
||||
MEDIUM: 'medium',
|
||||
HIGH: 'high',
|
||||
|
@ -120,7 +120,7 @@ const converter = ({
|
||||
convertedValue = toSpecifiedDenomination[toDenomination](convertedValue);
|
||||
}
|
||||
|
||||
if (numberOfDecimals) {
|
||||
if (numberOfDecimals !== undefined && numberOfDecimals !== null) {
|
||||
convertedValue = convertedValue.round(
|
||||
numberOfDecimals,
|
||||
BigNumber.ROUND_HALF_DOWN,
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"appState": {
|
||||
"isLoading": false,
|
||||
"gasIsLoading": false,
|
||||
"currentView": {
|
||||
"name": "accountDetail",
|
||||
|
@ -14,8 +14,8 @@ import AdvancedGasFeeDefaults from './advanced-gas-fee-defaults';
|
||||
const AdvancedGasFeePopover = () => {
|
||||
const t = useI18nContext();
|
||||
const {
|
||||
closeModal,
|
||||
closeAllModals,
|
||||
closeModal,
|
||||
currentModal,
|
||||
} = useTransactionModalContext();
|
||||
|
||||
|
@ -1,11 +1,13 @@
|
||||
/** Please import your files in alphabetical order **/
|
||||
@import 'account-list-item/index';
|
||||
@import 'account-menu/index';
|
||||
@import 'app-loading-spinner/index';
|
||||
@import 'import-token-link/index';
|
||||
@import 'advanced-gas-controls/index';
|
||||
@import 'alerts/alerts';
|
||||
@import 'app-header/index';
|
||||
@import 'asset-list-item/asset-list-item';
|
||||
@import 'cancel-speedup-popover/index';
|
||||
@import 'confirm-page-container/index';
|
||||
@import 'collectibles-items/index';
|
||||
@import 'collectibles-tab/index';
|
||||
@ -28,6 +30,8 @@
|
||||
@import 'gas-customization/gas-modal-page-container/index';
|
||||
@import 'gas-customization/gas-price-button-group/index';
|
||||
@import 'gas-customization/index';
|
||||
@import 'gas-details-item/index';
|
||||
@import 'gas-details-item/gas-details-item-title/index';
|
||||
@import 'gas-timing/index';
|
||||
@import 'home-notification/index';
|
||||
@import 'info-box/index';
|
||||
|
30
ui/components/app/app-loading-spinner/app-loading-spinner.js
Normal file
30
ui/components/app/app-loading-spinner/app-loading-spinner.js
Normal file
@ -0,0 +1,30 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import { getAppIsLoading } from '../../../selectors';
|
||||
import Spinner from '../../ui/spinner';
|
||||
|
||||
const AppLoadingSpinner = ({ className }) => {
|
||||
const appIsLoading = useSelector(getAppIsLoading);
|
||||
|
||||
if (!appIsLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${className} app-loading-spinner`}
|
||||
role="alert"
|
||||
aria-busy="true"
|
||||
>
|
||||
<Spinner color="#F7C06C" className="app-loading-spinner__inner" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
AppLoadingSpinner.propTypes = {
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
export default AppLoadingSpinner;
|
@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { screen } from '@testing-library/react';
|
||||
|
||||
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||
import configureStore from '../../../store/store';
|
||||
|
||||
import AppLoadingSpinner from './app-loading-spinner';
|
||||
|
||||
const render = (params) => {
|
||||
const store = configureStore({
|
||||
...params,
|
||||
});
|
||||
|
||||
return renderWithProvider(<AppLoadingSpinner />, store);
|
||||
};
|
||||
|
||||
describe('AppLoadingSpinner', () => {
|
||||
it('should return null if app state is not loading', () => {
|
||||
render();
|
||||
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show spinner if app state is loading', () => {
|
||||
render({ appState: { isLoading: true } });
|
||||
expect(screen.queryByRole('alert')).toBeInTheDocument();
|
||||
});
|
||||
});
|
1
ui/components/app/app-loading-spinner/index.js
Normal file
1
ui/components/app/app-loading-spinner/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './app-loading-spinner';
|
13
ui/components/app/app-loading-spinner/index.scss
Normal file
13
ui/components/app/app-loading-spinner/index.scss
Normal file
@ -0,0 +1,13 @@
|
||||
.app-loading-spinner {
|
||||
background-color: rgba(255, 255, 255, 0.75);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
&__inner {
|
||||
width: 50px;
|
||||
}
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import {
|
||||
EDIT_GAS_MODES,
|
||||
PRIORITY_LEVELS,
|
||||
} from '../../../../shared/constants/gas';
|
||||
import {
|
||||
ALIGN_ITEMS,
|
||||
DISPLAY,
|
||||
FLEX_DIRECTION,
|
||||
TYPOGRAPHY,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import { getAppIsLoading } from '../../../selectors';
|
||||
import { gasEstimateGreaterThanGasUsedPlusTenPercent } from '../../../helpers/utils/gas';
|
||||
import { useGasFeeContext } from '../../../contexts/gasFee';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import { useTransactionModalContext } from '../../../contexts/transaction-modal';
|
||||
import EditGasFeeButton from '../edit-gas-fee-button';
|
||||
import GasDetailsItem from '../gas-details-item';
|
||||
import Box from '../../ui/box';
|
||||
import Button from '../../ui/button';
|
||||
import I18nValue from '../../ui/i18n-value';
|
||||
import InfoTooltip from '../../ui/info-tooltip';
|
||||
import Popover from '../../ui/popover';
|
||||
import Typography from '../../ui/typography';
|
||||
import AppLoadingSpinner from '../app-loading-spinner';
|
||||
|
||||
const CancelSpeedupPopover = () => {
|
||||
const {
|
||||
cancelTransaction,
|
||||
editGasMode,
|
||||
gasFeeEstimates,
|
||||
speedUpTransaction,
|
||||
transaction,
|
||||
updateTransaction,
|
||||
updateTransactionToMinimumGasFee,
|
||||
updateTransactionUsingEstimate,
|
||||
} = useGasFeeContext();
|
||||
const t = useI18nContext();
|
||||
const { closeModal, currentModal } = useTransactionModalContext();
|
||||
const appIsLoading = useSelector(getAppIsLoading);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
transaction.previousGas ||
|
||||
appIsLoading ||
|
||||
currentModal !== 'cancelSpeedUpTransaction'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// If gas used previously + 10% is less than medium estimated gas
|
||||
// estimate is set to medium, else estimate is set to minimum
|
||||
const gasUsedLessThanMedium =
|
||||
gasFeeEstimates &&
|
||||
gasEstimateGreaterThanGasUsedPlusTenPercent(
|
||||
transaction,
|
||||
gasFeeEstimates,
|
||||
PRIORITY_LEVELS.MEDIUM,
|
||||
);
|
||||
if (gasUsedLessThanMedium) {
|
||||
updateTransactionUsingEstimate(PRIORITY_LEVELS.MEDIUM);
|
||||
return;
|
||||
}
|
||||
updateTransactionToMinimumGasFee();
|
||||
}, [
|
||||
appIsLoading,
|
||||
currentModal,
|
||||
editGasMode,
|
||||
gasFeeEstimates,
|
||||
transaction,
|
||||
updateTransaction,
|
||||
updateTransactionToMinimumGasFee,
|
||||
updateTransactionUsingEstimate,
|
||||
]);
|
||||
|
||||
if (currentModal !== 'cancelSpeedUpTransaction') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const submitTransactionChange = () => {
|
||||
if (editGasMode === EDIT_GAS_MODES.CANCEL) {
|
||||
cancelTransaction();
|
||||
} else {
|
||||
speedUpTransaction();
|
||||
}
|
||||
closeModal('cancelSpeedUpTransaction');
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover
|
||||
title={
|
||||
<>
|
||||
{editGasMode === EDIT_GAS_MODES.CANCEL
|
||||
? `❌${t('cancel')}`
|
||||
: `🚀${t('speedUp')}`}
|
||||
</>
|
||||
}
|
||||
onClose={() => closeModal('cancelSpeedUpTransaction')}
|
||||
className="cancel-speedup-popover"
|
||||
>
|
||||
<AppLoadingSpinner className="cancel-speedup-popover__spinner" />
|
||||
<div className="cancel-speedup-popover__wrapper">
|
||||
<Typography
|
||||
boxProps={{ alignItems: ALIGN_ITEMS.CENTER, display: DISPLAY.FLEX }}
|
||||
variant={TYPOGRAPHY.H6}
|
||||
margin={[0, 0, 2, 0]}
|
||||
>
|
||||
<I18nValue
|
||||
messageKey="cancelSpeedUpLabel"
|
||||
options={[
|
||||
<strong key="cancelSpeedupReplace">
|
||||
<I18nValue messageKey="replace" />
|
||||
</strong>,
|
||||
]}
|
||||
/>
|
||||
<InfoTooltip
|
||||
position="top"
|
||||
contentText={
|
||||
<Box>
|
||||
{t('cancelSpeedUpTransactionTooltip', [
|
||||
EDIT_GAS_MODES.CANCEL ? t('cancel') : t('speedUp'),
|
||||
])}
|
||||
<div>
|
||||
<a
|
||||
href="https://community.metamask.io/t/how-to-speed-up-or-cancel-transactions-on-metamask/3296"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('learnMoreUpperCase')}
|
||||
</a>
|
||||
</div>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Typography>
|
||||
<div className="cancel-speedup-popover__separator" />
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
alignItems={ALIGN_ITEMS.CENTER}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
marginTop={4}
|
||||
>
|
||||
<Box className="cancel-speedup-popover__edit-gas-button">
|
||||
<EditGasFeeButton />
|
||||
</Box>
|
||||
<Box className="cancel-speedup-popover__gas-details">
|
||||
<GasDetailsItem />
|
||||
</Box>
|
||||
</Box>
|
||||
<Button type="primary" onClick={submitTransactionChange}>
|
||||
<I18nValue messageKey="submit" />
|
||||
</Button>
|
||||
</div>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
export default CancelSpeedupPopover;
|
@ -0,0 +1,88 @@
|
||||
import React from 'react';
|
||||
import { act, screen } from '@testing-library/react';
|
||||
|
||||
import {
|
||||
EDIT_GAS_MODES,
|
||||
GAS_ESTIMATE_TYPES,
|
||||
} from '../../../../shared/constants/gas';
|
||||
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 configureStore from '../../../store/store';
|
||||
|
||||
import CancelSpeedupPopover from './cancel-speedup-popover';
|
||||
|
||||
jest.mock('../../../store/actions', () => ({
|
||||
disconnectGasFeeEstimatePoller: jest.fn(),
|
||||
getGasFeeTimeEstimate: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||
getGasFeeEstimatesAndStartPolling: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve()),
|
||||
addPollingTokenToAppState: jest.fn(),
|
||||
removePollingTokenFromAppState: jest.fn(),
|
||||
updateTransaction: () => ({ type: 'UPDATE_TRANSACTION_PARAMS' }),
|
||||
}));
|
||||
|
||||
jest.mock('../../../contexts/transaction-modal', () => ({
|
||||
useTransactionModalContext: () => ({
|
||||
closeModal: () => undefined,
|
||||
currentModal: 'cancelSpeedUpTransaction',
|
||||
}),
|
||||
}));
|
||||
|
||||
const render = (props) => {
|
||||
const store = configureStore({
|
||||
metamask: {
|
||||
...mockState.metamask,
|
||||
accounts: {
|
||||
[mockState.metamask.selectedAddress]: {
|
||||
address: mockState.metamask.selectedAddress,
|
||||
balance: '0x1F4',
|
||||
},
|
||||
},
|
||||
featureFlags: { advancedInlineGas: true },
|
||||
gasFeeEstimates:
|
||||
mockEstimates[GAS_ESTIMATE_TYPES.FEE_MARKET].gasFeeEstimates,
|
||||
},
|
||||
});
|
||||
|
||||
return renderWithProvider(
|
||||
<GasFeeContextProvider
|
||||
transaction={{
|
||||
userFeeLevel: 'minimum',
|
||||
txParams: {
|
||||
gas: '0x5208',
|
||||
maxFeePerGas: '0x59682f10',
|
||||
maxPriorityFeePerGas: '0x59682f00',
|
||||
},
|
||||
}}
|
||||
editGasMode={EDIT_GAS_MODES.CANCEL}
|
||||
{...props}
|
||||
>
|
||||
<CancelSpeedupPopover />
|
||||
</GasFeeContextProvider>,
|
||||
store,
|
||||
);
|
||||
};
|
||||
|
||||
describe('CancelSpeedupPopover', () => {
|
||||
it('should have ❌Cancel in header if editGasMode is cancel', async () => {
|
||||
await act(async () => render());
|
||||
expect(screen.queryByText('❌Cancel')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have 🚀Speed Up in header if editGasMode is speedup', async () => {
|
||||
await act(async () => render({ editGasMode: EDIT_GAS_MODES.SPEED_UP }));
|
||||
expect(screen.queryByText('🚀Speed Up')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show correct gas values', async () => {
|
||||
await act(async () =>
|
||||
render({
|
||||
editGasMode: EDIT_GAS_MODES.SPEED_UP,
|
||||
}),
|
||||
);
|
||||
expect(screen.queryAllByTitle('0.0000315 ETH').length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
1
ui/components/app/cancel-speedup-popover/index.js
Normal file
1
ui/components/app/cancel-speedup-popover/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './cancel-speedup-popover';
|
27
ui/components/app/cancel-speedup-popover/index.scss
Normal file
27
ui/components/app/cancel-speedup-popover/index.scss
Normal file
@ -0,0 +1,27 @@
|
||||
.cancel-speedup-popover {
|
||||
&__wrapper {
|
||||
padding: 0 16px 16px;
|
||||
|
||||
.info-tooltip {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&__edit-gas-button {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
&__gas-details {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
&__spinner {
|
||||
margin-top: -30px;
|
||||
height: calc(100% + 30px);
|
||||
}
|
||||
|
||||
&__separator {
|
||||
border-bottom: 1px solid $ui-grey;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
@ -41,14 +41,19 @@ export default function EditGasFeeButton({ userAcknowledgedGasMissing }) {
|
||||
) {
|
||||
icon = 'swapSuggested';
|
||||
title = 'swapSuggested';
|
||||
} else if (estimateUsed === PRIORITY_LEVELS.MINIMUM) {
|
||||
icon = undefined;
|
||||
title = 'minimumEstimate';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="edit-gas-fee-button">
|
||||
<button onClick={() => openModal('editGasFee')}>
|
||||
{icon && (
|
||||
<span className="edit-gas-fee-button__icon">
|
||||
{`${PRIORITY_LEVEL_ICON_MAP[icon]} `}
|
||||
</span>
|
||||
)}
|
||||
<span className="edit-gas-fee-button__label">{t(title)}</span>
|
||||
<i className="fas fa-chevron-right asset-list-item__chevron-right" />
|
||||
</button>
|
||||
|
@ -73,6 +73,11 @@ describe('EditGasFeeButton', () => {
|
||||
expect(screen.queryByText('Aggressive')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render edit link with text 10% Minimum if minimum gas estimates are selected', () => {
|
||||
render({ contextProps: { transaction: { userFeeLevel: 'minimum' } } });
|
||||
expect(screen.queryByText('10% Minimum')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render edit link with text Site suggested if site suggested estimated are used', () => {
|
||||
render({
|
||||
contextProps: {
|
||||
@ -103,6 +108,7 @@ describe('EditGasFeeButton', () => {
|
||||
render({
|
||||
contextProps: {
|
||||
defaultEstimateToUse: 'custom',
|
||||
transaction: {},
|
||||
},
|
||||
});
|
||||
expect(screen.queryByText('⚙')).toBeInTheDocument();
|
||||
|
@ -14,23 +14,39 @@ import Typography from '../../ui/typography/typography';
|
||||
import { COLORS, TYPOGRAPHY } from '../../../helpers/constants/design-system';
|
||||
import { INSUFFICIENT_FUNDS_ERROR_KEY } from '../../../helpers/constants/error-keys';
|
||||
import { useGasFeeContext } from '../../../contexts/gasFee';
|
||||
import AppLoadingSpinner from '../app-loading-spinner';
|
||||
import EditGasItem from './edit-gas-item';
|
||||
import NetworkStatistics from './network-statistics';
|
||||
|
||||
const EditGasFeePopover = () => {
|
||||
const { balanceError, editGasMode } = useGasFeeContext();
|
||||
const t = useI18nContext();
|
||||
const { closeModal, currentModal } = useTransactionModalContext();
|
||||
const {
|
||||
closeAllModals,
|
||||
closeModal,
|
||||
currentModal,
|
||||
openModalCount,
|
||||
} = useTransactionModalContext();
|
||||
|
||||
if (currentModal !== 'editGasFee') return null;
|
||||
|
||||
let popupTitle = 'editGasFeeModalTitle';
|
||||
if (editGasMode === EDIT_GAS_MODES.CANCEL) {
|
||||
popupTitle = 'editCancellationGasFeeModalTitle';
|
||||
} else if (editGasMode === EDIT_GAS_MODES.SPEED_UP) {
|
||||
popupTitle = 'editSpeedUpEditGasFeeModalTitle';
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover
|
||||
title={t('editGasFeeModalTitle')}
|
||||
onClose={() => closeModal('editGasFee')}
|
||||
title={t(popupTitle)}
|
||||
// below logic ensures that back button is visible only if there are other modals open before this.
|
||||
onBack={openModalCount === 1 ? undefined : () => closeModal('editGasFee')}
|
||||
onClose={closeAllModals}
|
||||
className="edit-gas-fee-popover"
|
||||
>
|
||||
<>
|
||||
<AppLoadingSpinner />
|
||||
<div className="edit-gas-fee-popover__wrapper">
|
||||
<div className="edit-gas-fee-popover__content">
|
||||
{balanceError && (
|
||||
@ -49,13 +65,17 @@ const EditGasFeePopover = () => {
|
||||
<I18nValue messageKey="maxFee" />
|
||||
</span>
|
||||
</div>
|
||||
{editGasMode !== EDIT_GAS_MODES.SWAPS && (
|
||||
{(editGasMode === EDIT_GAS_MODES.CANCEL ||
|
||||
editGasMode === EDIT_GAS_MODES.SPEED_UP) && (
|
||||
<EditGasItem priorityLevel={PRIORITY_LEVELS.MINIMUM} />
|
||||
)}
|
||||
{editGasMode === EDIT_GAS_MODES.MODIFY_IN_PLACE && (
|
||||
<EditGasItem priorityLevel={PRIORITY_LEVELS.LOW} />
|
||||
)}
|
||||
<EditGasItem priorityLevel={PRIORITY_LEVELS.MEDIUM} />
|
||||
<EditGasItem priorityLevel={PRIORITY_LEVELS.HIGH} />
|
||||
<div className="edit-gas-fee-popover__content__separator" />
|
||||
{editGasMode !== EDIT_GAS_MODES.SWAPS && (
|
||||
{editGasMode === EDIT_GAS_MODES.MODIFY_IN_PLACE && (
|
||||
<EditGasItem priorityLevel={PRIORITY_LEVELS.DAPP_SUGGESTED} />
|
||||
)}
|
||||
<EditGasItem priorityLevel={PRIORITY_LEVELS.CUSTOM} />
|
||||
|
@ -28,22 +28,24 @@ const MOCK_FEE_ESTIMATE = {
|
||||
low: {
|
||||
minWaitTimeEstimate: 360000,
|
||||
maxWaitTimeEstimate: 300000,
|
||||
suggestedMaxPriorityFeePerGas: '3',
|
||||
suggestedMaxFeePerGas: '53',
|
||||
suggestedMaxPriorityFeePerGas: 3,
|
||||
suggestedMaxFeePerGas: 53,
|
||||
},
|
||||
medium: {
|
||||
minWaitTimeEstimate: 30000,
|
||||
maxWaitTimeEstimate: 60000,
|
||||
suggestedMaxPriorityFeePerGas: '7',
|
||||
suggestedMaxFeePerGas: '70',
|
||||
suggestedMaxPriorityFeePerGas: 7,
|
||||
suggestedMaxFeePerGas: 70,
|
||||
},
|
||||
high: {
|
||||
minWaitTimeEstimate: 15000,
|
||||
maxWaitTimeEstimate: 15000,
|
||||
suggestedMaxPriorityFeePerGas: '10',
|
||||
suggestedMaxFeePerGas: '100',
|
||||
suggestedMaxPriorityFeePerGas: 10,
|
||||
suggestedMaxFeePerGas: 100,
|
||||
},
|
||||
estimatedBaseFee: '50',
|
||||
latestPriorityFeeRange: [2, 6],
|
||||
estimatedBaseFee: 50,
|
||||
networkCongestion: 0.7,
|
||||
};
|
||||
|
||||
const render = ({ txProps, contextProps } = {}) => {
|
||||
@ -141,4 +143,47 @@ describe('EditGasFeePopover', () => {
|
||||
expect(screen.queryByText('Time')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Max fee')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show correct header for edit gas mode', () => {
|
||||
render({
|
||||
contextProps: { editGasMode: EDIT_GAS_MODES.SWAPS },
|
||||
});
|
||||
expect(screen.queryByText('Edit gas fee')).toBeInTheDocument();
|
||||
render({
|
||||
contextProps: { editGasMode: EDIT_GAS_MODES.CANCEL },
|
||||
});
|
||||
expect(screen.queryByText('Edit cancellation gas fee')).toBeInTheDocument();
|
||||
render({
|
||||
contextProps: { editGasMode: EDIT_GAS_MODES.SPEED_UP },
|
||||
});
|
||||
expect(screen.queryByText('Edit speed up gas fee')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show low option for cancel mode', () => {
|
||||
render({
|
||||
contextProps: { editGasMode: EDIT_GAS_MODES.CANCEL },
|
||||
});
|
||||
expect(screen.queryByText('Low')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show low option for speedup mode', () => {
|
||||
render({
|
||||
contextProps: { editGasMode: EDIT_GAS_MODES.SPEED_UP },
|
||||
});
|
||||
expect(screen.queryByText('Low')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show minimum option for cancel gas mode', () => {
|
||||
render({
|
||||
contextProps: { editGasMode: EDIT_GAS_MODES.CANCEL },
|
||||
});
|
||||
expect(screen.queryByText('(minimum)')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show minimum option for speedup gas mode', () => {
|
||||
render({
|
||||
contextProps: { editGasMode: EDIT_GAS_MODES.SPEED_UP },
|
||||
});
|
||||
expect(screen.queryByText('(minimum)')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -1,109 +1,75 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { useSelector } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import { getMaximumGasTotalInHexWei } from '../../../../../shared/modules/gas.utils';
|
||||
import {
|
||||
EDIT_GAS_MODES,
|
||||
PRIORITY_LEVELS,
|
||||
} from '../../../../../shared/constants/gas';
|
||||
import {
|
||||
ALIGN_ITEMS,
|
||||
DISPLAY,
|
||||
} from '../../../../helpers/constants/design-system';
|
||||
import { PRIORITY_LEVEL_ICON_MAP } from '../../../../helpers/constants/gas';
|
||||
import { PRIMARY } from '../../../../helpers/constants/common';
|
||||
import {
|
||||
decGWEIToHexWEI,
|
||||
decimalToHex,
|
||||
hexWEIToDecGWEI,
|
||||
} from '../../../../helpers/utils/conversions.util';
|
||||
import LoadingHeartBeat from '../../../ui/loading-heartbeat';
|
||||
import { getAdvancedGasFeeValues } from '../../../../selectors';
|
||||
import { toHumanReadableTime } from '../../../../helpers/utils/util';
|
||||
import { useGasFeeContext } from '../../../../contexts/gasFee';
|
||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||
import { useTransactionModalContext } from '../../../../contexts/transaction-modal';
|
||||
import I18nValue from '../../../ui/i18n-value';
|
||||
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display';
|
||||
|
||||
import Box from '../../../ui/box';
|
||||
import EditGasToolTip from '../edit-gas-tooltip/edit-gas-tooltip';
|
||||
import InfoTooltip from '../../../ui/info-tooltip';
|
||||
import { useCustomTimeEstimate } from './useCustomTimeEstimate';
|
||||
import LoadingHeartBeat from '../../../ui/loading-heartbeat';
|
||||
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display';
|
||||
|
||||
import { useGasItemFeeDetails } from './useGasItemFeeDetails';
|
||||
|
||||
const getTitleAndIcon = (priorityLevel, t, editGasMode) => {
|
||||
let icon = priorityLevel;
|
||||
let title = t(priorityLevel);
|
||||
if (priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED) {
|
||||
title = t('dappSuggestedShortLabel');
|
||||
} else if (priorityLevel === PRIORITY_LEVELS.MINIMUM) {
|
||||
icon = null;
|
||||
title = (
|
||||
<Box display={DISPLAY.FLEX} alignItems={ALIGN_ITEMS.CENTER}>
|
||||
{t('minimumCancelSpeedupGasFee')}
|
||||
<span className="edit-gas-item__name__sufix">({t('minimum')})</span>
|
||||
</Box>
|
||||
);
|
||||
} else if (
|
||||
priorityLevel === PRIORITY_LEVELS.HIGH &&
|
||||
editGasMode === EDIT_GAS_MODES.SWAPS
|
||||
) {
|
||||
icon = 'swapSuggested';
|
||||
title = t('swapSuggested');
|
||||
}
|
||||
return { title, icon };
|
||||
};
|
||||
|
||||
const EditGasItem = ({ priorityLevel }) => {
|
||||
const {
|
||||
editGasMode,
|
||||
estimateUsed,
|
||||
gasFeeEstimates,
|
||||
gasLimit,
|
||||
maxFeePerGas: maxFeePerGasValue,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGasValue,
|
||||
updateTransactionUsingGasFeeEstimates,
|
||||
updateTransactionToMinimumGasFee,
|
||||
updateTransactionUsingDAPPSuggestedValues,
|
||||
updateTransactionUsingEstimate,
|
||||
transaction,
|
||||
} = useGasFeeContext();
|
||||
const t = useI18nContext();
|
||||
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues);
|
||||
const { closeModal, openModal } = useTransactionModalContext();
|
||||
const { dappSuggestedGasFees } = transaction;
|
||||
|
||||
let maxFeePerGas;
|
||||
let maxPriorityFeePerGas;
|
||||
let minWaitTime;
|
||||
|
||||
if (gasFeeEstimates?.[priorityLevel]) {
|
||||
maxFeePerGas = gasFeeEstimates[priorityLevel].suggestedMaxFeePerGas;
|
||||
maxPriorityFeePerGas =
|
||||
gasFeeEstimates[priorityLevel].suggestedMaxPriorityFeePerGas;
|
||||
} else if (
|
||||
priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED &&
|
||||
dappSuggestedGasFees
|
||||
) {
|
||||
maxFeePerGas = hexWEIToDecGWEI(
|
||||
dappSuggestedGasFees.maxFeePerGas || dappSuggestedGasFees.gasPrice,
|
||||
);
|
||||
maxPriorityFeePerGas = hexWEIToDecGWEI(
|
||||
dappSuggestedGasFees.maxPriorityFeePerGas || maxFeePerGas,
|
||||
);
|
||||
} else if (priorityLevel === PRIORITY_LEVELS.CUSTOM) {
|
||||
if (estimateUsed === PRIORITY_LEVELS.CUSTOM) {
|
||||
maxFeePerGas = maxFeePerGasValue;
|
||||
maxPriorityFeePerGas = maxPriorityFeePerGasValue;
|
||||
} else if (advancedGasFeeValues) {
|
||||
maxFeePerGas =
|
||||
gasFeeEstimates.estimatedBaseFee *
|
||||
parseFloat(advancedGasFeeValues.maxBaseFee);
|
||||
maxPriorityFeePerGas = advancedGasFeeValues.priorityFee;
|
||||
}
|
||||
}
|
||||
|
||||
const { waitTimeEstimate } = useCustomTimeEstimate({
|
||||
gasFeeEstimates,
|
||||
const {
|
||||
// for cancel or speedup estimateGreaterThaGasUse is true if previous gas used
|
||||
// was more than estimate for the priorityLevel
|
||||
estimateGreaterThanGasUse,
|
||||
hexMaximumTransactionFee,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
});
|
||||
|
||||
if (gasFeeEstimates[priorityLevel]) {
|
||||
minWaitTime =
|
||||
priorityLevel === PRIORITY_LEVELS.HIGH
|
||||
? gasFeeEstimates?.high.minWaitTimeEstimate
|
||||
: gasFeeEstimates?.low.maxWaitTimeEstimate;
|
||||
} else {
|
||||
minWaitTime = waitTimeEstimate;
|
||||
}
|
||||
|
||||
const hexMaximumTransactionFee = maxFeePerGas
|
||||
? getMaximumGasTotalInHexWei({
|
||||
gasLimit: decimalToHex(gasLimit),
|
||||
maxFeePerGas: decGWEIToHexWEI(maxFeePerGas),
|
||||
})
|
||||
: null;
|
||||
|
||||
const onOptionSelect = () => {
|
||||
if (priorityLevel === PRIORITY_LEVELS.CUSTOM) {
|
||||
openModal('advancedGasFee');
|
||||
} else {
|
||||
updateTransactionUsingGasFeeEstimates(priorityLevel);
|
||||
closeModal('editGasFee');
|
||||
}
|
||||
};
|
||||
minWaitTime,
|
||||
} = useGasItemFeeDetails(priorityLevel);
|
||||
|
||||
if (
|
||||
priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED &&
|
||||
@ -112,34 +78,44 @@ const EditGasItem = ({ priorityLevel }) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
let icon = priorityLevel;
|
||||
let title = priorityLevel;
|
||||
if (priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED) {
|
||||
title = 'dappSuggestedShortLabel';
|
||||
} else if (
|
||||
priorityLevel === PRIORITY_LEVELS.HIGH &&
|
||||
editGasMode === EDIT_GAS_MODES.SWAPS
|
||||
) {
|
||||
icon = 'swapSuggested';
|
||||
title = 'swapSuggested';
|
||||
const onOptionSelect = () => {
|
||||
if (priorityLevel === PRIORITY_LEVELS.CUSTOM) {
|
||||
openModal('advancedGasFee');
|
||||
} else {
|
||||
closeModal('editGasFee');
|
||||
|
||||
if (priorityLevel === PRIORITY_LEVELS.MINIMUM) {
|
||||
updateTransactionToMinimumGasFee();
|
||||
} else if (priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED) {
|
||||
updateTransactionUsingDAPPSuggestedValues();
|
||||
} else {
|
||||
updateTransactionUsingEstimate(priorityLevel);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const { title, icon } = getTitleAndIcon(priorityLevel, t, editGasMode);
|
||||
|
||||
return (
|
||||
<button
|
||||
className={classNames('edit-gas-item', {
|
||||
'edit-gas-item--selected': priorityLevel === estimateUsed,
|
||||
'edit-gas-item--disabled': estimateGreaterThanGasUse,
|
||||
})}
|
||||
onClick={onOptionSelect}
|
||||
aria-label={priorityLevel}
|
||||
autoFocus={priorityLevel === estimateUsed}
|
||||
disabled={estimateGreaterThanGasUse}
|
||||
>
|
||||
<span className="edit-gas-item__name">
|
||||
{icon && (
|
||||
<span
|
||||
className={`edit-gas-item__icon edit-gas-item__icon-${priorityLevel}`}
|
||||
>
|
||||
{PRIORITY_LEVEL_ICON_MAP[icon]}
|
||||
</span>
|
||||
<I18nValue messageKey={title} />
|
||||
)}
|
||||
{title}
|
||||
</span>
|
||||
<span
|
||||
className={`edit-gas-item__time-estimate edit-gas-item__time-estimate-${priorityLevel}`}
|
||||
@ -174,6 +150,7 @@ const EditGasItem = ({ priorityLevel }) => {
|
||||
editGasMode={editGasMode}
|
||||
gasLimit={gasLimit}
|
||||
transaction={transaction}
|
||||
estimateGreaterThanGasUse={estimateGreaterThanGasUse}
|
||||
/>
|
||||
}
|
||||
position="top"
|
||||
|
@ -42,7 +42,7 @@ const MOCK_FEE_ESTIMATE = {
|
||||
estimatedBaseFee: '50',
|
||||
};
|
||||
|
||||
const DAPP_SUGGESTED_ESTIMATE = {
|
||||
const ESTIMATE_MOCK = {
|
||||
maxFeePerGas: '0x59682f10',
|
||||
maxPriorityFeePerGas: '0x59682f00',
|
||||
};
|
||||
@ -65,6 +65,7 @@ const renderComponent = ({
|
||||
},
|
||||
selectedAddress: '0xAddress',
|
||||
featureFlags: { advancedInlineGas: true },
|
||||
gasEstimateType: 'fee-market',
|
||||
gasFeeEstimates: MOCK_FEE_ESTIMATE,
|
||||
advancedGasFee: {
|
||||
maxBaseFee: '1.5',
|
||||
@ -139,7 +140,7 @@ describe('EditGasItem', () => {
|
||||
it('should renders site gas estimate option for priorityLevel dappSuggested', () => {
|
||||
renderComponent({
|
||||
componentProps: { priorityLevel: 'dappSuggested' },
|
||||
transactionProps: { dappSuggestedGasFees: DAPP_SUGGESTED_ESTIMATE },
|
||||
transactionProps: { dappSuggestedGasFees: ESTIMATE_MOCK },
|
||||
});
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'dappSuggested' }),
|
||||
@ -162,4 +163,21 @@ describe('EditGasItem', () => {
|
||||
// below value of custom gas fee estimate is default obtained from state.metamask.advancedGasFee
|
||||
expect(screen.queryByTitle('0.001575 ETH')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should renders +10% gas estimate option for priorityLevel minimum', () => {
|
||||
renderComponent({
|
||||
componentProps: { priorityLevel: 'minimum' },
|
||||
transactionProps: {
|
||||
userFeeLevel: 'minimum',
|
||||
previousGas: ESTIMATE_MOCK,
|
||||
},
|
||||
contextProps: { editGasMode: EDIT_GAS_MODES.CANCEL },
|
||||
});
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'minimum' }),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByText('+10%')).toBeInTheDocument();
|
||||
expect(screen.queryByText('(minimum)')).toBeInTheDocument();
|
||||
expect(screen.queryByTitle('0.00003465 ETH')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -19,6 +19,11 @@
|
||||
background-color: $ui-1;
|
||||
}
|
||||
|
||||
button.edit-gas-item--disabled[disabled] {
|
||||
opacity: 0.25;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&__name {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@ -27,6 +32,11 @@
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
width: 36%;
|
||||
|
||||
&__sufix {
|
||||
font-weight: 400;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
|
@ -0,0 +1,126 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import {
|
||||
EDIT_GAS_MODES,
|
||||
PRIORITY_LEVELS,
|
||||
} from '../../../../../shared/constants/gas';
|
||||
import { getMaximumGasTotalInHexWei } from '../../../../../shared/modules/gas.utils';
|
||||
import {
|
||||
decGWEIToHexWEI,
|
||||
decimalToHex,
|
||||
hexWEIToDecGWEI,
|
||||
} from '../../../../helpers/utils/conversions.util';
|
||||
import {
|
||||
addTenPercentAndRound,
|
||||
gasEstimateGreaterThanGasUsedPlusTenPercent,
|
||||
} from '../../../../helpers/utils/gas';
|
||||
import { getAdvancedGasFeeValues } from '../../../../selectors';
|
||||
import { useGasFeeContext } from '../../../../contexts/gasFee';
|
||||
import { useCustomTimeEstimate } from './useCustomTimeEstimate';
|
||||
|
||||
export const useGasItemFeeDetails = (priorityLevel) => {
|
||||
const {
|
||||
editGasMode,
|
||||
estimateUsed,
|
||||
gasFeeEstimates,
|
||||
gasLimit,
|
||||
maxFeePerGas: maxFeePerGasValue,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGasValue,
|
||||
transaction,
|
||||
} = useGasFeeContext();
|
||||
const [estimateGreaterThanGasUse, setEstimateGreaterThanGasUse] = useState(
|
||||
false,
|
||||
);
|
||||
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues);
|
||||
|
||||
let maxFeePerGas;
|
||||
let maxPriorityFeePerGas;
|
||||
let minWaitTime;
|
||||
|
||||
const { dappSuggestedGasFees } = transaction;
|
||||
|
||||
if (gasFeeEstimates?.[priorityLevel]) {
|
||||
maxFeePerGas = gasFeeEstimates[priorityLevel].suggestedMaxFeePerGas;
|
||||
maxPriorityFeePerGas =
|
||||
gasFeeEstimates[priorityLevel].suggestedMaxPriorityFeePerGas;
|
||||
} else if (
|
||||
priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED &&
|
||||
dappSuggestedGasFees
|
||||
) {
|
||||
maxFeePerGas = hexWEIToDecGWEI(
|
||||
dappSuggestedGasFees.maxFeePerGas || dappSuggestedGasFees.gasPrice,
|
||||
);
|
||||
maxPriorityFeePerGas = hexWEIToDecGWEI(
|
||||
dappSuggestedGasFees.maxPriorityFeePerGas || maxFeePerGas,
|
||||
);
|
||||
} else if (priorityLevel === PRIORITY_LEVELS.CUSTOM) {
|
||||
if (estimateUsed === PRIORITY_LEVELS.CUSTOM) {
|
||||
maxFeePerGas = maxFeePerGasValue;
|
||||
maxPriorityFeePerGas = maxPriorityFeePerGasValue;
|
||||
} else if (advancedGasFeeValues) {
|
||||
maxFeePerGas =
|
||||
gasFeeEstimates.estimatedBaseFee *
|
||||
parseFloat(advancedGasFeeValues.maxBaseFee);
|
||||
maxPriorityFeePerGas = advancedGasFeeValues.priorityFee;
|
||||
}
|
||||
} else if (
|
||||
priorityLevel === PRIORITY_LEVELS.MINIMUM &&
|
||||
transaction.previousGas
|
||||
) {
|
||||
maxFeePerGas = hexWEIToDecGWEI(
|
||||
addTenPercentAndRound(transaction.previousGas?.maxFeePerGas),
|
||||
);
|
||||
maxPriorityFeePerGas = hexWEIToDecGWEI(
|
||||
addTenPercentAndRound(transaction.previousGas?.maxPriorityFeePerGas),
|
||||
);
|
||||
}
|
||||
|
||||
const { waitTimeEstimate } = useCustomTimeEstimate({
|
||||
gasFeeEstimates,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
});
|
||||
|
||||
if (gasFeeEstimates[priorityLevel]) {
|
||||
minWaitTime =
|
||||
priorityLevel === PRIORITY_LEVELS.HIGH
|
||||
? gasFeeEstimates?.high.minWaitTimeEstimate
|
||||
: gasFeeEstimates?.low.maxWaitTimeEstimate;
|
||||
} else {
|
||||
minWaitTime = waitTimeEstimate;
|
||||
}
|
||||
|
||||
const hexMaximumTransactionFee = maxFeePerGas
|
||||
? getMaximumGasTotalInHexWei({
|
||||
gasLimit: decimalToHex(gasLimit),
|
||||
maxFeePerGas: decGWEIToHexWEI(maxFeePerGas),
|
||||
})
|
||||
: null;
|
||||
|
||||
useEffect(() => {
|
||||
// For cancel and speed-up medium / high option is disabled if
|
||||
// gas used in transaction + 10% is greater tham estimate
|
||||
if (
|
||||
(editGasMode === EDIT_GAS_MODES.CANCEL ||
|
||||
editGasMode === EDIT_GAS_MODES.SPEED_UP) &&
|
||||
(priorityLevel === PRIORITY_LEVELS.MEDIUM ||
|
||||
priorityLevel === PRIORITY_LEVELS.HIGH)
|
||||
) {
|
||||
const estimateGreater = !gasEstimateGreaterThanGasUsedPlusTenPercent(
|
||||
transaction,
|
||||
gasFeeEstimates,
|
||||
priorityLevel,
|
||||
);
|
||||
setEstimateGreaterThanGasUse(estimateGreater);
|
||||
}
|
||||
}, [editGasMode, gasFeeEstimates, priorityLevel, transaction]);
|
||||
|
||||
return {
|
||||
estimateGreaterThanGasUse,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
minWaitTime,
|
||||
hexMaximumTransactionFee,
|
||||
};
|
||||
};
|
@ -12,6 +12,8 @@ import {
|
||||
import Typography from '../../../ui/typography';
|
||||
|
||||
const EditGasToolTip = ({
|
||||
editGasMode,
|
||||
estimateGreaterThanGasUse,
|
||||
gasLimit,
|
||||
priorityLevel,
|
||||
// maxFeePerGas & maxPriorityFeePerGas are derived from conditional logic
|
||||
@ -19,7 +21,6 @@ const EditGasToolTip = ({
|
||||
// the parent component (edit-gas-item) rather than recalculate them
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
editGasMode,
|
||||
transaction,
|
||||
t,
|
||||
}) => {
|
||||
@ -32,12 +33,26 @@ const EditGasToolTip = ({
|
||||
</span>,
|
||||
]);
|
||||
case PRIORITY_LEVELS.MEDIUM:
|
||||
if (estimateGreaterThanGasUse) {
|
||||
return t('disabledGasOptionToolTipMessage', [
|
||||
<span key={`disabled-priority-level-${priorityLevel}`}>
|
||||
{t(priorityLevel)}
|
||||
</span>,
|
||||
]);
|
||||
}
|
||||
return t('mediumGasSettingToolTipMessage', [
|
||||
<span key={priorityLevel}>
|
||||
<b>{t('medium')}</b>
|
||||
</span>,
|
||||
]);
|
||||
case PRIORITY_LEVELS.HIGH:
|
||||
if (estimateGreaterThanGasUse) {
|
||||
return t('disabledGasOptionToolTipMessage', [
|
||||
<span key={`disabled-priority-level-${priorityLevel}`}>
|
||||
{t(priorityLevel)}
|
||||
</span>,
|
||||
]);
|
||||
}
|
||||
if (editGasMode === EDIT_GAS_MODES.SWAPS) {
|
||||
return t('swapSuggestedGasSettingToolTipMessage');
|
||||
}
|
||||
@ -69,11 +84,13 @@ const EditGasToolTip = ({
|
||||
!(
|
||||
priorityLevel === PRIORITY_LEVELS.HIGH &&
|
||||
editGasMode === EDIT_GAS_MODES.SWAPS
|
||||
) ? (
|
||||
) &&
|
||||
!estimateGreaterThanGasUse ? (
|
||||
<img alt="" src={`./images/curve-${priorityLevel}.svg`} />
|
||||
) : null}
|
||||
{priorityLevel === PRIORITY_LEVELS.HIGH &&
|
||||
editGasMode !== EDIT_GAS_MODES.SWAPS ? (
|
||||
editGasMode !== EDIT_GAS_MODES.SWAPS &&
|
||||
!estimateGreaterThanGasUse ? (
|
||||
<div className="edit-gas-tooltip__container__dialog">
|
||||
<Typography variant={TYPOGRAPHY.H7} color={COLORS.WHITE}>
|
||||
{t('highGasSettingToolTipDialog')}
|
||||
@ -83,7 +100,8 @@ const EditGasToolTip = ({
|
||||
<div className="edit-gas-tooltip__container__message">
|
||||
<Typography variant={TYPOGRAPHY.H7}>{toolTipMessage()}</Typography>
|
||||
</div>
|
||||
{priorityLevel === PRIORITY_LEVELS.CUSTOM ? null : (
|
||||
{priorityLevel === PRIORITY_LEVELS.CUSTOM ||
|
||||
estimateGreaterThanGasUse ? null : (
|
||||
<div className="edit-gas-tooltip__container__values">
|
||||
<div>
|
||||
<Typography
|
||||
@ -140,9 +158,13 @@ const EditGasToolTip = ({
|
||||
};
|
||||
|
||||
EditGasToolTip.propTypes = {
|
||||
estimateGreaterThanGasUse: PropTypes.bool,
|
||||
priorityLevel: PropTypes.string,
|
||||
maxFeePerGas: PropTypes.string,
|
||||
maxPriorityFeePerGas: PropTypes.string,
|
||||
maxFeePerGas: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
maxPriorityFeePerGas: PropTypes.oneOfType([
|
||||
PropTypes.number,
|
||||
PropTypes.string,
|
||||
]),
|
||||
t: PropTypes.func,
|
||||
editGasMode: PropTypes.string,
|
||||
gasLimit: PropTypes.number,
|
||||
|
@ -4,10 +4,10 @@ import { useSelector } from 'react-redux';
|
||||
import { TYPOGRAPHY } from '../../../../helpers/constants/design-system';
|
||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||
import { getIsMainnet } from '../../../../selectors';
|
||||
import Box from '../../../../components/ui/box';
|
||||
import I18nValue from '../../../../components/ui/i18n-value';
|
||||
import InfoTooltip from '../../../../components/ui/info-tooltip/info-tooltip';
|
||||
import Typography from '../../../../components/ui/typography/typography';
|
||||
import Box from '../../../ui/box';
|
||||
import I18nValue from '../../../ui/i18n-value';
|
||||
import InfoTooltip from '../../../ui/info-tooltip/info-tooltip';
|
||||
import Typography from '../../../ui/typography/typography';
|
||||
|
||||
const GasDetailsItemTitle = () => {
|
||||
const t = useI18nContext();
|
||||
@ -16,7 +16,7 @@ const GasDetailsItemTitle = () => {
|
||||
return (
|
||||
<Box display="flex">
|
||||
<Box marginRight={1}>
|
||||
<I18nValue messageKey="transactionDetailGasHeadingV2" />
|
||||
<I18nValue messageKey="gas" />
|
||||
</Box>
|
||||
<span className="gas-details-item-title__estimate">
|
||||
(<I18nValue messageKey="transactionDetailGasInfoV2" />)
|
@ -1,29 +1,32 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { COLORS } from '../../../helpers/constants/design-system';
|
||||
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common';
|
||||
import { hexWEIToDecGWEI } from '../../../helpers/utils/conversions.util';
|
||||
|
||||
import Box from '../../../components/ui/box';
|
||||
import GasTiming from '../../../components/app/gas-timing/gas-timing.component';
|
||||
import I18nValue from '../../../components/ui/i18n-value';
|
||||
import LoadingHeartBeat from '../../../components/ui/loading-heartbeat';
|
||||
import TransactionDetailItem from '../../../components/app/transaction-detail-item/transaction-detail-item.component';
|
||||
import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display';
|
||||
import { getPreferences } from '../../../selectors';
|
||||
import { useGasFeeContext } from '../../../contexts/gasFee';
|
||||
|
||||
import Box from '../../ui/box';
|
||||
import I18nValue from '../../ui/i18n-value';
|
||||
import LoadingHeartBeat from '../../ui/loading-heartbeat';
|
||||
import GasTiming from '../gas-timing/gas-timing.component';
|
||||
import TransactionDetailItem from '../transaction-detail-item/transaction-detail-item.component';
|
||||
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display';
|
||||
import GasDetailsItemTitle from './gas-details-item-title';
|
||||
|
||||
const GasDetailsItem = ({
|
||||
hexMaximumTransactionFee,
|
||||
hexMinimumTransactionFee,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
userAcknowledgedGasMissing,
|
||||
useNativeCurrencyAsPrimaryCurrency,
|
||||
}) => {
|
||||
const { estimateUsed, hasSimulationError, transaction } = useGasFeeContext();
|
||||
const GasDetailsItem = ({ userAcknowledgedGasMissing = false }) => {
|
||||
const {
|
||||
estimateUsed,
|
||||
hasSimulationError,
|
||||
maximumCostInHexWei: hexMaximumTransactionFee,
|
||||
minimumCostInHexWei: hexMinimumTransactionFee,
|
||||
transaction,
|
||||
} = useGasFeeContext();
|
||||
|
||||
const { useNativeCurrencyAsPrimaryCurrency } = useSelector(getPreferences);
|
||||
|
||||
if (hasSimulationError && !userAcknowledgedGasMissing) return null;
|
||||
|
||||
@ -86,11 +89,9 @@ const GasDetailsItem = ({
|
||||
subTitle={
|
||||
<GasTiming
|
||||
maxPriorityFeePerGas={hexWEIToDecGWEI(
|
||||
maxPriorityFeePerGas || transaction.txParams.maxPriorityFeePerGas,
|
||||
)}
|
||||
maxFeePerGas={hexWEIToDecGWEI(
|
||||
maxFeePerGas || transaction.txParams.maxFeePerGas,
|
||||
transaction.txParams.maxPriorityFeePerGas,
|
||||
)}
|
||||
maxFeePerGas={hexWEIToDecGWEI(transaction.txParams.maxFeePerGas)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
@ -98,12 +99,7 @@ const GasDetailsItem = ({
|
||||
};
|
||||
|
||||
GasDetailsItem.propTypes = {
|
||||
hexMaximumTransactionFee: PropTypes.string,
|
||||
hexMinimumTransactionFee: PropTypes.string,
|
||||
maxFeePerGas: PropTypes.string,
|
||||
maxPriorityFeePerGas: PropTypes.string,
|
||||
userAcknowledgedGasMissing: PropTypes.bool.isRequired,
|
||||
useNativeCurrencyAsPrimaryCurrency: PropTypes.bool,
|
||||
userAcknowledgedGasMissing: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default GasDetailsItem;
|
@ -1,7 +1,9 @@
|
||||
import React from 'react';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
|
||||
import { ETH } from '../../../helpers/constants/common';
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../../shared/constants/gas';
|
||||
import mockEstimates from '../../../../test/data/mock-estimates.json';
|
||||
import mockState from '../../../../test/data/mock-state.json';
|
||||
import { GasFeeContextProvider } from '../../../contexts/gasFee';
|
||||
import { renderWithProvider } from '../../../../test/jest';
|
||||
import configureStore from '../../../store/store';
|
||||
@ -17,28 +19,36 @@ jest.mock('../../../store/actions', () => ({
|
||||
getGasFeeTimeEstimate: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||
}));
|
||||
|
||||
const render = ({ componentProps, contextProps } = {}) => {
|
||||
const render = ({ contextProps } = {}) => {
|
||||
const store = configureStore({
|
||||
metamask: {
|
||||
nativeCurrency: ETH,
|
||||
...mockState.metamask,
|
||||
accounts: {
|
||||
[mockState.metamask.selectedAddress]: {
|
||||
address: mockState.metamask.selectedAddress,
|
||||
balance: '0x1F4',
|
||||
},
|
||||
},
|
||||
preferences: {
|
||||
useNativeCurrencyAsPrimaryCurrency: true,
|
||||
},
|
||||
provider: {},
|
||||
cachedBalances: {},
|
||||
accounts: {
|
||||
'0xAddress': {
|
||||
address: '0xAddress',
|
||||
balance: '0x176e5b6f173ebe66',
|
||||
},
|
||||
},
|
||||
selectedAddress: '0xAddress',
|
||||
gasFeeEstimates: mockEstimates[GAS_ESTIMATE_TYPES.FEE_MARKET],
|
||||
},
|
||||
});
|
||||
|
||||
return renderWithProvider(
|
||||
<GasFeeContextProvider transaction={{ txParams: {} }} {...contextProps}>
|
||||
<GasDetailsItem userAcknowledgedGasMissing={false} {...componentProps} />
|
||||
<GasFeeContextProvider
|
||||
transaction={{
|
||||
txParams: {
|
||||
gas: '0x5208',
|
||||
maxFeePerGas: '0x59682f10',
|
||||
maxPriorityFeePerGas: '0x59682f00',
|
||||
},
|
||||
userFeeLevel: 'medium',
|
||||
}}
|
||||
{...contextProps}
|
||||
>
|
||||
<GasDetailsItem userAcknowledgedGasMissing={false} />
|
||||
</GasFeeContextProvider>,
|
||||
store,
|
||||
);
|
||||
@ -51,7 +61,7 @@ describe('GasDetailsItem', () => {
|
||||
expect(screen.queryByText('Gas')).toBeInTheDocument();
|
||||
expect(screen.queryByText('(estimated)')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Max fee:')).toBeInTheDocument();
|
||||
expect(screen.queryByText('ETH')).toBeInTheDocument();
|
||||
expect(screen.queryAllByText('ETH').length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
@ -93,17 +103,11 @@ describe('GasDetailsItem', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should should render gas fee details', async () => {
|
||||
render({
|
||||
componentProps: {
|
||||
hexMinimumTransactionFee: '0x1ca62a4f7800',
|
||||
hexMaximumTransactionFee: '0x290ee75e3d900',
|
||||
},
|
||||
});
|
||||
it('should render gas fee details', async () => {
|
||||
render();
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTitle('0.0000315 ETH')).toBeInTheDocument();
|
||||
expect(screen.queryByText('ETH')).toBeInTheDocument();
|
||||
expect(screen.queryByTitle('0.0007223')).toBeInTheDocument();
|
||||
expect(screen.queryAllByTitle('0.0000315 ETH').length).toBeGreaterThan(0);
|
||||
expect(screen.queryAllByText('ETH').length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
@ -6,5 +6,6 @@
|
||||
&__currency-container,
|
||||
&__gasfee-label {
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@ import React, { useMemo, useState, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import ListItem from '../../ui/list-item';
|
||||
import { useTransactionDisplayData } from '../../../hooks/useTransactionDisplayData';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
@ -16,13 +18,27 @@ import {
|
||||
TRANSACTION_STATUSES,
|
||||
} from '../../../../shared/constants/transaction';
|
||||
import { EDIT_GAS_MODES } from '../../../../shared/constants/gas';
|
||||
import EditGasPopover from '../edit-gas-popover';
|
||||
import {
|
||||
GasFeeContextProvider,
|
||||
useGasFeeContext,
|
||||
} from '../../../contexts/gasFee';
|
||||
import {
|
||||
TransactionModalContextProvider,
|
||||
useTransactionModalContext,
|
||||
} from '../../../contexts/transaction-modal';
|
||||
import { checkNetworkAndAccountSupports1559 } from '../../../selectors';
|
||||
import { isLegacyTransaction } from '../../../helpers/utils/transactions.util';
|
||||
import { useMetricEvent } from '../../../hooks/useMetricEvent';
|
||||
import Button from '../../ui/button';
|
||||
import AdvancedGasFeePopover from '../advanced-gas-fee-popover';
|
||||
import CancelButton from '../cancel-button';
|
||||
import CancelSpeedupPopover from '../cancel-speedup-popover';
|
||||
import EditGasFeePopover from '../edit-gas-fee-popover';
|
||||
import EditGasPopover from '../edit-gas-popover';
|
||||
|
||||
export default function TransactionListItem({
|
||||
function TransactionListItemInner({
|
||||
transactionGroup,
|
||||
setEditGasMode,
|
||||
isEarliestNonce = false,
|
||||
}) {
|
||||
const t = useI18nContext();
|
||||
@ -33,6 +49,8 @@ export default function TransactionListItem({
|
||||
false,
|
||||
);
|
||||
const [showRetryEditGasPopover, setShowRetryEditGasPopover] = useState(false);
|
||||
const { supportsEIP1559V2 } = useGasFeeContext();
|
||||
const { openModal } = useTransactionModalContext();
|
||||
|
||||
const {
|
||||
initialTransaction: { id },
|
||||
@ -58,19 +76,29 @@ export default function TransactionListItem({
|
||||
const retryTransaction = useCallback(
|
||||
async (event) => {
|
||||
event.stopPropagation();
|
||||
setShowRetryEditGasPopover(true);
|
||||
speedUpMetricsEvent();
|
||||
if (supportsEIP1559V2) {
|
||||
setEditGasMode(EDIT_GAS_MODES.SPEED_UP);
|
||||
openModal('cancelSpeedUpTransaction');
|
||||
} else {
|
||||
setShowRetryEditGasPopover(true);
|
||||
}
|
||||
},
|
||||
[speedUpMetricsEvent],
|
||||
[openModal, setEditGasMode, speedUpMetricsEvent, supportsEIP1559V2],
|
||||
);
|
||||
|
||||
const cancelTransaction = useCallback(
|
||||
(event) => {
|
||||
event.stopPropagation();
|
||||
setShowCancelEditGasPopover(true);
|
||||
cancelMetricsEvent();
|
||||
if (supportsEIP1559V2) {
|
||||
setEditGasMode(EDIT_GAS_MODES.CANCEL);
|
||||
openModal('cancelSpeedUpTransaction');
|
||||
} else {
|
||||
setShowCancelEditGasPopover(true);
|
||||
}
|
||||
},
|
||||
[cancelMetricsEvent],
|
||||
[cancelMetricsEvent, openModal, setEditGasMode, supportsEIP1559V2],
|
||||
);
|
||||
|
||||
const shouldShowSpeedUp = useShouldShowSpeedUp(
|
||||
@ -224,14 +252,14 @@ export default function TransactionListItem({
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{showRetryEditGasPopover && (
|
||||
{!supportsEIP1559V2 && showRetryEditGasPopover && (
|
||||
<EditGasPopover
|
||||
onClose={() => setShowRetryEditGasPopover(false)}
|
||||
mode={EDIT_GAS_MODES.SPEED_UP}
|
||||
transaction={transactionGroup.primaryTransaction}
|
||||
/>
|
||||
)}
|
||||
{showCancelEditGasPopover && (
|
||||
{!supportsEIP1559V2 && showCancelEditGasPopover && (
|
||||
<EditGasPopover
|
||||
onClose={() => setShowCancelEditGasPopover(false)}
|
||||
mode={EDIT_GAS_MODES.CANCEL}
|
||||
@ -242,7 +270,46 @@ export default function TransactionListItem({
|
||||
);
|
||||
}
|
||||
|
||||
TransactionListItem.propTypes = {
|
||||
TransactionListItemInner.propTypes = {
|
||||
transactionGroup: PropTypes.object.isRequired,
|
||||
isEarliestNonce: PropTypes.bool,
|
||||
setEditGasMode: PropTypes.func,
|
||||
};
|
||||
|
||||
const TransactionListItem = (props) => {
|
||||
const { transactionGroup } = props;
|
||||
const [editGasMode, setEditGasMode] = useState();
|
||||
const transaction = transactionGroup.primaryTransaction;
|
||||
const EIP_1559_V2_ENABLED =
|
||||
process.env.EIP_1559_V2 === true || process.env.EIP_1559_V2 === 'true';
|
||||
|
||||
const supportsEIP1559 =
|
||||
useSelector(checkNetworkAndAccountSupports1559) &&
|
||||
!isLegacyTransaction(transaction?.txParams);
|
||||
|
||||
const supportsEIP1559V2 = EIP_1559_V2_ENABLED && supportsEIP1559;
|
||||
|
||||
return (
|
||||
<GasFeeContextProvider
|
||||
transaction={transactionGroup.primaryTransaction}
|
||||
editGasMode={editGasMode}
|
||||
>
|
||||
<TransactionModalContextProvider captureEventEnabled={false}>
|
||||
<TransactionListItemInner {...props} setEditGasMode={setEditGasMode} />
|
||||
{supportsEIP1559V2 && (
|
||||
<>
|
||||
<CancelSpeedupPopover />
|
||||
<EditGasFeePopover />
|
||||
<AdvancedGasFeePopover />
|
||||
</>
|
||||
)}
|
||||
</TransactionModalContextProvider>
|
||||
</GasFeeContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
TransactionListItem.propTypes = {
|
||||
transactionGroup: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default TransactionListItem;
|
||||
|
@ -41,7 +41,7 @@ const Popover = ({
|
||||
centerTitle ? 'center' : '',
|
||||
)}
|
||||
>
|
||||
<h2 title={title}>
|
||||
<h2 title="popover">
|
||||
{onBack ? (
|
||||
<button
|
||||
className="fas fa-chevron-left popover-header__button"
|
||||
@ -84,7 +84,7 @@ Popover.propTypes = {
|
||||
/**
|
||||
* Show title of the popover
|
||||
*/
|
||||
title: PropTypes.string,
|
||||
title: PropTypes.node,
|
||||
/**
|
||||
* Show subtitle label on popover
|
||||
*/
|
||||
|
@ -63,6 +63,7 @@ export const TransactionModalContextProvider = ({
|
||||
closeAllModals,
|
||||
currentModal: openModals[openModals.length - 1],
|
||||
openModal,
|
||||
openModalCount: openModals.length,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
52
ui/helpers/utils/gas.js
Normal file
52
ui/helpers/utils/gas.js
Normal file
@ -0,0 +1,52 @@
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { addHexPrefix } from 'ethereumjs-util';
|
||||
|
||||
import { multiplyCurrencies } from '../../../shared/modules/conversion.utils';
|
||||
import { bnGreaterThan } from './util';
|
||||
import { hexWEIToDecGWEI } from './conversions.util';
|
||||
|
||||
export const gasEstimateGreaterThanGasUsedPlusTenPercent = (
|
||||
transaction,
|
||||
gasFeeEstimates,
|
||||
estimate,
|
||||
) => {
|
||||
let { maxFeePerGas: maxFeePerGasInTransaction } = transaction.txParams;
|
||||
maxFeePerGasInTransaction = new BigNumber(
|
||||
hexWEIToDecGWEI(addTenPercentAndRound(maxFeePerGasInTransaction)),
|
||||
);
|
||||
|
||||
const maxFeePerGasFromEstimate =
|
||||
gasFeeEstimates[estimate]?.suggestedMaxFeePerGas;
|
||||
return bnGreaterThan(maxFeePerGasFromEstimate, maxFeePerGasInTransaction);
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple helper to save on duplication to multiply the supplied wei hex string
|
||||
* by 1.10 to get bare minimum new gas fee.
|
||||
*
|
||||
* @param {string | undefined} hexStringValue - hex value in wei to be incremented
|
||||
* @returns {string | undefined} - hex value in WEI 10% higher than the param.
|
||||
*/
|
||||
export function addTenPercent(hexStringValue, conversionOptions = {}) {
|
||||
if (hexStringValue === undefined) return undefined;
|
||||
return addHexPrefix(
|
||||
multiplyCurrencies(hexStringValue, 1.1, {
|
||||
toNumericBase: 'hex',
|
||||
multiplicandBase: 16,
|
||||
multiplierBase: 10,
|
||||
numberOfDecimals: 0,
|
||||
...conversionOptions,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple helper to save on duplication to multiply the supplied wei hex string
|
||||
* by 1.10 to get bare minimum new gas fee.
|
||||
*
|
||||
* @param {string | undefined} hexStringValue - hex value in wei to be incremented
|
||||
* @returns {string | undefined} - hex value in WEI 10% higher than the param.
|
||||
*/
|
||||
export function addTenPercentAndRound(hexStringValue) {
|
||||
return addTenPercent(hexStringValue, { numberOfDecimals: 0 });
|
||||
}
|
52
ui/helpers/utils/gas.test.js
Normal file
52
ui/helpers/utils/gas.test.js
Normal file
@ -0,0 +1,52 @@
|
||||
import { PRIORITY_LEVELS } from '../../../shared/constants/gas';
|
||||
|
||||
import {
|
||||
addTenPercent,
|
||||
gasEstimateGreaterThanGasUsedPlusTenPercent,
|
||||
} from './gas';
|
||||
|
||||
describe('Gas utils', () => {
|
||||
describe('gasEstimateGreaterThanGasUsedPlusTenPercent', () => {
|
||||
const compareGas = (estimateValues) => {
|
||||
return gasEstimateGreaterThanGasUsedPlusTenPercent(
|
||||
{
|
||||
txParams: {
|
||||
maxFeePerGas: '0x59682f10',
|
||||
maxPriorityFeePerGas: '0x59682f00',
|
||||
},
|
||||
},
|
||||
{
|
||||
medium: estimateValues,
|
||||
},
|
||||
PRIORITY_LEVELS.MEDIUM,
|
||||
);
|
||||
};
|
||||
|
||||
it('should return true if gas used in transaction + 10% is greater that estimate', () => {
|
||||
const result = compareGas({
|
||||
suggestedMaxPriorityFeePerGas: '7',
|
||||
suggestedMaxFeePerGas: '70',
|
||||
});
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
|
||||
it('should return false if gas used in transaction + 10% is less that estimate', () => {
|
||||
const result = compareGas({
|
||||
suggestedMaxPriorityFeePerGas: '.5',
|
||||
suggestedMaxFeePerGas: '1',
|
||||
});
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addTenPercent', () => {
|
||||
it('should add 10% to hex value passed', () => {
|
||||
const result = addTenPercent('0x59682f00');
|
||||
expect(result).toStrictEqual('0x62590080');
|
||||
});
|
||||
it('should return undefined if undefined value is passed', () => {
|
||||
const result = addTenPercent(undefined);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
@ -143,6 +143,7 @@ export function useGasEstimates({
|
||||
estimatedBaseFee: supportsEIP1559
|
||||
? decGWEIToHexWEI(gasFeeEstimates.estimatedBaseFee ?? '0')
|
||||
: undefined,
|
||||
maximumCostInHexWei,
|
||||
minimumCostInHexWei,
|
||||
};
|
||||
}
|
||||
|
@ -198,6 +198,7 @@ export function useGasFeeInputs(
|
||||
estimatedMinimumFiat,
|
||||
estimatedMaximumNative,
|
||||
estimatedMinimumNative,
|
||||
maximumCostInHexWei,
|
||||
minimumCostInHexWei,
|
||||
} = useGasEstimates({
|
||||
editGasMode,
|
||||
@ -244,13 +245,19 @@ export function useGasFeeInputs(
|
||||
}, [minimumGasLimit, gasErrors.gasLimit, transaction]);
|
||||
|
||||
const {
|
||||
cancelTransaction,
|
||||
speedUpTransaction,
|
||||
updateTransaction,
|
||||
updateTransactionUsingGasFeeEstimates,
|
||||
updateTransactionToMinimumGasFee,
|
||||
updateTransactionUsingDAPPSuggestedValues,
|
||||
updateTransactionUsingEstimate,
|
||||
} = useTransactionFunctions({
|
||||
defaultEstimateToUse,
|
||||
editGasMode,
|
||||
gasFeeEstimates,
|
||||
gasLimit,
|
||||
maxPriorityFeePerGas,
|
||||
minimumGasLimit,
|
||||
transaction,
|
||||
});
|
||||
|
||||
@ -322,6 +329,8 @@ export function useGasFeeInputs(
|
||||
estimatedMaximumNative,
|
||||
estimatedMinimumNative,
|
||||
isGasEstimatesLoading,
|
||||
maximumCostInHexWei,
|
||||
minimumCostInHexWei,
|
||||
estimateUsed,
|
||||
gasFeeEstimates,
|
||||
gasEstimateType,
|
||||
@ -338,7 +347,11 @@ export function useGasFeeInputs(
|
||||
minimumGasLimitDec: hexToDecimal(minimumGasLimit),
|
||||
supportsEIP1559,
|
||||
supportsEIP1559V2,
|
||||
cancelTransaction,
|
||||
speedUpTransaction,
|
||||
updateTransaction,
|
||||
updateTransactionUsingGasFeeEstimates,
|
||||
updateTransactionToMinimumGasFee,
|
||||
updateTransactionUsingDAPPSuggestedValues,
|
||||
updateTransactionUsingEstimate,
|
||||
};
|
||||
}
|
||||
|
144
ui/hooks/gasFeeInput/useTransactionFunction.test.js
Normal file
144
ui/hooks/gasFeeInput/useTransactionFunction.test.js
Normal file
@ -0,0 +1,144 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
|
||||
import {
|
||||
CUSTOM_GAS_ESTIMATE,
|
||||
EDIT_GAS_MODES,
|
||||
GAS_RECOMMENDATIONS,
|
||||
} from '../../../shared/constants/gas';
|
||||
import mockState from '../../../test/data/mock-state.json';
|
||||
import * as Actions from '../../store/actions';
|
||||
import configureStore from '../../store/store';
|
||||
import { useGasFeeEstimates } from '../useGasFeeEstimates';
|
||||
import { FEE_MARKET_ESTIMATE_RETURN_VALUE } from './test-utils';
|
||||
import { useTransactionFunctions } from './useTransactionFunctions';
|
||||
|
||||
jest.mock('../useGasFeeEstimates', () => ({
|
||||
useGasFeeEstimates: jest.fn(),
|
||||
}));
|
||||
useGasFeeEstimates.mockImplementation(() => FEE_MARKET_ESTIMATE_RETURN_VALUE);
|
||||
|
||||
jest.mock('../../selectors', () => ({
|
||||
checkNetworkAndAccountSupports1559: () => true,
|
||||
}));
|
||||
|
||||
const wrapper = ({ children }) => (
|
||||
<Provider store={configureStore(mockState)}>{children}</Provider>
|
||||
);
|
||||
|
||||
const renderUseTransactionFunctions = (props) => {
|
||||
return renderHook(
|
||||
() =>
|
||||
useTransactionFunctions({
|
||||
defaultEstimateToUse: GAS_RECOMMENDATIONS.MEDIUM,
|
||||
editGasMode: EDIT_GAS_MODES.MODIFY_IN_PLACE,
|
||||
estimatedBaseFee: '0x59682f10',
|
||||
gasFeeEstimates: FEE_MARKET_ESTIMATE_RETURN_VALUE.gasFeeEstimates,
|
||||
gasLimit: '21000',
|
||||
maxPriorityFeePerGas: '0x59682f10',
|
||||
transaction: {
|
||||
userFeeLevel: CUSTOM_GAS_ESTIMATE,
|
||||
txParams: { maxFeePerGas: '0x5028', maxPriorityFeePerGas: '0x5028' },
|
||||
},
|
||||
...props,
|
||||
}),
|
||||
{ wrapper },
|
||||
);
|
||||
};
|
||||
|
||||
describe('useMaxPriorityFeePerGasInput', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should invoke action createCancelTransaction when cancelTransaction callback is invoked', () => {
|
||||
const mock = jest
|
||||
.spyOn(Actions, 'createCancelTransaction')
|
||||
.mockImplementation(() => ({ type: '' }));
|
||||
const { result } = renderUseTransactionFunctions();
|
||||
result.current.cancelTransaction();
|
||||
expect(mock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should invoke action createSpeedUpTransaction when speedUpTransaction callback is invoked', () => {
|
||||
const mock = jest
|
||||
.spyOn(Actions, 'createSpeedUpTransaction')
|
||||
.mockImplementation(() => ({ type: '' }));
|
||||
const { result } = renderUseTransactionFunctions();
|
||||
result.current.speedUpTransaction();
|
||||
expect(mock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should invoke action updateTransaction with 10% increased fee when updateTransactionToMinimumGasFee callback is invoked', () => {
|
||||
const mock = jest
|
||||
.spyOn(Actions, 'updateTransaction')
|
||||
.mockImplementation(() => ({ type: '' }));
|
||||
const { result } = renderUseTransactionFunctions();
|
||||
result.current.updateTransactionToMinimumGasFee();
|
||||
expect(mock).toHaveBeenCalledTimes(1);
|
||||
expect(mock).toHaveBeenCalledWith({
|
||||
txParams: {
|
||||
estimateSuggested: 'medium',
|
||||
estimateUsed: 'minimum',
|
||||
gas: '5208',
|
||||
gasLimit: '5208',
|
||||
maxFeePerGas: '0x582c',
|
||||
maxPriorityFeePerGas: '0x582c',
|
||||
},
|
||||
userFeeLevel: 'minimum',
|
||||
});
|
||||
});
|
||||
|
||||
it('should invoke action updateTransaction with estimate gas values fee when updateTransactionUsingEstimate callback is invoked', () => {
|
||||
const mock = jest
|
||||
.spyOn(Actions, 'updateTransaction')
|
||||
.mockImplementation(() => ({ type: '' }));
|
||||
const { result } = renderUseTransactionFunctions();
|
||||
result.current.updateTransactionUsingEstimate(GAS_RECOMMENDATIONS.LOW);
|
||||
expect(mock).toHaveBeenCalledTimes(1);
|
||||
expect(mock).toHaveBeenCalledWith({
|
||||
txParams: {
|
||||
estimateSuggested: 'medium',
|
||||
estimateUsed: 'low',
|
||||
gas: '5208',
|
||||
gasLimit: '5208',
|
||||
maxFeePerGas: 'c570bd200',
|
||||
maxPriorityFeePerGas: 'b2d05e00',
|
||||
},
|
||||
userFeeLevel: 'low',
|
||||
});
|
||||
});
|
||||
|
||||
it('should invoke action updateTransaction with dappSuggestedValues values fee when updateTransactionUsingDAPPSuggestedValues callback is invoked', () => {
|
||||
const mock = jest
|
||||
.spyOn(Actions, 'updateTransaction')
|
||||
.mockImplementation(() => ({ type: '' }));
|
||||
const { result } = renderUseTransactionFunctions({
|
||||
transaction: {
|
||||
userFeeLevel: CUSTOM_GAS_ESTIMATE,
|
||||
dappSuggestedGasFees: {
|
||||
maxFeePerGas: '0x5028',
|
||||
maxPriorityFeePerGas: '0x5028',
|
||||
},
|
||||
},
|
||||
});
|
||||
result.current.updateTransactionUsingDAPPSuggestedValues();
|
||||
expect(mock).toHaveBeenCalledTimes(1);
|
||||
expect(mock).toHaveBeenCalledWith({
|
||||
dappSuggestedGasFees: {
|
||||
maxFeePerGas: '0x5028',
|
||||
maxPriorityFeePerGas: '0x5028',
|
||||
},
|
||||
txParams: {
|
||||
estimateSuggested: 'medium',
|
||||
estimateUsed: 'dappSuggested',
|
||||
gas: '5208',
|
||||
gasLimit: '5208',
|
||||
maxFeePerGas: '0x5028',
|
||||
maxPriorityFeePerGas: '0x5028',
|
||||
},
|
||||
userFeeLevel: 'dappSuggested',
|
||||
});
|
||||
});
|
||||
});
|
@ -6,7 +6,10 @@ import {
|
||||
decimalToHex,
|
||||
decGWEIToHexWEI,
|
||||
} from '../../helpers/utils/conversions.util';
|
||||
import { addTenPercentAndRound } from '../../helpers/utils/gas';
|
||||
import {
|
||||
createCancelTransaction,
|
||||
createSpeedUpTransaction,
|
||||
updateCustomSwapsEIP1559GasParams,
|
||||
updateSwapsUserFeeLevel,
|
||||
updateTransaction as updateTransactionFn,
|
||||
@ -15,22 +18,41 @@ import {
|
||||
export const useTransactionFunctions = ({
|
||||
defaultEstimateToUse,
|
||||
editGasMode,
|
||||
estimatedBaseFee,
|
||||
gasFeeEstimates,
|
||||
gasLimit: gasLimitInTransaction,
|
||||
gasLimit: gasLimitValue,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGasValue,
|
||||
transaction,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const updateTransaction = useCallback(
|
||||
({
|
||||
estimateUsed,
|
||||
const getTxMeta = useCallback(() => {
|
||||
if (
|
||||
(editGasMode !== EDIT_GAS_MODES.CANCEL &&
|
||||
editGasMode !== EDIT_GAS_MODES.SPEED_UP) ||
|
||||
transaction.previousGas
|
||||
) {
|
||||
return {};
|
||||
}
|
||||
const {
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
gasLimit = gasLimitInTransaction,
|
||||
}) => {
|
||||
gasLimit,
|
||||
} = transaction?.txParams;
|
||||
return {
|
||||
previousGas: {
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
gasLimit,
|
||||
},
|
||||
};
|
||||
}, [editGasMode, transaction?.previousGas, transaction?.txParams]);
|
||||
|
||||
const updateTransaction = useCallback(
|
||||
({ estimateUsed, gasLimit, maxFeePerGas, maxPriorityFeePerGas }) => {
|
||||
const newGasSettings = {
|
||||
gas: decimalToHex(gasLimit),
|
||||
gasLimit: decimalToHex(gasLimit),
|
||||
gas: decimalToHex(gasLimit || gasLimitValue),
|
||||
gasLimit: decimalToHex(gasLimit || gasLimitValue),
|
||||
estimateSuggested: defaultEstimateToUse,
|
||||
estimateUsed,
|
||||
};
|
||||
@ -38,8 +60,10 @@ export const useTransactionFunctions = ({
|
||||
newGasSettings.maxFeePerGas = maxFeePerGas;
|
||||
}
|
||||
if (maxPriorityFeePerGas) {
|
||||
newGasSettings.maxPriorityFeePerGas = maxPriorityFeePerGas;
|
||||
newGasSettings.maxPriorityFeePerGas =
|
||||
maxPriorityFeePerGas || decGWEIToHexWEI(maxPriorityFeePerGasValue);
|
||||
}
|
||||
const txMeta = getTxMeta();
|
||||
|
||||
const updatedTxMeta = {
|
||||
...transaction,
|
||||
@ -48,6 +72,7 @@ export const useTransactionFunctions = ({
|
||||
...transaction.txParams,
|
||||
...newGasSettings,
|
||||
},
|
||||
...txMeta,
|
||||
};
|
||||
|
||||
if (editGasMode === EDIT_GAS_MODES.SWAPS) {
|
||||
@ -63,24 +88,46 @@ export const useTransactionFunctions = ({
|
||||
defaultEstimateToUse,
|
||||
dispatch,
|
||||
editGasMode,
|
||||
gasLimitInTransaction,
|
||||
gasLimitValue,
|
||||
getTxMeta,
|
||||
maxPriorityFeePerGasValue,
|
||||
transaction,
|
||||
],
|
||||
);
|
||||
|
||||
const updateTransactionUsingGasFeeEstimates = useCallback(
|
||||
(gasFeeEstimateToUse) => {
|
||||
if (gasFeeEstimateToUse === PRIORITY_LEVELS.DAPP_SUGGESTED) {
|
||||
const {
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
} = transaction?.dappSuggestedGasFees;
|
||||
const cancelTransaction = useCallback(() => {
|
||||
dispatch(
|
||||
createCancelTransaction(transaction.id, transaction.txParams, {
|
||||
estimatedBaseFee,
|
||||
}),
|
||||
);
|
||||
}, [dispatch, estimatedBaseFee, transaction]);
|
||||
|
||||
const speedUpTransaction = useCallback(() => {
|
||||
dispatch(
|
||||
createSpeedUpTransaction(transaction.id, transaction.txParams, {
|
||||
estimatedBaseFee,
|
||||
}),
|
||||
);
|
||||
}, [dispatch, estimatedBaseFee, transaction]);
|
||||
|
||||
const updateTransactionToMinimumGasFee = useCallback(() => {
|
||||
const { gas: gasLimit, maxFeePerGas, maxPriorityFeePerGas } =
|
||||
transaction.previousGas || transaction.txParams;
|
||||
|
||||
updateTransaction({
|
||||
estimateUsed: PRIORITY_LEVELS.DAPP_SUGGESTED,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
estimateUsed: PRIORITY_LEVELS.MINIMUM,
|
||||
gasLimit,
|
||||
maxFeePerGas: addTenPercentAndRound(maxFeePerGas),
|
||||
maxPriorityFeePerGas: addTenPercentAndRound(maxPriorityFeePerGas),
|
||||
});
|
||||
} else {
|
||||
}, [transaction, updateTransaction]);
|
||||
|
||||
const updateTransactionUsingEstimate = useCallback(
|
||||
(gasFeeEstimateToUse) => {
|
||||
if (!gasFeeEstimates[gasFeeEstimateToUse]) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
suggestedMaxFeePerGas,
|
||||
suggestedMaxPriorityFeePerGas,
|
||||
@ -90,10 +137,28 @@ export const useTransactionFunctions = ({
|
||||
maxFeePerGas: decGWEIToHexWEI(suggestedMaxFeePerGas),
|
||||
maxPriorityFeePerGas: decGWEIToHexWEI(suggestedMaxPriorityFeePerGas),
|
||||
});
|
||||
}
|
||||
},
|
||||
[gasFeeEstimates, transaction?.dappSuggestedGasFees, updateTransaction],
|
||||
[gasFeeEstimates, updateTransaction],
|
||||
);
|
||||
|
||||
return { updateTransaction, updateTransactionUsingGasFeeEstimates };
|
||||
const updateTransactionUsingDAPPSuggestedValues = useCallback(() => {
|
||||
const {
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
} = transaction?.dappSuggestedGasFees;
|
||||
updateTransaction({
|
||||
estimateUsed: PRIORITY_LEVELS.DAPP_SUGGESTED,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
});
|
||||
}, [transaction, updateTransaction]);
|
||||
|
||||
return {
|
||||
cancelTransaction,
|
||||
speedUpTransaction,
|
||||
updateTransaction,
|
||||
updateTransactionToMinimumGasFee,
|
||||
updateTransactionUsingDAPPSuggestedValues,
|
||||
updateTransactionUsingEstimate,
|
||||
};
|
||||
};
|
||||
|
@ -1,28 +1,10 @@
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { addHexPrefix } from 'ethereumjs-util';
|
||||
import { useMemo } from 'react';
|
||||
import { multiplyCurrencies } from '../../shared/modules/conversion.utils';
|
||||
import { isEIP1559Transaction } from '../../shared/modules/transaction.utils';
|
||||
import { decGWEIToHexWEI } from '../helpers/utils/conversions.util';
|
||||
import { addTenPercent } from '../helpers/utils/gas';
|
||||
import { useGasFeeEstimates } from './useGasFeeEstimates';
|
||||
|
||||
/**
|
||||
* Simple helper to save on duplication to multiply the supplied wei hex string
|
||||
* by 1.10 to get bare minimum new gas fee.
|
||||
*
|
||||
* @param {string} hexStringValue - hex value in wei to be incremented
|
||||
* @returns {string} - hex value in WEI 10% higher than the param.
|
||||
*/
|
||||
function addTenPercent(hexStringValue) {
|
||||
return addHexPrefix(
|
||||
multiplyCurrencies(hexStringValue, 1.1, {
|
||||
toNumericBase: 'hex',
|
||||
multiplicandBase: 16,
|
||||
multiplierBase: 10,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper that returns the higher of two options for a new gas fee:
|
||||
* The original fee + 10% or
|
||||
|
@ -41,6 +41,7 @@ import TransactionDetail from '../../components/app/transaction-detail/transacti
|
||||
import TransactionDetailItem from '../../components/app/transaction-detail-item/transaction-detail-item.component';
|
||||
import InfoTooltip from '../../components/ui/info-tooltip/info-tooltip';
|
||||
import LoadingHeartBeat from '../../components/ui/loading-heartbeat';
|
||||
import GasDetailsItem from '../../components/app/gas-details-item';
|
||||
import GasTiming from '../../components/app/gas-timing/gas-timing.component';
|
||||
import LedgerInstructionField from '../../components/app/ledger-instruction-field';
|
||||
import MultiLayerFeeMessage from '../../components/app/multilayer-fee-message';
|
||||
@ -60,11 +61,11 @@ import {
|
||||
import Typography from '../../components/ui/typography/typography';
|
||||
import { MIN_GAS_LIMIT_DEC } from '../send/send.constants';
|
||||
|
||||
import GasDetailsItem from './gas-details-item';
|
||||
import TransactionAlerts from './transaction-alerts';
|
||||
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
const EIP_1559_V2_ENABLED = process.env.EIP_1559_V2;
|
||||
const EIP_1559_V2_ENABLED =
|
||||
process.env.EIP_1559_V2 === true || process.env.EIP_1559_V2 === 'true';
|
||||
|
||||
const renderHeartBeatIfNotInTest = () =>
|
||||
process.env.IN_TEST ? null : <LoadingHeartBeat />;
|
||||
@ -437,15 +438,6 @@ export default class ConfirmTransactionBase extends Component {
|
||||
return this.supportsEIP1559V2 ? (
|
||||
<GasDetailsItem
|
||||
key="gas_details"
|
||||
hexMaximumTransactionFee={hexMaximumTransactionFee}
|
||||
hexMinimumTransactionFee={hexMinimumTransactionFee}
|
||||
maxFeePerGas={maxFeePerGas}
|
||||
maxPriorityFeePerGas={maxPriorityFeePerGas}
|
||||
supportsEIP1559={supportsEIP1559}
|
||||
txData={txData}
|
||||
useNativeCurrencyAsPrimaryCurrency={
|
||||
useNativeCurrencyAsPrimaryCurrency
|
||||
}
|
||||
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
|
||||
/>
|
||||
) : (
|
||||
|
@ -5,8 +5,6 @@
|
||||
@import 'confirm-approve/index';
|
||||
@import 'confirm-decrypt-message/confirm-decrypt-message';
|
||||
@import 'confirm-encryption-public-key/confirm-encryption-public-key';
|
||||
@import 'confirm-transaction-base/gas-details-item/gas-details-item';
|
||||
@import 'confirm-transaction-base/gas-details-item/gas-details-item-title/gas-details-item-title';
|
||||
@import 'confirm-transaction-base/transaction-alerts/transaction-alerts';
|
||||
@import 'confirmation/confirmation';
|
||||
@import 'connected-sites/index';
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
TYPOGRAPHY,
|
||||
FONT_WEIGHT,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import GasDetailsItemTitle from '../../confirm-transaction-base/gas-details-item/gas-details-item-title';
|
||||
import GasDetailsItemTitle from '../../../components/app/gas-details-item/gas-details-item-title';
|
||||
|
||||
const GAS_FEES_LEARN_MORE_URL =
|
||||
'https://community.metamask.io/t/what-is-gas-why-do-transactions-take-so-long/3172';
|
||||
|
@ -408,6 +408,10 @@ export function getGasIsLoading(state) {
|
||||
return state.appState.gasIsLoading;
|
||||
}
|
||||
|
||||
export function getAppIsLoading(state) {
|
||||
return state.appState.isLoading;
|
||||
}
|
||||
|
||||
export function getCurrentCurrency(state) {
|
||||
return state.metamask.currentCurrency;
|
||||
}
|
||||
|
@ -240,4 +240,8 @@ describe('Selectors', () => {
|
||||
);
|
||||
expect(isAdvancedGasFeeDefault).toStrictEqual(true);
|
||||
});
|
||||
it('#getAppIsLoading', () => {
|
||||
const appIsLoading = selectors.getAppIsLoading(mockState);
|
||||
expect(appIsLoading).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user