mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Update send and confirm state management, and tx controller gas defaults, for EIP1559 (#11549)
wip Documentation improvements for send slice support of EIP1559 Remove console.log in send duck Property lookup safety improvement in selectors/confirm-transaction Add code accidentally removed in rebase Update addTxGasDefaults and _getDefaultGasFees to work with new estimate types, and ensure we correctly handle gas price estimates when on EIP1559 networks (#11615) * Fix typo Remove console.log in send duck * Update addTxGasDefaults and _getDefaultGasFees to work correctly with all new gas fee estimate types * Don't show gas timing support when not on eip1559 compatible network * Hide gas timing component on transaction screen when on a non-1559 network * Improve comments, tests and edge case handling * Ensure eip1559 fees are applied and updated correctly when eip1559 estimate api fails * Lint fix Co-authored-by: Brad Decker <git@braddecker.dev> Remove console.log Handle possible gasEstimateType undefined Remove unnecessary nonce field position change in confirm-page-container-content__details
This commit is contained in:
parent
f1140087b7
commit
e0953d9f68
@ -27,7 +27,11 @@ import {
|
|||||||
TRANSACTION_ENVELOPE_TYPES,
|
TRANSACTION_ENVELOPE_TYPES,
|
||||||
} from '../../../../shared/constants/transaction';
|
} from '../../../../shared/constants/transaction';
|
||||||
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
|
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
|
||||||
import { GAS_LIMITS } from '../../../../shared/constants/gas';
|
import {
|
||||||
|
GAS_LIMITS,
|
||||||
|
GAS_ESTIMATE_TYPES,
|
||||||
|
} from '../../../../shared/constants/gas';
|
||||||
|
import { decGWEIToHexWEI } from '../../../../shared/modules/conversion.utils';
|
||||||
import {
|
import {
|
||||||
HARDFORKS,
|
HARDFORKS,
|
||||||
MAINNET,
|
MAINNET,
|
||||||
@ -107,6 +111,7 @@ export default class TransactionController extends EventEmitter {
|
|||||||
this.inProcessOfSigning = new Set();
|
this.inProcessOfSigning = new Set();
|
||||||
this._trackMetaMetricsEvent = opts.trackMetaMetricsEvent;
|
this._trackMetaMetricsEvent = opts.trackMetaMetricsEvent;
|
||||||
this._getParticipateInMetrics = opts.getParticipateInMetrics;
|
this._getParticipateInMetrics = opts.getParticipateInMetrics;
|
||||||
|
this._getEIP1559GasFeeEstimates = opts.getEIP1559GasFeeEstimates;
|
||||||
|
|
||||||
this.memStore = new ObservableStore({});
|
this.memStore = new ObservableStore({});
|
||||||
this.query = new EthQuery(this.provider);
|
this.query = new EthQuery(this.provider);
|
||||||
@ -399,7 +404,13 @@ export default class TransactionController extends EventEmitter {
|
|||||||
* @returns {Promise<object>} resolves with txMeta
|
* @returns {Promise<object>} resolves with txMeta
|
||||||
*/
|
*/
|
||||||
async addTxGasDefaults(txMeta, getCodeResponse) {
|
async addTxGasDefaults(txMeta, getCodeResponse) {
|
||||||
const defaultGasPrice = await this._getDefaultGasPrice(txMeta);
|
const eip1559Compatibility = await this.getEIP1559Compatibility();
|
||||||
|
|
||||||
|
const {
|
||||||
|
gasPrice: defaultGasPrice,
|
||||||
|
maxFeePerGas: defaultMaxFeePerGas,
|
||||||
|
maxPriorityFeePerGas: defaultMaxPriorityFeePerGas,
|
||||||
|
} = await this._getDefaultGasFees(txMeta, eip1559Compatibility);
|
||||||
const {
|
const {
|
||||||
gasLimit: defaultGasLimit,
|
gasLimit: defaultGasLimit,
|
||||||
simulationFails,
|
simulationFails,
|
||||||
@ -410,6 +421,67 @@ export default class TransactionController extends EventEmitter {
|
|||||||
if (simulationFails) {
|
if (simulationFails) {
|
||||||
txMeta.simulationFails = simulationFails;
|
txMeta.simulationFails = simulationFails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eip1559Compatibility) {
|
||||||
|
// If the dapp has suggested a gas price, but no maxFeePerGas or maxPriorityFeePerGas
|
||||||
|
// then we set maxFeePerGas and maxPriorityFeePerGas to the suggested gasPrice.
|
||||||
|
if (
|
||||||
|
txMeta.txParams.gasPrice &&
|
||||||
|
!txMeta.txParams.maxFeePerGas &&
|
||||||
|
!txMeta.txParams.maxPriorityFeePerGas
|
||||||
|
) {
|
||||||
|
txMeta.txParams.maxFeePerGas = txMeta.txParams.gasPrice;
|
||||||
|
txMeta.txParams.maxPriorityFeePerGas = txMeta.txParams.gasPrice;
|
||||||
|
} else {
|
||||||
|
if (defaultMaxFeePerGas && !txMeta.txParams.maxFeePerGas) {
|
||||||
|
// If the dapp has not set the gasPrice or the maxFeePerGas, then we set maxFeePerGas
|
||||||
|
// with the one returned by the gasFeeController, if that is available.
|
||||||
|
txMeta.txParams.maxFeePerGas = defaultMaxFeePerGas;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
defaultMaxPriorityFeePerGas &&
|
||||||
|
!txMeta.txParams.maxPriorityFeePerGas
|
||||||
|
) {
|
||||||
|
// If the dapp has not set the gasPrice or the maxPriorityFeePerGas, then we set maxPriorityFeePerGas
|
||||||
|
// with the one returned by the gasFeeController, if that is available.
|
||||||
|
txMeta.txParams.maxPriorityFeePerGas = defaultMaxPriorityFeePerGas;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultGasPrice && !txMeta.txParams.maxFeePerGas) {
|
||||||
|
// If the dapp has not set the gasPrice or the maxFeePerGas, and no maxFeePerGas is available
|
||||||
|
// from the gasFeeController, then we set maxFeePerGas to the defaultGasPrice, assuming it is
|
||||||
|
// available.
|
||||||
|
txMeta.txParams.maxFeePerGas = defaultGasPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
txMeta.txParams.maxFeePerGas &&
|
||||||
|
!txMeta.txParams.maxPriorityFeePerGas
|
||||||
|
) {
|
||||||
|
// If the dapp has not set the gasPrice or the maxPriorityFeePerGas, and no maxPriorityFeePerGas is
|
||||||
|
// available from the gasFeeController, then we set maxPriorityFeePerGas to
|
||||||
|
// txMeta.txParams.maxFeePerGas, which will either be the gasPrice from the controller, the maxFeePerGas
|
||||||
|
// set by the dapp, or the maxFeePerGas from the controller.
|
||||||
|
txMeta.txParams.maxPriorityFeePerGas = txMeta.txParams.maxFeePerGas;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We remove the gasPrice param entirely when on an eip1559 compatible network
|
||||||
|
|
||||||
|
delete txMeta.txParams.gasPrice;
|
||||||
|
} else {
|
||||||
|
// We ensure that maxFeePerGas and maxPriorityFeePerGas are not in the transaction params
|
||||||
|
// when not on a EIP1559 compatible network
|
||||||
|
|
||||||
|
delete txMeta.txParams.maxPriorityFeePerGas;
|
||||||
|
delete txMeta.txParams.maxFeePerGas;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have gotten to this point, and none of gasPrice, maxPriorityFeePerGas or maxFeePerGas are
|
||||||
|
// set on txParams, it means that either we are on a non-EIP1559 network and the dapp didn't suggest
|
||||||
|
// a gas price, or we are on an EIP1559 network, and none of gasPrice, maxPriorityFeePerGas or maxFeePerGas
|
||||||
|
// were available from either the dapp or the network.
|
||||||
if (
|
if (
|
||||||
defaultGasPrice &&
|
defaultGasPrice &&
|
||||||
!txMeta.txParams.gasPrice &&
|
!txMeta.txParams.gasPrice &&
|
||||||
@ -418,6 +490,7 @@ export default class TransactionController extends EventEmitter {
|
|||||||
) {
|
) {
|
||||||
txMeta.txParams.gasPrice = defaultGasPrice;
|
txMeta.txParams.gasPrice = defaultGasPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defaultGasLimit && !txMeta.txParams.gas) {
|
if (defaultGasLimit && !txMeta.txParams.gas) {
|
||||||
txMeta.txParams.gas = defaultGasLimit;
|
txMeta.txParams.gas = defaultGasLimit;
|
||||||
}
|
}
|
||||||
@ -425,20 +498,59 @@ export default class TransactionController extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets default gas price, or returns `undefined` if gas price is already set
|
* Gets default gas fees, or returns `undefined` if gas fees are already set
|
||||||
* @param {Object} txMeta - The txMeta object
|
* @param {Object} txMeta - The txMeta object
|
||||||
* @returns {Promise<string|undefined>} The default gas price
|
* @returns {Promise<string|undefined>} The default gas price
|
||||||
*/
|
*/
|
||||||
async _getDefaultGasPrice(txMeta) {
|
async _getDefaultGasFees(txMeta, eip1559Compatibility) {
|
||||||
if (
|
if (
|
||||||
txMeta.txParams.gasPrice ||
|
(!eip1559Compatibility && txMeta.txParams.gasPrice) ||
|
||||||
(txMeta.txParams.maxFeePerGas && txMeta.txParams.maxPriorityFeePerGas)
|
(txMeta.txParams.maxFeePerGas && txMeta.txParams.maxPriorityFeePerGas)
|
||||||
) {
|
) {
|
||||||
return undefined;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
gasFeeEstimates,
|
||||||
|
gasEstimateType,
|
||||||
|
} = await this._getEIP1559GasFeeEstimates();
|
||||||
|
if (
|
||||||
|
eip1559Compatibility &&
|
||||||
|
gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
medium: { suggestedMaxPriorityFeePerGas, suggestedMaxFeePerGas } = {},
|
||||||
|
} = gasFeeEstimates;
|
||||||
|
|
||||||
|
if (suggestedMaxPriorityFeePerGas && suggestedMaxFeePerGas) {
|
||||||
|
return {
|
||||||
|
maxFeePerGas: decGWEIToHexWEI(suggestedMaxFeePerGas),
|
||||||
|
maxPriorityFeePerGas: decGWEIToHexWEI(
|
||||||
|
suggestedMaxPriorityFeePerGas,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (gasEstimateType === GAS_ESTIMATE_TYPES.LEGACY) {
|
||||||
|
// The LEGACY type includes low, medium and high estimates of
|
||||||
|
// gas price values.
|
||||||
|
return {
|
||||||
|
gasPrice: decGWEIToHexWEI(gasFeeEstimates.medium),
|
||||||
|
};
|
||||||
|
} else if (gasEstimateType === GAS_ESTIMATE_TYPES.ETH_GASPRICE) {
|
||||||
|
// The ETH_GASPRICE type just includes a single gas price property,
|
||||||
|
// which we can assume was retrieved from eth_gasPrice
|
||||||
|
return {
|
||||||
|
gasPrice: decGWEIToHexWEI(gasFeeEstimates.gasPrice),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
const gasPrice = await this.query.gasPrice();
|
const gasPrice = await this.query.gasPrice();
|
||||||
|
|
||||||
return addHexPrefix(gasPrice.toString(16));
|
return { gasPrice: gasPrice && addHexPrefix(gasPrice.toString(16)) };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -682,6 +794,7 @@ export default class TransactionController extends EventEmitter {
|
|||||||
this.txStateManager.setTxStatusApproved(txId);
|
this.txStateManager.setTxStatusApproved(txId);
|
||||||
// get next nonce
|
// get next nonce
|
||||||
const txMeta = this.txStateManager.getTransaction(txId);
|
const txMeta = this.txStateManager.getTransaction(txId);
|
||||||
|
|
||||||
const fromAddress = txMeta.txParams.from;
|
const fromAddress = txMeta.txParams.from;
|
||||||
// wait for a nonce
|
// wait for a nonce
|
||||||
let { customNonceValue } = txMeta;
|
let { customNonceValue } = txMeta;
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
TRANSACTION_TYPES,
|
TRANSACTION_TYPES,
|
||||||
} from '../../../../shared/constants/transaction';
|
} from '../../../../shared/constants/transaction';
|
||||||
import { SECOND } from '../../../../shared/constants/time';
|
import { SECOND } from '../../../../shared/constants/time';
|
||||||
|
import { GAS_ESTIMATE_TYPES } from '../../../../shared/constants/gas';
|
||||||
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
|
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
|
||||||
import TransactionController, { TRANSACTION_EVENTS } from '.';
|
import TransactionController, { TRANSACTION_EVENTS } from '.';
|
||||||
|
|
||||||
@ -50,9 +51,8 @@ describe('Transaction Controller', function () {
|
|||||||
return '0xee6b2800';
|
return '0xee6b2800';
|
||||||
},
|
},
|
||||||
networkStore: new ObservableStore(currentNetworkId),
|
networkStore: new ObservableStore(currentNetworkId),
|
||||||
getEIP1559Compatibility: () => Promise.resolve(true),
|
getCurrentNetworkEIP1559Compatibility: () => Promise.resolve(false),
|
||||||
getCurrentNetworkEIP1559Compatibility: () => Promise.resolve(true),
|
getCurrentAccountEIP1559Compatibility: () => false,
|
||||||
getCurrentAccountEIP1559Compatibility: () => true,
|
|
||||||
txHistoryLimit: 10,
|
txHistoryLimit: 10,
|
||||||
blockTracker: blockTrackerStub,
|
blockTracker: blockTrackerStub,
|
||||||
signTransaction: (ethTx) =>
|
signTransaction: (ethTx) =>
|
||||||
@ -64,6 +64,7 @@ describe('Transaction Controller', function () {
|
|||||||
getCurrentChainId: () => currentChainId,
|
getCurrentChainId: () => currentChainId,
|
||||||
getParticipateInMetrics: () => false,
|
getParticipateInMetrics: () => false,
|
||||||
trackMetaMetricsEvent: () => undefined,
|
trackMetaMetricsEvent: () => undefined,
|
||||||
|
getEIP1559GasFeeEstimates: () => undefined,
|
||||||
});
|
});
|
||||||
txController.nonceTracker.getNonceLock = () =>
|
txController.nonceTracker.getNonceLock = () =>
|
||||||
Promise.resolve({ nextNonce: 0, releaseLock: noop });
|
Promise.resolve({ nextNonce: 0, releaseLock: noop });
|
||||||
@ -419,6 +420,237 @@ describe('Transaction Controller', function () {
|
|||||||
'should have added the gas field',
|
'should have added the gas field',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add EIP1559 tx defaults', async function () {
|
||||||
|
const TEST_MAX_FEE_PER_GAS = '0x12a05f200';
|
||||||
|
const TEST_MAX_PRIORITY_FEE_PER_GAS = '0x77359400';
|
||||||
|
|
||||||
|
const stub1 = sinon
|
||||||
|
.stub(txController, 'getEIP1559Compatibility')
|
||||||
|
.returns(true);
|
||||||
|
|
||||||
|
const stub2 = sinon
|
||||||
|
.stub(txController, '_getDefaultGasFees')
|
||||||
|
.callsFake(() => ({
|
||||||
|
maxFeePerGas: TEST_MAX_FEE_PER_GAS,
|
||||||
|
maxPriorityFeePerGas: TEST_MAX_PRIORITY_FEE_PER_GAS,
|
||||||
|
}));
|
||||||
|
|
||||||
|
txController.txStateManager._addTransactionsToState([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
status: TRANSACTION_STATUSES.UNAPPROVED,
|
||||||
|
metamaskNetworkId: currentNetworkId,
|
||||||
|
txParams: {
|
||||||
|
to: VALID_ADDRESS,
|
||||||
|
from: VALID_ADDRESS_TWO,
|
||||||
|
},
|
||||||
|
history: [{}],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const txMeta = {
|
||||||
|
id: 1,
|
||||||
|
txParams: {
|
||||||
|
from: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
|
||||||
|
to: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
|
||||||
|
},
|
||||||
|
history: [{}],
|
||||||
|
};
|
||||||
|
providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' };
|
||||||
|
providerResultStub.eth_estimateGas = '5209';
|
||||||
|
|
||||||
|
const txMetaWithDefaults = await txController.addTxGasDefaults(txMeta);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
txMetaWithDefaults.txParams.maxFeePerGas,
|
||||||
|
TEST_MAX_FEE_PER_GAS,
|
||||||
|
'should have added the correct max fee per gas',
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
txMetaWithDefaults.txParams.maxPriorityFeePerGas,
|
||||||
|
TEST_MAX_PRIORITY_FEE_PER_GAS,
|
||||||
|
'should have added the correct max priority fee per gas',
|
||||||
|
);
|
||||||
|
stub1.restore();
|
||||||
|
stub2.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add gasPrice as maxFeePerGas and maxPriorityFeePerGas if there are no sources of other fee data available', async function () {
|
||||||
|
const TEST_GASPRICE = '0x12a05f200';
|
||||||
|
|
||||||
|
const stub1 = sinon
|
||||||
|
.stub(txController, 'getEIP1559Compatibility')
|
||||||
|
.returns(true);
|
||||||
|
|
||||||
|
const stub2 = sinon
|
||||||
|
.stub(txController, '_getDefaultGasFees')
|
||||||
|
.callsFake(() => ({ gasPrice: TEST_GASPRICE }));
|
||||||
|
|
||||||
|
txController.txStateManager._addTransactionsToState([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
status: TRANSACTION_STATUSES.UNAPPROVED,
|
||||||
|
metamaskNetworkId: currentNetworkId,
|
||||||
|
txParams: {
|
||||||
|
to: VALID_ADDRESS,
|
||||||
|
from: VALID_ADDRESS_TWO,
|
||||||
|
},
|
||||||
|
history: [{}],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const txMeta = {
|
||||||
|
id: 1,
|
||||||
|
txParams: {
|
||||||
|
from: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
|
||||||
|
to: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
|
||||||
|
},
|
||||||
|
history: [{}],
|
||||||
|
};
|
||||||
|
providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' };
|
||||||
|
providerResultStub.eth_estimateGas = '5209';
|
||||||
|
|
||||||
|
const txMetaWithDefaults = await txController.addTxGasDefaults(txMeta);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
txMetaWithDefaults.txParams.maxFeePerGas,
|
||||||
|
TEST_GASPRICE,
|
||||||
|
'should have added the correct max fee per gas',
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
txMetaWithDefaults.txParams.maxPriorityFeePerGas,
|
||||||
|
TEST_GASPRICE,
|
||||||
|
'should have added the correct max priority fee per gas',
|
||||||
|
);
|
||||||
|
stub1.restore();
|
||||||
|
stub2.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not add gasPrice if the fee data is available from the dapp', async function () {
|
||||||
|
const TEST_GASPRICE = '0x12a05f200';
|
||||||
|
const TEST_MAX_FEE_PER_GAS = '0x12a05f200';
|
||||||
|
const TEST_MAX_PRIORITY_FEE_PER_GAS = '0x77359400';
|
||||||
|
|
||||||
|
const stub1 = sinon
|
||||||
|
.stub(txController, 'getEIP1559Compatibility')
|
||||||
|
.returns(true);
|
||||||
|
|
||||||
|
const stub2 = sinon
|
||||||
|
.stub(txController, '_getDefaultGasFees')
|
||||||
|
.callsFake(() => ({ gasPrice: TEST_GASPRICE }));
|
||||||
|
|
||||||
|
txController.txStateManager._addTransactionsToState([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
status: TRANSACTION_STATUSES.UNAPPROVED,
|
||||||
|
metamaskNetworkId: currentNetworkId,
|
||||||
|
txParams: {
|
||||||
|
to: VALID_ADDRESS,
|
||||||
|
from: VALID_ADDRESS_TWO,
|
||||||
|
maxFeePerGas: TEST_MAX_FEE_PER_GAS,
|
||||||
|
maxPriorityFeePerGas: TEST_MAX_PRIORITY_FEE_PER_GAS,
|
||||||
|
},
|
||||||
|
history: [{}],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const txMeta = {
|
||||||
|
id: 1,
|
||||||
|
txParams: {
|
||||||
|
from: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
|
||||||
|
to: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
|
||||||
|
},
|
||||||
|
history: [{}],
|
||||||
|
};
|
||||||
|
providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' };
|
||||||
|
providerResultStub.eth_estimateGas = '5209';
|
||||||
|
|
||||||
|
const txMetaWithDefaults = await txController.addTxGasDefaults(txMeta);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
txMetaWithDefaults.txParams.maxFeePerGas,
|
||||||
|
TEST_MAX_FEE_PER_GAS,
|
||||||
|
'should have added the correct max fee per gas',
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
txMetaWithDefaults.txParams.maxPriorityFeePerGas,
|
||||||
|
TEST_MAX_PRIORITY_FEE_PER_GAS,
|
||||||
|
'should have added the correct max priority fee per gas',
|
||||||
|
);
|
||||||
|
stub1.restore();
|
||||||
|
stub2.restore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('_getDefaultGasFees', function () {
|
||||||
|
let getGasFeeStub;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
getGasFeeStub = sinon.stub(txController, '_getEIP1559GasFeeEstimates');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
getGasFeeStub.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the correct fee data when the gas estimate type is FEE_MARKET', async function () {
|
||||||
|
const EXPECTED_MAX_FEE_PER_GAS = '12a05f200';
|
||||||
|
const EXPECTED_MAX_PRIORITY_FEE_PER_GAS = '77359400';
|
||||||
|
|
||||||
|
getGasFeeStub.callsFake(() => ({
|
||||||
|
gasFeeEstimates: {
|
||||||
|
medium: {
|
||||||
|
suggestedMaxPriorityFeePerGas: '2',
|
||||||
|
suggestedMaxFeePerGas: '5',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const defaultGasFees = await txController._getDefaultGasFees(
|
||||||
|
{ txParams: {} },
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(defaultGasFees, {
|
||||||
|
maxPriorityFeePerGas: EXPECTED_MAX_PRIORITY_FEE_PER_GAS,
|
||||||
|
maxFeePerGas: EXPECTED_MAX_FEE_PER_GAS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the correct fee data when the gas estimate type is LEGACY', async function () {
|
||||||
|
const EXPECTED_GAS_PRICE = '77359400';
|
||||||
|
|
||||||
|
getGasFeeStub.callsFake(() => ({
|
||||||
|
gasFeeEstimates: { medium: '2' },
|
||||||
|
gasEstimateType: GAS_ESTIMATE_TYPES.LEGACY,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const defaultGasFees = await txController._getDefaultGasFees(
|
||||||
|
{ txParams: {} },
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(defaultGasFees, {
|
||||||
|
gasPrice: EXPECTED_GAS_PRICE,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the correct fee data when the gas estimate type is ETH_GASPRICE', async function () {
|
||||||
|
const EXPECTED_GAS_PRICE = '77359400';
|
||||||
|
|
||||||
|
getGasFeeStub.callsFake(() => ({
|
||||||
|
gasFeeEstimates: { gasPrice: '2' },
|
||||||
|
gasEstimateType: GAS_ESTIMATE_TYPES.ETH_GASPRICE,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const defaultGasFees = await txController._getDefaultGasFees(
|
||||||
|
{ txParams: {} },
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(defaultGasFees, {
|
||||||
|
gasPrice: EXPECTED_GAS_PRICE,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#addTransaction', function () {
|
describe('#addTransaction', function () {
|
||||||
@ -807,6 +1039,9 @@ describe('Transaction Controller', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('sets txParams.type to 0x2 (EIP-1559)', async function () {
|
it('sets txParams.type to 0x2 (EIP-1559)', async function () {
|
||||||
|
const eip1559CompatibilityStub = sinon
|
||||||
|
.stub(txController, 'getEIP1559Compatibility')
|
||||||
|
.returns(true);
|
||||||
txController.txStateManager._addTransactionsToState([
|
txController.txStateManager._addTransactionsToState([
|
||||||
{
|
{
|
||||||
status: TRANSACTION_STATUSES.UNAPPROVED,
|
status: TRANSACTION_STATUSES.UNAPPROVED,
|
||||||
@ -825,6 +1060,7 @@ describe('Transaction Controller', function () {
|
|||||||
]);
|
]);
|
||||||
await txController.signTransaction('2');
|
await txController.signTransaction('2');
|
||||||
assert.equal(fromTxDataSpy.getCall(0).args[0].type, '0x2');
|
assert.equal(fromTxDataSpy.getCall(0).args[0].type, '0x2');
|
||||||
|
eip1559CompatibilityStub.restore();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -423,6 +423,9 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
),
|
),
|
||||||
getParticipateInMetrics: () =>
|
getParticipateInMetrics: () =>
|
||||||
this.metaMetricsController.state.participateInMetaMetrics,
|
this.metaMetricsController.state.participateInMetaMetrics,
|
||||||
|
getEIP1559GasFeeEstimates: this.gasFeeController.fetchGasFeeEstimates.bind(
|
||||||
|
this.gasFeeController,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
this.txController.on('newUnapprovedTx', () => opts.showUserConfirmation());
|
this.txController.on('newUnapprovedTx', () => opts.showUserConfirmation());
|
||||||
|
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
restoreMocks: true,
|
restoreMocks: true,
|
||||||
coverageDirectory: 'jest-coverage/',
|
coverageDirectory: 'jest-coverage/',
|
||||||
collectCoverageFrom: ['<rootDir>/ui/**/swaps/**'],
|
collectCoverageFrom: [
|
||||||
|
'<rootDir>/ui/**/swaps/**',
|
||||||
|
'<rootDir>/ui/ducks/send/**',
|
||||||
|
],
|
||||||
coveragePathIgnorePatterns: ['.stories.js', '.snap'],
|
coveragePathIgnorePatterns: ['.stories.js', '.snap'],
|
||||||
coverageThreshold: {
|
coverageThreshold: {
|
||||||
global: {
|
global: {
|
||||||
|
@ -268,7 +268,7 @@ const toNegative = (n, options = {}) => {
|
|||||||
return multiplyCurrencies(n, -1, options);
|
return multiplyCurrencies(n, -1, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function decGWEIToHexWEI(decGWEI) {
|
function decGWEIToHexWEI(decGWEI) {
|
||||||
return conversionUtil(decGWEI, {
|
return conversionUtil(decGWEI, {
|
||||||
fromNumericBase: 'dec',
|
fromNumericBase: 'dec',
|
||||||
toNumericBase: 'hex',
|
toNumericBase: 'hex',
|
||||||
@ -288,4 +288,5 @@ export {
|
|||||||
conversionMax,
|
conversionMax,
|
||||||
toNegative,
|
toNegative,
|
||||||
subtractCurrencies,
|
subtractCurrencies,
|
||||||
|
decGWEIToHexWEI,
|
||||||
};
|
};
|
||||||
|
@ -34,31 +34,33 @@ export default function AdvancedGasControls({
|
|||||||
maxFeeFiat,
|
maxFeeFiat,
|
||||||
gasErrors,
|
gasErrors,
|
||||||
minimumGasLimit = 21000,
|
minimumGasLimit = 21000,
|
||||||
|
networkSupportsEIP1559,
|
||||||
}) {
|
}) {
|
||||||
const t = useContext(I18nContext);
|
const t = useContext(I18nContext);
|
||||||
|
|
||||||
const suggestedValues = {};
|
const suggestedValues = {};
|
||||||
|
|
||||||
switch (gasEstimateType) {
|
if (networkSupportsEIP1559) {
|
||||||
case GAS_ESTIMATE_TYPES.FEE_MARKET:
|
suggestedValues.maxFeePerGas =
|
||||||
suggestedValues.maxPriorityFeePerGas =
|
gasFeeEstimates?.[estimateToUse]?.suggestedMaxFeePerGas ||
|
||||||
gasFeeEstimates?.[estimateToUse]?.suggestedMaxPriorityFeePerGas;
|
gasFeeEstimates?.gasPrice;
|
||||||
suggestedValues.maxFeePerGas =
|
suggestedValues.maxPriorityFeePerGas =
|
||||||
gasFeeEstimates?.[estimateToUse]?.suggestedMaxFeePerGas;
|
gasFeeEstimates?.[estimateToUse]?.suggestedMaxPriorityFeePerGas ||
|
||||||
break;
|
suggestedValues.maxFeePerGas;
|
||||||
case GAS_ESTIMATE_TYPES.LEGACY:
|
} else {
|
||||||
suggestedValues.gasPrice = gasFeeEstimates?.[estimateToUse];
|
switch (gasEstimateType) {
|
||||||
break;
|
case GAS_ESTIMATE_TYPES.LEGACY:
|
||||||
case GAS_ESTIMATE_TYPES.ETH_GASPRICE:
|
suggestedValues.gasPrice = gasFeeEstimates?.[estimateToUse];
|
||||||
suggestedValues.gasPrice = gasFeeEstimates?.gasPrice;
|
break;
|
||||||
break;
|
case GAS_ESTIMATE_TYPES.ETH_GASPRICE:
|
||||||
default:
|
suggestedValues.gasPrice = gasFeeEstimates?.gasPrice;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const showFeeMarketFields =
|
const showFeeMarketFields = networkSupportsEIP1559;
|
||||||
process.env.SHOW_EIP_1559_UI &&
|
|
||||||
gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="advanced-gas-controls">
|
<div className="advanced-gas-controls">
|
||||||
@ -233,4 +235,5 @@ AdvancedGasControls.propTypes = {
|
|||||||
maxFeeFiat: PropTypes.string,
|
maxFeeFiat: PropTypes.string,
|
||||||
gasErrors: PropTypes.object,
|
gasErrors: PropTypes.object,
|
||||||
minimumGasLimit: PropTypes.number,
|
minimumGasLimit: PropTypes.number,
|
||||||
|
networkSupportsEIP1559: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
@ -7,10 +7,10 @@ import {
|
|||||||
EDIT_GAS_MODES,
|
EDIT_GAS_MODES,
|
||||||
} from '../../../../shared/constants/gas';
|
} from '../../../../shared/constants/gas';
|
||||||
|
|
||||||
import { isEIP1559Network } from '../../../ducks/metamask/metamask';
|
|
||||||
|
|
||||||
import Button from '../../ui/button';
|
import Button from '../../ui/button';
|
||||||
import Typography from '../../ui/typography/typography';
|
import Typography from '../../ui/typography/typography';
|
||||||
|
import { isEIP1559Network } from '../../../ducks/metamask/metamask';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
COLORS,
|
COLORS,
|
||||||
TYPOGRAPHY,
|
TYPOGRAPHY,
|
||||||
@ -63,6 +63,7 @@ export default function EditGasDisplay({
|
|||||||
balanceError,
|
balanceError,
|
||||||
}) {
|
}) {
|
||||||
const t = useContext(I18nContext);
|
const t = useContext(I18nContext);
|
||||||
|
const supportsEIP1559 = useSelector(isEIP1559Network);
|
||||||
|
|
||||||
const dappSuggestedAndTxParamGasFeesAreTheSame = areDappSuggestedAndTxParamGasFeesTheSame(
|
const dappSuggestedAndTxParamGasFeesAreTheSame = areDappSuggestedAndTxParamGasFeesTheSame(
|
||||||
transaction,
|
transaction,
|
||||||
@ -220,6 +221,7 @@ export default function EditGasDisplay({
|
|||||||
gasErrors={gasErrors}
|
gasErrors={gasErrors}
|
||||||
onManualChange={onManualChange}
|
onManualChange={onManualChange}
|
||||||
minimumGasLimit={minimumGasLimit}
|
minimumGasLimit={minimumGasLimit}
|
||||||
|
networkSupportsEIP1559={supportsEIP1559}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import React, { useCallback, useContext, useState } from 'react';
|
import React, { useCallback, useContext, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { isEIP1559Network } from '../../../ducks/metamask/metamask';
|
||||||
import { useGasFeeInputs } from '../../../hooks/useGasFeeInputs';
|
import { useGasFeeInputs } from '../../../hooks/useGasFeeInputs';
|
||||||
import { useShouldAnimateGasEstimations } from '../../../hooks/useShouldAnimateGasEstimations';
|
import { useShouldAnimateGasEstimations } from '../../../hooks/useShouldAnimateGasEstimations';
|
||||||
|
|
||||||
import {
|
import { EDIT_GAS_MODES } from '../../../../shared/constants/gas';
|
||||||
GAS_ESTIMATE_TYPES,
|
|
||||||
EDIT_GAS_MODES,
|
|
||||||
} from '../../../../shared/constants/gas';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
decGWEIToHexWEI,
|
decGWEIToHexWEI,
|
||||||
@ -44,6 +42,7 @@ export default function EditGasPopover({
|
|||||||
const t = useContext(I18nContext);
|
const t = useContext(I18nContext);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const showSidebar = useSelector((state) => state.appState.sidebar.isOpen);
|
const showSidebar = useSelector((state) => state.appState.sidebar.isOpen);
|
||||||
|
const supportsEIP1559 = useSelector(isEIP1559Network);
|
||||||
|
|
||||||
const shouldAnimate = useShouldAnimateGasEstimations();
|
const shouldAnimate = useShouldAnimateGasEstimations();
|
||||||
|
|
||||||
@ -110,19 +109,20 @@ export default function EditGasPopover({
|
|||||||
closePopover();
|
closePopover();
|
||||||
}
|
}
|
||||||
|
|
||||||
const newGasSettings =
|
const newGasSettings = supportsEIP1559
|
||||||
gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET
|
? {
|
||||||
? {
|
gas: decimalToHex(gasLimit),
|
||||||
gas: decimalToHex(gasLimit),
|
gasLimit: decimalToHex(gasLimit),
|
||||||
gasLimit: decimalToHex(gasLimit),
|
maxFeePerGas: decGWEIToHexWEI(maxFeePerGas ?? gasPrice),
|
||||||
maxFeePerGas: decGWEIToHexWEI(maxFeePerGas),
|
maxPriorityFeePerGas: decGWEIToHexWEI(
|
||||||
maxPriorityFeePerGas: decGWEIToHexWEI(maxPriorityFeePerGas),
|
maxPriorityFeePerGas ?? maxFeePerGas ?? gasPrice,
|
||||||
}
|
),
|
||||||
: {
|
}
|
||||||
gas: decimalToHex(gasLimit),
|
: {
|
||||||
gasLimit: decimalToHex(gasLimit),
|
gas: decimalToHex(gasLimit),
|
||||||
gasPrice: decGWEIToHexWEI(gasPrice),
|
gasLimit: decimalToHex(gasLimit),
|
||||||
};
|
gasPrice: decGWEIToHexWEI(gasPrice),
|
||||||
|
};
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case EDIT_GAS_MODES.CANCEL:
|
case EDIT_GAS_MODES.CANCEL:
|
||||||
@ -144,7 +144,7 @@ export default function EditGasPopover({
|
|||||||
break;
|
break;
|
||||||
case EDIT_GAS_MODES.SWAPS:
|
case EDIT_GAS_MODES.SWAPS:
|
||||||
// This popover component should only be used for the "FEE_MARKET" type in Swaps.
|
// This popover component should only be used for the "FEE_MARKET" type in Swaps.
|
||||||
if (gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET) {
|
if (supportsEIP1559) {
|
||||||
dispatch(updateCustomSwapsEIP1559GasParams(newGasSettings));
|
dispatch(updateCustomSwapsEIP1559GasParams(newGasSettings));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -162,7 +162,7 @@ export default function EditGasPopover({
|
|||||||
gasPrice,
|
gasPrice,
|
||||||
maxFeePerGas,
|
maxFeePerGas,
|
||||||
maxPriorityFeePerGas,
|
maxPriorityFeePerGas,
|
||||||
gasEstimateType,
|
supportsEIP1559,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let title = t('editGasTitle');
|
let title = t('editGasTitle');
|
||||||
|
@ -73,6 +73,7 @@ import {
|
|||||||
getGasEstimateType,
|
getGasEstimateType,
|
||||||
getTokens,
|
getTokens,
|
||||||
getUnapprovedTxs,
|
getUnapprovedTxs,
|
||||||
|
isEIP1559Network,
|
||||||
} from '../metamask/metamask';
|
} from '../metamask/metamask';
|
||||||
import { resetEnsResolution } from '../ens';
|
import { resetEnsResolution } from '../ens';
|
||||||
import {
|
import {
|
||||||
@ -81,6 +82,7 @@ import {
|
|||||||
} from '../../../shared/modules/hexstring-utils';
|
} from '../../../shared/modules/hexstring-utils';
|
||||||
import { CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP } from '../../../shared/constants/network';
|
import { CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP } from '../../../shared/constants/network';
|
||||||
import { ETH, GWEI } from '../../helpers/constants/common';
|
import { ETH, GWEI } from '../../helpers/constants/common';
|
||||||
|
import { TRANSACTION_ENVELOPE_TYPES } from '../../../shared/constants/transaction';
|
||||||
|
|
||||||
// typedefs
|
// typedefs
|
||||||
/**
|
/**
|
||||||
@ -91,7 +93,7 @@ const name = 'send';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The Stages that the send slice can be in
|
* The Stages that the send slice can be in
|
||||||
* 1. UNINITIALIZED - The send state is idle, and hasn't yet fetched required
|
* 1. INACTIVE - The send state is idle, and hasn't yet fetched required
|
||||||
* data for gasPrice and gasLimit estimations, etc.
|
* data for gasPrice and gasLimit estimations, etc.
|
||||||
* 2. ADD_RECIPIENT - The user is selecting which address to send an asset to
|
* 2. ADD_RECIPIENT - The user is selecting which address to send an asset to
|
||||||
* 3. DRAFT - The send form is shown for a transaction yet to be sent to the
|
* 3. DRAFT - The send form is shown for a transaction yet to be sent to the
|
||||||
@ -407,6 +409,7 @@ export const initializeSendState = createAsyncThunk(
|
|||||||
const state = thunkApi.getState();
|
const state = thunkApi.getState();
|
||||||
const isNonStandardEthChain = getIsNonStandardEthChain(state);
|
const isNonStandardEthChain = getIsNonStandardEthChain(state);
|
||||||
const chainId = getCurrentChainId(state);
|
const chainId = getCurrentChainId(state);
|
||||||
|
const eip1559support = isEIP1559Network(state);
|
||||||
const {
|
const {
|
||||||
send: { asset, stage, recipient, amount, draftTransaction },
|
send: { asset, stage, recipient, amount, draftTransaction },
|
||||||
metamask,
|
metamask,
|
||||||
@ -424,7 +427,10 @@ export const initializeSendState = createAsyncThunk(
|
|||||||
// selector and returns the account at the specified address.
|
// selector and returns the account at the specified address.
|
||||||
const account = getTargetAccount(state, fromAddress);
|
const account = getTargetAccount(state, fromAddress);
|
||||||
|
|
||||||
// Default gasPrice to 1 gwei if all estimation fails
|
// Default gasPrice to 1 gwei if all estimation fails, this is only used
|
||||||
|
// for gasLimit estimation and won't be set directly in state. Instead, we
|
||||||
|
// will return the gasFeeEstimates and gasEstimateType so that the reducer
|
||||||
|
// can set the appropriate gas fees in state.
|
||||||
let gasPrice = '0x1';
|
let gasPrice = '0x1';
|
||||||
let gasEstimatePollToken = null;
|
let gasEstimatePollToken = null;
|
||||||
|
|
||||||
@ -434,6 +440,9 @@ export const initializeSendState = createAsyncThunk(
|
|||||||
metamask: { gasFeeEstimates, gasEstimateType },
|
metamask: { gasFeeEstimates, gasEstimateType },
|
||||||
} = thunkApi.getState();
|
} = thunkApi.getState();
|
||||||
|
|
||||||
|
// Because we are only interested in getting a gasLimit estimation we only
|
||||||
|
// need to worry about gasPrice. So we use maxFeePerGas as gasPrice if we
|
||||||
|
// have a fee market estimation.
|
||||||
if (gasEstimateType === GAS_ESTIMATE_TYPES.LEGACY) {
|
if (gasEstimateType === GAS_ESTIMATE_TYPES.LEGACY) {
|
||||||
gasPrice = getGasPriceInHexWei(gasFeeEstimates.medium);
|
gasPrice = getGasPriceInHexWei(gasFeeEstimates.medium);
|
||||||
} else if (gasEstimateType === GAS_ESTIMATE_TYPES.ETH_GASPRICE) {
|
} else if (gasEstimateType === GAS_ESTIMATE_TYPES.ETH_GASPRICE) {
|
||||||
@ -442,6 +451,10 @@ export const initializeSendState = createAsyncThunk(
|
|||||||
gasPrice = getGasPriceInHexWei(
|
gasPrice = getGasPriceInHexWei(
|
||||||
gasFeeEstimates.medium.suggestedMaxFeePerGas,
|
gasFeeEstimates.medium.suggestedMaxFeePerGas,
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
gasPrice = gasFeeEstimates.gasPrice
|
||||||
|
? getRoundedGasPrice(gasFeeEstimates.gasPrice)
|
||||||
|
: '0x0';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a basic gasLimit in the event that other estimation fails
|
// Set a basic gasLimit in the event that other estimation fails
|
||||||
@ -493,19 +506,25 @@ export const initializeSendState = createAsyncThunk(
|
|||||||
assetBalance: balance,
|
assetBalance: balance,
|
||||||
chainId: getCurrentChainId(state),
|
chainId: getCurrentChainId(state),
|
||||||
tokens: getTokens(state),
|
tokens: getTokens(state),
|
||||||
gasPrice,
|
gasFeeEstimates,
|
||||||
|
gasEstimateType,
|
||||||
gasLimit,
|
gasLimit,
|
||||||
gasTotal: addHexPrefix(calcGasTotal(gasLimit, gasPrice)),
|
gasTotal: addHexPrefix(calcGasTotal(gasLimit, gasPrice)),
|
||||||
gasEstimatePollToken,
|
gasEstimatePollToken,
|
||||||
|
eip1559support,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const initialState = {
|
export const initialState = {
|
||||||
// which stage of the send flow is the user on
|
// which stage of the send flow is the user on
|
||||||
stage: SEND_STAGES.UNINITIALIZED,
|
stage: SEND_STAGES.INACTIVE,
|
||||||
// status of the send slice, either VALID or INVALID
|
// status of the send slice, either VALID or INVALID
|
||||||
status: SEND_STATUSES.VALID,
|
status: SEND_STATUSES.VALID,
|
||||||
|
// Determines type of transaction being sent, defaulted to 0x0 (legacy)
|
||||||
|
transactionType: TRANSACTION_ENVELOPE_TYPES.LEGACY,
|
||||||
|
// tracks whether the current network supports EIP 1559 transactions
|
||||||
|
eip1559support: false,
|
||||||
account: {
|
account: {
|
||||||
// from account address, defaults to selected account. will be the account
|
// from account address, defaults to selected account. will be the account
|
||||||
// the original transaction was sent from in the case of the EDIT stage
|
// the original transaction was sent from in the case of the EDIT stage
|
||||||
@ -524,6 +543,10 @@ export const initialState = {
|
|||||||
gasLimit: '0x0',
|
gasLimit: '0x0',
|
||||||
// price in wei to pay per gas
|
// price in wei to pay per gas
|
||||||
gasPrice: '0x0',
|
gasPrice: '0x0',
|
||||||
|
// maximum price in wei to pay per gas
|
||||||
|
maxFeePerGas: '0x0',
|
||||||
|
// maximum priority fee in wei to pay per gas
|
||||||
|
maxPriorityFeePerGas: '0x0',
|
||||||
// expected price in wei necessary to pay per gas used for a transaction
|
// expected price in wei necessary to pay per gas used for a transaction
|
||||||
// to be included in a reasonable timeframe. Comes from GasFeeController.
|
// to be included in a reasonable timeframe. Comes from GasFeeController.
|
||||||
gasPriceEstimate: '0x0',
|
gasPriceEstimate: '0x0',
|
||||||
@ -570,6 +593,7 @@ export const initialState = {
|
|||||||
value: '0x0',
|
value: '0x0',
|
||||||
gas: '0x0',
|
gas: '0x0',
|
||||||
gasPrice: '0x0',
|
gasPrice: '0x0',
|
||||||
|
type: TRANSACTION_ENVELOPE_TYPES.LEGACY,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
recipient: {
|
recipient: {
|
||||||
@ -683,9 +707,17 @@ const slice = createSlice({
|
|||||||
* field and send state, then updates the draft transaction.
|
* field and send state, then updates the draft transaction.
|
||||||
*/
|
*/
|
||||||
calculateGasTotal: (state) => {
|
calculateGasTotal: (state) => {
|
||||||
state.gas.gasTotal = addHexPrefix(
|
// use maxFeePerGas as the multiplier if working with a FEE_MARKET transaction
|
||||||
calcGasTotal(state.gas.gasLimit, state.gas.gasPrice),
|
// otherwise use gasPrice
|
||||||
);
|
if (state.transactionType === TRANSACTION_ENVELOPE_TYPES.FEE_MARKET) {
|
||||||
|
state.gas.gasTotal = addHexPrefix(
|
||||||
|
calcGasTotal(state.gas.gasLimit, state.gas.maxFeePerGas),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
state.gas.gasTotal = addHexPrefix(
|
||||||
|
calcGasTotal(state.gas.gasLimit, state.gas.gasPrice),
|
||||||
|
);
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
state.amount.mode === AMOUNT_MODES.MAX &&
|
state.amount.mode === AMOUNT_MODES.MAX &&
|
||||||
state.asset.type === ASSET_TYPES.NATIVE
|
state.asset.type === ASSET_TYPES.NATIVE
|
||||||
@ -705,12 +737,86 @@ const slice = createSlice({
|
|||||||
slice.caseReducers.calculateGasTotal(state);
|
slice.caseReducers.calculateGasTotal(state);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* sets the provided gasPrice in state and then recomputes the gasTotal
|
* Sets the appropriate gas fees in state and determines and sets the
|
||||||
|
* appropriate transactionType based on gas fee fields received.
|
||||||
*/
|
*/
|
||||||
updateGasPrice: (state, action) => {
|
updateGasFees: (state, action) => {
|
||||||
state.gas.gasPrice = addHexPrefix(action.payload);
|
if (
|
||||||
|
action.payload.transactionType === TRANSACTION_ENVELOPE_TYPES.FEE_MARKET
|
||||||
|
) {
|
||||||
|
state.gas.maxFeePerGas = addHexPrefix(action.payload.maxFeePerGas);
|
||||||
|
state.gas.maxPriorityFeePerGas = addHexPrefix(
|
||||||
|
action.payload.maxPriorityFeePerGas,
|
||||||
|
);
|
||||||
|
state.transactionType = TRANSACTION_ENVELOPE_TYPES.FEE_MARKET;
|
||||||
|
} else {
|
||||||
|
// Until we remove the old UI we don't want to automatically update
|
||||||
|
// gasPrice if the user has already manually changed the field value.
|
||||||
|
// When receiving a new estimate the isAutomaticUpdate property will be
|
||||||
|
// on the payload (and set to true). If isAutomaticUpdate is true,
|
||||||
|
// then we check if the previous estimate was '0x0' or if the previous
|
||||||
|
// gasPrice equals the previous gasEstimate. if either of those cases
|
||||||
|
// are true then we update the gasPrice otherwise we skip it because
|
||||||
|
// it indicates the user has ejected from the estimates by modifying
|
||||||
|
// the field.
|
||||||
|
if (
|
||||||
|
action.payload.isAutomaticUpdate !== true ||
|
||||||
|
state.gas.gasPriceEstimate === '0x0' ||
|
||||||
|
state.gas.gasPrice === state.gas.gasPriceEstimate
|
||||||
|
) {
|
||||||
|
state.gas.gasPrice = addHexPrefix(action.payload.gasPrice);
|
||||||
|
}
|
||||||
|
state.transactionType = TRANSACTION_ENVELOPE_TYPES.LEGACY;
|
||||||
|
}
|
||||||
slice.caseReducers.calculateGasTotal(state);
|
slice.caseReducers.calculateGasTotal(state);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Sets the appropriate gas fees in state after receiving new estimates.
|
||||||
|
*/
|
||||||
|
updateGasFeeEstimates: (state, action) => {
|
||||||
|
const { gasFeeEstimates, gasEstimateType } = action.payload;
|
||||||
|
let gasPriceEstimate = '0x0';
|
||||||
|
switch (gasEstimateType) {
|
||||||
|
case GAS_ESTIMATE_TYPES.FEE_MARKET:
|
||||||
|
slice.caseReducers.updateGasFees(state, {
|
||||||
|
payload: {
|
||||||
|
transactionType: TRANSACTION_ENVELOPE_TYPES.FEE_MARKET,
|
||||||
|
maxFeePerGas: getGasPriceInHexWei(
|
||||||
|
gasFeeEstimates.medium.suggestedMaxFeePerGas,
|
||||||
|
),
|
||||||
|
maxPriorityFeePerGas: getGasPriceInHexWei(
|
||||||
|
gasFeeEstimates.medium.suggestedMaxPriorityFeePerGas,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case GAS_ESTIMATE_TYPES.LEGACY:
|
||||||
|
gasPriceEstimate = getRoundedGasPrice(gasFeeEstimates.medium);
|
||||||
|
slice.caseReducers.updateGasFees(state, {
|
||||||
|
payload: {
|
||||||
|
gasPrice: gasPriceEstimate,
|
||||||
|
type: TRANSACTION_ENVELOPE_TYPES.LEGACY,
|
||||||
|
isAutomaticUpdate: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case GAS_ESTIMATE_TYPES.ETH_GASPRICE:
|
||||||
|
gasPriceEstimate = getRoundedGasPrice(gasFeeEstimates.gasPrice);
|
||||||
|
slice.caseReducers.updateGasFees(state, {
|
||||||
|
payload: {
|
||||||
|
gasPrice: getRoundedGasPrice(gasFeeEstimates.gasPrice),
|
||||||
|
type: TRANSACTION_ENVELOPE_TYPES.LEGACY,
|
||||||
|
isAutomaticUpdate: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case GAS_ESTIMATE_TYPES.NONE:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Record the latest gasPriceEstimate for future comparisons
|
||||||
|
state.gas.gasPriceEstimate = addHexPrefix(gasPriceEstimate);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* sets the amount mode to the provided value as long as it is one of the
|
* sets the amount mode to the provided value as long as it is one of the
|
||||||
* supported modes (MAX|INPUT)
|
* supported modes (MAX|INPUT)
|
||||||
@ -778,9 +884,15 @@ const slice = createSlice({
|
|||||||
// We keep a copy of txParams in state that could be submitted to the
|
// We keep a copy of txParams in state that could be submitted to the
|
||||||
// network if the form state is valid.
|
// network if the form state is valid.
|
||||||
if (state.status === SEND_STATUSES.VALID) {
|
if (state.status === SEND_STATUSES.VALID) {
|
||||||
|
// We don't/shouldn't modify the from address when editing an
|
||||||
|
// existing transaction.
|
||||||
if (state.stage !== SEND_STAGES.EDIT) {
|
if (state.stage !== SEND_STAGES.EDIT) {
|
||||||
state.draftTransaction.txParams.from = state.account.address;
|
state.draftTransaction.txParams.from = state.account.address;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gasLimit always needs to be set regardless of the asset being sent
|
||||||
|
// or the type of transaction.
|
||||||
|
state.draftTransaction.txParams.gas = state.gas.gasLimit;
|
||||||
switch (state.asset.type) {
|
switch (state.asset.type) {
|
||||||
case ASSET_TYPES.TOKEN:
|
case ASSET_TYPES.TOKEN:
|
||||||
// When sending a token the to address is the contract address of
|
// When sending a token the to address is the contract address of
|
||||||
@ -789,8 +901,6 @@ const slice = createSlice({
|
|||||||
// amount.
|
// amount.
|
||||||
state.draftTransaction.txParams.to = state.asset.details.address;
|
state.draftTransaction.txParams.to = state.asset.details.address;
|
||||||
state.draftTransaction.txParams.value = '0x0';
|
state.draftTransaction.txParams.value = '0x0';
|
||||||
state.draftTransaction.txParams.gas = state.gas.gasLimit;
|
|
||||||
state.draftTransaction.txParams.gasPrice = state.gas.gasPrice;
|
|
||||||
state.draftTransaction.txParams.data = generateTokenTransferData({
|
state.draftTransaction.txParams.data = generateTokenTransferData({
|
||||||
toAddress: state.recipient.address,
|
toAddress: state.recipient.address,
|
||||||
amount: state.amount.value,
|
amount: state.amount.value,
|
||||||
@ -804,11 +914,46 @@ const slice = createSlice({
|
|||||||
// populated with the user input provided in hex field.
|
// populated with the user input provided in hex field.
|
||||||
state.draftTransaction.txParams.to = state.recipient.address;
|
state.draftTransaction.txParams.to = state.recipient.address;
|
||||||
state.draftTransaction.txParams.value = state.amount.value;
|
state.draftTransaction.txParams.value = state.amount.value;
|
||||||
state.draftTransaction.txParams.gas = state.gas.gasLimit;
|
|
||||||
state.draftTransaction.txParams.gasPrice = state.gas.gasPrice;
|
|
||||||
state.draftTransaction.txParams.data =
|
state.draftTransaction.txParams.data =
|
||||||
state.draftTransaction.userInputHexData ?? undefined;
|
state.draftTransaction.userInputHexData ?? undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We need to make sure that we only include the right gas fee fields
|
||||||
|
// based on the type of transaction the network supports. We will also set
|
||||||
|
// the type param here. We must delete the opposite fields to avoid
|
||||||
|
// stale data in txParams.
|
||||||
|
if (state.eip1559support) {
|
||||||
|
state.draftTransaction.txParams.type =
|
||||||
|
TRANSACTION_ENVELOPE_TYPES.FEE_MARKET;
|
||||||
|
|
||||||
|
state.draftTransaction.txParams.maxFeePerGas = state.gas.maxFeePerGas;
|
||||||
|
state.draftTransaction.txParams.maxPriorityFeePerGas =
|
||||||
|
state.gas.maxPriorityFeePerGas;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!state.draftTransaction.txParams.maxFeePerGas ||
|
||||||
|
state.draftTransaction.txParams.maxFeePerGas === '0x0'
|
||||||
|
) {
|
||||||
|
state.draftTransaction.txParams.maxFeePerGas = state.gas.gasPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!state.draftTransaction.txParams.maxPriorityFeePerGas ||
|
||||||
|
state.draftTransaction.txParams.maxPriorityFeePerGas === '0x0'
|
||||||
|
) {
|
||||||
|
state.draftTransaction.txParams.maxPriorityFeePerGas =
|
||||||
|
state.draftTransaction.txParams.maxFeePerGas;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete state.draftTransaction.txParams.gasPrice;
|
||||||
|
} else {
|
||||||
|
delete state.draftTransaction.txParams.maxFeePerGas;
|
||||||
|
delete state.draftTransaction.txParams.maxPriorityFeePerGas;
|
||||||
|
|
||||||
|
state.draftTransaction.txParams.gasPrice = state.gas.gasPrice;
|
||||||
|
state.draftTransaction.txParams.type =
|
||||||
|
TRANSACTION_ENVELOPE_TYPES.LEGACY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
useDefaultGas: (state) => {
|
useDefaultGas: (state) => {
|
||||||
@ -937,7 +1082,7 @@ const slice = createSlice({
|
|||||||
case state.asset.type === ASSET_TYPES.TOKEN &&
|
case state.asset.type === ASSET_TYPES.TOKEN &&
|
||||||
state.asset.details === null:
|
state.asset.details === null:
|
||||||
case state.stage === SEND_STAGES.ADD_RECIPIENT:
|
case state.stage === SEND_STAGES.ADD_RECIPIENT:
|
||||||
case state.stage === SEND_STAGES.UNINITIALIZED:
|
case state.stage === SEND_STAGES.INACTIVE:
|
||||||
case state.gas.isGasEstimateLoading:
|
case state.gas.isGasEstimateLoading:
|
||||||
case new BigNumber(state.gas.gasLimit, 16).lessThan(
|
case new BigNumber(state.gas.gasLimit, 16).lessThan(
|
||||||
new BigNumber(state.gas.minimumGasLimit),
|
new BigNumber(state.gas.minimumGasLimit),
|
||||||
@ -1039,17 +1184,23 @@ const slice = createSlice({
|
|||||||
.addCase(initializeSendState.fulfilled, (state, action) => {
|
.addCase(initializeSendState.fulfilled, (state, action) => {
|
||||||
// writes the computed initialized state values into the slice and then
|
// writes the computed initialized state values into the slice and then
|
||||||
// calculates slice validity using the caseReducers.
|
// calculates slice validity using the caseReducers.
|
||||||
|
state.eip1559support = action.payload.eip1559support;
|
||||||
state.account.address = action.payload.address;
|
state.account.address = action.payload.address;
|
||||||
state.account.balance = action.payload.nativeBalance;
|
state.account.balance = action.payload.nativeBalance;
|
||||||
state.asset.balance = action.payload.assetBalance;
|
state.asset.balance = action.payload.assetBalance;
|
||||||
state.gas.gasLimit = action.payload.gasLimit;
|
state.gas.gasLimit = action.payload.gasLimit;
|
||||||
state.gas.gasPrice = action.payload.gasPrice;
|
slice.caseReducers.updateGasFeeEstimates(state, {
|
||||||
|
payload: {
|
||||||
|
gasFeeEstimates: action.payload.gasFeeEstimates,
|
||||||
|
gasEstimateType: action.payload.gasEstimateType,
|
||||||
|
},
|
||||||
|
});
|
||||||
state.gas.gasTotal = action.payload.gasTotal;
|
state.gas.gasTotal = action.payload.gasTotal;
|
||||||
state.gas.gasEstimatePollToken = action.payload.gasEstimatePollToken;
|
state.gas.gasEstimatePollToken = action.payload.gasEstimatePollToken;
|
||||||
if (action.payload.gasEstimatePollToken) {
|
if (action.payload.gasEstimatePollToken) {
|
||||||
state.gas.isGasEstimateLoading = false;
|
state.gas.isGasEstimateLoading = false;
|
||||||
}
|
}
|
||||||
if (state.stage !== SEND_STAGES.UNINITIALIZED) {
|
if (state.stage !== SEND_STAGES.INACTIVE) {
|
||||||
slice.caseReducers.validateRecipientUserInput(state, {
|
slice.caseReducers.validateRecipientUserInput(state, {
|
||||||
payload: {
|
payload: {
|
||||||
chainId: action.payload.chainId,
|
chainId: action.payload.chainId,
|
||||||
@ -1058,7 +1209,7 @@ const slice = createSlice({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
state.stage =
|
state.stage =
|
||||||
state.stage === SEND_STAGES.UNINITIALIZED
|
state.stage === SEND_STAGES.INACTIVE
|
||||||
? SEND_STAGES.ADD_RECIPIENT
|
? SEND_STAGES.ADD_RECIPIENT
|
||||||
: state.stage;
|
: state.stage;
|
||||||
slice.caseReducers.validateAmountField(state);
|
slice.caseReducers.validateAmountField(state);
|
||||||
@ -1091,33 +1242,9 @@ const slice = createSlice({
|
|||||||
.addCase(GAS_FEE_ESTIMATES_UPDATED, (state, action) => {
|
.addCase(GAS_FEE_ESTIMATES_UPDATED, (state, action) => {
|
||||||
// When the gasFeeController updates its gas fee estimates we need to
|
// When the gasFeeController updates its gas fee estimates we need to
|
||||||
// update and validate state based on those new values
|
// update and validate state based on those new values
|
||||||
const { gasFeeEstimates, gasEstimateType } = action.payload;
|
slice.caseReducers.updateGasFeeEstimates(state, {
|
||||||
let payload = null;
|
payload: action.payload,
|
||||||
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 = getRoundedGasPrice(gasFeeEstimates.gasPrice);
|
|
||||||
}
|
|
||||||
// If a new gasPrice can be derived, and either the gasPriceEstimate
|
|
||||||
// was '0x0' or the gasPrice selected matches the previous estimate,
|
|
||||||
// update the gasPrice. This will ensure that we only update the
|
|
||||||
// gasPrice if the user is using our previous estimated value.
|
|
||||||
if (
|
|
||||||
payload &&
|
|
||||||
(state.gas.gasPriceEstimate === '0x0' ||
|
|
||||||
state.gas.gasPrice === state.gas.gasPriceEstimate)
|
|
||||||
) {
|
|
||||||
slice.caseReducers.updateGasPrice(state, {
|
|
||||||
payload,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record the latest gasPriceEstimate for future comparisons
|
|
||||||
state.gas.gasPriceEstimate = payload ?? state.gas.gasPriceEstimate;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -1130,15 +1257,37 @@ const {
|
|||||||
useDefaultGas,
|
useDefaultGas,
|
||||||
useCustomGas,
|
useCustomGas,
|
||||||
updateGasLimit,
|
updateGasLimit,
|
||||||
updateGasPrice,
|
|
||||||
validateRecipientUserInput,
|
validateRecipientUserInput,
|
||||||
updateRecipientSearchMode,
|
updateRecipientSearchMode,
|
||||||
} = actions;
|
} = actions;
|
||||||
|
|
||||||
export { useDefaultGas, useCustomGas, updateGasLimit, updateGasPrice };
|
export { useDefaultGas, useCustomGas, updateGasLimit };
|
||||||
|
|
||||||
// Action Creators
|
// Action Creators
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is a temporary placeholder to support the old UI in both the
|
||||||
|
* gas modal and the send flow. Soon we won't need to modify gasPrice from the
|
||||||
|
* send flow based on user input, it'll just be a shallow copy of the current
|
||||||
|
* estimate. This method is necessary because the internal structure of this
|
||||||
|
* slice has been changed such that it is agnostic to transaction envelope
|
||||||
|
* type, and this method calls into the new structure in the appropriate way.
|
||||||
|
*
|
||||||
|
* @deprecated - don't extend the usage of this temporary method
|
||||||
|
* @param {string} gasPrice - new gas price in hex wei
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
export function updateGasPrice(gasPrice) {
|
||||||
|
return (dispatch) => {
|
||||||
|
dispatch(
|
||||||
|
actions.updateGasFees({
|
||||||
|
gasPrice,
|
||||||
|
transactionType: TRANSACTION_ENVELOPE_TYPES.LEGACY,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function resetSendState() {
|
export function resetSendState() {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
@ -1587,7 +1736,7 @@ export function getSendErrors(state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isSendStateInitialized(state) {
|
export function isSendStateInitialized(state) {
|
||||||
return state[name].stage !== SEND_STAGES.UNINITIALIZED;
|
return state[name].stage !== SEND_STAGES.INACTIVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSendFormInvalid(state) {
|
export function isSendFormInvalid(state) {
|
||||||
|
@ -10,9 +10,15 @@ import {
|
|||||||
KNOWN_RECIPIENT_ADDRESS_WARNING,
|
KNOWN_RECIPIENT_ADDRESS_WARNING,
|
||||||
NEGATIVE_ETH_ERROR,
|
NEGATIVE_ETH_ERROR,
|
||||||
} from '../../pages/send/send.constants';
|
} from '../../pages/send/send.constants';
|
||||||
import { RINKEBY_CHAIN_ID } from '../../../shared/constants/network';
|
import {
|
||||||
|
MAINNET_CHAIN_ID,
|
||||||
|
RINKEBY_CHAIN_ID,
|
||||||
|
} from '../../../shared/constants/network';
|
||||||
import { GAS_ESTIMATE_TYPES, GAS_LIMITS } from '../../../shared/constants/gas';
|
import { GAS_ESTIMATE_TYPES, GAS_LIMITS } from '../../../shared/constants/gas';
|
||||||
import { TRANSACTION_TYPES } from '../../../shared/constants/transaction';
|
import {
|
||||||
|
TRANSACTION_ENVELOPE_TYPES,
|
||||||
|
TRANSACTION_TYPES,
|
||||||
|
} from '../../../shared/constants/transaction';
|
||||||
import sendReducer, {
|
import sendReducer, {
|
||||||
initialState,
|
initialState,
|
||||||
initializeSendState,
|
initializeSendState,
|
||||||
@ -32,6 +38,30 @@ import sendReducer, {
|
|||||||
AMOUNT_MODES,
|
AMOUNT_MODES,
|
||||||
RECIPIENT_SEARCH_MODES,
|
RECIPIENT_SEARCH_MODES,
|
||||||
editTransaction,
|
editTransaction,
|
||||||
|
getGasLimit,
|
||||||
|
getGasPrice,
|
||||||
|
getGasTotal,
|
||||||
|
gasFeeIsInError,
|
||||||
|
getMinimumGasLimitForSend,
|
||||||
|
getGasInputMode,
|
||||||
|
GAS_INPUT_MODES,
|
||||||
|
getSendAsset,
|
||||||
|
getSendAssetAddress,
|
||||||
|
getIsAssetSendable,
|
||||||
|
getSendAmount,
|
||||||
|
getIsBalanceInsufficient,
|
||||||
|
getSendMaxModeState,
|
||||||
|
sendAmountIsInError,
|
||||||
|
getSendHexData,
|
||||||
|
getSendTo,
|
||||||
|
getIsUsingMyAccountForRecipientSearch,
|
||||||
|
getRecipientUserInput,
|
||||||
|
getRecipient,
|
||||||
|
getSendErrors,
|
||||||
|
isSendStateInitialized,
|
||||||
|
isSendFormInvalid,
|
||||||
|
getSendStage,
|
||||||
|
updateGasPrice,
|
||||||
} from './send';
|
} from './send';
|
||||||
|
|
||||||
const mockStore = createMockStore([thunk]);
|
const mockStore = createMockStore([thunk]);
|
||||||
@ -89,8 +119,48 @@ describe('Send Slice', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('updateGasFees', () => {
|
||||||
|
it('should work with FEE_MARKET gas fees', () => {
|
||||||
|
const action = {
|
||||||
|
type: 'send/updateGasFees',
|
||||||
|
payload: {
|
||||||
|
transactionType: TRANSACTION_ENVELOPE_TYPES.FEE_MARKET,
|
||||||
|
maxFeePerGas: '0x2',
|
||||||
|
maxPriorityFeePerGas: '0x1',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const result = sendReducer(initialState, action);
|
||||||
|
|
||||||
|
expect(result.gas.maxFeePerGas).toStrictEqual(
|
||||||
|
action.payload.maxFeePerGas,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.gas.maxPriorityFeePerGas).toStrictEqual(
|
||||||
|
action.payload.maxPriorityFeePerGas,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.transactionType).toBe(
|
||||||
|
TRANSACTION_ENVELOPE_TYPES.FEE_MARKET,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with LEGACY gas fees', () => {
|
||||||
|
const action = {
|
||||||
|
type: 'send/updateGasFees',
|
||||||
|
payload: {
|
||||||
|
transactionType: TRANSACTION_ENVELOPE_TYPES.LEGACY,
|
||||||
|
gasPrice: '0x1',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const result = sendReducer(initialState, action);
|
||||||
|
|
||||||
|
expect(result.gas.gasPrice).toStrictEqual(action.payload.gasPrice);
|
||||||
|
expect(result.transactionType).toBe(TRANSACTION_ENVELOPE_TYPES.LEGACY);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('updateUserInputHexData', () => {
|
describe('updateUserInputHexData', () => {
|
||||||
it('should', () => {
|
it('should update the state with the provided data', () => {
|
||||||
const action = {
|
const action = {
|
||||||
type: 'send/updateUserInputHexData',
|
type: 'send/updateUserInputHexData',
|
||||||
payload: 'TestData',
|
payload: 'TestData',
|
||||||
@ -142,56 +212,6 @@ describe('Send Slice', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('updateGasPrice', () => {
|
|
||||||
const action = {
|
|
||||||
type: 'send/updateGasPrice',
|
|
||||||
payload: '0x3b9aca00', // 1000000000
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should update gas price and update draft transaction with validated state', () => {
|
|
||||||
const validSendState = {
|
|
||||||
...initialState,
|
|
||||||
stage: SEND_STAGES.DRAFT,
|
|
||||||
account: {
|
|
||||||
balance: '0x56bc75e2d63100000',
|
|
||||||
},
|
|
||||||
asset: {
|
|
||||||
balance: '0x56bc75e2d63100000',
|
|
||||||
type: ASSET_TYPES.NATIVE,
|
|
||||||
},
|
|
||||||
gas: {
|
|
||||||
isGasEstimateLoading: false,
|
|
||||||
gasTotal: '0x1319718a5000', // 21000000000000
|
|
||||||
gasLimit: '0x5208', // 21000
|
|
||||||
minimumGasLimit: '0x5208',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = sendReducer(validSendState, action);
|
|
||||||
|
|
||||||
expect(result.gas.gasPrice).toStrictEqual(action.payload);
|
|
||||||
expect(result.draftTransaction.txParams.gasPrice).toStrictEqual(
|
|
||||||
action.payload,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should recalculate gasTotal', () => {
|
|
||||||
const gasState = {
|
|
||||||
gas: {
|
|
||||||
gasLimit: '0x5208', // 21000,
|
|
||||||
gasPrice: '0x0',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const state = { ...initialState, ...gasState };
|
|
||||||
const result = sendReducer(state, action);
|
|
||||||
|
|
||||||
expect(result.gas.gasPrice).toStrictEqual(action.payload);
|
|
||||||
expect(result.gas.gasLimit).toStrictEqual(gasState.gas.gasLimit);
|
|
||||||
expect(result.gas.gasTotal).toStrictEqual('0x1319718a5000'); // 21000000000000
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('updateAmountMode', () => {
|
describe('updateAmountMode', () => {
|
||||||
it('should change to INPUT amount mode', () => {
|
it('should change to INPUT amount mode', () => {
|
||||||
const emptyAmountModeState = {
|
const emptyAmountModeState = {
|
||||||
@ -342,89 +362,199 @@ describe('Send Slice', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('updateDraftTransaction', () => {
|
describe('updateDraftTransaction', () => {
|
||||||
it('should', () => {
|
describe('with LEGACY transactions', () => {
|
||||||
const detailsForDraftTransactionState = {
|
it('should properly set fields', () => {
|
||||||
...initialState,
|
const detailsForDraftTransactionState = {
|
||||||
status: SEND_STATUSES.VALID,
|
...initialState,
|
||||||
account: {
|
status: SEND_STATUSES.VALID,
|
||||||
address: '0xCurrentAddress',
|
transactionType: TRANSACTION_ENVELOPE_TYPES.LEGACY,
|
||||||
},
|
account: {
|
||||||
asset: {
|
address: '0xCurrentAddress',
|
||||||
type: '',
|
},
|
||||||
},
|
asset: {
|
||||||
recipient: {
|
type: '',
|
||||||
address: '0xRecipientAddress',
|
},
|
||||||
},
|
recipient: {
|
||||||
amount: {
|
address: '0xRecipientAddress',
|
||||||
value: '0x1',
|
},
|
||||||
},
|
amount: {
|
||||||
gas: {
|
value: '0x1',
|
||||||
gasPrice: '0x3b9aca00', // 1000000000
|
},
|
||||||
gasLimit: '0x5208', // 21000
|
gas: {
|
||||||
},
|
gasPrice: '0x3b9aca00', // 1000000000
|
||||||
};
|
gasLimit: '0x5208', // 21000
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const action = {
|
const action = {
|
||||||
type: 'send/updateDraftTransaction',
|
type: 'send/updateDraftTransaction',
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = sendReducer(detailsForDraftTransactionState, action);
|
const result = sendReducer(detailsForDraftTransactionState, action);
|
||||||
|
|
||||||
expect(result.draftTransaction.txParams.to).toStrictEqual(
|
expect(result.draftTransaction.txParams.to).toStrictEqual(
|
||||||
detailsForDraftTransactionState.recipient.address,
|
detailsForDraftTransactionState.recipient.address,
|
||||||
);
|
);
|
||||||
expect(result.draftTransaction.txParams.value).toStrictEqual(
|
expect(result.draftTransaction.txParams.value).toStrictEqual(
|
||||||
detailsForDraftTransactionState.amount.value,
|
detailsForDraftTransactionState.amount.value,
|
||||||
);
|
);
|
||||||
expect(result.draftTransaction.txParams.gas).toStrictEqual(
|
expect(result.draftTransaction.txParams.gas).toStrictEqual(
|
||||||
detailsForDraftTransactionState.gas.gasLimit,
|
detailsForDraftTransactionState.gas.gasLimit,
|
||||||
);
|
);
|
||||||
expect(result.draftTransaction.txParams.gasPrice).toStrictEqual(
|
expect(result.draftTransaction.txParams.gasPrice).toStrictEqual(
|
||||||
detailsForDraftTransactionState.gas.gasPrice,
|
detailsForDraftTransactionState.gas.gasPrice,
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the draftTransaction txParams recipient to token address when asset is type TOKEN', () => {
|
||||||
|
const detailsForDraftTransactionState = {
|
||||||
|
...initialState,
|
||||||
|
status: SEND_STATUSES.VALID,
|
||||||
|
transactionType: TRANSACTION_ENVELOPE_TYPES.LEGACY,
|
||||||
|
account: {
|
||||||
|
address: '0xCurrentAddress',
|
||||||
|
},
|
||||||
|
asset: {
|
||||||
|
type: ASSET_TYPES.TOKEN,
|
||||||
|
details: {
|
||||||
|
address: '0xTokenAddress',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
value: '0x1',
|
||||||
|
},
|
||||||
|
gas: {
|
||||||
|
gasPrice: '0x3b9aca00', // 1000000000
|
||||||
|
gasLimit: '0x5208', // 21000
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = {
|
||||||
|
type: 'send/updateDraftTransaction',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = sendReducer(detailsForDraftTransactionState, action);
|
||||||
|
|
||||||
|
expect(result.draftTransaction.txParams.to).toStrictEqual(
|
||||||
|
detailsForDraftTransactionState.asset.details.address,
|
||||||
|
);
|
||||||
|
expect(result.draftTransaction.txParams.value).toStrictEqual('0x0');
|
||||||
|
expect(result.draftTransaction.txParams.gas).toStrictEqual(
|
||||||
|
detailsForDraftTransactionState.gas.gasLimit,
|
||||||
|
);
|
||||||
|
expect(result.draftTransaction.txParams.gasPrice).toStrictEqual(
|
||||||
|
detailsForDraftTransactionState.gas.gasPrice,
|
||||||
|
);
|
||||||
|
expect(result.draftTransaction.txParams.data).toStrictEqual(
|
||||||
|
'0xa9059cbb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001',
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update the draftTransaction txParams recipient to token address when asset is type TOKEN', () => {
|
describe('with FEE_MARKET transactions', () => {
|
||||||
const detailsForDraftTransactionState = {
|
it('should properly set fields', () => {
|
||||||
...initialState,
|
const detailsForDraftTransactionState = {
|
||||||
status: SEND_STATUSES.VALID,
|
...initialState,
|
||||||
account: {
|
status: SEND_STATUSES.VALID,
|
||||||
address: '0xCurrentAddress',
|
transactionType: TRANSACTION_ENVELOPE_TYPES.FEE_MARKET,
|
||||||
},
|
account: {
|
||||||
asset: {
|
address: '0xCurrentAddress',
|
||||||
type: ASSET_TYPES.TOKEN,
|
|
||||||
details: {
|
|
||||||
address: '0xTokenAddress',
|
|
||||||
},
|
},
|
||||||
},
|
asset: {
|
||||||
amount: {
|
type: '',
|
||||||
value: '0x1',
|
},
|
||||||
},
|
recipient: {
|
||||||
gas: {
|
address: '0xRecipientAddress',
|
||||||
gasPrice: '0x3b9aca00', // 1000000000
|
},
|
||||||
gasLimit: '0x5208', // 21000
|
amount: {
|
||||||
},
|
value: '0x1',
|
||||||
};
|
},
|
||||||
|
gas: {
|
||||||
|
maxFeePerGas: '0x2540be400', // 10 GWEI
|
||||||
|
maxPriorityFeePerGas: '0x3b9aca00', // 1 GWEI
|
||||||
|
gasLimit: '0x5208', // 21000
|
||||||
|
},
|
||||||
|
eip1559support: true,
|
||||||
|
};
|
||||||
|
|
||||||
const action = {
|
const action = {
|
||||||
type: 'send/updateDraftTransaction',
|
type: 'send/updateDraftTransaction',
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = sendReducer(detailsForDraftTransactionState, action);
|
const result = sendReducer(detailsForDraftTransactionState, action);
|
||||||
|
|
||||||
expect(result.draftTransaction.txParams.to).toStrictEqual(
|
expect(result.draftTransaction.txParams.to).toStrictEqual(
|
||||||
detailsForDraftTransactionState.asset.details.address,
|
detailsForDraftTransactionState.recipient.address,
|
||||||
);
|
);
|
||||||
expect(result.draftTransaction.txParams.value).toStrictEqual('0x0');
|
expect(result.draftTransaction.txParams.value).toStrictEqual(
|
||||||
expect(result.draftTransaction.txParams.gas).toStrictEqual(
|
detailsForDraftTransactionState.amount.value,
|
||||||
detailsForDraftTransactionState.gas.gasLimit,
|
);
|
||||||
);
|
expect(result.draftTransaction.txParams.gas).toStrictEqual(
|
||||||
expect(result.draftTransaction.txParams.gasPrice).toStrictEqual(
|
detailsForDraftTransactionState.gas.gasLimit,
|
||||||
detailsForDraftTransactionState.gas.gasPrice,
|
);
|
||||||
);
|
expect(result.draftTransaction.txParams.gasPrice).toBeUndefined();
|
||||||
expect(result.draftTransaction.txParams.data).toStrictEqual(
|
expect(result.draftTransaction.txParams.maxFeePerGas).toStrictEqual(
|
||||||
'0xa9059cbb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001',
|
detailsForDraftTransactionState.gas.maxFeePerGas,
|
||||||
);
|
);
|
||||||
|
expect(
|
||||||
|
result.draftTransaction.txParams.maxPriorityFeePerGas,
|
||||||
|
).toStrictEqual(
|
||||||
|
detailsForDraftTransactionState.gas.maxPriorityFeePerGas,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the draftTransaction txParams recipient to token address when asset is type TOKEN', () => {
|
||||||
|
const detailsForDraftTransactionState = {
|
||||||
|
...initialState,
|
||||||
|
status: SEND_STATUSES.VALID,
|
||||||
|
transactionType: TRANSACTION_ENVELOPE_TYPES.FEE_MARKET,
|
||||||
|
account: {
|
||||||
|
address: '0xCurrentAddress',
|
||||||
|
},
|
||||||
|
asset: {
|
||||||
|
type: ASSET_TYPES.TOKEN,
|
||||||
|
details: {
|
||||||
|
address: '0xTokenAddress',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
value: '0x1',
|
||||||
|
},
|
||||||
|
gas: {
|
||||||
|
maxFeePerGas: '0x2540be400', // 10 GWEI
|
||||||
|
maxPriorityFeePerGas: '0x3b9aca00', // 1 GWEI
|
||||||
|
gasLimit: '0x5208', // 21000
|
||||||
|
},
|
||||||
|
eip1559support: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = {
|
||||||
|
type: 'send/updateDraftTransaction',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = sendReducer(detailsForDraftTransactionState, action);
|
||||||
|
|
||||||
|
expect(result.draftTransaction.txParams.to).toStrictEqual(
|
||||||
|
detailsForDraftTransactionState.asset.details.address,
|
||||||
|
);
|
||||||
|
expect(result.draftTransaction.txParams.value).toStrictEqual('0x0');
|
||||||
|
expect(result.draftTransaction.txParams.gas).toStrictEqual(
|
||||||
|
detailsForDraftTransactionState.gas.gasLimit,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.draftTransaction.txParams.maxFeePerGas).toStrictEqual(
|
||||||
|
detailsForDraftTransactionState.gas.maxFeePerGas,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
result.draftTransaction.txParams.maxPriorityFeePerGas,
|
||||||
|
).toStrictEqual(
|
||||||
|
detailsForDraftTransactionState.gas.maxPriorityFeePerGas,
|
||||||
|
);
|
||||||
|
expect(result.draftTransaction.txParams.gasPrice).toBeUndefined();
|
||||||
|
expect(result.draftTransaction.txParams.data).toStrictEqual(
|
||||||
|
'0xa9059cbb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001',
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1032,6 +1162,36 @@ describe('Send Slice', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Action Creators', () => {
|
describe('Action Creators', () => {
|
||||||
|
describe('updateGasPrice', () => {
|
||||||
|
it('should update gas price and update draft transaction with validated state', async () => {
|
||||||
|
const store = mockStore({
|
||||||
|
send: {
|
||||||
|
gas: {
|
||||||
|
gasPrice: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const newGasPrice = '0x0';
|
||||||
|
|
||||||
|
await store.dispatch(updateGasPrice(newGasPrice));
|
||||||
|
|
||||||
|
const actionResult = store.getActions();
|
||||||
|
|
||||||
|
const expectedActionResult = [
|
||||||
|
{
|
||||||
|
type: 'send/updateGasFees',
|
||||||
|
payload: {
|
||||||
|
gasPrice: '0x0',
|
||||||
|
transactionType: TRANSACTION_ENVELOPE_TYPES.LEGACY,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(actionResult).toStrictEqual(expectedActionResult);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('UpdateSendAmount', () => {
|
describe('UpdateSendAmount', () => {
|
||||||
const defaultSendAmountState = {
|
const defaultSendAmountState = {
|
||||||
send: {
|
send: {
|
||||||
@ -2013,4 +2173,329 @@ describe('Send Slice', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('selectors', () => {
|
||||||
|
describe('gas selectors', () => {
|
||||||
|
it('has a selector that gets gasLimit', () => {
|
||||||
|
expect(getGasLimit({ send: initialState })).toBe('0x0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has a selector that gets gasPrice', () => {
|
||||||
|
expect(getGasPrice({ send: initialState })).toBe('0x0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has a selector that gets gasTotal', () => {
|
||||||
|
expect(getGasTotal({ send: initialState })).toBe('0x0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has a selector to determine if gas fee is in error', () => {
|
||||||
|
expect(gasFeeIsInError({ send: initialState })).toBe(false);
|
||||||
|
expect(
|
||||||
|
gasFeeIsInError({
|
||||||
|
send: {
|
||||||
|
...initialState,
|
||||||
|
gas: {
|
||||||
|
...initialState.gas,
|
||||||
|
error: 'yes',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has a selector that gets minimumGasLimit', () => {
|
||||||
|
expect(getMinimumGasLimitForSend({ send: initialState })).toBe(
|
||||||
|
GAS_LIMITS.SIMPLE,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getGasInputMode selector', () => {
|
||||||
|
it('returns BASIC when on mainnet and advanced inline gas is false', () => {
|
||||||
|
expect(
|
||||||
|
getGasInputMode({
|
||||||
|
metamask: {
|
||||||
|
provider: { chainId: MAINNET_CHAIN_ID },
|
||||||
|
featureFlags: { advancedInlineGas: false },
|
||||||
|
},
|
||||||
|
send: initialState,
|
||||||
|
}),
|
||||||
|
).toBe(GAS_INPUT_MODES.BASIC);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns BASIC when on localhost and advanced inline gas is false and IN_TEST is set', () => {
|
||||||
|
process.env.IN_TEST = true;
|
||||||
|
expect(
|
||||||
|
getGasInputMode({
|
||||||
|
metamask: {
|
||||||
|
provider: { chainId: '0x539' },
|
||||||
|
featureFlags: { advancedInlineGas: false },
|
||||||
|
},
|
||||||
|
send: initialState,
|
||||||
|
}),
|
||||||
|
).toBe(GAS_INPUT_MODES.BASIC);
|
||||||
|
process.env.IN_TEST = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns INLINE when on mainnet and advanced inline gas is true', () => {
|
||||||
|
expect(
|
||||||
|
getGasInputMode({
|
||||||
|
metamask: {
|
||||||
|
provider: { chainId: MAINNET_CHAIN_ID },
|
||||||
|
featureFlags: { advancedInlineGas: true },
|
||||||
|
},
|
||||||
|
send: initialState,
|
||||||
|
}),
|
||||||
|
).toBe(GAS_INPUT_MODES.INLINE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns INLINE when on mainnet and advanced inline gas is false but eth_gasPrice estimate is used', () => {
|
||||||
|
expect(
|
||||||
|
getGasInputMode({
|
||||||
|
metamask: {
|
||||||
|
provider: { chainId: MAINNET_CHAIN_ID },
|
||||||
|
featureFlags: { advancedInlineGas: false },
|
||||||
|
gasEstimateType: GAS_ESTIMATE_TYPES.ETH_GASPRICE,
|
||||||
|
},
|
||||||
|
send: initialState,
|
||||||
|
}),
|
||||||
|
).toBe(GAS_INPUT_MODES.INLINE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns INLINE when on mainnet and advanced inline gas is false but eth_gasPrice estimate is used even IN_TEST', () => {
|
||||||
|
process.env.IN_TEST = true;
|
||||||
|
expect(
|
||||||
|
getGasInputMode({
|
||||||
|
metamask: {
|
||||||
|
provider: { chainId: MAINNET_CHAIN_ID },
|
||||||
|
featureFlags: { advancedInlineGas: false },
|
||||||
|
gasEstimateType: GAS_ESTIMATE_TYPES.ETH_GASPRICE,
|
||||||
|
},
|
||||||
|
send: initialState,
|
||||||
|
}),
|
||||||
|
).toBe(GAS_INPUT_MODES.INLINE);
|
||||||
|
process.env.IN_TEST = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns CUSTOM if isCustomGasSet is true', () => {
|
||||||
|
expect(
|
||||||
|
getGasInputMode({
|
||||||
|
metamask: {
|
||||||
|
provider: { chainId: MAINNET_CHAIN_ID },
|
||||||
|
featureFlags: { advancedInlineGas: true },
|
||||||
|
},
|
||||||
|
send: {
|
||||||
|
...initialState,
|
||||||
|
gas: {
|
||||||
|
...initialState.send,
|
||||||
|
isCustomGasSet: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(GAS_INPUT_MODES.CUSTOM);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('asset selectors', () => {
|
||||||
|
it('has a selector to get the asset', () => {
|
||||||
|
expect(getSendAsset({ send: initialState })).toMatchObject(
|
||||||
|
initialState.asset,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has a selector to get the asset address', () => {
|
||||||
|
expect(
|
||||||
|
getSendAssetAddress({
|
||||||
|
send: {
|
||||||
|
...initialState,
|
||||||
|
asset: {
|
||||||
|
balance: '0x0',
|
||||||
|
details: { address: '0x0' },
|
||||||
|
type: ASSET_TYPES.TOKEN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe('0x0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has a selector that determines if asset is sendable based on ERC721 status', () => {
|
||||||
|
expect(getIsAssetSendable({ send: initialState })).toBe(true);
|
||||||
|
expect(
|
||||||
|
getIsAssetSendable({
|
||||||
|
send: {
|
||||||
|
...initialState,
|
||||||
|
asset: {
|
||||||
|
...initialState,
|
||||||
|
type: ASSET_TYPES.TOKEN,
|
||||||
|
details: { isERC721: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('amount selectors', () => {
|
||||||
|
it('has a selector to get send amount', () => {
|
||||||
|
expect(getSendAmount({ send: initialState })).toBe('0x0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has a selector to get if there is an insufficient funds error', () => {
|
||||||
|
expect(getIsBalanceInsufficient({ send: initialState })).toBe(false);
|
||||||
|
expect(
|
||||||
|
getIsBalanceInsufficient({
|
||||||
|
send: {
|
||||||
|
...initialState,
|
||||||
|
gas: { ...initialState.gas, error: INSUFFICIENT_FUNDS_ERROR },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has a selector to get max mode state', () => {
|
||||||
|
expect(getSendMaxModeState({ send: initialState })).toBe(false);
|
||||||
|
expect(
|
||||||
|
getSendMaxModeState({
|
||||||
|
send: {
|
||||||
|
...initialState,
|
||||||
|
amount: { ...initialState.amount, mode: AMOUNT_MODES.MAX },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has a selector to get the user entered hex data', () => {
|
||||||
|
expect(getSendHexData({ send: initialState })).toBeNull();
|
||||||
|
expect(
|
||||||
|
getSendHexData({
|
||||||
|
send: {
|
||||||
|
...initialState,
|
||||||
|
draftTransaction: {
|
||||||
|
...initialState.draftTransaction,
|
||||||
|
userInputHexData: '0x0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe('0x0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has a selector to get if there is an amount error', () => {
|
||||||
|
expect(sendAmountIsInError({ send: initialState })).toBe(false);
|
||||||
|
expect(
|
||||||
|
sendAmountIsInError({
|
||||||
|
send: {
|
||||||
|
...initialState,
|
||||||
|
amount: { ...initialState.amount, error: 'any' },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('recipient selectors', () => {
|
||||||
|
it('has a selector to get recipient address', () => {
|
||||||
|
expect(getSendTo({ send: initialState })).toBe('');
|
||||||
|
expect(
|
||||||
|
getSendTo({
|
||||||
|
send: {
|
||||||
|
...initialState,
|
||||||
|
recipient: { ...initialState.recipient, address: '0xb' },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe('0xb');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has a selector to check if using the my accounts option for recipient selection', () => {
|
||||||
|
expect(
|
||||||
|
getIsUsingMyAccountForRecipientSearch({ send: initialState }),
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
getIsUsingMyAccountForRecipientSearch({
|
||||||
|
send: {
|
||||||
|
...initialState,
|
||||||
|
recipient: {
|
||||||
|
...initialState.recipient,
|
||||||
|
mode: RECIPIENT_SEARCH_MODES.MY_ACCOUNTS,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has a selector to get recipient user input in input field', () => {
|
||||||
|
expect(getRecipientUserInput({ send: initialState })).toBe('');
|
||||||
|
expect(
|
||||||
|
getRecipientUserInput({
|
||||||
|
send: {
|
||||||
|
...initialState,
|
||||||
|
recipient: {
|
||||||
|
...initialState.recipient,
|
||||||
|
userInput: 'domain.eth',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe('domain.eth');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has a selector to get recipient state', () => {
|
||||||
|
expect(getRecipient({ send: initialState })).toMatchObject(
|
||||||
|
initialState.recipient,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('send validity selectors', () => {
|
||||||
|
it('has a selector to get send errors', () => {
|
||||||
|
expect(getSendErrors({ send: initialState })).toMatchObject({
|
||||||
|
gasFee: null,
|
||||||
|
amount: null,
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
getSendErrors({
|
||||||
|
send: {
|
||||||
|
...initialState,
|
||||||
|
gas: {
|
||||||
|
...initialState.gas,
|
||||||
|
error: 'gasFeeTest',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
...initialState.amount,
|
||||||
|
error: 'amountTest',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toMatchObject({ gasFee: 'gasFeeTest', amount: 'amountTest' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has a selector to get send state initialization status', () => {
|
||||||
|
expect(isSendStateInitialized({ send: initialState })).toBe(false);
|
||||||
|
expect(
|
||||||
|
isSendStateInitialized({
|
||||||
|
send: {
|
||||||
|
...initialState,
|
||||||
|
stage: SEND_STATUSES.ADD_RECIPIENT,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has a selector to get send state validity', () => {
|
||||||
|
expect(isSendFormInvalid({ send: initialState })).toBe(false);
|
||||||
|
expect(
|
||||||
|
isSendFormInvalid({
|
||||||
|
send: { ...initialState, status: SEND_STATUSES.INVALID },
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has a selector to get send stage', () => {
|
||||||
|
expect(getSendStage({ send: initialState })).toBe(SEND_STAGES.INACTIVE);
|
||||||
|
expect(
|
||||||
|
getSendStage({
|
||||||
|
send: { ...initialState, stage: SEND_STAGES.ADD_RECIPIENT },
|
||||||
|
}),
|
||||||
|
).toBe(SEND_STAGES.ADD_RECIPIENT);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { GAS_ESTIMATE_TYPES } from '../../shared/constants/gas';
|
import { GAS_ESTIMATE_TYPES } from '../../shared/constants/gas';
|
||||||
import {
|
import {
|
||||||
@ -7,10 +6,7 @@ import {
|
|||||||
getGasFeeEstimates,
|
getGasFeeEstimates,
|
||||||
isEIP1559Network,
|
isEIP1559Network,
|
||||||
} from '../ducks/metamask/metamask';
|
} from '../ducks/metamask/metamask';
|
||||||
import {
|
import { useSafeGasEstimatePolling } from './useSafeGasEstimatePolling';
|
||||||
disconnectGasFeeEstimatePoller,
|
|
||||||
getGasFeeEstimatesAndStartPolling,
|
|
||||||
} from '../store/actions';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {keyof typeof GAS_ESTIMATE_TYPES} GasEstimateTypes
|
* @typedef {keyof typeof GAS_ESTIMATE_TYPES} GasEstimateTypes
|
||||||
@ -43,31 +39,17 @@ export function useGasFeeEstimates() {
|
|||||||
const gasEstimateType = useSelector(getGasEstimateType);
|
const gasEstimateType = useSelector(getGasEstimateType);
|
||||||
const gasFeeEstimates = useSelector(getGasFeeEstimates);
|
const gasFeeEstimates = useSelector(getGasFeeEstimates);
|
||||||
const estimatedGasFeeTimeBounds = useSelector(getEstimatedGasFeeTimeBounds);
|
const estimatedGasFeeTimeBounds = useSelector(getEstimatedGasFeeTimeBounds);
|
||||||
useEffect(() => {
|
useSafeGasEstimatePolling();
|
||||||
let active = true;
|
|
||||||
let pollToken;
|
|
||||||
getGasFeeEstimatesAndStartPolling().then((newPollToken) => {
|
|
||||||
if (active) {
|
|
||||||
pollToken = newPollToken;
|
|
||||||
} else {
|
|
||||||
disconnectGasFeeEstimatePoller(newPollToken);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return () => {
|
|
||||||
active = false;
|
|
||||||
if (pollToken) {
|
|
||||||
disconnectGasFeeEstimatePoller(pollToken);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// We consider the gas estimate to be loading if the gasEstimateType is
|
// We consider the gas estimate to be loading if the gasEstimateType is
|
||||||
// 'NONE' or if the current gasEstimateType does not match the type we expect
|
// 'NONE' or if the current gasEstimateType cannot be supported by the current
|
||||||
// for the current network. e.g, a ETH_GASPRICE estimate when on a network
|
// network
|
||||||
// supporting EIP-1559.
|
const isEIP1559TolerableEstimateType =
|
||||||
|
gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET ||
|
||||||
|
gasEstimateType === GAS_ESTIMATE_TYPES.ETH_GASPRICE;
|
||||||
const isGasEstimatesLoading =
|
const isGasEstimatesLoading =
|
||||||
gasEstimateType === GAS_ESTIMATE_TYPES.NONE ||
|
gasEstimateType === GAS_ESTIMATE_TYPES.NONE ||
|
||||||
(supportsEIP1559 && gasEstimateType !== GAS_ESTIMATE_TYPES.FEE_MARKET) ||
|
(supportsEIP1559 && !isEIP1559TolerableEstimateType) ||
|
||||||
(!supportsEIP1559 && gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET);
|
(!supportsEIP1559 && gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -173,11 +173,11 @@ describe('useGasFeeEstimates', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('indicates that gas estimates are loading when gasEstimateType is not FEE_MARKET but network supports EIP-1559', () => {
|
it('indicates that gas estimates are loading when gasEstimateType is not FEE_MARKET or ETH_GASPRICE, but network supports EIP-1559', () => {
|
||||||
useSelector.mockImplementation(
|
useSelector.mockImplementation(
|
||||||
generateUseSelectorRouter({
|
generateUseSelectorRouter({
|
||||||
isEIP1559Network: true,
|
isEIP1559Network: true,
|
||||||
gasEstimateType: GAS_ESTIMATE_TYPES.ETH_GASPRICE,
|
gasEstimateType: GAS_ESTIMATE_TYPES.LEGACY,
|
||||||
gasFeeEstimates: {
|
gasFeeEstimates: {
|
||||||
gasPrice: '10',
|
gasPrice: '10',
|
||||||
},
|
},
|
||||||
@ -189,7 +189,7 @@ describe('useGasFeeEstimates', () => {
|
|||||||
} = renderHook(() => useGasFeeEstimates());
|
} = renderHook(() => useGasFeeEstimates());
|
||||||
expect(current).toMatchObject({
|
expect(current).toMatchObject({
|
||||||
gasFeeEstimates: { gasPrice: '10' },
|
gasFeeEstimates: { gasPrice: '10' },
|
||||||
gasEstimateType: GAS_ESTIMATE_TYPES.ETH_GASPRICE,
|
gasEstimateType: GAS_ESTIMATE_TYPES.LEGACY,
|
||||||
estimatedGasFeeTimeBounds: undefined,
|
estimatedGasFeeTimeBounds: undefined,
|
||||||
isGasEstimatesLoading: true,
|
isGasEstimatesLoading: true,
|
||||||
});
|
});
|
||||||
|
@ -18,6 +18,8 @@ import {
|
|||||||
getMinimumGasTotalInHexWei,
|
getMinimumGasTotalInHexWei,
|
||||||
} from '../../shared/modules/gas.utils';
|
} from '../../shared/modules/gas.utils';
|
||||||
import { PRIMARY, SECONDARY } from '../helpers/constants/common';
|
import { PRIMARY, SECONDARY } from '../helpers/constants/common';
|
||||||
|
import { isEIP1559Network } from '../ducks/metamask/metamask';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
hexWEIToDecGWEI,
|
hexWEIToDecGWEI,
|
||||||
decGWEIToHexWEI,
|
decGWEIToHexWEI,
|
||||||
@ -173,7 +175,7 @@ export function useGasFeeInputs(
|
|||||||
) {
|
) {
|
||||||
const { balance: ethBalance } = useSelector(getSelectedAccount);
|
const { balance: ethBalance } = useSelector(getSelectedAccount);
|
||||||
const txData = useSelector(txDataSelector);
|
const txData = useSelector(txDataSelector);
|
||||||
|
const networkSupportsEIP1559 = useSelector(isEIP1559Network);
|
||||||
// We need to know whether to show fiat conversions or not, so that we can
|
// We need to know whether to show fiat conversions or not, so that we can
|
||||||
// default our fiat values to empty strings if showing fiat is not wanted or
|
// default our fiat values to empty strings if showing fiat is not wanted or
|
||||||
// possible.
|
// possible.
|
||||||
@ -282,12 +284,13 @@ export function useGasFeeInputs(
|
|||||||
const gasSettings = {
|
const gasSettings = {
|
||||||
gasLimit: decimalToHex(gasLimit),
|
gasLimit: decimalToHex(gasLimit),
|
||||||
};
|
};
|
||||||
|
if (networkSupportsEIP1559) {
|
||||||
if (gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET) {
|
gasSettings.maxFeePerGas = maxFeePerGasToUse
|
||||||
gasSettings.maxFeePerGas = decGWEIToHexWEI(maxFeePerGasToUse);
|
? decGWEIToHexWEI(maxFeePerGasToUse)
|
||||||
gasSettings.maxPriorityFeePerGas = decGWEIToHexWEI(
|
: decGWEIToHexWEI(gasPriceToUse || '0');
|
||||||
maxPriorityFeePerGasToUse,
|
gasSettings.maxPriorityFeePerGas = maxPriorityFeePerGasToUse
|
||||||
);
|
? decGWEIToHexWEI(maxPriorityFeePerGasToUse)
|
||||||
|
: gasSettings.maxFeePerGas;
|
||||||
gasSettings.baseFeePerGas = decGWEIToHexWEI(
|
gasSettings.baseFeePerGas = decGWEIToHexWEI(
|
||||||
gasFeeEstimates.estimatedBaseFee ?? '0',
|
gasFeeEstimates.estimatedBaseFee ?? '0',
|
||||||
);
|
);
|
||||||
|
@ -3,9 +3,11 @@ import { useSelector } from 'react-redux';
|
|||||||
import { GAS_ESTIMATE_TYPES } from '../../shared/constants/gas';
|
import { GAS_ESTIMATE_TYPES } from '../../shared/constants/gas';
|
||||||
import { multiplyCurrencies } from '../../shared/modules/conversion.utils';
|
import { multiplyCurrencies } from '../../shared/modules/conversion.utils';
|
||||||
import {
|
import {
|
||||||
|
isEIP1559Network,
|
||||||
getConversionRate,
|
getConversionRate,
|
||||||
getNativeCurrency,
|
getNativeCurrency,
|
||||||
} from '../ducks/metamask/metamask';
|
} from '../ducks/metamask/metamask';
|
||||||
|
|
||||||
import { ETH, PRIMARY } from '../helpers/constants/common';
|
import { ETH, PRIMARY } from '../helpers/constants/common';
|
||||||
import {
|
import {
|
||||||
getCurrentCurrency,
|
getCurrentCurrency,
|
||||||
@ -102,7 +104,9 @@ const HIGH_FEE_MARKET_ESTIMATE_RETURN_VALUE = {
|
|||||||
estimatedGasFeeTimeBounds: {},
|
estimatedGasFeeTimeBounds: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateUseSelectorRouter = () => (selector) => {
|
const generateUseSelectorRouter = ({ isEIP1559NetworkResponse } = {}) => (
|
||||||
|
selector,
|
||||||
|
) => {
|
||||||
if (selector === getConversionRate) {
|
if (selector === getConversionRate) {
|
||||||
return MOCK_ETH_USD_CONVERSION_RATE;
|
return MOCK_ETH_USD_CONVERSION_RATE;
|
||||||
}
|
}
|
||||||
@ -127,6 +131,9 @@ const generateUseSelectorRouter = () => (selector) => {
|
|||||||
balance: '0x440aa47cc2556',
|
balance: '0x440aa47cc2556',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (selector === isEIP1559Network) {
|
||||||
|
return isEIP1559NetworkResponse;
|
||||||
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -178,6 +185,9 @@ describe('useGasFeeInputs', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('updates values when user modifies gasPrice', () => {
|
it('updates values when user modifies gasPrice', () => {
|
||||||
|
useSelector.mockImplementation(
|
||||||
|
generateUseSelectorRouter({ isEIP1559NetworkResponse: false }),
|
||||||
|
);
|
||||||
const { result } = renderHook(() => useGasFeeInputs());
|
const { result } = renderHook(() => useGasFeeInputs());
|
||||||
expect(result.current.gasPrice).toBe(
|
expect(result.current.gasPrice).toBe(
|
||||||
LEGACY_GAS_ESTIMATE_RETURN_VALUE.gasFeeEstimates.medium,
|
LEGACY_GAS_ESTIMATE_RETURN_VALUE.gasFeeEstimates.medium,
|
||||||
@ -242,6 +252,9 @@ describe('useGasFeeInputs', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('updates values when user modifies maxFeePerGas', () => {
|
it('updates values when user modifies maxFeePerGas', () => {
|
||||||
|
useSelector.mockImplementation(
|
||||||
|
generateUseSelectorRouter({ isEIP1559NetworkResponse: true }),
|
||||||
|
);
|
||||||
const { result } = renderHook(() => useGasFeeInputs());
|
const { result } = renderHook(() => useGasFeeInputs());
|
||||||
expect(result.current.maxFeePerGas).toBe(
|
expect(result.current.maxFeePerGas).toBe(
|
||||||
FEE_MARKET_ESTIMATE_RETURN_VALUE.gasFeeEstimates.medium
|
FEE_MARKET_ESTIMATE_RETURN_VALUE.gasFeeEstimates.medium
|
||||||
@ -297,7 +310,9 @@ describe('useGasFeeInputs', () => {
|
|||||||
useGasFeeEstimates.mockImplementation(
|
useGasFeeEstimates.mockImplementation(
|
||||||
() => HIGH_FEE_MARKET_ESTIMATE_RETURN_VALUE,
|
() => HIGH_FEE_MARKET_ESTIMATE_RETURN_VALUE,
|
||||||
);
|
);
|
||||||
useSelector.mockImplementation(generateUseSelectorRouter());
|
useSelector.mockImplementation(
|
||||||
|
generateUseSelectorRouter({ isEIP1559NetworkResponse: true }),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true', () => {
|
it('should return true', () => {
|
||||||
|
33
ui/hooks/useSafeGasEstimatePolling.js
Normal file
33
ui/hooks/useSafeGasEstimatePolling.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
disconnectGasFeeEstimatePoller,
|
||||||
|
getGasFeeEstimatesAndStartPolling,
|
||||||
|
} from '../store/actions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a reusable hook that can be used for safely updating the polling
|
||||||
|
* data in the gas fee controller. It makes a request to get estimates and
|
||||||
|
* begin polling, keeping track of the poll token for the lifetime of the hook.
|
||||||
|
* It then disconnects polling upon unmount. If the hook is unmounted while waiting
|
||||||
|
* for `getGasFeeEstimatesAndStartPolling` to resolve, the `active` flag ensures
|
||||||
|
* that a call to disconnect happens after promise resolution.
|
||||||
|
*/
|
||||||
|
export function useSafeGasEstimatePolling() {
|
||||||
|
useEffect(() => {
|
||||||
|
let active = true;
|
||||||
|
let pollToken;
|
||||||
|
getGasFeeEstimatesAndStartPolling().then((newPollToken) => {
|
||||||
|
if (active) {
|
||||||
|
pollToken = newPollToken;
|
||||||
|
} else {
|
||||||
|
disconnectGasFeeEstimatePoller(newPollToken);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
active = false;
|
||||||
|
if (pollToken) {
|
||||||
|
disconnectGasFeeEstimatePoller(pollToken);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
}
|
@ -39,6 +39,10 @@ import InfoTooltip from '../../components/ui/info-tooltip/info-tooltip';
|
|||||||
import GasTiming from '../../components/app/gas-timing/gas-timing.component';
|
import GasTiming from '../../components/app/gas-timing/gas-timing.component';
|
||||||
|
|
||||||
import { COLORS } from '../../helpers/constants/design-system';
|
import { COLORS } from '../../helpers/constants/design-system';
|
||||||
|
import {
|
||||||
|
disconnectGasFeeEstimatePoller,
|
||||||
|
getGasFeeEstimatesAndStartPolling,
|
||||||
|
} from '../../store/actions';
|
||||||
|
|
||||||
export default class ConfirmTransactionBase extends Component {
|
export default class ConfirmTransactionBase extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -58,7 +62,8 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
fromAddress: PropTypes.string,
|
fromAddress: PropTypes.string,
|
||||||
fromName: PropTypes.string,
|
fromName: PropTypes.string,
|
||||||
hexTransactionAmount: PropTypes.string,
|
hexTransactionAmount: PropTypes.string,
|
||||||
hexTransactionFee: PropTypes.string,
|
hexMinimumTransactionFee: PropTypes.string,
|
||||||
|
hexMaximumTransactionFee: PropTypes.string,
|
||||||
hexTransactionTotal: PropTypes.string,
|
hexTransactionTotal: PropTypes.string,
|
||||||
methodData: PropTypes.object,
|
methodData: PropTypes.object,
|
||||||
nonce: PropTypes.string,
|
nonce: PropTypes.string,
|
||||||
@ -191,7 +196,7 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
const {
|
const {
|
||||||
balance,
|
balance,
|
||||||
conversionRate,
|
conversionRate,
|
||||||
hexTransactionFee,
|
hexMaximumTransactionFee,
|
||||||
txData: { simulationFails, txParams: { value: amount } = {} } = {},
|
txData: { simulationFails, txParams: { value: amount } = {} } = {},
|
||||||
customGas,
|
customGas,
|
||||||
noGasPrice,
|
noGasPrice,
|
||||||
@ -201,7 +206,7 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
balance &&
|
balance &&
|
||||||
!isBalanceSufficient({
|
!isBalanceSufficient({
|
||||||
amount,
|
amount,
|
||||||
gasTotal: hexTransactionFee || '0x0',
|
gasTotal: hexMaximumTransactionFee || '0x0',
|
||||||
balance,
|
balance,
|
||||||
conversionRate,
|
conversionRate,
|
||||||
});
|
});
|
||||||
@ -282,7 +287,7 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
const {
|
const {
|
||||||
primaryTotalTextOverride,
|
primaryTotalTextOverride,
|
||||||
secondaryTotalTextOverride,
|
secondaryTotalTextOverride,
|
||||||
hexTransactionFee,
|
hexMinimumTransactionFee,
|
||||||
hexTransactionTotal,
|
hexTransactionTotal,
|
||||||
useNonceField,
|
useNonceField,
|
||||||
customNonceValue,
|
customNonceValue,
|
||||||
@ -422,14 +427,14 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
detailText={
|
detailText={
|
||||||
<UserPreferencedCurrencyDisplay
|
<UserPreferencedCurrencyDisplay
|
||||||
type={PRIMARY}
|
type={PRIMARY}
|
||||||
value={hexTransactionFee}
|
value={hexMinimumTransactionFee}
|
||||||
hideLabel={false}
|
hideLabel={false}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
detailTotal={
|
detailTotal={
|
||||||
<UserPreferencedCurrencyDisplay
|
<UserPreferencedCurrencyDisplay
|
||||||
type={SECONDARY}
|
type={SECONDARY}
|
||||||
value={hexTransactionFee}
|
value={hexMinimumTransactionFee}
|
||||||
hideLabel
|
hideLabel
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@ -496,7 +501,7 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
<div className="confirm-page-container-content__gas-fee">
|
<div className="confirm-page-container-content__gas-fee">
|
||||||
<ConfirmDetailRow
|
<ConfirmDetailRow
|
||||||
label={t('gasFee')}
|
label={t('gasFee')}
|
||||||
value={hexTransactionFee}
|
value={hexMinimumTransactionFee}
|
||||||
headerText={showGasEditButton ? t('edit') : ''}
|
headerText={showGasEditButton ? t('edit') : ''}
|
||||||
headerTextClassName={
|
headerTextClassName={
|
||||||
showGasEditButton ? 'confirm-detail-row__header-text--edit' : ''
|
showGasEditButton ? 'confirm-detail-row__header-text--edit' : ''
|
||||||
@ -769,6 +774,7 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this._isMounted = true;
|
||||||
const {
|
const {
|
||||||
toAddress,
|
toAddress,
|
||||||
txData: { origin } = {},
|
txData: { origin } = {},
|
||||||
@ -795,9 +801,28 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
if (toAddress) {
|
if (toAddress) {
|
||||||
tryReverseResolveAddress(toAddress);
|
tryReverseResolveAddress(toAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This makes a request to get estimates and begin polling, keeping track of the poll
|
||||||
|
* token in component state.
|
||||||
|
* It then disconnects polling upon componentWillUnmount. If the hook is unmounted
|
||||||
|
* while waiting for `getGasFeeEstimatesAndStartPolling` to resolve, the `_isMounted`
|
||||||
|
* flag ensures that a call to disconnect happens after promise resolution.
|
||||||
|
*/
|
||||||
|
getGasFeeEstimatesAndStartPolling().then((pollingToken) => {
|
||||||
|
if (this._isMounted) {
|
||||||
|
this.setState({ pollingToken });
|
||||||
|
} else {
|
||||||
|
disconnectGasFeeEstimatePoller(pollingToken);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
this._isMounted = false;
|
||||||
|
if (this.state.pollingToken) {
|
||||||
|
disconnectGasFeeEstimatePoller(this.state.pollingToken);
|
||||||
|
}
|
||||||
this._removeBeforeUnload();
|
this._removeBeforeUnload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,10 @@ import {
|
|||||||
import { getMostRecentOverviewPage } from '../../ducks/history/history';
|
import { getMostRecentOverviewPage } from '../../ducks/history/history';
|
||||||
import { transactionMatchesNetwork } from '../../../shared/modules/transaction.utils';
|
import { transactionMatchesNetwork } from '../../../shared/modules/transaction.utils';
|
||||||
import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils';
|
import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils';
|
||||||
import { updateTransactionGasFees } from '../../ducks/metamask/metamask';
|
import {
|
||||||
|
updateTransactionGasFees,
|
||||||
|
isEIP1559Network,
|
||||||
|
} from '../../ducks/metamask/metamask';
|
||||||
import ConfirmTransactionBase from './confirm-transaction-base.component';
|
import ConfirmTransactionBase from './confirm-transaction-base.component';
|
||||||
|
|
||||||
const casedContractMap = Object.keys(contractMap).reduce((acc, base) => {
|
const casedContractMap = Object.keys(contractMap).reduce((acc, base) => {
|
||||||
@ -65,6 +68,7 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
} = ownProps;
|
} = ownProps;
|
||||||
const { id: paramsTransactionId } = params;
|
const { id: paramsTransactionId } = params;
|
||||||
const isMainnet = getIsMainnet(state);
|
const isMainnet = getIsMainnet(state);
|
||||||
|
const supportsEIP1599 = isEIP1559Network(state);
|
||||||
const { confirmTransaction, metamask } = state;
|
const { confirmTransaction, metamask } = state;
|
||||||
const {
|
const {
|
||||||
ensResolutionsByAddress,
|
ensResolutionsByAddress,
|
||||||
@ -111,7 +115,8 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
hexTransactionAmount,
|
hexTransactionAmount,
|
||||||
hexTransactionFee,
|
hexMinimumTransactionFee,
|
||||||
|
hexMaximumTransactionFee,
|
||||||
hexTransactionTotal,
|
hexTransactionTotal,
|
||||||
} = transactionFeeSelector(state, transaction);
|
} = transactionFeeSelector(state, transaction);
|
||||||
|
|
||||||
@ -158,7 +163,8 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
toName,
|
toName,
|
||||||
toNickname,
|
toNickname,
|
||||||
hexTransactionAmount,
|
hexTransactionAmount,
|
||||||
hexTransactionFee,
|
hexMinimumTransactionFee,
|
||||||
|
hexMaximumTransactionFee,
|
||||||
hexTransactionTotal,
|
hexTransactionTotal,
|
||||||
txData: fullTxData,
|
txData: fullTxData,
|
||||||
tokenData,
|
tokenData,
|
||||||
@ -187,6 +193,7 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
isMainnet,
|
isMainnet,
|
||||||
isEthGasPrice,
|
isEthGasPrice,
|
||||||
noGasPrice,
|
noGasPrice,
|
||||||
|
supportsEIP1599,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -27,10 +27,7 @@ export default function SendHeader() {
|
|||||||
|
|
||||||
let title = asset.type === ASSET_TYPES.NATIVE ? t('send') : t('sendTokens');
|
let title = asset.type === ASSET_TYPES.NATIVE ? t('send') : t('sendTokens');
|
||||||
|
|
||||||
if (
|
if (stage === SEND_STAGES.ADD_RECIPIENT || stage === SEND_STAGES.INACTIVE) {
|
||||||
stage === SEND_STAGES.ADD_RECIPIENT ||
|
|
||||||
stage === SEND_STAGES.UNINITIALIZED
|
|
||||||
) {
|
|
||||||
title = t('addRecipient');
|
title = t('addRecipient');
|
||||||
} else if (stage === SEND_STAGES.EDIT) {
|
} else if (stage === SEND_STAGES.EDIT) {
|
||||||
title = t('edit');
|
title = t('edit');
|
||||||
|
@ -21,7 +21,7 @@ jest.mock('react-router-dom', () => {
|
|||||||
|
|
||||||
describe('SendHeader Component', () => {
|
describe('SendHeader Component', () => {
|
||||||
describe('Title', () => {
|
describe('Title', () => {
|
||||||
it('should render "Add Recipient" for UNINITIALIZED or ADD_RECIPIENT stages', () => {
|
it('should render "Add Recipient" for INACTIVE or ADD_RECIPIENT stages', () => {
|
||||||
const { getByText, rerender } = renderWithProvider(
|
const { getByText, rerender } = renderWithProvider(
|
||||||
<SendHeader />,
|
<SendHeader />,
|
||||||
configureMockStore(middleware)({
|
configureMockStore(middleware)({
|
||||||
|
@ -4,14 +4,25 @@ import { calcTokenAmount } from '../helpers/utils/token-util';
|
|||||||
import {
|
import {
|
||||||
roundExponential,
|
roundExponential,
|
||||||
getValueFromWeiHex,
|
getValueFromWeiHex,
|
||||||
getHexGasTotal,
|
|
||||||
getTransactionFee,
|
getTransactionFee,
|
||||||
addFiat,
|
addFiat,
|
||||||
addEth,
|
addEth,
|
||||||
} from '../helpers/utils/confirm-tx.util';
|
} from '../helpers/utils/confirm-tx.util';
|
||||||
import { sumHexes } from '../helpers/utils/transactions.util';
|
import { sumHexes } from '../helpers/utils/transactions.util';
|
||||||
import { transactionMatchesNetwork } from '../../shared/modules/transaction.utils';
|
import { transactionMatchesNetwork } from '../../shared/modules/transaction.utils';
|
||||||
import { getNativeCurrency } from '../ducks/metamask/metamask';
|
import {
|
||||||
|
getGasEstimateType,
|
||||||
|
getGasFeeEstimates,
|
||||||
|
getNativeCurrency,
|
||||||
|
isEIP1559Network,
|
||||||
|
} from '../ducks/metamask/metamask';
|
||||||
|
import { TRANSACTION_ENVELOPE_TYPES } from '../../shared/constants/transaction';
|
||||||
|
import { decGWEIToHexWEI } from '../helpers/utils/conversions.util';
|
||||||
|
import { GAS_ESTIMATE_TYPES } from '../../shared/constants/gas';
|
||||||
|
import {
|
||||||
|
getMaximumGasTotalInHexWei,
|
||||||
|
getMinimumGasTotalInHexWei,
|
||||||
|
} from '../../shared/modules/gas.utils';
|
||||||
import { getAveragePriceEstimateInHexWEI } from './custom-gas';
|
import { getAveragePriceEstimateInHexWEI } from './custom-gas';
|
||||||
import { getCurrentChainId, deprecatedGetCurrentNetworkId } from './selectors';
|
import { getCurrentChainId, deprecatedGetCurrentNetworkId } from './selectors';
|
||||||
|
|
||||||
@ -218,17 +229,56 @@ export const transactionFeeSelector = function (state, txData) {
|
|||||||
const currentCurrency = currentCurrencySelector(state);
|
const currentCurrency = currentCurrencySelector(state);
|
||||||
const conversionRate = conversionRateSelector(state);
|
const conversionRate = conversionRateSelector(state);
|
||||||
const nativeCurrency = getNativeCurrency(state);
|
const nativeCurrency = getNativeCurrency(state);
|
||||||
|
const gasFeeEstimates = getGasFeeEstimates(state) || {};
|
||||||
|
const gasEstimateType = getGasEstimateType(state);
|
||||||
|
const networkSupportsEIP1559 = isEIP1559Network(state);
|
||||||
|
|
||||||
const { txParams: { value = '0x0', gas: gasLimit = '0x0' } = {} } = txData;
|
const gasEstimationObject = {
|
||||||
|
gasLimit: txData.txParams?.gas ?? '0x0',
|
||||||
|
};
|
||||||
|
|
||||||
// if the gas price from our infura endpoint is null or undefined
|
if (networkSupportsEIP1559) {
|
||||||
// use the metaswap average price estimation as a fallback
|
const { medium = {}, gasPrice = '0' } = gasFeeEstimates;
|
||||||
let { txParams: { gasPrice } = {} } = txData;
|
if (txData.txParams?.type === TRANSACTION_ENVELOPE_TYPES.LEGACY) {
|
||||||
|
gasEstimationObject.gasPrice =
|
||||||
if (!gasPrice) {
|
txData.txParams?.gasPrice ?? decGWEIToHexWEI(gasPrice);
|
||||||
gasPrice = getAveragePriceEstimateInHexWEI(state) || '0x0';
|
} else {
|
||||||
|
const { suggestedMaxPriorityFeePerGas, suggestedMaxFeePerGas } = medium;
|
||||||
|
gasEstimationObject.maxFeePerGas =
|
||||||
|
txData.txParams?.maxFeePerGas ??
|
||||||
|
decGWEIToHexWEI(suggestedMaxFeePerGas || gasPrice);
|
||||||
|
gasEstimationObject.maxPriorityFeePerGas =
|
||||||
|
txData.txParams?.maxPriorityFeePerGas ??
|
||||||
|
((suggestedMaxPriorityFeePerGas &&
|
||||||
|
decGWEIToHexWEI(suggestedMaxPriorityFeePerGas)) ||
|
||||||
|
gasEstimationObject.maxFeePerGas);
|
||||||
|
gasEstimationObject.baseFeePerGas = decGWEIToHexWEI(
|
||||||
|
gasFeeEstimates.estimatedBaseFee,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (gasEstimateType) {
|
||||||
|
case GAS_ESTIMATE_TYPES.NONE:
|
||||||
|
gasEstimationObject.gasPrice = txData.txParams?.gasPrice ?? '0x0';
|
||||||
|
break;
|
||||||
|
case GAS_ESTIMATE_TYPES.ETH_GASPRICE:
|
||||||
|
gasEstimationObject.gasPrice =
|
||||||
|
txData.txParams?.gasPrice ??
|
||||||
|
decGWEIToHexWEI(gasFeeEstimates.gasPrice);
|
||||||
|
break;
|
||||||
|
case GAS_ESTIMATE_TYPES.LEGACY:
|
||||||
|
gasEstimationObject.gasPrice =
|
||||||
|
txData.txParams?.gasPrice ?? getAveragePriceEstimateInHexWEI(state);
|
||||||
|
break;
|
||||||
|
case GAS_ESTIMATE_TYPES.FEE_MARKET:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { txParams: { value = '0x0' } = {} } = txData;
|
||||||
|
|
||||||
const fiatTransactionAmount = getValueFromWeiHex({
|
const fiatTransactionAmount = getValueFromWeiHex({
|
||||||
value,
|
value,
|
||||||
fromCurrency: nativeCurrency,
|
fromCurrency: nativeCurrency,
|
||||||
@ -244,17 +294,31 @@ export const transactionFeeSelector = function (state, txData) {
|
|||||||
numberOfDecimals: 6,
|
numberOfDecimals: 6,
|
||||||
});
|
});
|
||||||
|
|
||||||
const hexTransactionFee = getHexGasTotal({ gasLimit, gasPrice });
|
const hexMinimumTransactionFee = getMinimumGasTotalInHexWei(
|
||||||
|
gasEstimationObject,
|
||||||
|
);
|
||||||
|
const hexMaximumTransactionFee = getMaximumGasTotalInHexWei(
|
||||||
|
gasEstimationObject,
|
||||||
|
);
|
||||||
|
|
||||||
const fiatTransactionFee = getTransactionFee({
|
const fiatMinimumTransactionFee = getTransactionFee({
|
||||||
value: hexTransactionFee,
|
value: hexMinimumTransactionFee,
|
||||||
fromCurrency: nativeCurrency,
|
fromCurrency: nativeCurrency,
|
||||||
toCurrency: currentCurrency,
|
toCurrency: currentCurrency,
|
||||||
numberOfDecimals: 2,
|
numberOfDecimals: 2,
|
||||||
conversionRate,
|
conversionRate,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const fiatMaximumTransactionFee = getTransactionFee({
|
||||||
|
value: hexMaximumTransactionFee,
|
||||||
|
fromCurrency: nativeCurrency,
|
||||||
|
toCurrency: currentCurrency,
|
||||||
|
numberOfDecimals: 2,
|
||||||
|
conversionRate,
|
||||||
|
});
|
||||||
|
|
||||||
const ethTransactionFee = getTransactionFee({
|
const ethTransactionFee = getTransactionFee({
|
||||||
value: hexTransactionFee,
|
value: hexMinimumTransactionFee,
|
||||||
fromCurrency: nativeCurrency,
|
fromCurrency: nativeCurrency,
|
||||||
toCurrency: nativeCurrency,
|
toCurrency: nativeCurrency,
|
||||||
numberOfDecimals: 6,
|
numberOfDecimals: 6,
|
||||||
@ -262,18 +326,20 @@ export const transactionFeeSelector = function (state, txData) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const fiatTransactionTotal = addFiat(
|
const fiatTransactionTotal = addFiat(
|
||||||
fiatTransactionFee,
|
fiatMinimumTransactionFee,
|
||||||
fiatTransactionAmount,
|
fiatTransactionAmount,
|
||||||
);
|
);
|
||||||
const ethTransactionTotal = addEth(ethTransactionFee, ethTransactionAmount);
|
const ethTransactionTotal = addEth(ethTransactionFee, ethTransactionAmount);
|
||||||
const hexTransactionTotal = sumHexes(value, hexTransactionFee);
|
const hexTransactionTotal = sumHexes(value, hexMinimumTransactionFee);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hexTransactionAmount: value,
|
hexTransactionAmount: value,
|
||||||
fiatTransactionAmount,
|
fiatTransactionAmount,
|
||||||
ethTransactionAmount,
|
ethTransactionAmount,
|
||||||
hexTransactionFee,
|
hexMinimumTransactionFee,
|
||||||
fiatTransactionFee,
|
fiatMinimumTransactionFee,
|
||||||
|
hexMaximumTransactionFee,
|
||||||
|
fiatMaximumTransactionFee,
|
||||||
ethTransactionFee,
|
ethTransactionFee,
|
||||||
fiatTransactionTotal,
|
fiatTransactionTotal,
|
||||||
ethTransactionTotal,
|
ethTransactionTotal,
|
||||||
|
Loading…
Reference in New Issue
Block a user