diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index b3cbda168..f4ad0c777 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -710,6 +710,9 @@
"editGasEducationModalTitle": {
"message": "How to choose?"
},
+ "editGasFeeModalTitle": {
+ "message": "Edit gas fee"
+ },
"editGasHigh": {
"message": "High"
},
@@ -1011,6 +1014,9 @@
"message": "Gas limit must be at least $1",
"description": "$1 is the custom gas limit, in decimal."
},
+ "gasOption": {
+ "message": "Gas option"
+ },
"gasPrice": {
"message": "Gas Price (GWEI)"
},
@@ -1032,10 +1038,18 @@
"gasPriceLabel": {
"message": "Gas price"
},
+ "gasTimingHoursShort": {
+ "message": "$1 hrs",
+ "description": "$1 represents a number of hours"
+ },
"gasTimingMinutes": {
"message": "$1 minutes",
"description": "$1 represents a number of minutes"
},
+ "gasTimingMinutesShort": {
+ "message": "$1 min",
+ "description": "$1 represents a number of minutes"
+ },
"gasTimingNegative": {
"message": "Maybe in $1",
"description": "$1 represents an amount of time"
@@ -1048,6 +1062,10 @@
"message": "$1 seconds",
"description": "$1 represents a number of seconds"
},
+ "gasTimingSecondsShort": {
+ "message": "$1 sec",
+ "description": "$1 represents a number of seconds"
+ },
"gasTimingVeryPositive": {
"message": "Very likely in < $1",
"description": "$1 represents an amount of time"
@@ -2793,6 +2811,9 @@
"thisWillCreate": {
"message": "This will create a new wallet and Secret Recovery Phrase"
},
+ "time": {
+ "message": "Time"
+ },
"tips": {
"message": "Tips"
},
diff --git a/ui/components/app/app-components.scss b/ui/components/app/app-components.scss
index d76c2998f..e5a5a1882 100644
--- a/ui/components/app/app-components.scss
+++ b/ui/components/app/app-components.scss
@@ -13,6 +13,8 @@
@import 'connected-status-indicator/index';
@import 'edit-gas-display/index';
@import 'edit-gas-display-education/index';
+@import 'edit-gas-fee-popover/index';
+@import 'edit-gas-fee-popover/edit-gas-item/index';
@import 'gas-customization/gas-modal-page-container/index';
@import 'gas-customization/gas-price-button-group/index';
@import 'gas-customization/index';
diff --git a/ui/components/app/confirm-page-container/confirm-page-container.component.js b/ui/components/app/confirm-page-container/confirm-page-container.component.js
index 117318e3d..33c07d56a 100644
--- a/ui/components/app/confirm-page-container/confirm-page-container.component.js
+++ b/ui/components/app/confirm-page-container/confirm-page-container.component.js
@@ -8,12 +8,16 @@ import { GasFeeContextProvider } from '../../../contexts/gasFee';
import ErrorMessage from '../../ui/error-message';
import { TRANSACTION_TYPES } from '../../../../shared/constants/transaction';
import Dialog from '../../ui/dialog';
+import EditGasFeePopover from '../edit-gas-fee-popover/edit-gas-fee-popover';
import {
ConfirmPageContainerHeader,
ConfirmPageContainerContent,
ConfirmPageContainerNavigation,
} from '.';
+// eslint-disable-next-line prefer-destructuring
+const EIP_1559_V2 = process.env.EIP_1559_V2;
+
export default class ConfirmPageContainer extends Component {
static contextTypes = {
t: PropTypes.func,
@@ -225,13 +229,16 @@ export default class ConfirmPageContainer extends Component {
)}
)}
- {editingGas && (
+ {editingGas && !EIP_1559_V2 && (
)}
+ {editingGas && EIP_1559_V2 && (
+
+ )}
);
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
new file mode 100644
index 000000000..84f31a256
--- /dev/null
+++ b/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.js
@@ -0,0 +1,49 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { useI18nContext } from '../../../hooks/useI18nContext';
+import Popover from '../../ui/popover';
+import I18nValue from '../../ui/i18n-value';
+import LoadingHeartBeat from '../../ui/loading-heartbeat';
+
+import EditGasItem from './edit-gas-item';
+
+const EditGasFeePopover = ({ onClose }) => {
+ const t = useI18nContext();
+
+ return (
+
+ <>
+ {process.env.IN_TEST === 'true' ? null : }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+
+ );
+};
+
+EditGasFeePopover.propTypes = {
+ onClose: PropTypes.func,
+};
+
+export default EditGasFeePopover;
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
new file mode 100644
index 000000000..cd4f772dc
--- /dev/null
+++ b/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.test.js
@@ -0,0 +1,93 @@
+import React from 'react';
+import { screen } from '@testing-library/react';
+
+import { renderWithProvider } from '../../../../test/lib/render-helpers';
+import { ETH } from '../../../helpers/constants/common';
+import configureStore from '../../../store/store';
+import { GasFeeContextProvider } from '../../../contexts/gasFee';
+
+import EditGasFeePopover from './edit-gas-fee-popover';
+
+jest.mock('../../../store/actions', () => ({
+ disconnectGasFeeEstimatePoller: jest.fn(),
+ getGasFeeEstimatesAndStartPolling: jest
+ .fn()
+ .mockImplementation(() => Promise.resolve()),
+ addPollingTokenToAppState: jest.fn(),
+}));
+
+const MOCK_FEE_ESTIMATE = {
+ low: {
+ minWaitTimeEstimate: 360000,
+ maxWaitTimeEstimate: 300000,
+ suggestedMaxPriorityFeePerGas: '3',
+ suggestedMaxFeePerGas: '53',
+ },
+ medium: {
+ minWaitTimeEstimate: 30000,
+ maxWaitTimeEstimate: 60000,
+ suggestedMaxPriorityFeePerGas: '7',
+ suggestedMaxFeePerGas: '70',
+ },
+ high: {
+ minWaitTimeEstimate: 15000,
+ maxWaitTimeEstimate: 15000,
+ suggestedMaxPriorityFeePerGas: '10',
+ suggestedMaxFeePerGas: '100',
+ },
+ estimatedBaseFee: '50',
+};
+
+const renderComponent = () => {
+ const store = configureStore({
+ metamask: {
+ nativeCurrency: ETH,
+ provider: {},
+ cachedBalances: {},
+ accounts: {
+ '0xAddress': {
+ address: '0xAddress',
+ balance: '0x176e5b6f173ebe66',
+ },
+ },
+ selectedAddress: '0xAddress',
+ featureFlags: { advancedInlineGas: true },
+ gasFeeEstimates: MOCK_FEE_ESTIMATE,
+ },
+ });
+
+ return renderWithProvider(
+
+
+ ,
+ store,
+ );
+};
+
+describe('EditGasFeePopover', () => {
+ it('should renders low / medium / high options', () => {
+ renderComponent();
+
+ expect(screen.queryByText('🐢')).toBeInTheDocument();
+ expect(screen.queryByText('🦊')).toBeInTheDocument();
+ expect(screen.queryByText('🦍')).toBeInTheDocument();
+ expect(screen.queryByText('Low')).toBeInTheDocument();
+ expect(screen.queryByText('Market')).toBeInTheDocument();
+ expect(screen.queryByText('Aggressive')).toBeInTheDocument();
+ });
+
+ it('should show time estimates', () => {
+ renderComponent();
+ console.log(document.body.innerHTML);
+ expect(screen.queryByText('6 min')).toBeInTheDocument();
+ expect(screen.queryByText('30 sec')).toBeInTheDocument();
+ expect(screen.queryByText('15 sec')).toBeInTheDocument();
+ });
+
+ it('should show gas fee estimates', () => {
+ renderComponent();
+ expect(screen.queryByTitle('0.001113 ETH')).toBeInTheDocument();
+ expect(screen.queryByTitle('0.00147 ETH')).toBeInTheDocument();
+ expect(screen.queryByTitle('0.0021 ETH')).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
new file mode 100644
index 000000000..59cf3ac01
--- /dev/null
+++ b/ui/components/app/edit-gas-fee-popover/edit-gas-item/edit-gas-item.js
@@ -0,0 +1,84 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+
+import { getMaximumGasTotalInHexWei } from '../../../../../shared/modules/gas.utils';
+import { PRIORITY_LEVEL_ICON_MAP } from '../../../../helpers/constants/gas';
+import { PRIMARY } from '../../../../helpers/constants/common';
+import {
+ decGWEIToHexWEI,
+ decimalToHex,
+} from '../../../../helpers/utils/conversions.util';
+import { toHumanReadableTime } from '../../../../helpers/utils/util';
+import { useGasFeeContext } from '../../../../contexts/gasFee';
+import { useI18nContext } from '../../../../hooks/useI18nContext';
+import I18nValue from '../../../ui/i18n-value';
+import InfoTooltip from '../../../ui/info-tooltip';
+import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display';
+
+const EditGasItem = ({ estimateType, onClose }) => {
+ const {
+ estimateUsed,
+ gasFeeEstimates,
+ gasLimit,
+ setEstimateToUse,
+ updateTransaction,
+ } = useGasFeeContext();
+ const t = useI18nContext();
+
+ const { minWaitTimeEstimate, suggestedMaxFeePerGas } =
+ gasFeeEstimates[estimateType] || {};
+ const hexMaximumTransactionFee = suggestedMaxFeePerGas
+ ? getMaximumGasTotalInHexWei({
+ gasLimit: decimalToHex(gasLimit),
+ maxFeePerGas: decGWEIToHexWEI(suggestedMaxFeePerGas),
+ })
+ : null;
+
+ const onOptionSelect = () => {
+ setEstimateToUse(estimateType);
+ updateTransaction(estimateType);
+ onClose();
+ };
+
+ return (
+
+
+
+ {PRIORITY_LEVEL_ICON_MAP[estimateType]}
+
+
+
+
+ {minWaitTimeEstimate && toHumanReadableTime(t, minWaitTimeEstimate)}
+
+
+
+
+
+
+
+
+ );
+};
+
+EditGasItem.propTypes = {
+ estimateType: PropTypes.string,
+ onClose: PropTypes.func,
+};
+
+export default EditGasItem;
diff --git a/ui/components/app/edit-gas-fee-popover/edit-gas-item/edit-gas-item.test.js b/ui/components/app/edit-gas-fee-popover/edit-gas-item/edit-gas-item.test.js
new file mode 100644
index 000000000..4d8a40f88
--- /dev/null
+++ b/ui/components/app/edit-gas-fee-popover/edit-gas-item/edit-gas-item.test.js
@@ -0,0 +1,93 @@
+import React from 'react';
+import { screen } from '@testing-library/react';
+
+import { renderWithProvider } from '../../../../../test/lib/render-helpers';
+import { ETH } from '../../../../helpers/constants/common';
+import configureStore from '../../../../store/store';
+import { GasFeeContextProvider } from '../../../../contexts/gasFee';
+
+import EditGasItem from './edit-gas-item';
+
+jest.mock('../../../../store/actions', () => ({
+ disconnectGasFeeEstimatePoller: jest.fn(),
+ getGasFeeEstimatesAndStartPolling: jest
+ .fn()
+ .mockImplementation(() => Promise.resolve()),
+ addPollingTokenToAppState: jest.fn(),
+}));
+
+const MOCK_FEE_ESTIMATE = {
+ low: {
+ minWaitTimeEstimate: 360000,
+ maxWaitTimeEstimate: 300000,
+ suggestedMaxPriorityFeePerGas: '3',
+ suggestedMaxFeePerGas: '53',
+ },
+ medium: {
+ minWaitTimeEstimate: 30000,
+ maxWaitTimeEstimate: 60000,
+ suggestedMaxPriorityFeePerGas: '7',
+ suggestedMaxFeePerGas: '70',
+ },
+ high: {
+ minWaitTimeEstimate: 15000,
+ maxWaitTimeEstimate: 15000,
+ suggestedMaxPriorityFeePerGas: '10',
+ suggestedMaxFeePerGas: '100',
+ },
+ estimatedBaseFee: '50',
+};
+
+const renderComponent = (props) => {
+ const store = configureStore({
+ metamask: {
+ nativeCurrency: ETH,
+ provider: {},
+ cachedBalances: {},
+ accounts: {
+ '0xAddress': {
+ address: '0xAddress',
+ balance: '0x176e5b6f173ebe66',
+ },
+ },
+ selectedAddress: '0xAddress',
+ featureFlags: { advancedInlineGas: true },
+ gasFeeEstimates: MOCK_FEE_ESTIMATE,
+ },
+ });
+
+ return renderWithProvider(
+
+
+ ,
+ store,
+ );
+};
+
+describe('EditGasItem', () => {
+ it('should renders low gas estimate options for estimateType low', () => {
+ renderComponent({ estimateType: 'low' });
+
+ expect(screen.queryByText('🐢')).toBeInTheDocument();
+ expect(screen.queryByText('Low')).toBeInTheDocument();
+ expect(screen.queryByText('6 min')).toBeInTheDocument();
+ expect(screen.queryByTitle('0.001113 ETH')).toBeInTheDocument();
+ });
+
+ it('should renders market gas estimate options for estimateType medium', () => {
+ renderComponent({ estimateType: 'medium' });
+
+ expect(screen.queryByText('🦊')).toBeInTheDocument();
+ expect(screen.queryByText('Market')).toBeInTheDocument();
+ expect(screen.queryByText('30 sec')).toBeInTheDocument();
+ expect(screen.queryByTitle('0.00147 ETH')).toBeInTheDocument();
+ });
+
+ it('should renders aggressive gas estimate options for estimateType high', () => {
+ renderComponent({ estimateType: 'high' });
+
+ expect(screen.queryByText('🦍')).toBeInTheDocument();
+ expect(screen.queryByText('15 sec')).toBeInTheDocument();
+ expect(screen.queryByTitle('0.0021 ETH')).toBeInTheDocument();
+ });
+});
diff --git a/ui/components/app/edit-gas-fee-popover/edit-gas-item/index.js b/ui/components/app/edit-gas-fee-popover/edit-gas-item/index.js
new file mode 100644
index 000000000..3ba916857
--- /dev/null
+++ b/ui/components/app/edit-gas-fee-popover/edit-gas-item/index.js
@@ -0,0 +1 @@
+export { default } from './edit-gas-item';
diff --git a/ui/components/app/edit-gas-fee-popover/edit-gas-item/index.scss b/ui/components/app/edit-gas-fee-popover/edit-gas-item/index.scss
new file mode 100644
index 000000000..17072a813
--- /dev/null
+++ b/ui/components/app/edit-gas-fee-popover/edit-gas-item/index.scss
@@ -0,0 +1,56 @@
+.edit-gas-item {
+ border-radius: 24px;
+ color: $ui-4;
+ cursor: pointer;
+ font-size: 12px;
+ margin: 12px 0;
+ padding: 4px 12px;
+ height: 32px;
+
+ &--selected {
+ background-color: $ui-1;
+ }
+
+ &__name {
+ display: inline-block;
+ color: $ui-black;
+ font-size: 12px;
+ font-weight: bold;
+ width: 40%;
+ }
+
+ &__icon {
+ margin-right: 4px;
+ }
+
+ &__time-estimate {
+ display: inline-block;
+ width: 20%;
+ }
+
+ &__fee-estimate {
+ display: inline-block;
+ width: 30%;
+ white-space: nowrap;
+ }
+
+ &__tooltip {
+ display: inline-block;
+ text-align: right;
+ width: 10%;
+
+ .info-tooltip {
+ display: inline-block;
+ }
+ }
+
+ &__time-estimate-low,
+ &__fee-estimate-high {
+ color: $secondary-1;
+ }
+
+ &__time-estimate-medium,
+ &__time-estimate-high {
+ color: $success-3;
+ }
+}
diff --git a/ui/components/app/edit-gas-fee-popover/index.js b/ui/components/app/edit-gas-fee-popover/index.js
new file mode 100644
index 000000000..d2e6862b9
--- /dev/null
+++ b/ui/components/app/edit-gas-fee-popover/index.js
@@ -0,0 +1 @@
+export { default } from './edit-gas-fee-popover';
diff --git a/ui/components/app/edit-gas-fee-popover/index.scss b/ui/components/app/edit-gas-fee-popover/index.scss
new file mode 100644
index 000000000..0e149efbc
--- /dev/null
+++ b/ui/components/app/edit-gas-fee-popover/index.scss
@@ -0,0 +1,35 @@
+.edit-gas-fee-popover {
+ @media screen and (min-width: $break-large) {
+ max-height: 84vh;
+ }
+
+ &__wrapper {
+ border-top: 1px solid $ui-grey;
+ }
+
+ &__content {
+ padding: 16px 12px;
+
+ &__header {
+ color: $ui-4;
+ font-size: 10px;
+ font-weight: 700;
+ margin: 0 12px;
+
+ &-option {
+ display: inline-block;
+ width: 40%;
+ }
+
+ &-time {
+ display: inline-block;
+ width: 20%;
+ }
+
+ &-max-fee {
+ display: inline-block;
+ width: 30%;
+ }
+ }
+ }
+}
diff --git a/ui/components/app/gas-timing/gas-timing.component.js b/ui/components/app/gas-timing/gas-timing.component.js
index 41fc6bed5..1da1c6a43 100644
--- a/ui/components/app/gas-timing/gas-timing.component.js
+++ b/ui/components/app/gas-timing/gas-timing.component.js
@@ -50,7 +50,7 @@ export default function GasTiming({
const [customEstimatedTime, setCustomEstimatedTime] = useState(null);
const t = useContext(I18nContext);
- const { estimateToUse } = useGasFeeContext();
+ const { estimateUsed } = useGasFeeContext();
// If the user has chosen a value lower than the low gas fee estimate,
// We'll need to use the useEffect hook below to make a call to calculate
@@ -155,7 +155,7 @@ export default function GasTiming({
]);
}
} else {
- if (!EIP_1559_V2 || estimateToUse === 'low') {
+ if (!EIP_1559_V2 || estimateUsed === 'low') {
attitude = 'negative';
}
// If the user has chosen a value less than our low estimate,
diff --git a/ui/components/app/transaction-detail/transaction-detail.component.js b/ui/components/app/transaction-detail/transaction-detail.component.js
index b06d43af8..64677883a 100644
--- a/ui/components/app/transaction-detail/transaction-detail.component.js
+++ b/ui/components/app/transaction-detail/transaction-detail.component.js
@@ -1,40 +1,29 @@
-import React, { useContext } from 'react';
+import React from 'react';
import PropTypes from 'prop-types';
-import { I18nContext } from '../../../contexts/i18n';
import { useGasFeeContext } from '../../../contexts/gasFee';
import InfoTooltip from '../../ui/info-tooltip/info-tooltip';
import Typography from '../../ui/typography/typography';
import TransactionDetailItem from '../transaction-detail-item/transaction-detail-item.component';
import { COLORS } from '../../../helpers/constants/design-system';
-
-const GasLevelIconMap = {
- low: '🐢',
- medium: '🦊',
- high: '🦍',
- dappSuggested: '🌐',
- custom: '⚙',
-};
+import { PRIORITY_LEVEL_ICON_MAP } from '../../../helpers/constants/gas';
+import { useI18nContext } from '../../../hooks/useI18nContext';
export default function TransactionDetail({ rows = [], onEdit }) {
// eslint-disable-next-line prefer-destructuring
const EIP_1559_V2 = process.env.EIP_1559_V2;
- const t = useContext(I18nContext);
+ const t = useI18nContext();
const {
- estimateToUse,
gasLimit,
gasPrice,
- isUsingDappSuggestedGasFees,
+ estimateUsed,
maxFeePerGas,
maxPriorityFeePerGas,
transaction,
supportsEIP1559,
} = useGasFeeContext();
- const estimateUsed = isUsingDappSuggestedGasFees
- ? 'dappSuggested'
- : estimateToUse;
if (EIP_1559_V2 && estimateUsed) {
return (
@@ -42,7 +31,7 @@ export default function TransactionDetail({ rows = [], onEdit }) {