mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 01:47:00 +01:00
wire up gasFeeController (#11421)
This commit is contained in:
parent
64adfe7b11
commit
68dfc98f40
@ -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…
Reference in New Issue
Block a user