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": {
|
"cancelSpeedUp": {
|
||||||
"message": "cancel or speed up a tranaction."
|
"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": {
|
"cancellationGasFee": {
|
||||||
"message": "Cancellation Gas Fee"
|
"message": "Cancellation Gas Fee"
|
||||||
},
|
},
|
||||||
@ -739,6 +747,10 @@
|
|||||||
"directDepositEtherExplainer": {
|
"directDepositEtherExplainer": {
|
||||||
"message": "If you already have some Ether, the quickest way to get Ether in your new wallet by direct deposit."
|
"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": {
|
"disconnect": {
|
||||||
"message": "Disconnect"
|
"message": "Disconnect"
|
||||||
},
|
},
|
||||||
@ -793,6 +805,9 @@
|
|||||||
"editAddressNickname": {
|
"editAddressNickname": {
|
||||||
"message": "Edit address nickname"
|
"message": "Edit address nickname"
|
||||||
},
|
},
|
||||||
|
"editCancellationGasFeeModalTitle": {
|
||||||
|
"message": "Edit cancellation gas fee"
|
||||||
|
},
|
||||||
"editContact": {
|
"editContact": {
|
||||||
"message": "Edit Contact"
|
"message": "Edit Contact"
|
||||||
},
|
},
|
||||||
@ -921,6 +936,9 @@
|
|||||||
"editPermission": {
|
"editPermission": {
|
||||||
"message": "Edit Permission"
|
"message": "Edit Permission"
|
||||||
},
|
},
|
||||||
|
"editSpeedUpEditGasFeeModalTitle": {
|
||||||
|
"message": "Edit speed up gas fee"
|
||||||
|
},
|
||||||
"enableAutoDetect": {
|
"enableAutoDetect": {
|
||||||
"message": " Enable Autodetect"
|
"message": " Enable Autodetect"
|
||||||
},
|
},
|
||||||
@ -1157,6 +1175,9 @@
|
|||||||
"functionType": {
|
"functionType": {
|
||||||
"message": "Function Type"
|
"message": "Function Type"
|
||||||
},
|
},
|
||||||
|
"gas": {
|
||||||
|
"message": "Gas"
|
||||||
|
},
|
||||||
"gasDisplayAcknowledgeDappButtonText": {
|
"gasDisplayAcknowledgeDappButtonText": {
|
||||||
"message": "Edit suggested gas fee"
|
"message": "Edit suggested gas fee"
|
||||||
},
|
},
|
||||||
@ -1709,6 +1730,15 @@
|
|||||||
"metametricsTitle": {
|
"metametricsTitle": {
|
||||||
"message": "Join 6M+ users to improve MetaMask"
|
"message": "Join 6M+ users to improve MetaMask"
|
||||||
},
|
},
|
||||||
|
"minimum": {
|
||||||
|
"message": "minimum"
|
||||||
|
},
|
||||||
|
"minimumCancelSpeedupGasFee": {
|
||||||
|
"message": "+10%"
|
||||||
|
},
|
||||||
|
"minimumEstimate": {
|
||||||
|
"message": "10% Minimum"
|
||||||
|
},
|
||||||
"mismatchedChain": {
|
"mismatchedChain": {
|
||||||
"message": "The network details for this chain ID do not match our records. We recommend that you $1 before proceeding.",
|
"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"
|
"description": "$1 is a clickable link with text defined by the 'mismatchedChainLinkText' key"
|
||||||
@ -2289,6 +2319,9 @@
|
|||||||
"removeNFT": {
|
"removeNFT": {
|
||||||
"message": "Remove NFT"
|
"message": "Remove NFT"
|
||||||
},
|
},
|
||||||
|
"replace": {
|
||||||
|
"message": "replace"
|
||||||
|
},
|
||||||
"requestsAwaitingAcknowledgement": {
|
"requestsAwaitingAcknowledgement": {
|
||||||
"message": "requests waiting to be acknowledged"
|
"message": "requests waiting to be acknowledged"
|
||||||
},
|
},
|
||||||
@ -3169,9 +3202,6 @@
|
|||||||
"transactionDetailGasHeading": {
|
"transactionDetailGasHeading": {
|
||||||
"message": "Estimated gas fee"
|
"message": "Estimated gas fee"
|
||||||
},
|
},
|
||||||
"transactionDetailGasHeadingV2": {
|
|
||||||
"message": "Gas"
|
|
||||||
},
|
|
||||||
"transactionDetailGasInfoV2": {
|
"transactionDetailGasInfoV2": {
|
||||||
"message": "estimated"
|
"message": "estimated"
|
||||||
},
|
},
|
||||||
|
@ -34,6 +34,7 @@ export const GAS_RECOMMENDATIONS = {
|
|||||||
* These represent types of gas estimation
|
* These represent types of gas estimation
|
||||||
*/
|
*/
|
||||||
export const PRIORITY_LEVELS = {
|
export const PRIORITY_LEVELS = {
|
||||||
|
MINIMUM: 'minimum',
|
||||||
LOW: 'low',
|
LOW: 'low',
|
||||||
MEDIUM: 'medium',
|
MEDIUM: 'medium',
|
||||||
HIGH: 'high',
|
HIGH: 'high',
|
||||||
|
@ -120,7 +120,7 @@ const converter = ({
|
|||||||
convertedValue = toSpecifiedDenomination[toDenomination](convertedValue);
|
convertedValue = toSpecifiedDenomination[toDenomination](convertedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (numberOfDecimals) {
|
if (numberOfDecimals !== undefined && numberOfDecimals !== null) {
|
||||||
convertedValue = convertedValue.round(
|
convertedValue = convertedValue.round(
|
||||||
numberOfDecimals,
|
numberOfDecimals,
|
||||||
BigNumber.ROUND_HALF_DOWN,
|
BigNumber.ROUND_HALF_DOWN,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"appState": {
|
"appState": {
|
||||||
|
"isLoading": false,
|
||||||
"gasIsLoading": false,
|
"gasIsLoading": false,
|
||||||
"currentView": {
|
"currentView": {
|
||||||
"name": "accountDetail",
|
"name": "accountDetail",
|
||||||
|
@ -14,8 +14,8 @@ import AdvancedGasFeeDefaults from './advanced-gas-fee-defaults';
|
|||||||
const AdvancedGasFeePopover = () => {
|
const AdvancedGasFeePopover = () => {
|
||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
const {
|
const {
|
||||||
closeModal,
|
|
||||||
closeAllModals,
|
closeAllModals,
|
||||||
|
closeModal,
|
||||||
currentModal,
|
currentModal,
|
||||||
} = useTransactionModalContext();
|
} = useTransactionModalContext();
|
||||||
|
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
/** Please import your files in alphabetical order **/
|
/** Please import your files in alphabetical order **/
|
||||||
@import 'account-list-item/index';
|
@import 'account-list-item/index';
|
||||||
@import 'account-menu/index';
|
@import 'account-menu/index';
|
||||||
|
@import 'app-loading-spinner/index';
|
||||||
@import 'import-token-link/index';
|
@import 'import-token-link/index';
|
||||||
@import 'advanced-gas-controls/index';
|
@import 'advanced-gas-controls/index';
|
||||||
@import 'alerts/alerts';
|
@import 'alerts/alerts';
|
||||||
@import 'app-header/index';
|
@import 'app-header/index';
|
||||||
@import 'asset-list-item/asset-list-item';
|
@import 'asset-list-item/asset-list-item';
|
||||||
|
@import 'cancel-speedup-popover/index';
|
||||||
@import 'confirm-page-container/index';
|
@import 'confirm-page-container/index';
|
||||||
@import 'collectibles-items/index';
|
@import 'collectibles-items/index';
|
||||||
@import 'collectibles-tab/index';
|
@import 'collectibles-tab/index';
|
||||||
@ -28,6 +30,8 @@
|
|||||||
@import 'gas-customization/gas-modal-page-container/index';
|
@import 'gas-customization/gas-modal-page-container/index';
|
||||||
@import 'gas-customization/gas-price-button-group/index';
|
@import 'gas-customization/gas-price-button-group/index';
|
||||||
@import 'gas-customization/index';
|
@import 'gas-customization/index';
|
||||||
|
@import 'gas-details-item/index';
|
||||||
|
@import 'gas-details-item/gas-details-item-title/index';
|
||||||
@import 'gas-timing/index';
|
@import 'gas-timing/index';
|
||||||
@import 'home-notification/index';
|
@import 'home-notification/index';
|
||||||
@import 'info-box/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';
|
icon = 'swapSuggested';
|
||||||
title = 'swapSuggested';
|
title = 'swapSuggested';
|
||||||
|
} else if (estimateUsed === PRIORITY_LEVELS.MINIMUM) {
|
||||||
|
icon = undefined;
|
||||||
|
title = 'minimumEstimate';
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="edit-gas-fee-button">
|
<div className="edit-gas-fee-button">
|
||||||
<button onClick={() => openModal('editGasFee')}>
|
<button onClick={() => openModal('editGasFee')}>
|
||||||
<span className="edit-gas-fee-button__icon">
|
{icon && (
|
||||||
{`${PRIORITY_LEVEL_ICON_MAP[icon]} `}
|
<span className="edit-gas-fee-button__icon">
|
||||||
</span>
|
{`${PRIORITY_LEVEL_ICON_MAP[icon]} `}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<span className="edit-gas-fee-button__label">{t(title)}</span>
|
<span className="edit-gas-fee-button__label">{t(title)}</span>
|
||||||
<i className="fas fa-chevron-right asset-list-item__chevron-right" />
|
<i className="fas fa-chevron-right asset-list-item__chevron-right" />
|
||||||
</button>
|
</button>
|
||||||
|
@ -73,6 +73,11 @@ describe('EditGasFeeButton', () => {
|
|||||||
expect(screen.queryByText('Aggressive')).toBeInTheDocument();
|
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', () => {
|
it('should render edit link with text Site suggested if site suggested estimated are used', () => {
|
||||||
render({
|
render({
|
||||||
contextProps: {
|
contextProps: {
|
||||||
@ -103,6 +108,7 @@ describe('EditGasFeeButton', () => {
|
|||||||
render({
|
render({
|
||||||
contextProps: {
|
contextProps: {
|
||||||
defaultEstimateToUse: 'custom',
|
defaultEstimateToUse: 'custom',
|
||||||
|
transaction: {},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(screen.queryByText('⚙')).toBeInTheDocument();
|
expect(screen.queryByText('⚙')).toBeInTheDocument();
|
||||||
|
@ -14,23 +14,39 @@ import Typography from '../../ui/typography/typography';
|
|||||||
import { COLORS, TYPOGRAPHY } from '../../../helpers/constants/design-system';
|
import { COLORS, TYPOGRAPHY } from '../../../helpers/constants/design-system';
|
||||||
import { INSUFFICIENT_FUNDS_ERROR_KEY } from '../../../helpers/constants/error-keys';
|
import { INSUFFICIENT_FUNDS_ERROR_KEY } from '../../../helpers/constants/error-keys';
|
||||||
import { useGasFeeContext } from '../../../contexts/gasFee';
|
import { useGasFeeContext } from '../../../contexts/gasFee';
|
||||||
|
import AppLoadingSpinner from '../app-loading-spinner';
|
||||||
import EditGasItem from './edit-gas-item';
|
import EditGasItem from './edit-gas-item';
|
||||||
import NetworkStatistics from './network-statistics';
|
import NetworkStatistics from './network-statistics';
|
||||||
|
|
||||||
const EditGasFeePopover = () => {
|
const EditGasFeePopover = () => {
|
||||||
const { balanceError, editGasMode } = useGasFeeContext();
|
const { balanceError, editGasMode } = useGasFeeContext();
|
||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
const { closeModal, currentModal } = useTransactionModalContext();
|
const {
|
||||||
|
closeAllModals,
|
||||||
|
closeModal,
|
||||||
|
currentModal,
|
||||||
|
openModalCount,
|
||||||
|
} = useTransactionModalContext();
|
||||||
|
|
||||||
if (currentModal !== 'editGasFee') return null;
|
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 (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
title={t('editGasFeeModalTitle')}
|
title={t(popupTitle)}
|
||||||
onClose={() => closeModal('editGasFee')}
|
// 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"
|
className="edit-gas-fee-popover"
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
|
<AppLoadingSpinner />
|
||||||
<div className="edit-gas-fee-popover__wrapper">
|
<div className="edit-gas-fee-popover__wrapper">
|
||||||
<div className="edit-gas-fee-popover__content">
|
<div className="edit-gas-fee-popover__content">
|
||||||
{balanceError && (
|
{balanceError && (
|
||||||
@ -49,13 +65,17 @@ const EditGasFeePopover = () => {
|
|||||||
<I18nValue messageKey="maxFee" />
|
<I18nValue messageKey="maxFee" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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.LOW} />
|
||||||
)}
|
)}
|
||||||
<EditGasItem priorityLevel={PRIORITY_LEVELS.MEDIUM} />
|
<EditGasItem priorityLevel={PRIORITY_LEVELS.MEDIUM} />
|
||||||
<EditGasItem priorityLevel={PRIORITY_LEVELS.HIGH} />
|
<EditGasItem priorityLevel={PRIORITY_LEVELS.HIGH} />
|
||||||
<div className="edit-gas-fee-popover__content__separator" />
|
<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.DAPP_SUGGESTED} />
|
||||||
)}
|
)}
|
||||||
<EditGasItem priorityLevel={PRIORITY_LEVELS.CUSTOM} />
|
<EditGasItem priorityLevel={PRIORITY_LEVELS.CUSTOM} />
|
||||||
|
@ -28,22 +28,24 @@ const MOCK_FEE_ESTIMATE = {
|
|||||||
low: {
|
low: {
|
||||||
minWaitTimeEstimate: 360000,
|
minWaitTimeEstimate: 360000,
|
||||||
maxWaitTimeEstimate: 300000,
|
maxWaitTimeEstimate: 300000,
|
||||||
suggestedMaxPriorityFeePerGas: '3',
|
suggestedMaxPriorityFeePerGas: 3,
|
||||||
suggestedMaxFeePerGas: '53',
|
suggestedMaxFeePerGas: 53,
|
||||||
},
|
},
|
||||||
medium: {
|
medium: {
|
||||||
minWaitTimeEstimate: 30000,
|
minWaitTimeEstimate: 30000,
|
||||||
maxWaitTimeEstimate: 60000,
|
maxWaitTimeEstimate: 60000,
|
||||||
suggestedMaxPriorityFeePerGas: '7',
|
suggestedMaxPriorityFeePerGas: 7,
|
||||||
suggestedMaxFeePerGas: '70',
|
suggestedMaxFeePerGas: 70,
|
||||||
},
|
},
|
||||||
high: {
|
high: {
|
||||||
minWaitTimeEstimate: 15000,
|
minWaitTimeEstimate: 15000,
|
||||||
maxWaitTimeEstimate: 15000,
|
maxWaitTimeEstimate: 15000,
|
||||||
suggestedMaxPriorityFeePerGas: '10',
|
suggestedMaxPriorityFeePerGas: 10,
|
||||||
suggestedMaxFeePerGas: '100',
|
suggestedMaxFeePerGas: 100,
|
||||||
},
|
},
|
||||||
estimatedBaseFee: '50',
|
latestPriorityFeeRange: [2, 6],
|
||||||
|
estimatedBaseFee: 50,
|
||||||
|
networkCongestion: 0.7,
|
||||||
};
|
};
|
||||||
|
|
||||||
const render = ({ txProps, contextProps } = {}) => {
|
const render = ({ txProps, contextProps } = {}) => {
|
||||||
@ -141,4 +143,47 @@ describe('EditGasFeePopover', () => {
|
|||||||
expect(screen.queryByText('Time')).not.toBeInTheDocument();
|
expect(screen.queryByText('Time')).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText('Max fee')).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 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 {
|
import {
|
||||||
EDIT_GAS_MODES,
|
EDIT_GAS_MODES,
|
||||||
PRIORITY_LEVELS,
|
PRIORITY_LEVELS,
|
||||||
} from '../../../../../shared/constants/gas';
|
} from '../../../../../shared/constants/gas';
|
||||||
|
import {
|
||||||
|
ALIGN_ITEMS,
|
||||||
|
DISPLAY,
|
||||||
|
} from '../../../../helpers/constants/design-system';
|
||||||
import { PRIORITY_LEVEL_ICON_MAP } from '../../../../helpers/constants/gas';
|
import { PRIORITY_LEVEL_ICON_MAP } from '../../../../helpers/constants/gas';
|
||||||
import { PRIMARY } from '../../../../helpers/constants/common';
|
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 { toHumanReadableTime } from '../../../../helpers/utils/util';
|
||||||
import { useGasFeeContext } from '../../../../contexts/gasFee';
|
import { useGasFeeContext } from '../../../../contexts/gasFee';
|
||||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||||
import { useTransactionModalContext } from '../../../../contexts/transaction-modal';
|
import { useTransactionModalContext } from '../../../../contexts/transaction-modal';
|
||||||
import I18nValue from '../../../ui/i18n-value';
|
import Box from '../../../ui/box';
|
||||||
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display';
|
|
||||||
|
|
||||||
import EditGasToolTip from '../edit-gas-tooltip/edit-gas-tooltip';
|
import EditGasToolTip from '../edit-gas-tooltip/edit-gas-tooltip';
|
||||||
import InfoTooltip from '../../../ui/info-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 EditGasItem = ({ priorityLevel }) => {
|
||||||
const {
|
const {
|
||||||
editGasMode,
|
editGasMode,
|
||||||
estimateUsed,
|
estimateUsed,
|
||||||
gasFeeEstimates,
|
|
||||||
gasLimit,
|
gasLimit,
|
||||||
maxFeePerGas: maxFeePerGasValue,
|
updateTransactionToMinimumGasFee,
|
||||||
maxPriorityFeePerGas: maxPriorityFeePerGasValue,
|
updateTransactionUsingDAPPSuggestedValues,
|
||||||
updateTransactionUsingGasFeeEstimates,
|
updateTransactionUsingEstimate,
|
||||||
transaction,
|
transaction,
|
||||||
} = useGasFeeContext();
|
} = useGasFeeContext();
|
||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues);
|
|
||||||
const { closeModal, openModal } = useTransactionModalContext();
|
const { closeModal, openModal } = useTransactionModalContext();
|
||||||
const { dappSuggestedGasFees } = transaction;
|
const { dappSuggestedGasFees } = transaction;
|
||||||
|
|
||||||
let maxFeePerGas;
|
const {
|
||||||
let maxPriorityFeePerGas;
|
// for cancel or speedup estimateGreaterThaGasUse is true if previous gas used
|
||||||
let minWaitTime;
|
// was more than estimate for the priorityLevel
|
||||||
|
estimateGreaterThanGasUse,
|
||||||
if (gasFeeEstimates?.[priorityLevel]) {
|
hexMaximumTransactionFee,
|
||||||
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,
|
|
||||||
maxFeePerGas,
|
maxFeePerGas,
|
||||||
maxPriorityFeePerGas,
|
maxPriorityFeePerGas,
|
||||||
});
|
minWaitTime,
|
||||||
|
} = useGasItemFeeDetails(priorityLevel);
|
||||||
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');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED &&
|
priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED &&
|
||||||
@ -112,34 +78,44 @@ const EditGasItem = ({ priorityLevel }) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let icon = priorityLevel;
|
const onOptionSelect = () => {
|
||||||
let title = priorityLevel;
|
if (priorityLevel === PRIORITY_LEVELS.CUSTOM) {
|
||||||
if (priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED) {
|
openModal('advancedGasFee');
|
||||||
title = 'dappSuggestedShortLabel';
|
} else {
|
||||||
} else if (
|
closeModal('editGasFee');
|
||||||
priorityLevel === PRIORITY_LEVELS.HIGH &&
|
|
||||||
editGasMode === EDIT_GAS_MODES.SWAPS
|
if (priorityLevel === PRIORITY_LEVELS.MINIMUM) {
|
||||||
) {
|
updateTransactionToMinimumGasFee();
|
||||||
icon = 'swapSuggested';
|
} else if (priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED) {
|
||||||
title = 'swapSuggested';
|
updateTransactionUsingDAPPSuggestedValues();
|
||||||
}
|
} else {
|
||||||
|
updateTransactionUsingEstimate(priorityLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { title, icon } = getTitleAndIcon(priorityLevel, t, editGasMode);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={classNames('edit-gas-item', {
|
className={classNames('edit-gas-item', {
|
||||||
'edit-gas-item--selected': priorityLevel === estimateUsed,
|
'edit-gas-item--selected': priorityLevel === estimateUsed,
|
||||||
|
'edit-gas-item--disabled': estimateGreaterThanGasUse,
|
||||||
})}
|
})}
|
||||||
onClick={onOptionSelect}
|
onClick={onOptionSelect}
|
||||||
aria-label={priorityLevel}
|
aria-label={priorityLevel}
|
||||||
autoFocus={priorityLevel === estimateUsed}
|
autoFocus={priorityLevel === estimateUsed}
|
||||||
|
disabled={estimateGreaterThanGasUse}
|
||||||
>
|
>
|
||||||
<span className="edit-gas-item__name">
|
<span className="edit-gas-item__name">
|
||||||
<span
|
{icon && (
|
||||||
className={`edit-gas-item__icon edit-gas-item__icon-${priorityLevel}`}
|
<span
|
||||||
>
|
className={`edit-gas-item__icon edit-gas-item__icon-${priorityLevel}`}
|
||||||
{PRIORITY_LEVEL_ICON_MAP[icon]}
|
>
|
||||||
</span>
|
{PRIORITY_LEVEL_ICON_MAP[icon]}
|
||||||
<I18nValue messageKey={title} />
|
</span>
|
||||||
|
)}
|
||||||
|
{title}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={`edit-gas-item__time-estimate edit-gas-item__time-estimate-${priorityLevel}`}
|
className={`edit-gas-item__time-estimate edit-gas-item__time-estimate-${priorityLevel}`}
|
||||||
@ -174,6 +150,7 @@ const EditGasItem = ({ priorityLevel }) => {
|
|||||||
editGasMode={editGasMode}
|
editGasMode={editGasMode}
|
||||||
gasLimit={gasLimit}
|
gasLimit={gasLimit}
|
||||||
transaction={transaction}
|
transaction={transaction}
|
||||||
|
estimateGreaterThanGasUse={estimateGreaterThanGasUse}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
position="top"
|
position="top"
|
||||||
|
@ -42,7 +42,7 @@ const MOCK_FEE_ESTIMATE = {
|
|||||||
estimatedBaseFee: '50',
|
estimatedBaseFee: '50',
|
||||||
};
|
};
|
||||||
|
|
||||||
const DAPP_SUGGESTED_ESTIMATE = {
|
const ESTIMATE_MOCK = {
|
||||||
maxFeePerGas: '0x59682f10',
|
maxFeePerGas: '0x59682f10',
|
||||||
maxPriorityFeePerGas: '0x59682f00',
|
maxPriorityFeePerGas: '0x59682f00',
|
||||||
};
|
};
|
||||||
@ -65,6 +65,7 @@ const renderComponent = ({
|
|||||||
},
|
},
|
||||||
selectedAddress: '0xAddress',
|
selectedAddress: '0xAddress',
|
||||||
featureFlags: { advancedInlineGas: true },
|
featureFlags: { advancedInlineGas: true },
|
||||||
|
gasEstimateType: 'fee-market',
|
||||||
gasFeeEstimates: MOCK_FEE_ESTIMATE,
|
gasFeeEstimates: MOCK_FEE_ESTIMATE,
|
||||||
advancedGasFee: {
|
advancedGasFee: {
|
||||||
maxBaseFee: '1.5',
|
maxBaseFee: '1.5',
|
||||||
@ -139,7 +140,7 @@ describe('EditGasItem', () => {
|
|||||||
it('should renders site gas estimate option for priorityLevel dappSuggested', () => {
|
it('should renders site gas estimate option for priorityLevel dappSuggested', () => {
|
||||||
renderComponent({
|
renderComponent({
|
||||||
componentProps: { priorityLevel: 'dappSuggested' },
|
componentProps: { priorityLevel: 'dappSuggested' },
|
||||||
transactionProps: { dappSuggestedGasFees: DAPP_SUGGESTED_ESTIMATE },
|
transactionProps: { dappSuggestedGasFees: ESTIMATE_MOCK },
|
||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
screen.queryByRole('button', { name: 'dappSuggested' }),
|
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
|
// below value of custom gas fee estimate is default obtained from state.metamask.advancedGasFee
|
||||||
expect(screen.queryByTitle('0.001575 ETH')).toBeInTheDocument();
|
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;
|
background-color: $ui-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.edit-gas-item--disabled[disabled] {
|
||||||
|
opacity: 0.25;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
&__name {
|
&__name {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -27,6 +32,11 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
width: 36%;
|
width: 36%;
|
||||||
|
|
||||||
|
&__sufix {
|
||||||
|
font-weight: 400;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__icon {
|
&__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';
|
import Typography from '../../../ui/typography';
|
||||||
|
|
||||||
const EditGasToolTip = ({
|
const EditGasToolTip = ({
|
||||||
|
editGasMode,
|
||||||
|
estimateGreaterThanGasUse,
|
||||||
gasLimit,
|
gasLimit,
|
||||||
priorityLevel,
|
priorityLevel,
|
||||||
// maxFeePerGas & maxPriorityFeePerGas are derived from conditional logic
|
// maxFeePerGas & maxPriorityFeePerGas are derived from conditional logic
|
||||||
@ -19,7 +21,6 @@ const EditGasToolTip = ({
|
|||||||
// the parent component (edit-gas-item) rather than recalculate them
|
// the parent component (edit-gas-item) rather than recalculate them
|
||||||
maxFeePerGas,
|
maxFeePerGas,
|
||||||
maxPriorityFeePerGas,
|
maxPriorityFeePerGas,
|
||||||
editGasMode,
|
|
||||||
transaction,
|
transaction,
|
||||||
t,
|
t,
|
||||||
}) => {
|
}) => {
|
||||||
@ -32,12 +33,26 @@ const EditGasToolTip = ({
|
|||||||
</span>,
|
</span>,
|
||||||
]);
|
]);
|
||||||
case PRIORITY_LEVELS.MEDIUM:
|
case PRIORITY_LEVELS.MEDIUM:
|
||||||
|
if (estimateGreaterThanGasUse) {
|
||||||
|
return t('disabledGasOptionToolTipMessage', [
|
||||||
|
<span key={`disabled-priority-level-${priorityLevel}`}>
|
||||||
|
{t(priorityLevel)}
|
||||||
|
</span>,
|
||||||
|
]);
|
||||||
|
}
|
||||||
return t('mediumGasSettingToolTipMessage', [
|
return t('mediumGasSettingToolTipMessage', [
|
||||||
<span key={priorityLevel}>
|
<span key={priorityLevel}>
|
||||||
<b>{t('medium')}</b>
|
<b>{t('medium')}</b>
|
||||||
</span>,
|
</span>,
|
||||||
]);
|
]);
|
||||||
case PRIORITY_LEVELS.HIGH:
|
case PRIORITY_LEVELS.HIGH:
|
||||||
|
if (estimateGreaterThanGasUse) {
|
||||||
|
return t('disabledGasOptionToolTipMessage', [
|
||||||
|
<span key={`disabled-priority-level-${priorityLevel}`}>
|
||||||
|
{t(priorityLevel)}
|
||||||
|
</span>,
|
||||||
|
]);
|
||||||
|
}
|
||||||
if (editGasMode === EDIT_GAS_MODES.SWAPS) {
|
if (editGasMode === EDIT_GAS_MODES.SWAPS) {
|
||||||
return t('swapSuggestedGasSettingToolTipMessage');
|
return t('swapSuggestedGasSettingToolTipMessage');
|
||||||
}
|
}
|
||||||
@ -69,11 +84,13 @@ const EditGasToolTip = ({
|
|||||||
!(
|
!(
|
||||||
priorityLevel === PRIORITY_LEVELS.HIGH &&
|
priorityLevel === PRIORITY_LEVELS.HIGH &&
|
||||||
editGasMode === EDIT_GAS_MODES.SWAPS
|
editGasMode === EDIT_GAS_MODES.SWAPS
|
||||||
) ? (
|
) &&
|
||||||
|
!estimateGreaterThanGasUse ? (
|
||||||
<img alt="" src={`./images/curve-${priorityLevel}.svg`} />
|
<img alt="" src={`./images/curve-${priorityLevel}.svg`} />
|
||||||
) : null}
|
) : null}
|
||||||
{priorityLevel === PRIORITY_LEVELS.HIGH &&
|
{priorityLevel === PRIORITY_LEVELS.HIGH &&
|
||||||
editGasMode !== EDIT_GAS_MODES.SWAPS ? (
|
editGasMode !== EDIT_GAS_MODES.SWAPS &&
|
||||||
|
!estimateGreaterThanGasUse ? (
|
||||||
<div className="edit-gas-tooltip__container__dialog">
|
<div className="edit-gas-tooltip__container__dialog">
|
||||||
<Typography variant={TYPOGRAPHY.H7} color={COLORS.WHITE}>
|
<Typography variant={TYPOGRAPHY.H7} color={COLORS.WHITE}>
|
||||||
{t('highGasSettingToolTipDialog')}
|
{t('highGasSettingToolTipDialog')}
|
||||||
@ -83,7 +100,8 @@ const EditGasToolTip = ({
|
|||||||
<div className="edit-gas-tooltip__container__message">
|
<div className="edit-gas-tooltip__container__message">
|
||||||
<Typography variant={TYPOGRAPHY.H7}>{toolTipMessage()}</Typography>
|
<Typography variant={TYPOGRAPHY.H7}>{toolTipMessage()}</Typography>
|
||||||
</div>
|
</div>
|
||||||
{priorityLevel === PRIORITY_LEVELS.CUSTOM ? null : (
|
{priorityLevel === PRIORITY_LEVELS.CUSTOM ||
|
||||||
|
estimateGreaterThanGasUse ? null : (
|
||||||
<div className="edit-gas-tooltip__container__values">
|
<div className="edit-gas-tooltip__container__values">
|
||||||
<div>
|
<div>
|
||||||
<Typography
|
<Typography
|
||||||
@ -140,9 +158,13 @@ const EditGasToolTip = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
EditGasToolTip.propTypes = {
|
EditGasToolTip.propTypes = {
|
||||||
|
estimateGreaterThanGasUse: PropTypes.bool,
|
||||||
priorityLevel: PropTypes.string,
|
priorityLevel: PropTypes.string,
|
||||||
maxFeePerGas: PropTypes.string,
|
maxFeePerGas: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||||
maxPriorityFeePerGas: PropTypes.string,
|
maxPriorityFeePerGas: PropTypes.oneOfType([
|
||||||
|
PropTypes.number,
|
||||||
|
PropTypes.string,
|
||||||
|
]),
|
||||||
t: PropTypes.func,
|
t: PropTypes.func,
|
||||||
editGasMode: PropTypes.string,
|
editGasMode: PropTypes.string,
|
||||||
gasLimit: PropTypes.number,
|
gasLimit: PropTypes.number,
|
||||||
|
@ -4,10 +4,10 @@ import { useSelector } from 'react-redux';
|
|||||||
import { TYPOGRAPHY } from '../../../../helpers/constants/design-system';
|
import { TYPOGRAPHY } from '../../../../helpers/constants/design-system';
|
||||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||||
import { getIsMainnet } from '../../../../selectors';
|
import { getIsMainnet } from '../../../../selectors';
|
||||||
import Box from '../../../../components/ui/box';
|
import Box from '../../../ui/box';
|
||||||
import I18nValue from '../../../../components/ui/i18n-value';
|
import I18nValue from '../../../ui/i18n-value';
|
||||||
import InfoTooltip from '../../../../components/ui/info-tooltip/info-tooltip';
|
import InfoTooltip from '../../../ui/info-tooltip/info-tooltip';
|
||||||
import Typography from '../../../../components/ui/typography/typography';
|
import Typography from '../../../ui/typography/typography';
|
||||||
|
|
||||||
const GasDetailsItemTitle = () => {
|
const GasDetailsItemTitle = () => {
|
||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
@ -16,7 +16,7 @@ const GasDetailsItemTitle = () => {
|
|||||||
return (
|
return (
|
||||||
<Box display="flex">
|
<Box display="flex">
|
||||||
<Box marginRight={1}>
|
<Box marginRight={1}>
|
||||||
<I18nValue messageKey="transactionDetailGasHeadingV2" />
|
<I18nValue messageKey="gas" />
|
||||||
</Box>
|
</Box>
|
||||||
<span className="gas-details-item-title__estimate">
|
<span className="gas-details-item-title__estimate">
|
||||||
(<I18nValue messageKey="transactionDetailGasInfoV2" />)
|
(<I18nValue messageKey="transactionDetailGasInfoV2" />)
|
@ -1,29 +1,32 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { COLORS } from '../../../helpers/constants/design-system';
|
import { COLORS } from '../../../helpers/constants/design-system';
|
||||||
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common';
|
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common';
|
||||||
import { hexWEIToDecGWEI } from '../../../helpers/utils/conversions.util';
|
import { hexWEIToDecGWEI } from '../../../helpers/utils/conversions.util';
|
||||||
|
import { getPreferences } from '../../../selectors';
|
||||||
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 { useGasFeeContext } from '../../../contexts/gasFee';
|
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';
|
import GasDetailsItemTitle from './gas-details-item-title';
|
||||||
|
|
||||||
const GasDetailsItem = ({
|
const GasDetailsItem = ({ userAcknowledgedGasMissing = false }) => {
|
||||||
hexMaximumTransactionFee,
|
const {
|
||||||
hexMinimumTransactionFee,
|
estimateUsed,
|
||||||
maxFeePerGas,
|
hasSimulationError,
|
||||||
maxPriorityFeePerGas,
|
maximumCostInHexWei: hexMaximumTransactionFee,
|
||||||
userAcknowledgedGasMissing,
|
minimumCostInHexWei: hexMinimumTransactionFee,
|
||||||
useNativeCurrencyAsPrimaryCurrency,
|
transaction,
|
||||||
}) => {
|
} = useGasFeeContext();
|
||||||
const { estimateUsed, hasSimulationError, transaction } = useGasFeeContext();
|
|
||||||
|
const { useNativeCurrencyAsPrimaryCurrency } = useSelector(getPreferences);
|
||||||
|
|
||||||
if (hasSimulationError && !userAcknowledgedGasMissing) return null;
|
if (hasSimulationError && !userAcknowledgedGasMissing) return null;
|
||||||
|
|
||||||
@ -86,11 +89,9 @@ const GasDetailsItem = ({
|
|||||||
subTitle={
|
subTitle={
|
||||||
<GasTiming
|
<GasTiming
|
||||||
maxPriorityFeePerGas={hexWEIToDecGWEI(
|
maxPriorityFeePerGas={hexWEIToDecGWEI(
|
||||||
maxPriorityFeePerGas || transaction.txParams.maxPriorityFeePerGas,
|
transaction.txParams.maxPriorityFeePerGas,
|
||||||
)}
|
|
||||||
maxFeePerGas={hexWEIToDecGWEI(
|
|
||||||
maxFeePerGas || transaction.txParams.maxFeePerGas,
|
|
||||||
)}
|
)}
|
||||||
|
maxFeePerGas={hexWEIToDecGWEI(transaction.txParams.maxFeePerGas)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -98,12 +99,7 @@ const GasDetailsItem = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
GasDetailsItem.propTypes = {
|
GasDetailsItem.propTypes = {
|
||||||
hexMaximumTransactionFee: PropTypes.string,
|
userAcknowledgedGasMissing: PropTypes.bool,
|
||||||
hexMinimumTransactionFee: PropTypes.string,
|
|
||||||
maxFeePerGas: PropTypes.string,
|
|
||||||
maxPriorityFeePerGas: PropTypes.string,
|
|
||||||
userAcknowledgedGasMissing: PropTypes.bool.isRequired,
|
|
||||||
useNativeCurrencyAsPrimaryCurrency: PropTypes.bool,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GasDetailsItem;
|
export default GasDetailsItem;
|
@ -1,7 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { screen, waitFor } from '@testing-library/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 { GasFeeContextProvider } from '../../../contexts/gasFee';
|
||||||
import { renderWithProvider } from '../../../../test/jest';
|
import { renderWithProvider } from '../../../../test/jest';
|
||||||
import configureStore from '../../../store/store';
|
import configureStore from '../../../store/store';
|
||||||
@ -17,28 +19,36 @@ jest.mock('../../../store/actions', () => ({
|
|||||||
getGasFeeTimeEstimate: jest.fn().mockImplementation(() => Promise.resolve()),
|
getGasFeeTimeEstimate: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const render = ({ componentProps, contextProps } = {}) => {
|
const render = ({ contextProps } = {}) => {
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
metamask: {
|
metamask: {
|
||||||
nativeCurrency: ETH,
|
...mockState.metamask,
|
||||||
|
accounts: {
|
||||||
|
[mockState.metamask.selectedAddress]: {
|
||||||
|
address: mockState.metamask.selectedAddress,
|
||||||
|
balance: '0x1F4',
|
||||||
|
},
|
||||||
|
},
|
||||||
preferences: {
|
preferences: {
|
||||||
useNativeCurrencyAsPrimaryCurrency: true,
|
useNativeCurrencyAsPrimaryCurrency: true,
|
||||||
},
|
},
|
||||||
provider: {},
|
gasFeeEstimates: mockEstimates[GAS_ESTIMATE_TYPES.FEE_MARKET],
|
||||||
cachedBalances: {},
|
|
||||||
accounts: {
|
|
||||||
'0xAddress': {
|
|
||||||
address: '0xAddress',
|
|
||||||
balance: '0x176e5b6f173ebe66',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
selectedAddress: '0xAddress',
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return renderWithProvider(
|
return renderWithProvider(
|
||||||
<GasFeeContextProvider transaction={{ txParams: {} }} {...contextProps}>
|
<GasFeeContextProvider
|
||||||
<GasDetailsItem userAcknowledgedGasMissing={false} {...componentProps} />
|
transaction={{
|
||||||
|
txParams: {
|
||||||
|
gas: '0x5208',
|
||||||
|
maxFeePerGas: '0x59682f10',
|
||||||
|
maxPriorityFeePerGas: '0x59682f00',
|
||||||
|
},
|
||||||
|
userFeeLevel: 'medium',
|
||||||
|
}}
|
||||||
|
{...contextProps}
|
||||||
|
>
|
||||||
|
<GasDetailsItem userAcknowledgedGasMissing={false} />
|
||||||
</GasFeeContextProvider>,
|
</GasFeeContextProvider>,
|
||||||
store,
|
store,
|
||||||
);
|
);
|
||||||
@ -51,7 +61,7 @@ describe('GasDetailsItem', () => {
|
|||||||
expect(screen.queryByText('Gas')).toBeInTheDocument();
|
expect(screen.queryByText('Gas')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('(estimated)')).toBeInTheDocument();
|
expect(screen.queryByText('(estimated)')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Max fee:')).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 () => {
|
it('should render gas fee details', async () => {
|
||||||
render({
|
render();
|
||||||
componentProps: {
|
|
||||||
hexMinimumTransactionFee: '0x1ca62a4f7800',
|
|
||||||
hexMaximumTransactionFee: '0x290ee75e3d900',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.queryByTitle('0.0000315 ETH')).toBeInTheDocument();
|
expect(screen.queryAllByTitle('0.0000315 ETH').length).toBeGreaterThan(0);
|
||||||
expect(screen.queryByText('ETH')).toBeInTheDocument();
|
expect(screen.queryAllByText('ETH').length).toBeGreaterThan(0);
|
||||||
expect(screen.queryByTitle('0.0007223')).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -6,5 +6,6 @@
|
|||||||
&__currency-container,
|
&__currency-container,
|
||||||
&__gasfee-label {
|
&__gasfee-label {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,6 +2,8 @@ import React, { useMemo, useState, useCallback } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import ListItem from '../../ui/list-item';
|
import ListItem from '../../ui/list-item';
|
||||||
import { useTransactionDisplayData } from '../../../hooks/useTransactionDisplayData';
|
import { useTransactionDisplayData } from '../../../hooks/useTransactionDisplayData';
|
||||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||||
@ -16,13 +18,27 @@ import {
|
|||||||
TRANSACTION_STATUSES,
|
TRANSACTION_STATUSES,
|
||||||
} from '../../../../shared/constants/transaction';
|
} from '../../../../shared/constants/transaction';
|
||||||
import { EDIT_GAS_MODES } from '../../../../shared/constants/gas';
|
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 { useMetricEvent } from '../../../hooks/useMetricEvent';
|
||||||
import Button from '../../ui/button';
|
import Button from '../../ui/button';
|
||||||
|
import AdvancedGasFeePopover from '../advanced-gas-fee-popover';
|
||||||
import CancelButton from '../cancel-button';
|
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,
|
transactionGroup,
|
||||||
|
setEditGasMode,
|
||||||
isEarliestNonce = false,
|
isEarliestNonce = false,
|
||||||
}) {
|
}) {
|
||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
@ -33,6 +49,8 @@ export default function TransactionListItem({
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
const [showRetryEditGasPopover, setShowRetryEditGasPopover] = useState(false);
|
const [showRetryEditGasPopover, setShowRetryEditGasPopover] = useState(false);
|
||||||
|
const { supportsEIP1559V2 } = useGasFeeContext();
|
||||||
|
const { openModal } = useTransactionModalContext();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
initialTransaction: { id },
|
initialTransaction: { id },
|
||||||
@ -58,19 +76,29 @@ export default function TransactionListItem({
|
|||||||
const retryTransaction = useCallback(
|
const retryTransaction = useCallback(
|
||||||
async (event) => {
|
async (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
setShowRetryEditGasPopover(true);
|
|
||||||
speedUpMetricsEvent();
|
speedUpMetricsEvent();
|
||||||
|
if (supportsEIP1559V2) {
|
||||||
|
setEditGasMode(EDIT_GAS_MODES.SPEED_UP);
|
||||||
|
openModal('cancelSpeedUpTransaction');
|
||||||
|
} else {
|
||||||
|
setShowRetryEditGasPopover(true);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[speedUpMetricsEvent],
|
[openModal, setEditGasMode, speedUpMetricsEvent, supportsEIP1559V2],
|
||||||
);
|
);
|
||||||
|
|
||||||
const cancelTransaction = useCallback(
|
const cancelTransaction = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
setShowCancelEditGasPopover(true);
|
|
||||||
cancelMetricsEvent();
|
cancelMetricsEvent();
|
||||||
|
if (supportsEIP1559V2) {
|
||||||
|
setEditGasMode(EDIT_GAS_MODES.CANCEL);
|
||||||
|
openModal('cancelSpeedUpTransaction');
|
||||||
|
} else {
|
||||||
|
setShowCancelEditGasPopover(true);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[cancelMetricsEvent],
|
[cancelMetricsEvent, openModal, setEditGasMode, supportsEIP1559V2],
|
||||||
);
|
);
|
||||||
|
|
||||||
const shouldShowSpeedUp = useShouldShowSpeedUp(
|
const shouldShowSpeedUp = useShouldShowSpeedUp(
|
||||||
@ -224,14 +252,14 @@ export default function TransactionListItem({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{showRetryEditGasPopover && (
|
{!supportsEIP1559V2 && showRetryEditGasPopover && (
|
||||||
<EditGasPopover
|
<EditGasPopover
|
||||||
onClose={() => setShowRetryEditGasPopover(false)}
|
onClose={() => setShowRetryEditGasPopover(false)}
|
||||||
mode={EDIT_GAS_MODES.SPEED_UP}
|
mode={EDIT_GAS_MODES.SPEED_UP}
|
||||||
transaction={transactionGroup.primaryTransaction}
|
transaction={transactionGroup.primaryTransaction}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{showCancelEditGasPopover && (
|
{!supportsEIP1559V2 && showCancelEditGasPopover && (
|
||||||
<EditGasPopover
|
<EditGasPopover
|
||||||
onClose={() => setShowCancelEditGasPopover(false)}
|
onClose={() => setShowCancelEditGasPopover(false)}
|
||||||
mode={EDIT_GAS_MODES.CANCEL}
|
mode={EDIT_GAS_MODES.CANCEL}
|
||||||
@ -242,7 +270,46 @@ export default function TransactionListItem({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
TransactionListItem.propTypes = {
|
TransactionListItemInner.propTypes = {
|
||||||
transactionGroup: PropTypes.object.isRequired,
|
transactionGroup: PropTypes.object.isRequired,
|
||||||
isEarliestNonce: PropTypes.bool,
|
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' : '',
|
centerTitle ? 'center' : '',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<h2 title={title}>
|
<h2 title="popover">
|
||||||
{onBack ? (
|
{onBack ? (
|
||||||
<button
|
<button
|
||||||
className="fas fa-chevron-left popover-header__button"
|
className="fas fa-chevron-left popover-header__button"
|
||||||
@ -84,7 +84,7 @@ Popover.propTypes = {
|
|||||||
/**
|
/**
|
||||||
* Show title of the popover
|
* Show title of the popover
|
||||||
*/
|
*/
|
||||||
title: PropTypes.string,
|
title: PropTypes.node,
|
||||||
/**
|
/**
|
||||||
* Show subtitle label on popover
|
* Show subtitle label on popover
|
||||||
*/
|
*/
|
||||||
|
@ -63,6 +63,7 @@ export const TransactionModalContextProvider = ({
|
|||||||
closeAllModals,
|
closeAllModals,
|
||||||
currentModal: openModals[openModals.length - 1],
|
currentModal: openModals[openModals.length - 1],
|
||||||
openModal,
|
openModal,
|
||||||
|
openModalCount: openModals.length,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{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
|
estimatedBaseFee: supportsEIP1559
|
||||||
? decGWEIToHexWEI(gasFeeEstimates.estimatedBaseFee ?? '0')
|
? decGWEIToHexWEI(gasFeeEstimates.estimatedBaseFee ?? '0')
|
||||||
: undefined,
|
: undefined,
|
||||||
|
maximumCostInHexWei,
|
||||||
minimumCostInHexWei,
|
minimumCostInHexWei,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -198,6 +198,7 @@ export function useGasFeeInputs(
|
|||||||
estimatedMinimumFiat,
|
estimatedMinimumFiat,
|
||||||
estimatedMaximumNative,
|
estimatedMaximumNative,
|
||||||
estimatedMinimumNative,
|
estimatedMinimumNative,
|
||||||
|
maximumCostInHexWei,
|
||||||
minimumCostInHexWei,
|
minimumCostInHexWei,
|
||||||
} = useGasEstimates({
|
} = useGasEstimates({
|
||||||
editGasMode,
|
editGasMode,
|
||||||
@ -244,13 +245,19 @@ export function useGasFeeInputs(
|
|||||||
}, [minimumGasLimit, gasErrors.gasLimit, transaction]);
|
}, [minimumGasLimit, gasErrors.gasLimit, transaction]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
cancelTransaction,
|
||||||
|
speedUpTransaction,
|
||||||
updateTransaction,
|
updateTransaction,
|
||||||
updateTransactionUsingGasFeeEstimates,
|
updateTransactionToMinimumGasFee,
|
||||||
|
updateTransactionUsingDAPPSuggestedValues,
|
||||||
|
updateTransactionUsingEstimate,
|
||||||
} = useTransactionFunctions({
|
} = useTransactionFunctions({
|
||||||
defaultEstimateToUse,
|
defaultEstimateToUse,
|
||||||
editGasMode,
|
editGasMode,
|
||||||
gasFeeEstimates,
|
gasFeeEstimates,
|
||||||
gasLimit,
|
gasLimit,
|
||||||
|
maxPriorityFeePerGas,
|
||||||
|
minimumGasLimit,
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -322,6 +329,8 @@ export function useGasFeeInputs(
|
|||||||
estimatedMaximumNative,
|
estimatedMaximumNative,
|
||||||
estimatedMinimumNative,
|
estimatedMinimumNative,
|
||||||
isGasEstimatesLoading,
|
isGasEstimatesLoading,
|
||||||
|
maximumCostInHexWei,
|
||||||
|
minimumCostInHexWei,
|
||||||
estimateUsed,
|
estimateUsed,
|
||||||
gasFeeEstimates,
|
gasFeeEstimates,
|
||||||
gasEstimateType,
|
gasEstimateType,
|
||||||
@ -338,7 +347,11 @@ export function useGasFeeInputs(
|
|||||||
minimumGasLimitDec: hexToDecimal(minimumGasLimit),
|
minimumGasLimitDec: hexToDecimal(minimumGasLimit),
|
||||||
supportsEIP1559,
|
supportsEIP1559,
|
||||||
supportsEIP1559V2,
|
supportsEIP1559V2,
|
||||||
|
cancelTransaction,
|
||||||
|
speedUpTransaction,
|
||||||
updateTransaction,
|
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,
|
decimalToHex,
|
||||||
decGWEIToHexWEI,
|
decGWEIToHexWEI,
|
||||||
} from '../../helpers/utils/conversions.util';
|
} from '../../helpers/utils/conversions.util';
|
||||||
|
import { addTenPercentAndRound } from '../../helpers/utils/gas';
|
||||||
import {
|
import {
|
||||||
|
createCancelTransaction,
|
||||||
|
createSpeedUpTransaction,
|
||||||
updateCustomSwapsEIP1559GasParams,
|
updateCustomSwapsEIP1559GasParams,
|
||||||
updateSwapsUserFeeLevel,
|
updateSwapsUserFeeLevel,
|
||||||
updateTransaction as updateTransactionFn,
|
updateTransaction as updateTransactionFn,
|
||||||
@ -15,22 +18,41 @@ import {
|
|||||||
export const useTransactionFunctions = ({
|
export const useTransactionFunctions = ({
|
||||||
defaultEstimateToUse,
|
defaultEstimateToUse,
|
||||||
editGasMode,
|
editGasMode,
|
||||||
|
estimatedBaseFee,
|
||||||
gasFeeEstimates,
|
gasFeeEstimates,
|
||||||
gasLimit: gasLimitInTransaction,
|
gasLimit: gasLimitValue,
|
||||||
|
maxPriorityFeePerGas: maxPriorityFeePerGasValue,
|
||||||
transaction,
|
transaction,
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const updateTransaction = useCallback(
|
const getTxMeta = useCallback(() => {
|
||||||
({
|
if (
|
||||||
estimateUsed,
|
(editGasMode !== EDIT_GAS_MODES.CANCEL &&
|
||||||
|
editGasMode !== EDIT_GAS_MODES.SPEED_UP) ||
|
||||||
|
transaction.previousGas
|
||||||
|
) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const {
|
||||||
maxFeePerGas,
|
maxFeePerGas,
|
||||||
maxPriorityFeePerGas,
|
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 = {
|
const newGasSettings = {
|
||||||
gas: decimalToHex(gasLimit),
|
gas: decimalToHex(gasLimit || gasLimitValue),
|
||||||
gasLimit: decimalToHex(gasLimit),
|
gasLimit: decimalToHex(gasLimit || gasLimitValue),
|
||||||
estimateSuggested: defaultEstimateToUse,
|
estimateSuggested: defaultEstimateToUse,
|
||||||
estimateUsed,
|
estimateUsed,
|
||||||
};
|
};
|
||||||
@ -38,8 +60,10 @@ export const useTransactionFunctions = ({
|
|||||||
newGasSettings.maxFeePerGas = maxFeePerGas;
|
newGasSettings.maxFeePerGas = maxFeePerGas;
|
||||||
}
|
}
|
||||||
if (maxPriorityFeePerGas) {
|
if (maxPriorityFeePerGas) {
|
||||||
newGasSettings.maxPriorityFeePerGas = maxPriorityFeePerGas;
|
newGasSettings.maxPriorityFeePerGas =
|
||||||
|
maxPriorityFeePerGas || decGWEIToHexWEI(maxPriorityFeePerGasValue);
|
||||||
}
|
}
|
||||||
|
const txMeta = getTxMeta();
|
||||||
|
|
||||||
const updatedTxMeta = {
|
const updatedTxMeta = {
|
||||||
...transaction,
|
...transaction,
|
||||||
@ -48,6 +72,7 @@ export const useTransactionFunctions = ({
|
|||||||
...transaction.txParams,
|
...transaction.txParams,
|
||||||
...newGasSettings,
|
...newGasSettings,
|
||||||
},
|
},
|
||||||
|
...txMeta,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (editGasMode === EDIT_GAS_MODES.SWAPS) {
|
if (editGasMode === EDIT_GAS_MODES.SWAPS) {
|
||||||
@ -63,37 +88,77 @@ export const useTransactionFunctions = ({
|
|||||||
defaultEstimateToUse,
|
defaultEstimateToUse,
|
||||||
dispatch,
|
dispatch,
|
||||||
editGasMode,
|
editGasMode,
|
||||||
gasLimitInTransaction,
|
gasLimitValue,
|
||||||
|
getTxMeta,
|
||||||
|
maxPriorityFeePerGasValue,
|
||||||
transaction,
|
transaction,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateTransactionUsingGasFeeEstimates = useCallback(
|
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.MINIMUM,
|
||||||
|
gasLimit,
|
||||||
|
maxFeePerGas: addTenPercentAndRound(maxFeePerGas),
|
||||||
|
maxPriorityFeePerGas: addTenPercentAndRound(maxPriorityFeePerGas),
|
||||||
|
});
|
||||||
|
}, [transaction, updateTransaction]);
|
||||||
|
|
||||||
|
const updateTransactionUsingEstimate = useCallback(
|
||||||
(gasFeeEstimateToUse) => {
|
(gasFeeEstimateToUse) => {
|
||||||
if (gasFeeEstimateToUse === PRIORITY_LEVELS.DAPP_SUGGESTED) {
|
if (!gasFeeEstimates[gasFeeEstimateToUse]) {
|
||||||
const {
|
return;
|
||||||
maxFeePerGas,
|
|
||||||
maxPriorityFeePerGas,
|
|
||||||
} = transaction?.dappSuggestedGasFees;
|
|
||||||
updateTransaction({
|
|
||||||
estimateUsed: PRIORITY_LEVELS.DAPP_SUGGESTED,
|
|
||||||
maxFeePerGas,
|
|
||||||
maxPriorityFeePerGas,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const {
|
|
||||||
suggestedMaxFeePerGas,
|
|
||||||
suggestedMaxPriorityFeePerGas,
|
|
||||||
} = gasFeeEstimates[gasFeeEstimateToUse];
|
|
||||||
updateTransaction({
|
|
||||||
estimateUsed: gasFeeEstimateToUse,
|
|
||||||
maxFeePerGas: decGWEIToHexWEI(suggestedMaxFeePerGas),
|
|
||||||
maxPriorityFeePerGas: decGWEIToHexWEI(suggestedMaxPriorityFeePerGas),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
const {
|
||||||
|
suggestedMaxFeePerGas,
|
||||||
|
suggestedMaxPriorityFeePerGas,
|
||||||
|
} = gasFeeEstimates[gasFeeEstimateToUse];
|
||||||
|
updateTransaction({
|
||||||
|
estimateUsed: gasFeeEstimateToUse,
|
||||||
|
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 BigNumber from 'bignumber.js';
|
||||||
import { addHexPrefix } from 'ethereumjs-util';
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { multiplyCurrencies } from '../../shared/modules/conversion.utils';
|
|
||||||
import { isEIP1559Transaction } from '../../shared/modules/transaction.utils';
|
import { isEIP1559Transaction } from '../../shared/modules/transaction.utils';
|
||||||
import { decGWEIToHexWEI } from '../helpers/utils/conversions.util';
|
import { decGWEIToHexWEI } from '../helpers/utils/conversions.util';
|
||||||
|
import { addTenPercent } from '../helpers/utils/gas';
|
||||||
import { useGasFeeEstimates } from './useGasFeeEstimates';
|
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:
|
* Helper that returns the higher of two options for a new gas fee:
|
||||||
* The original fee + 10% or
|
* 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 TransactionDetailItem from '../../components/app/transaction-detail-item/transaction-detail-item.component';
|
||||||
import InfoTooltip from '../../components/ui/info-tooltip/info-tooltip';
|
import InfoTooltip from '../../components/ui/info-tooltip/info-tooltip';
|
||||||
import LoadingHeartBeat from '../../components/ui/loading-heartbeat';
|
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 GasTiming from '../../components/app/gas-timing/gas-timing.component';
|
||||||
import LedgerInstructionField from '../../components/app/ledger-instruction-field';
|
import LedgerInstructionField from '../../components/app/ledger-instruction-field';
|
||||||
import MultiLayerFeeMessage from '../../components/app/multilayer-fee-message';
|
import MultiLayerFeeMessage from '../../components/app/multilayer-fee-message';
|
||||||
@ -60,11 +61,11 @@ import {
|
|||||||
import Typography from '../../components/ui/typography/typography';
|
import Typography from '../../components/ui/typography/typography';
|
||||||
import { MIN_GAS_LIMIT_DEC } from '../send/send.constants';
|
import { MIN_GAS_LIMIT_DEC } from '../send/send.constants';
|
||||||
|
|
||||||
import GasDetailsItem from './gas-details-item';
|
|
||||||
import TransactionAlerts from './transaction-alerts';
|
import TransactionAlerts from './transaction-alerts';
|
||||||
|
|
||||||
// eslint-disable-next-line prefer-destructuring
|
// 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 = () =>
|
const renderHeartBeatIfNotInTest = () =>
|
||||||
process.env.IN_TEST ? null : <LoadingHeartBeat />;
|
process.env.IN_TEST ? null : <LoadingHeartBeat />;
|
||||||
@ -437,15 +438,6 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
return this.supportsEIP1559V2 ? (
|
return this.supportsEIP1559V2 ? (
|
||||||
<GasDetailsItem
|
<GasDetailsItem
|
||||||
key="gas_details"
|
key="gas_details"
|
||||||
hexMaximumTransactionFee={hexMaximumTransactionFee}
|
|
||||||
hexMinimumTransactionFee={hexMinimumTransactionFee}
|
|
||||||
maxFeePerGas={maxFeePerGas}
|
|
||||||
maxPriorityFeePerGas={maxPriorityFeePerGas}
|
|
||||||
supportsEIP1559={supportsEIP1559}
|
|
||||||
txData={txData}
|
|
||||||
useNativeCurrencyAsPrimaryCurrency={
|
|
||||||
useNativeCurrencyAsPrimaryCurrency
|
|
||||||
}
|
|
||||||
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
|
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
@ -5,8 +5,6 @@
|
|||||||
@import 'confirm-approve/index';
|
@import 'confirm-approve/index';
|
||||||
@import 'confirm-decrypt-message/confirm-decrypt-message';
|
@import 'confirm-decrypt-message/confirm-decrypt-message';
|
||||||
@import 'confirm-encryption-public-key/confirm-encryption-public-key';
|
@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 'confirm-transaction-base/transaction-alerts/transaction-alerts';
|
||||||
@import 'confirmation/confirmation';
|
@import 'confirmation/confirmation';
|
||||||
@import 'connected-sites/index';
|
@import 'connected-sites/index';
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
TYPOGRAPHY,
|
TYPOGRAPHY,
|
||||||
FONT_WEIGHT,
|
FONT_WEIGHT,
|
||||||
} from '../../../helpers/constants/design-system';
|
} 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 =
|
const GAS_FEES_LEARN_MORE_URL =
|
||||||
'https://community.metamask.io/t/what-is-gas-why-do-transactions-take-so-long/3172';
|
'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;
|
return state.appState.gasIsLoading;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAppIsLoading(state) {
|
||||||
|
return state.appState.isLoading;
|
||||||
|
}
|
||||||
|
|
||||||
export function getCurrentCurrency(state) {
|
export function getCurrentCurrency(state) {
|
||||||
return state.metamask.currentCurrency;
|
return state.metamask.currentCurrency;
|
||||||
}
|
}
|
||||||
|
@ -240,4 +240,8 @@ describe('Selectors', () => {
|
|||||||
);
|
);
|
||||||
expect(isAdvancedGasFeeDefault).toStrictEqual(true);
|
expect(isAdvancedGasFeeDefault).toStrictEqual(true);
|
||||||
});
|
});
|
||||||
|
it('#getAppIsLoading', () => {
|
||||||
|
const appIsLoading = selectors.getAppIsLoading(mockState);
|
||||||
|
expect(appIsLoading).toStrictEqual(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user