diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index f8840e320..8c8570eda 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -422,6 +422,14 @@ "cancelSpeedUp": { "message": "cancel or speed up a tranaction." }, + "cancelSpeedUpLabel": { + "message": "This gas fee will $1 the original.", + "description": "$1 is text 'replace' in bold" + }, + "cancelSpeedUpTransactionTooltip": { + "message": "To $1 a transaction the gas fee must be increased by at least 10% for it to be recognized by the network.", + "description": "$1 is string 'cancel' or 'speed up'" + }, "cancellationGasFee": { "message": "Cancellation Gas Fee" }, @@ -739,6 +747,10 @@ "directDepositEtherExplainer": { "message": "If you already have some Ether, the quickest way to get Ether in your new wallet by direct deposit." }, + "disabledGasOptionToolTipMessage": { + "message": "“$1” is disabled because it does not meet the minimum of a 10% increase from the original gas fee.", + "description": "$1 is gas estimate type which can be market or aggressive" + }, "disconnect": { "message": "Disconnect" }, @@ -793,6 +805,9 @@ "editAddressNickname": { "message": "Edit address nickname" }, + "editCancellationGasFeeModalTitle": { + "message": "Edit cancellation gas fee" + }, "editContact": { "message": "Edit Contact" }, @@ -921,6 +936,9 @@ "editPermission": { "message": "Edit Permission" }, + "editSpeedUpEditGasFeeModalTitle": { + "message": "Edit speed up gas fee" + }, "enableAutoDetect": { "message": " Enable Autodetect" }, @@ -1157,6 +1175,9 @@ "functionType": { "message": "Function Type" }, + "gas": { + "message": "Gas" + }, "gasDisplayAcknowledgeDappButtonText": { "message": "Edit suggested gas fee" }, @@ -1709,6 +1730,15 @@ "metametricsTitle": { "message": "Join 6M+ users to improve MetaMask" }, + "minimum": { + "message": "minimum" + }, + "minimumCancelSpeedupGasFee": { + "message": "+10%" + }, + "minimumEstimate": { + "message": "10% Minimum" + }, "mismatchedChain": { "message": "The network details for this chain ID do not match our records. We recommend that you $1 before proceeding.", "description": "$1 is a clickable link with text defined by the 'mismatchedChainLinkText' key" @@ -2289,6 +2319,9 @@ "removeNFT": { "message": "Remove NFT" }, + "replace": { + "message": "replace" + }, "requestsAwaitingAcknowledgement": { "message": "requests waiting to be acknowledged" }, @@ -3169,9 +3202,6 @@ "transactionDetailGasHeading": { "message": "Estimated gas fee" }, - "transactionDetailGasHeadingV2": { - "message": "Gas" - }, "transactionDetailGasInfoV2": { "message": "estimated" }, diff --git a/shared/constants/gas.js b/shared/constants/gas.js index 01d490960..1663d6a0d 100644 --- a/shared/constants/gas.js +++ b/shared/constants/gas.js @@ -34,6 +34,7 @@ export const GAS_RECOMMENDATIONS = { * These represent types of gas estimation */ export const PRIORITY_LEVELS = { + MINIMUM: 'minimum', LOW: 'low', MEDIUM: 'medium', HIGH: 'high', diff --git a/shared/modules/conversion.utils.js b/shared/modules/conversion.utils.js index 1fdc87e60..1663712d9 100644 --- a/shared/modules/conversion.utils.js +++ b/shared/modules/conversion.utils.js @@ -120,7 +120,7 @@ const converter = ({ convertedValue = toSpecifiedDenomination[toDenomination](convertedValue); } - if (numberOfDecimals) { + if (numberOfDecimals !== undefined && numberOfDecimals !== null) { convertedValue = convertedValue.round( numberOfDecimals, BigNumber.ROUND_HALF_DOWN, diff --git a/test/data/mock-state.json b/test/data/mock-state.json index 9adeaea9b..cc5bfc51b 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -1,5 +1,6 @@ { "appState": { + "isLoading": false, "gasIsLoading": false, "currentView": { "name": "accountDetail", diff --git a/ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-popover.js b/ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-popover.js index 65eae89a3..b49954db2 100644 --- a/ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-popover.js +++ b/ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-popover.js @@ -14,8 +14,8 @@ import AdvancedGasFeeDefaults from './advanced-gas-fee-defaults'; const AdvancedGasFeePopover = () => { const t = useI18nContext(); const { - closeModal, closeAllModals, + closeModal, currentModal, } = useTransactionModalContext(); diff --git a/ui/components/app/app-components.scss b/ui/components/app/app-components.scss index 079337c45..87e0b8921 100644 --- a/ui/components/app/app-components.scss +++ b/ui/components/app/app-components.scss @@ -1,11 +1,13 @@ /** Please import your files in alphabetical order **/ @import 'account-list-item/index'; @import 'account-menu/index'; +@import 'app-loading-spinner/index'; @import 'import-token-link/index'; @import 'advanced-gas-controls/index'; @import 'alerts/alerts'; @import 'app-header/index'; @import 'asset-list-item/asset-list-item'; +@import 'cancel-speedup-popover/index'; @import 'confirm-page-container/index'; @import 'collectibles-items/index'; @import 'collectibles-tab/index'; @@ -28,6 +30,8 @@ @import 'gas-customization/gas-modal-page-container/index'; @import 'gas-customization/gas-price-button-group/index'; @import 'gas-customization/index'; +@import 'gas-details-item/index'; +@import 'gas-details-item/gas-details-item-title/index'; @import 'gas-timing/index'; @import 'home-notification/index'; @import 'info-box/index'; diff --git a/ui/components/app/app-loading-spinner/app-loading-spinner.js b/ui/components/app/app-loading-spinner/app-loading-spinner.js new file mode 100644 index 000000000..e40ed3a5b --- /dev/null +++ b/ui/components/app/app-loading-spinner/app-loading-spinner.js @@ -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 ( +
+ +
+ ); +}; + +AppLoadingSpinner.propTypes = { + className: PropTypes.string, +}; + +export default AppLoadingSpinner; diff --git a/ui/components/app/app-loading-spinner/app-loading-spinner.test.js b/ui/components/app/app-loading-spinner/app-loading-spinner.test.js new file mode 100644 index 000000000..f56d8b8be --- /dev/null +++ b/ui/components/app/app-loading-spinner/app-loading-spinner.test.js @@ -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(, 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(); + }); +}); diff --git a/ui/components/app/app-loading-spinner/index.js b/ui/components/app/app-loading-spinner/index.js new file mode 100644 index 000000000..c20efeffd --- /dev/null +++ b/ui/components/app/app-loading-spinner/index.js @@ -0,0 +1 @@ +export { default } from './app-loading-spinner'; diff --git a/ui/components/app/app-loading-spinner/index.scss b/ui/components/app/app-loading-spinner/index.scss new file mode 100644 index 000000000..7f12ef827 --- /dev/null +++ b/ui/components/app/app-loading-spinner/index.scss @@ -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; + } +} diff --git a/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.js b/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.js new file mode 100644 index 000000000..42d65126b --- /dev/null +++ b/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.js @@ -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 ( + + {editGasMode === EDIT_GAS_MODES.CANCEL + ? `❌${t('cancel')}` + : `🚀${t('speedUp')}`} + + } + onClose={() => closeModal('cancelSpeedUpTransaction')} + className="cancel-speedup-popover" + > + +
+ + + + , + ]} + /> + + {t('cancelSpeedUpTransactionTooltip', [ + EDIT_GAS_MODES.CANCEL ? t('cancel') : t('speedUp'), + ])} + + + } + /> + +
+ + + + + + + + + +
+ + ); +}; + +export default CancelSpeedupPopover; diff --git a/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.test.js b/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.test.js new file mode 100644 index 000000000..510f22eed --- /dev/null +++ b/ui/components/app/cancel-speedup-popover/cancel-speedup-popover.test.js @@ -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( + + + , + 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); + }); +}); diff --git a/ui/components/app/cancel-speedup-popover/index.js b/ui/components/app/cancel-speedup-popover/index.js new file mode 100644 index 000000000..a0242877a --- /dev/null +++ b/ui/components/app/cancel-speedup-popover/index.js @@ -0,0 +1 @@ +export { default } from './cancel-speedup-popover'; diff --git a/ui/components/app/cancel-speedup-popover/index.scss b/ui/components/app/cancel-speedup-popover/index.scss new file mode 100644 index 000000000..f852ddd2f --- /dev/null +++ b/ui/components/app/cancel-speedup-popover/index.scss @@ -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%; + } +} diff --git a/ui/components/app/edit-gas-fee-button/edit-gas-fee-button.js b/ui/components/app/edit-gas-fee-button/edit-gas-fee-button.js index 8ce452cb9..ba7e19949 100644 --- a/ui/components/app/edit-gas-fee-button/edit-gas-fee-button.js +++ b/ui/components/app/edit-gas-fee-button/edit-gas-fee-button.js @@ -41,14 +41,19 @@ export default function EditGasFeeButton({ userAcknowledgedGasMissing }) { ) { icon = 'swapSuggested'; title = 'swapSuggested'; + } else if (estimateUsed === PRIORITY_LEVELS.MINIMUM) { + icon = undefined; + title = 'minimumEstimate'; } return (
diff --git a/ui/components/app/edit-gas-fee-button/edit-gas-fee-button.test.js b/ui/components/app/edit-gas-fee-button/edit-gas-fee-button.test.js index 3a17ea070..c9a7217af 100644 --- a/ui/components/app/edit-gas-fee-button/edit-gas-fee-button.test.js +++ b/ui/components/app/edit-gas-fee-button/edit-gas-fee-button.test.js @@ -73,6 +73,11 @@ describe('EditGasFeeButton', () => { expect(screen.queryByText('Aggressive')).toBeInTheDocument(); }); + it('should render edit link with text 10% Minimum if minimum gas estimates are selected', () => { + render({ contextProps: { transaction: { userFeeLevel: 'minimum' } } }); + expect(screen.queryByText('10% Minimum')).toBeInTheDocument(); + }); + it('should render edit link with text Site suggested if site suggested estimated are used', () => { render({ contextProps: { @@ -103,6 +108,7 @@ describe('EditGasFeeButton', () => { render({ contextProps: { defaultEstimateToUse: 'custom', + transaction: {}, }, }); expect(screen.queryByText('⚙')).toBeInTheDocument(); diff --git a/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.js b/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.js index fca821623..33d7411f4 100644 --- a/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.js +++ b/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.js @@ -14,23 +14,39 @@ import Typography from '../../ui/typography/typography'; import { COLORS, TYPOGRAPHY } from '../../../helpers/constants/design-system'; import { INSUFFICIENT_FUNDS_ERROR_KEY } from '../../../helpers/constants/error-keys'; import { useGasFeeContext } from '../../../contexts/gasFee'; +import AppLoadingSpinner from '../app-loading-spinner'; import EditGasItem from './edit-gas-item'; import NetworkStatistics from './network-statistics'; const EditGasFeePopover = () => { const { balanceError, editGasMode } = useGasFeeContext(); const t = useI18nContext(); - const { closeModal, currentModal } = useTransactionModalContext(); + const { + closeAllModals, + closeModal, + currentModal, + openModalCount, + } = useTransactionModalContext(); if (currentModal !== 'editGasFee') return null; + let popupTitle = 'editGasFeeModalTitle'; + if (editGasMode === EDIT_GAS_MODES.CANCEL) { + popupTitle = 'editCancellationGasFeeModalTitle'; + } else if (editGasMode === EDIT_GAS_MODES.SPEED_UP) { + popupTitle = 'editSpeedUpEditGasFeeModalTitle'; + } + return ( closeModal('editGasFee')} + title={t(popupTitle)} + // below logic ensures that back button is visible only if there are other modals open before this. + onBack={openModalCount === 1 ? undefined : () => closeModal('editGasFee')} + onClose={closeAllModals} className="edit-gas-fee-popover" > <> +
{balanceError && ( @@ -49,13 +65,17 @@ const EditGasFeePopover = () => {
- {editGasMode !== EDIT_GAS_MODES.SWAPS && ( + {(editGasMode === EDIT_GAS_MODES.CANCEL || + editGasMode === EDIT_GAS_MODES.SPEED_UP) && ( + + )} + {editGasMode === EDIT_GAS_MODES.MODIFY_IN_PLACE && ( )}
- {editGasMode !== EDIT_GAS_MODES.SWAPS && ( + {editGasMode === EDIT_GAS_MODES.MODIFY_IN_PLACE && ( )} diff --git a/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.test.js b/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.test.js index a65a9ecca..de51aca8e 100644 --- a/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.test.js +++ b/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.test.js @@ -28,22 +28,24 @@ const MOCK_FEE_ESTIMATE = { low: { minWaitTimeEstimate: 360000, maxWaitTimeEstimate: 300000, - suggestedMaxPriorityFeePerGas: '3', - suggestedMaxFeePerGas: '53', + suggestedMaxPriorityFeePerGas: 3, + suggestedMaxFeePerGas: 53, }, medium: { minWaitTimeEstimate: 30000, maxWaitTimeEstimate: 60000, - suggestedMaxPriorityFeePerGas: '7', - suggestedMaxFeePerGas: '70', + suggestedMaxPriorityFeePerGas: 7, + suggestedMaxFeePerGas: 70, }, high: { minWaitTimeEstimate: 15000, maxWaitTimeEstimate: 15000, - suggestedMaxPriorityFeePerGas: '10', - suggestedMaxFeePerGas: '100', + suggestedMaxPriorityFeePerGas: 10, + suggestedMaxFeePerGas: 100, }, - estimatedBaseFee: '50', + latestPriorityFeeRange: [2, 6], + estimatedBaseFee: 50, + networkCongestion: 0.7, }; const render = ({ txProps, contextProps } = {}) => { @@ -141,4 +143,47 @@ describe('EditGasFeePopover', () => { expect(screen.queryByText('Time')).not.toBeInTheDocument(); expect(screen.queryByText('Max fee')).toBeInTheDocument(); }); + + it('should show correct header for edit gas mode', () => { + render({ + contextProps: { editGasMode: EDIT_GAS_MODES.SWAPS }, + }); + expect(screen.queryByText('Edit gas fee')).toBeInTheDocument(); + render({ + contextProps: { editGasMode: EDIT_GAS_MODES.CANCEL }, + }); + expect(screen.queryByText('Edit cancellation gas fee')).toBeInTheDocument(); + render({ + contextProps: { editGasMode: EDIT_GAS_MODES.SPEED_UP }, + }); + expect(screen.queryByText('Edit speed up gas fee')).toBeInTheDocument(); + }); + + it('should not show low option for cancel mode', () => { + render({ + contextProps: { editGasMode: EDIT_GAS_MODES.CANCEL }, + }); + expect(screen.queryByText('Low')).not.toBeInTheDocument(); + }); + + it('should not show low option for speedup mode', () => { + render({ + contextProps: { editGasMode: EDIT_GAS_MODES.SPEED_UP }, + }); + expect(screen.queryByText('Low')).not.toBeInTheDocument(); + }); + + it('should show minimum option for cancel gas mode', () => { + render({ + contextProps: { editGasMode: EDIT_GAS_MODES.CANCEL }, + }); + expect(screen.queryByText('(minimum)')).toBeInTheDocument(); + }); + + it('should show minimum option for speedup gas mode', () => { + render({ + contextProps: { editGasMode: EDIT_GAS_MODES.SPEED_UP }, + }); + expect(screen.queryByText('(minimum)')).toBeInTheDocument(); + }); }); diff --git a/ui/components/app/edit-gas-fee-popover/edit-gas-item/edit-gas-item.js b/ui/components/app/edit-gas-fee-popover/edit-gas-item/edit-gas-item.js index 64ee11b88..52f13413b 100644 --- a/ui/components/app/edit-gas-fee-popover/edit-gas-item/edit-gas-item.js +++ b/ui/components/app/edit-gas-fee-popover/edit-gas-item/edit-gas-item.js @@ -1,145 +1,121 @@ -import React from 'react'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { useSelector } from 'react-redux'; +import PropTypes from 'prop-types'; +import React from 'react'; -import { getMaximumGasTotalInHexWei } from '../../../../../shared/modules/gas.utils'; import { EDIT_GAS_MODES, PRIORITY_LEVELS, } from '../../../../../shared/constants/gas'; +import { + ALIGN_ITEMS, + DISPLAY, +} from '../../../../helpers/constants/design-system'; import { PRIORITY_LEVEL_ICON_MAP } from '../../../../helpers/constants/gas'; import { PRIMARY } from '../../../../helpers/constants/common'; -import { - decGWEIToHexWEI, - decimalToHex, - hexWEIToDecGWEI, -} from '../../../../helpers/utils/conversions.util'; -import LoadingHeartBeat from '../../../ui/loading-heartbeat'; -import { getAdvancedGasFeeValues } from '../../../../selectors'; import { toHumanReadableTime } from '../../../../helpers/utils/util'; import { useGasFeeContext } from '../../../../contexts/gasFee'; import { useI18nContext } from '../../../../hooks/useI18nContext'; import { useTransactionModalContext } from '../../../../contexts/transaction-modal'; -import I18nValue from '../../../ui/i18n-value'; -import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'; - +import Box from '../../../ui/box'; import EditGasToolTip from '../edit-gas-tooltip/edit-gas-tooltip'; import InfoTooltip from '../../../ui/info-tooltip'; -import { useCustomTimeEstimate } from './useCustomTimeEstimate'; +import LoadingHeartBeat from '../../../ui/loading-heartbeat'; +import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'; + +import { useGasItemFeeDetails } from './useGasItemFeeDetails'; + +const getTitleAndIcon = (priorityLevel, t, editGasMode) => { + let icon = priorityLevel; + let title = t(priorityLevel); + if (priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED) { + title = t('dappSuggestedShortLabel'); + } else if (priorityLevel === PRIORITY_LEVELS.MINIMUM) { + icon = null; + title = ( + + {t('minimumCancelSpeedupGasFee')} + ({t('minimum')}) + + ); + } else if ( + priorityLevel === PRIORITY_LEVELS.HIGH && + editGasMode === EDIT_GAS_MODES.SWAPS + ) { + icon = 'swapSuggested'; + title = t('swapSuggested'); + } + return { title, icon }; +}; const EditGasItem = ({ priorityLevel }) => { const { editGasMode, estimateUsed, - gasFeeEstimates, gasLimit, - maxFeePerGas: maxFeePerGasValue, - maxPriorityFeePerGas: maxPriorityFeePerGasValue, - updateTransactionUsingGasFeeEstimates, + updateTransactionToMinimumGasFee, + updateTransactionUsingDAPPSuggestedValues, + updateTransactionUsingEstimate, transaction, } = useGasFeeContext(); const t = useI18nContext(); - const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues); const { closeModal, openModal } = useTransactionModalContext(); const { dappSuggestedGasFees } = transaction; - let maxFeePerGas; - let maxPriorityFeePerGas; - let minWaitTime; - - if (gasFeeEstimates?.[priorityLevel]) { - maxFeePerGas = gasFeeEstimates[priorityLevel].suggestedMaxFeePerGas; - maxPriorityFeePerGas = - gasFeeEstimates[priorityLevel].suggestedMaxPriorityFeePerGas; - } else if ( - priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED && - dappSuggestedGasFees - ) { - maxFeePerGas = hexWEIToDecGWEI( - dappSuggestedGasFees.maxFeePerGas || dappSuggestedGasFees.gasPrice, - ); - maxPriorityFeePerGas = hexWEIToDecGWEI( - dappSuggestedGasFees.maxPriorityFeePerGas || maxFeePerGas, - ); - } else if (priorityLevel === PRIORITY_LEVELS.CUSTOM) { - if (estimateUsed === PRIORITY_LEVELS.CUSTOM) { - maxFeePerGas = maxFeePerGasValue; - maxPriorityFeePerGas = maxPriorityFeePerGasValue; - } else if (advancedGasFeeValues) { - maxFeePerGas = - gasFeeEstimates.estimatedBaseFee * - parseFloat(advancedGasFeeValues.maxBaseFee); - maxPriorityFeePerGas = advancedGasFeeValues.priorityFee; - } - } - - const { waitTimeEstimate } = useCustomTimeEstimate({ - gasFeeEstimates, + const { + // for cancel or speedup estimateGreaterThaGasUse is true if previous gas used + // was more than estimate for the priorityLevel + estimateGreaterThanGasUse, + hexMaximumTransactionFee, maxFeePerGas, maxPriorityFeePerGas, - }); + minWaitTime, + } = useGasItemFeeDetails(priorityLevel); - if (gasFeeEstimates[priorityLevel]) { - minWaitTime = - priorityLevel === PRIORITY_LEVELS.HIGH - ? gasFeeEstimates?.high.minWaitTimeEstimate - : gasFeeEstimates?.low.maxWaitTimeEstimate; - } else { - minWaitTime = waitTimeEstimate; + if ( + priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED && + !dappSuggestedGasFees + ) { + return null; } - 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 (priorityLevel === PRIORITY_LEVELS.MINIMUM) { + updateTransactionToMinimumGasFee(); + } else if (priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED) { + updateTransactionUsingDAPPSuggestedValues(); + } else { + updateTransactionUsingEstimate(priorityLevel); + } } }; - if ( - priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED && - !dappSuggestedGasFees - ) { - return null; - } - - let icon = priorityLevel; - let title = priorityLevel; - if (priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED) { - title = 'dappSuggestedShortLabel'; - } else if ( - priorityLevel === PRIORITY_LEVELS.HIGH && - editGasMode === EDIT_GAS_MODES.SWAPS - ) { - icon = 'swapSuggested'; - title = 'swapSuggested'; - } + const { title, icon } = getTitleAndIcon(priorityLevel, t, editGasMode); return (