From 68dfc98f400eb6b8b306a7dfe0eed25c325a9bc7 Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Thu, 8 Jul 2021 15:23:00 -0500 Subject: [PATCH] wire up gasFeeController (#11421) --- app/scripts/metamask-controller.js | 40 +++++++ shared/constants/gas.js | 11 ++ .../confirm-page-container.component.js | 6 + .../edit-gas-popover.component.js | 8 +- ...gas-modal-page-container-container.test.js | 38 ++---- .../gas-modal-page-container.container.js | 28 ++--- ui/ducks/metamask/metamask.js | 57 +++++++++ ui/ducks/send/send.js | 112 ++++++++++++++---- ui/ducks/send/send.test.js | 1 + .../confirm-transaction-base.component.js | 37 ++++-- .../confirm-transaction-base.container.js | 26 ++-- .../amount-max-button/amount-max-button.js | 9 +- .../send-content/send-content.component.js | 2 +- ui/store/actionConstants.js | 1 + ui/store/actions.js | 41 ++++++- yarn.lock | 7 +- 16 files changed, 324 insertions(+), 100 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 7bd321280..f74041946 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -24,6 +24,7 @@ import { CurrencyRateController, PhishingController, NotificationController, + GasFeeController, } from '@metamask/controllers'; import { TRANSACTION_STATUSES } from '../../shared/constants/transaction'; import { MAINNET_CHAIN_ID } from '../../shared/constants/network'; @@ -174,6 +175,32 @@ export default class MetamaskController extends EventEmitter { initState: initState.MetaMetricsController, }); + const gasFeeMessenger = controllerMessenger.getRestricted({ + name: 'GasFeeController', + }); + + this.gasFeeController = new GasFeeController({ + interval: 10000, + messenger: gasFeeMessenger, + getProvider: () => + this.networkController.getProviderAndBlockTracker().provider, + onNetworkStateChange: this.networkController.on.bind( + this.networkController, + NETWORK_EVENTS.NETWORK_DID_CHANGE, + ), + getCurrentNetworkEIP1559Compatibility: this.networkController.getEIP1559Compatibility.bind( + this.networkController, + ), + getCurrentAccountEIP1559Compatibility: this.getCurrentAccountEIP1559Compatibility.bind( + this, + ), + getCurrentNetworkLegacyGasAPICompatibility: () => + this.networkController.getCurrentChainId() === MAINNET_CHAIN_ID, + getChainId: this.networkController.getCurrentChainId.bind( + this.networkController, + ), + }); + this.appStateController = new AppStateController({ addUnlockListener: this.on.bind(this, 'unlock'), isUnlocked: this.isUnlocked.bind(this), @@ -470,6 +497,7 @@ export default class MetamaskController extends EventEmitter { PermissionsMetadata: this.permissionsController.store, ThreeBoxController: this.threeBoxController.store, NotificationController: this.notificationController, + GasFeeController: this.gasFeeController, }); this.memStore = new ComposableObservableStore({ @@ -501,6 +529,7 @@ export default class MetamaskController extends EventEmitter { EnsController: this.ensController.store, ApprovalController: this.approvalController, NotificationController: this.notificationController, + GasFeeController: this.gasFeeController, }, controllerMessenger, }); @@ -1027,6 +1056,17 @@ export default class MetamaskController extends EventEmitter { this.notificationController.updateViewed, this.notificationController, ), + + // GasFeeController + getGasFeeEstimatesAndStartPolling: nodeify( + this.gasFeeController.getGasFeeEstimatesAndStartPolling, + this.gasFeeController, + ), + + disconnectGasFeeEstimatePoller: nodeify( + this.gasFeeController.disconnectPoller, + this.gasFeeController, + ), }; } diff --git a/shared/constants/gas.js b/shared/constants/gas.js index 6f53aaeae..b149458cf 100644 --- a/shared/constants/gas.js +++ b/shared/constants/gas.js @@ -9,3 +9,14 @@ export const GAS_LIMITS = { // a base estimate for token transfers. BASE_TOKEN_ESTIMATE: addHexPrefix(ONE_HUNDRED_THOUSAND.toString(16)), }; + +/** + * These are already declared in @metamask/controllers but importing them from + * that module and re-exporting causes the UI bundle size to expand beyond 4MB + */ +export const GAS_ESTIMATE_TYPES = { + FEE_MARKET: 'fee-market', + LEGACY: 'legacy', + ETH_GASPRICE: 'eth_gasPrice', + NONE: 'none', +}; 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 71923e546..8e3b8d858 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 @@ -2,6 +2,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import SenderToRecipient from '../../ui/sender-to-recipient'; import { PageContainerFooter } from '../../ui/page-container'; +import EditGasPopover from '../edit-gas-popover'; import { ConfirmPageContainerHeader, ConfirmPageContainerContent, @@ -60,6 +61,8 @@ export default class ConfirmPageContainer extends Component { onCancel: PropTypes.func, onSubmit: PropTypes.func, disabled: PropTypes.bool, + editingGas: PropTypes.bool, + handleCloseEditGas: PropTypes.func, }; render() { @@ -105,6 +108,8 @@ export default class ConfirmPageContainer extends Component { showAccountInHeader, origin, ethGasPriceWarning, + editingGas, + handleCloseEditGas, } = this.props; const renderAssetImage = contentComponent || !identiconAddress; @@ -183,6 +188,7 @@ export default class ConfirmPageContainer extends Component { )} )} + {editingGas && } ); } diff --git a/ui/components/app/edit-gas-popover/edit-gas-popover.component.js b/ui/components/app/edit-gas-popover/edit-gas-popover.component.js index 76810dcfc..f27f6525d 100644 --- a/ui/components/app/edit-gas-popover/edit-gas-popover.component.js +++ b/ui/components/app/edit-gas-popover/edit-gas-popover.component.js @@ -14,6 +14,7 @@ export default function EditGasPopover({ popoverTitle, confirmButtonText, editGasDisplayProps, + onClose, }) { const t = useContext(I18nContext); const dispatch = useDispatch(); @@ -27,12 +28,14 @@ export default function EditGasPopover({ * the modal in testing */ const closePopover = useCallback(() => { - if (showSidebar) { + if (onClose) { + onClose(); + } else if (showSidebar) { dispatch(hideSidebar()); } else { dispatch(hideModal()); } - }, [showSidebar, dispatch]); + }, [showSidebar, onClose, dispatch]); const title = showEducationContent ? t('editGasEducationModalTitle') @@ -73,6 +76,7 @@ EditGasPopover.propTypes = { editGasDisplayProps: PropTypes.object, confirmButtonText: PropTypes.string, showEducationButton: PropTypes.bool, + onClose: PropTypes.func, }; EditGasPopover.defaultProps = { diff --git a/ui/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container-container.test.js b/ui/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container-container.test.js index d82a8fb0a..37e853cd1 100644 --- a/ui/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container-container.test.js +++ b/ui/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container-container.test.js @@ -49,6 +49,10 @@ jest.mock('../../../../store/actions', () => ({ updateTransaction: jest.fn(), })); +jest.mock('../../../../ducks/metamask/metamask.js', () => ({ + updateTransactionGasFees: jest.fn(), +})); + jest.mock('../../../../ducks/gas/gas.duck', () => ({ setCustomGasPrice: jest.fn(), setCustomGasLimit: jest.fn(), @@ -79,6 +83,7 @@ describe('gas-modal-page-container container', () => { afterEach(() => { dispatchSpy.resetHistory(); + jest.clearAllMocks(); }); describe('useCustomGas()', () => { @@ -137,17 +142,6 @@ describe('gas-modal-page-container container', () => { expect(updateGasPrice).toHaveBeenCalledWith('aaaa'); }); }); - - describe('updateConfirmTxGasAndCalculate()', () => { - it('should dispatch a updateGasAndCalculate action with the correct props', () => { - mapDispatchToPropsObject.updateConfirmTxGasAndCalculate('ffff', 'aaaa'); - expect(dispatchSpy.callCount).toStrictEqual(3); - expect(setCustomGasPrice).toHaveBeenCalled(); - expect(setCustomGasLimit).toHaveBeenCalled(); - expect(setCustomGasLimit).toHaveBeenCalledWith('0xffff'); - expect(setCustomGasPrice).toHaveBeenCalledWith('0xaaaa'); - }); - }); }); describe('mergeProps', () => { @@ -169,7 +163,7 @@ describe('gas-modal-page-container container', () => { updateCustomGasPrice: sinon.spy(), useCustomGas: sinon.spy(), setGasData: sinon.spy(), - updateConfirmTxGasAndCalculate: sinon.spy(), + updateTransactionGasFees: sinon.spy(), someOtherDispatchProp: sinon.spy(), createSpeedUpTransaction: sinon.spy(), hideSidebar: sinon.spy(), @@ -192,18 +186,14 @@ describe('gas-modal-page-container container', () => { ).toStrictEqual('bar'); expect(result.someOwnProp).toStrictEqual(123); - expect( - dispatchProps.updateConfirmTxGasAndCalculate.callCount, - ).toStrictEqual(0); + expect(dispatchProps.updateTransactionGasFees.callCount).toStrictEqual(0); expect(dispatchProps.setGasData.callCount).toStrictEqual(0); expect(dispatchProps.useCustomGas.callCount).toStrictEqual(0); expect(dispatchProps.hideModal.callCount).toStrictEqual(0); result.onSubmit(); - expect( - dispatchProps.updateConfirmTxGasAndCalculate.callCount, - ).toStrictEqual(1); + expect(dispatchProps.updateTransactionGasFees.callCount).toStrictEqual(1); expect(dispatchProps.setGasData.callCount).toStrictEqual(0); expect(dispatchProps.useCustomGas.callCount).toStrictEqual(0); expect(dispatchProps.hideModal.callCount).toStrictEqual(1); @@ -236,18 +226,14 @@ describe('gas-modal-page-container container', () => { ).toStrictEqual('bar'); expect(result.someOwnProp).toStrictEqual(123); - expect( - dispatchProps.updateConfirmTxGasAndCalculate.callCount, - ).toStrictEqual(0); + expect(dispatchProps.updateTransactionGasFees.callCount).toStrictEqual(0); expect(dispatchProps.setGasData.callCount).toStrictEqual(0); expect(dispatchProps.useCustomGas.callCount).toStrictEqual(0); expect(dispatchProps.cancelAndClose.callCount).toStrictEqual(0); result.onSubmit('mockNewLimit', 'mockNewPrice'); - expect( - dispatchProps.updateConfirmTxGasAndCalculate.callCount, - ).toStrictEqual(0); + expect(dispatchProps.updateTransactionGasFees.callCount).toStrictEqual(0); expect(dispatchProps.setGasData.callCount).toStrictEqual(1); expect(dispatchProps.setGasData.getCall(0).args).toStrictEqual([ 'mockNewLimit', @@ -276,9 +262,7 @@ describe('gas-modal-page-container container', () => { result.onSubmit(); - expect( - dispatchProps.updateConfirmTxGasAndCalculate.callCount, - ).toStrictEqual(0); + expect(dispatchProps.updateTransactionGasFees.callCount).toStrictEqual(0); expect(dispatchProps.setGasData.callCount).toStrictEqual(0); expect(dispatchProps.useCustomGas.callCount).toStrictEqual(0); expect(dispatchProps.cancelAndClose.callCount).toStrictEqual(1); diff --git a/ui/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js b/ui/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js index 736700c0c..a1a262589 100644 --- a/ui/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js +++ b/ui/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js @@ -5,7 +5,6 @@ import { createRetryTransaction, createSpeedUpTransaction, hideSidebar, - updateTransaction, } from '../../../../store/actions'; import { setCustomGasPrice, @@ -59,6 +58,7 @@ import { import { MIN_GAS_LIMIT_DEC } from '../../../../pages/send/send.constants'; import { TRANSACTION_STATUSES } from '../../../../../shared/constants/transaction'; import { GAS_LIMITS } from '../../../../../shared/constants/gas'; +import { updateTransactionGasFees } from '../../../../ducks/metamask/metamask'; import GasModalPageContainer from './gas-modal-page-container.component'; const mapStateToProps = (state, ownProps) => { @@ -208,6 +208,9 @@ const mapDispatchToProps = (dispatch) => { }, hideModal: () => dispatch(hideModal()), useCustomGas: () => dispatch(useCustomGas()), + updateTransactionGasFees: (gasFees) => { + dispatch(updateTransactionGasFees({ ...gasFees, expectHexWei: true })); + }, updateCustomGasPrice, updateCustomGasLimit: (newLimit) => dispatch(setCustomGasLimit(addHexPrefix(newLimit))), @@ -215,11 +218,6 @@ const mapDispatchToProps = (dispatch) => { dispatch(updateGasLimit(newLimit)); dispatch(updateGasPrice(newPrice)); }, - updateConfirmTxGasAndCalculate: (gasLimit, gasPrice, updatedTx) => { - updateCustomGasPrice(gasPrice); - dispatch(setCustomGasLimit(addHexPrefix(gasLimit.toString(16)))); - return dispatch(updateTransaction(updatedTx)); - }, createRetryTransaction: (txId, customGasSettings) => { return dispatch(createRetryTransaction(txId, customGasSettings)); }, @@ -247,9 +245,9 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { const { useCustomGas: dispatchUseCustomGas, setGasData: dispatchSetGasData, - updateConfirmTxGasAndCalculate: dispatchUpdateConfirmTxGasAndCalculate, createSpeedUpTransaction: dispatchCreateSpeedUpTransaction, createRetryTransaction: dispatchCreateRetryTransaction, + updateTransactionGasFees: dispatchUpdateTransactionGasFees, hideSidebar: dispatchHideSidebar, cancelAndClose: dispatchCancelAndClose, hideModal: dispatchHideModal, @@ -268,16 +266,14 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { return; } if (isConfirm) { - const updatedTx = { - ...transaction, - txParams: { - ...transaction.txParams, - gas: gasLimit, - gasPrice, - }, - }; - dispatchUpdateConfirmTxGasAndCalculate(gasLimit, gasPrice, updatedTx); + dispatchUpdateTransactionGasFees({ + gasLimit, + gasPrice, + transaction, + isModal: true, + }); dispatchHideModal(); + dispatchCancelAndClose(); } else if (isSpeedUp) { dispatchCreateSpeedUpTransaction(txId, { gasPrice, gasLimit }); dispatchHideSidebar(); diff --git a/ui/ducks/metamask/metamask.js b/ui/ducks/metamask/metamask.js index e72fb8ec7..5663c2029 100644 --- a/ui/ducks/metamask/metamask.js +++ b/ui/ducks/metamask/metamask.js @@ -1,3 +1,4 @@ +import { addHexPrefix, isHexString } from 'ethereumjs-util'; import * as actionConstants from '../../store/actionConstants'; import { ALERT_TYPES } from '../../../shared/constants/alerts'; import { NETWORK_TYPE_RPC } from '../../../shared/constants/network'; @@ -5,6 +6,9 @@ import { accountsWithSendEtherInfoSelector, getAddressBook, } from '../../selectors'; +import { updateTransaction } from '../../store/actions'; +import { setCustomGasLimit, setCustomGasPrice } from '../gas/gas.duck'; +import { decGWEIToHexWEI } from '../../helpers/utils/conversions.util'; export default function reduceMetamask(state = {}, action) { const metamaskState = { @@ -198,6 +202,47 @@ export default function reduceMetamask(state = {}, action) { } } +const toHexWei = (value, expectHexWei) => { + return addHexPrefix(expectHexWei ? value : decGWEIToHexWEI(value)); +}; + +// Action Creators +export function updateTransactionGasFees({ + gasPrice, + gasLimit, + maxPriorityFeePerGas, + maxFeePerGas, + transaction, + expectHexWei = false, +}) { + return async (dispatch) => { + const txParamsCopy = { ...transaction.txParams, gas: gasLimit }; + if (gasPrice) { + dispatch( + setCustomGasPrice(toHexWei(txParamsCopy.gasPrice, expectHexWei)), + ); + txParamsCopy.gasPrice = toHexWei(gasPrice, expectHexWei); + } else if (maxFeePerGas && maxPriorityFeePerGas) { + txParamsCopy.maxFeePerGas = toHexWei(maxFeePerGas, expectHexWei); + txParamsCopy.maxPriorityFeePerGas = addHexPrefix( + decGWEIToHexWEI(maxPriorityFeePerGas), + ); + } + const updatedTx = { + ...transaction, + txParams: txParamsCopy, + }; + + const customGasLimit = isHexString(addHexPrefix(gasLimit)) + ? addHexPrefix(gasLimit) + : addHexPrefix(gasLimit.toString(16)); + dispatch(setCustomGasLimit(customGasLimit)); + await dispatch(updateTransaction(updatedTx)); + }; +} + +// Selectors + export const getCurrentLocale = (state) => state.metamask.currentLocale; export const getAlertEnabledness = (state) => state.metamask.alertEnabledness; @@ -242,3 +287,15 @@ export function getUnapprovedTxs(state) { export function isEIP1559Network(state) { return state.metamask.networkDetails.EIPS[1559] === true; } + +export function getGasEstimateType(state) { + return state.metamask.gasEstimateType; +} + +export function getGasFeeEstimates(state) { + return state.metamask.gasFeeEstimates; +} + +export function getEstimatedGasFeeTimeBounds(state) { + return state.metamask.estimatedGasFeeTimeBounds; +} diff --git a/ui/ducks/send/send.js b/ui/ducks/send/send.js index 70bb7e7b1..a7a98072e 100644 --- a/ui/ducks/send/send.js +++ b/ui/ducks/send/send.js @@ -10,7 +10,7 @@ import { multiplyCurrencies, subtractCurrencies, } from '../../../shared/modules/conversion.utils'; -import { GAS_LIMITS } from '../../../shared/constants/gas'; +import { GAS_ESTIMATE_TYPES, GAS_LIMITS } from '../../../shared/constants/gas'; import { CONTRACT_ADDRESS_ERROR, INSUFFICIENT_FUNDS_ERROR, @@ -40,8 +40,10 @@ import { getIsNonStandardEthChain, } from '../../selectors'; import { + disconnectGasFeeEstimatePoller, displayWarning, estimateGas, + getGasFeeEstimatesAndStartPolling, hideLoadingIndication, showConfTxPage, showLoadingIndication, @@ -62,6 +64,7 @@ import { SELECTED_ACCOUNT_CHANGED, ACCOUNT_CHANGED, ADDRESS_BOOK_UPDATED, + GAS_FEE_ESTIMATES_UPDATED, } from '../../store/actionConstants'; import { calcTokenAmount, @@ -389,17 +392,44 @@ export const initializeSendState = createAsyncThunk( // the getMetaMaskAccounts selector. getTargetAccount consumes this // selector and returns the account at the specified address. const account = getTargetAccount(state, fromAddress); - // Initiate gas slices work to fetch gasPrice estimates. We need to get the - // new state after this is set to determine if initialization can proceed. - await thunkApi.dispatch(fetchBasicGasEstimates()); - const { - gas: { basicEstimateStatus, basicEstimates }, - } = thunkApi.getState(); + // Default gasPrice to 1 gwei if all estimation fails - const gasPrice = - basicEstimateStatus === BASIC_ESTIMATE_STATES.READY - ? getGasPriceInHexWei(basicEstimates.average) - : '0x1'; + let gasPrice = '0x1'; + let basicEstimateStatus = BASIC_ESTIMATE_STATES.LOADING; + let gasEstimatePollToken = null; + + if (Boolean(process.env.SHOW_EIP_1559_UI) === false) { + // Initiate gas slices work to fetch gasPrice estimates. We need to get the + // new state after this is set to determine if initialization can proceed. + await thunkApi.dispatch(fetchBasicGasEstimates()); + const { + gas: { basicEstimates, basicEstimateStatus: apiBasicEstimateStatus }, + } = thunkApi.getState(); + + basicEstimateStatus = apiBasicEstimateStatus; + + if (basicEstimateStatus === BASIC_ESTIMATE_STATES.READY) { + gasPrice = getGasPriceInHexWei(basicEstimates.average); + } + } else { + // Instruct the background process that polling for gas prices should begin + gasEstimatePollToken = await getGasFeeEstimatesAndStartPolling(); + const { + metamask: { gasFeeEstimates, gasEstimateType }, + } = thunkApi.getState(); + + if (gasEstimateType === GAS_ESTIMATE_TYPES.LEGACY) { + gasPrice = getGasPriceInHexWei(gasFeeEstimates.medium); + } else if (gasEstimateType === GAS_ESTIMATE_TYPES.ETH_GASPRICE) { + gasPrice = getGasPriceInHexWei(gasFeeEstimates.gasPrice); + } else if (gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET) { + gasPrice = getGasPriceInHexWei( + gasFeeEstimates.medium.suggestedMaxFeePerGas, + ); + } + + basicEstimateStatus = BASIC_ESTIMATE_STATES.READY; + } // Set a basic gasLimit in the event that other estimation fails let gasLimit = asset.type === ASSET_TYPES.TOKEN @@ -412,7 +442,7 @@ export const initializeSendState = createAsyncThunk( // Run our estimateGasLimit logic to get a more accurate estimation of // required gas. If this value isn't nullish, set it as the new gasLimit const estimatedGasLimit = await estimateGasLimitForSend({ - gasPrice: getGasPriceInHexWei(basicEstimates.average), + gasPrice, blockGasLimit: metamask.blockGasLimit, selectedAddress: fromAddress, sendToken: asset.details, @@ -451,6 +481,7 @@ export const initializeSendState = createAsyncThunk( gasPrice, gasLimit, gasTotal: addHexPrefix(calcGasTotal(gasLimit, gasPrice)), + gasEstimatePollToken, }; }, ); @@ -470,6 +501,8 @@ export const initialState = { gas: { // indicate whether the gas estimate is loading isGasEstimateLoading: true, + // String token indentifying a listener for polling on the gasFeeController + gasEstimatePollToken: null, // has the user set custom gas in the custom gas modal isCustomGasSet: false, // maximum gas needed for tx @@ -991,6 +1024,10 @@ const slice = createSlice({ state.gas.gasLimit = action.payload.gasLimit; state.gas.gasPrice = action.payload.gasPrice; state.gas.gasTotal = action.payload.gasTotal; + state.gas.gasEstimatePollToken = action.payload.gasEstimatePollToken; + if (action.payload.gasEstimatePollToken) { + state.gas.isGasEstimateLoading = false; + } if (state.stage !== SEND_STAGES.UNINITIALIZED) { slice.caseReducers.validateRecipientUserInput(state, { payload: { @@ -1030,9 +1067,11 @@ const slice = createSlice({ // the gasPrice in our slice. We call into the caseReducer // updateGasPrice to also tap into the appropriate follow up checks // and gasTotal calculation. - slice.caseReducers.updateGasPrice(state, { - payload: getGasPriceInHexWei(action.value.average), - }); + if (Boolean(process.env.SHOW_EIP_1559_UI) === false) { + slice.caseReducers.updateGasPrice(state, { + payload: getGasPriceInHexWei(action.value.average), + }); + } }) .addCase(BASIC_GAS_ESTIMATE_STATUS, (state, action) => { // When we fetch gas prices we should temporarily set the form invalid @@ -1053,6 +1092,28 @@ const slice = createSlice({ state.gas.isGasEstimateLoading = false; slice.caseReducers.validateSendState(state); } + }) + .addCase(GAS_FEE_ESTIMATES_UPDATED, (state, action) => { + // When the gasFeeController updates its gas fee estimates we need to + // update and validate state based on those new values + if (process.env.SHOW_EIP_1559_UI) { + const { gasFeeEstimates, gasEstimateType } = action.payload; + let payload = null; + if (gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET) { + payload = getGasPriceInHexWei( + gasFeeEstimates.medium.suggestedMaxFeePerGas, + ); + } else if (gasEstimateType === GAS_ESTIMATE_TYPES.LEGACY) { + payload = getGasPriceInHexWei(gasFeeEstimates.medium); + } else if (gasEstimateType === GAS_ESTIMATE_TYPES.ETH_GASPRICE) { + payload = getGasPriceInHexWei(gasFeeEstimates.gasPrice); + } + if (payload) { + slice.caseReducers.updateGasPrice(state, { + payload, + }); + } + } }); }, }); @@ -1066,21 +1127,26 @@ const { useCustomGas, updateGasLimit, updateGasPrice, - resetSendState, validateRecipientUserInput, updateRecipientSearchMode, } = actions; -export { - useDefaultGas, - useCustomGas, - updateGasLimit, - updateGasPrice, - resetSendState, -}; +export { useDefaultGas, useCustomGas, updateGasLimit, updateGasPrice }; // Action Creators +export function resetSendState() { + return async (dispatch, getState) => { + const state = getState(); + dispatch(actions.resetSendState()); + + if (state[name].gas.gasEstimatePollToken) { + await disconnectGasFeeEstimatePoller( + state[name].gas.gasEstimatePollToken, + ); + } + }; +} /** * Updates the amount the user intends to send and performs side effects. * 1. If the current mode is MAX change to INPUT diff --git a/ui/ducks/send/send.test.js b/ui/ducks/send/send.test.js index 57f84071f..4b96e7a24 100644 --- a/ui/ducks/send/send.test.js +++ b/ui/ducks/send/send.test.js @@ -42,6 +42,7 @@ jest.mock('../../store/actions', () => { return { ...actual, estimateGas: jest.fn(() => Promise.resolve('0x0')), + getGasFeeEstimatesAndStartPolling: jest.fn(() => Promise.resolve()), updateTokenType: jest.fn(() => Promise.resolve({ isERC721: false })), }; }); diff --git a/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js index e58b30c2e..3e3c4197f 100644 --- a/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js +++ b/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js @@ -113,6 +113,7 @@ export default class ConfirmTransactionBase extends Component { submitError: null, submitWarning: '', ethGasPriceWarning: '', + editingGas: false, }; componentDidUpdate(prevProps) { @@ -259,7 +260,17 @@ export default class ConfirmTransactionBase extends Component { }, }); - showCustomizeGasModal(); + if (process.env.SHOW_EIP_1559_UI) { + this.setState({ editingGas: true }); + } else { + showCustomizeGasModal(); + } + } + + // TODO: rename this 'handleCloseEditGas' later when we remove the + // SHOW_EIP_1559_UI flag/branch + handleCloseNewGasPopover() { + this.setState({ editingGas: false }); } renderDetails() { @@ -389,6 +400,13 @@ export default class ConfirmTransactionBase extends Component { ); } + const showInlineControls = process.env.SHOW_EIP_1559_UI + ? advancedInlineGasShown + : advancedInlineGasShown || notMainnetOrTest || gasPriceFetchFailure; + + const showGasEditButton = process.env.SHOW_EIP_1559_UI + ? !showInlineControls + : !(notMainnetOrTest || gasPriceFetchFailure); return (
@@ -396,24 +414,18 @@ export default class ConfirmTransactionBase extends Component { this.handleEditGas() + showGasEditButton ? () => this.handleEditGas() : null } secondaryText={ hideFiatConversion ? t('noConversionRateAvailable') : '' } /> - {advancedInlineGasShown || notMainnetOrTest || gasPriceFetchFailure - ? inlineGasControls - : null} + {showInlineControls ? inlineGasControls : null} {noGasPrice ? (
@@ -735,6 +747,7 @@ export default class ConfirmTransactionBase extends Component { submitError, submitWarning, ethGasPriceWarning, + editingGas, } = this.state; const { name } = methodData; @@ -802,6 +815,8 @@ export default class ConfirmTransactionBase extends Component { hideSenderToRecipient={hideSenderToRecipient} origin={txData.origin} ethGasPriceWarning={ethGasPriceWarning} + editingGas={editingGas} + handleCloseEditGas={() => this.handleCloseNewGasPopover()} /> ); } diff --git a/ui/pages/confirm-transaction-base/confirm-transaction-base.container.js b/ui/pages/confirm-transaction-base/confirm-transaction-base.container.js index 9cc4e7741..df2a0b670 100644 --- a/ui/pages/confirm-transaction-base/confirm-transaction-base.container.js +++ b/ui/pages/confirm-transaction-base/confirm-transaction-base.container.js @@ -10,7 +10,6 @@ import { cancelTxs, updateAndApproveTx, showModal, - updateTransaction, getNextNonce, tryReverseResolveAddress, setDefaultHomeActiveTabName, @@ -39,6 +38,7 @@ import { import { getMostRecentOverviewPage } from '../../ducks/history/history'; import { transactionMatchesNetwork } from '../../../shared/modules/transaction.utils'; import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils'; +import { updateTransactionGasFees } from '../../ducks/metamask/metamask'; import ConfirmTransactionBase from './confirm-transaction-base.component'; const casedContractMap = Object.keys(contractMap).reduce((acc, base) => { @@ -213,9 +213,6 @@ export const mapDispatchToProps = (dispatch) => { }), ); }, - updateGasAndCalculate: (updatedTx) => { - return dispatch(updateTransaction(updatedTx)); - }, showRejectTransactionsConfirmationModal: ({ onSubmit, unapprovedTxCount, @@ -231,6 +228,9 @@ export const mapDispatchToProps = (dispatch) => { getNextNonce: () => dispatch(getNextNonce()), setDefaultHomeActiveTabName: (tabName) => dispatch(setDefaultHomeActiveTabName(tabName)), + updateTransactionGasFees: (gasFees) => { + dispatch(updateTransactionGasFees({ ...gasFees, expectHexWei: true })); + }, }; }; @@ -286,7 +286,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { const { cancelAllTransactions: dispatchCancelAllTransactions, showCustomizeGasModal: dispatchShowCustomizeGasModal, - updateGasAndCalculate: dispatchUpdateGasAndCalculate, + updateTransactionGasFees: dispatchUpdateTransactionGasFees, ...otherDispatchProps } = dispatchProps; @@ -303,21 +303,17 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { showCustomizeGasModal: () => dispatchShowCustomizeGasModal({ txData, - onSubmit: (customGas) => dispatchUpdateGasAndCalculate(customGas), + onSubmit: (customGas) => dispatchUpdateTransactionGasFees(customGas), validate: validateEditGas, }), cancelAllTransactions: () => dispatchCancelAllTransactions(valuesFor(unapprovedTxs)), updateGasAndCalculate: ({ gasLimit, gasPrice }) => { - const updatedTx = { - ...txData, - txParams: { - ...txData.txParams, - gas: gasLimit, - gasPrice, - }, - }; - dispatchUpdateGasAndCalculate(updatedTx); + dispatchUpdateTransactionGasFees({ + gasLimit, + gasPrice, + transaction: txData, + }); }, }; }; diff --git a/ui/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.js b/ui/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.js index 7f143879b..03676e3c5 100644 --- a/ui/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.js +++ b/ui/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.js @@ -29,17 +29,20 @@ export default function AmountMaxButton() { dispatch(toggleSendMaxMode()); }; + const disabled = process.env.SHOW_EIP_1559_UI + ? isDraftTransactionInvalid + : buttonDataLoading || isDraftTransactionInvalid; + return (