wire up gasFeeController (#11421)

feature/default_network_editable
Brad Decker 3 years ago committed by GitHub
parent 64adfe7b11
commit 68dfc98f40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 40
      app/scripts/metamask-controller.js
  2. 11
      shared/constants/gas.js
  3. 6
      ui/components/app/confirm-page-container/confirm-page-container.component.js
  4. 8
      ui/components/app/edit-gas-popover/edit-gas-popover.component.js
  5. 38
      ui/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container-container.test.js
  6. 28
      ui/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js
  7. 57
      ui/ducks/metamask/metamask.js
  8. 112
      ui/ducks/send/send.js
  9. 1
      ui/ducks/send/send.test.js
  10. 37
      ui/pages/confirm-transaction-base/confirm-transaction-base.component.js
  11. 26
      ui/pages/confirm-transaction-base/confirm-transaction-base.container.js
  12. 9
      ui/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.js
  13. 2
      ui/pages/send/send-content/send-content.component.js
  14. 1
      ui/store/actionConstants.js
  15. 41
      ui/store/actions.js
  16. 7
      yarn.lock

@ -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,
),
};
}

@ -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',
};

@ -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 {
)}
</PageContainerFooter>
)}
{editingGas && <EditGasPopover onClose={handleCloseEditGas} />}
</div>
);
}

@ -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 = {

@ -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);

@ -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();

@ -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;
}

@ -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

@ -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 })),
};
});

@ -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 {
</div>
);
}
const showInlineControls = process.env.SHOW_EIP_1559_UI
? advancedInlineGasShown
: advancedInlineGasShown || notMainnetOrTest || gasPriceFetchFailure;
const showGasEditButton = process.env.SHOW_EIP_1559_UI
? !showInlineControls
: !(notMainnetOrTest || gasPriceFetchFailure);
return (
<div className="confirm-page-container-content__details">
@ -396,24 +414,18 @@ export default class ConfirmTransactionBase extends Component {
<ConfirmDetailRow
label={t('gasFee')}
value={hexTransactionFee}
headerText={notMainnetOrTest || gasPriceFetchFailure ? '' : 'Edit'}
headerText={showGasEditButton ? 'Edit' : ''}
headerTextClassName={
notMainnetOrTest || gasPriceFetchFailure
? ''
: 'confirm-detail-row__header-text--edit'
showGasEditButton ? 'confirm-detail-row__header-text--edit' : ''
}
onHeaderClick={
notMainnetOrTest || gasPriceFetchFailure
? null
: () => this.handleEditGas()
showGasEditButton ? () => this.handleEditGas() : null
}
secondaryText={
hideFiatConversion ? t('noConversionRateAvailable') : ''
}
/>
{advancedInlineGasShown || notMainnetOrTest || gasPriceFetchFailure
? inlineGasControls
: null}
{showInlineControls ? inlineGasControls : null}
{noGasPrice ? (
<div className="confirm-page-container-content__error-container">
<ErrorMessage errorKey={GAS_PRICE_FETCH_FAILURE_ERROR_KEY} />
@ -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()}
/>
);
}

@ -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,
});
},
};
};

@ -29,17 +29,20 @@ export default function AmountMaxButton() {
dispatch(toggleSendMaxMode());
};
const disabled = process.env.SHOW_EIP_1559_UI
? isDraftTransactionInvalid
: buttonDataLoading || isDraftTransactionInvalid;
return (
<button
className="send-v2__amount-max"
disabled={buttonDataLoading || isDraftTransactionInvalid}
disabled={disabled}
onClick={onMaxClick}
>
<input type="checkbox" checked={maxModeOn} readOnly />
<div
className={classnames('send-v2__amount-max__button', {
'send-v2__amount-max__button__disabled':
buttonDataLoading || isDraftTransactionInvalid,
'send-v2__amount-max__button__disabled': disabled,
})}
>
{t('max')}

@ -57,7 +57,7 @@ export default class SendContent extends Component {
{this.maybeRenderAddContact()}
<SendAssetRow />
<SendAmountRow />
<SendGasRow />
{process.env.SHOW_EIP_1559_UI ? null : <SendGasRow />}
{this.props.showHexData && <SendHexDataRow />}
</div>
</PageContainerContent>

@ -19,6 +19,7 @@ export const SELECTED_ACCOUNT_CHANGED = 'SELECTED_ACCOUNT_CHANGED';
export const ACCOUNT_CHANGED = 'ACCOUNT_CHANGED';
export const CHAIN_CHANGED = 'CHAIN_CHANGED';
export const ADDRESS_BOOK_UPDATED = 'ADDRESS_BOOK_UPDATED';
export const GAS_FEE_ESTIMATES_UPDATED = 'GAS_FEE_ESTIMATES_UPDATED';
export const FORGOT_PASSWORD = 'FORGOT_PASSWORD';
export const CLOSE_WELCOME_SCREEN = 'CLOSE_WELCOME_SCREEN';
// unlock screen

@ -20,7 +20,10 @@ import {
} from '../selectors';
import { computeEstimatedGasLimit, resetSendState } from '../ducks/send';
import { switchedToUnconnectedAccount } from '../ducks/alerts/unconnected-account';
import { getUnconnectedAccountAlertEnabledness } from '../ducks/metamask/metamask';
import {
getUnconnectedAccountAlertEnabledness,
isEIP1559Network,
} from '../ducks/metamask/metamask';
import { LISTED_CONTRACT_ADDRESSES } from '../../shared/constants/tokens';
import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils';
import * as actionConstants from './actionConstants';
@ -1056,6 +1059,19 @@ export function updateMetamaskState(newState) {
});
}
// track when gasFeeEstimates change
if (
isEqual(currentState.gasFeeEstimates, newState.gasFeeEstimates) === false
) {
dispatch({
type: actionConstants.GAS_FEE_ESTIMATES_UPDATED,
payload: {
gasFeeEstimates: newState.gasFeeEstimates,
gasEstimateType: newState.gasEstimateType,
isEIP1559Network: isEIP1559Network({ metamask: newState }),
},
});
}
if (provider.chainId !== newProvider.chainId) {
dispatch({
type: actionConstants.CHAIN_CHANGED,
@ -2708,6 +2724,29 @@ export async function updateTokenType(tokenAddress) {
return token;
}
/**
* initiates polling for gas fee estimates.
*
* @returns {string} a unique identify of the polling request that can be used
* to remove that request from consideration of whether polling needs to
* continue.
*/
export function getGasFeeEstimatesAndStartPolling() {
return promisifiedBackground.getGasFeeEstimatesAndStartPolling();
}
/**
* Informs the GasFeeController that a specific token is no longer requiring
* gas fee estimates. If all tokens unsubscribe the controller stops polling.
*
* @param {string} pollToken - Poll token received from calling
* `getGasFeeEstimatesAndStartPolling`.
* @returns {void}
*/
export function disconnectGasFeeEstimatePoller(pollToken) {
return promisifiedBackground.disconnectGasFeeEstimatePoller(pollToken);
}
// MetaMetrics
/**
* @typedef {import('../../shared/constants/metametrics').MetaMetricsEventPayload} MetaMetricsEventPayload

@ -2646,7 +2646,12 @@
semver "^7.3.5"
yargs "^17.0.1"
"@metamask/contract-metadata@^1.19.0", "@metamask/contract-metadata@^1.27.0":
"@metamask/contract-metadata@^1.19.0":
version "1.25.0"
resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-1.25.0.tgz#442ace91fb40165310764b68d8096d0017bb0492"
integrity sha512-yhmYB9CQPv0dckNcPoWDcgtrdUp0OgK0uvkRE5QIBv4b3qENI1/03BztvK2ijbTuMlORUpjPq7/1MQDUPoRPVw==
"@metamask/contract-metadata@^1.27.0":
version "1.27.0"
resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-1.27.0.tgz#1e65b821bad6f7d8313dd881116e4366b0662f75"
integrity sha512-ZAvuROiHjSksy40buCL4M6m16bAmXQl6vjllK/XQxt9+UElhwJSp7PJ1ZULuZXmznBBY64WXlCPjm94JhW4l3g==

Loading…
Cancel
Save