diff --git a/app/scripts/controllers/incoming-transactions.js b/app/scripts/controllers/incoming-transactions.js index e5fc03543..4d13532fc 100644 --- a/app/scripts/controllers/incoming-transactions.js +++ b/app/scripts/controllers/incoming-transactions.js @@ -34,8 +34,10 @@ const fetchWithTimeout = getFetchWithTimeout(SECOND * 30); * @typedef {Object} EtherscanTransaction * @property {string} blockNumber - The number of the block this transaction was found in, in decimal * @property {string} from - The hex-prefixed address of the sender - * @property {string} gas - The gas limit, in decimal WEI - * @property {string} gasPrice - The gas price, in decimal WEI + * @property {string} gas - The gas limit, in decimal GWEI + * @property {string} [gasPrice] - The gas price, in decimal WEI + * @property {string} [maxFeePerGas] - The maximum fee per gas, inclusive of tip, in decimal WEI + * @property {string} [maxPriorityFeePerGas] - The maximum tip per gas in decimal WEI * @property {string} hash - The hex-prefixed transaction hash * @property {string} isError - Whether the transaction was confirmed or failed (0 for confirmed, 1 for failed) * @property {string} nonce - The transaction nonce, in decimal @@ -267,6 +269,25 @@ export default class IncomingTransactionsController { etherscanTransaction.isError === '0' ? TRANSACTION_STATUSES.CONFIRMED : TRANSACTION_STATUSES.FAILED; + const txParams = { + from: etherscanTransaction.from, + gas: bnToHex(new BN(etherscanTransaction.gas)), + nonce: bnToHex(new BN(etherscanTransaction.nonce)), + to: etherscanTransaction.to, + value: bnToHex(new BN(etherscanTransaction.value)), + }; + + if (etherscanTransaction.gasPrice) { + txParams.gasPrice = bnToHex(new BN(etherscanTransaction.gasPrice)); + } else if (etherscanTransaction.maxFeePerGas) { + txParams.maxFeePerGas = bnToHex( + new BN(etherscanTransaction.maxFeePerGas), + ); + txParams.maxPriorityFeePerGas = bnToHex( + new BN(etherscanTransaction.maxPriorityFeePerGas), + ); + } + return { blockNumber: etherscanTransaction.blockNumber, id: createId(), @@ -274,14 +295,7 @@ export default class IncomingTransactionsController { metamaskNetworkId: CHAIN_ID_TO_NETWORK_ID_MAP[chainId], status, time, - txParams: { - from: etherscanTransaction.from, - gas: bnToHex(new BN(etherscanTransaction.gas)), - gasPrice: bnToHex(new BN(etherscanTransaction.gasPrice)), - nonce: bnToHex(new BN(etherscanTransaction.nonce)), - to: etherscanTransaction.to, - value: bnToHex(new BN(etherscanTransaction.value)), - }, + txParams, hash: etherscanTransaction.hash, type: TRANSACTION_TYPES.INCOMING, }; diff --git a/app/scripts/controllers/incoming-transactions.test.js b/app/scripts/controllers/incoming-transactions.test.js index f4cac0309..70b511ace 100644 --- a/app/scripts/controllers/incoming-transactions.test.js +++ b/app/scripts/controllers/incoming-transactions.test.js @@ -103,15 +103,34 @@ function getMockBlockTracker() { /** * Returns a transaction object matching the expected format returned * by the Etherscan API - * - * @param {string} [toAddress] - The hex-prefixed address of the recipient - * @param {number} [blockNumber] - The block number for the transaction + * @param {Object} [params] - options bag + * @param {string} [params.toAddress] - The hex-prefixed address of the recipient + * @param {number} [params.blockNumber] - The block number for the transaction + * @param {boolean} [params.useEIP1559] - Use EIP-1559 gas fields + * @param * @returns {EtherscanTransaction} */ -const getFakeEtherscanTransaction = ( +const getFakeEtherscanTransaction = ({ toAddress = MOCK_SELECTED_ADDRESS, blockNumber = 10, -) => { + useEIP1559 = false, + hash = '0xfake', +} = {}) => { + if (useEIP1559) { + return { + blockNumber: blockNumber.toString(), + from: '0xfake', + gas: '0', + maxFeePerGas: '10', + maxPriorityFeePerGas: '1', + hash, + isError: '0', + nonce: '100', + timeStamp: '16000000000000', + to: toAddress, + value: '0', + }; + } return { blockNumber: blockNumber.toString(), from: '0xfake', @@ -243,7 +262,13 @@ describe('IncomingTransactionsController', function () { 200, JSON.stringify({ status: '1', - result: [getFakeEtherscanTransaction()], + result: [ + getFakeEtherscanTransaction(), + getFakeEtherscanTransaction({ + hash: '0xfakeeip1559', + useEIP1559: true, + }), + ], }), ); const updateStateStub = sinon.stub( @@ -263,6 +288,9 @@ describe('IncomingTransactionsController', function () { const actualStateWithoutGenerated = cloneDeep(actualState); delete actualStateWithoutGenerated?.incomingTransactions?.['0xfake']?.id; + delete actualStateWithoutGenerated?.incomingTransactions?.[ + '0xfakeeip1559' + ]?.id; assert.ok( typeof generatedTxId === 'number' && generatedTxId > 0, @@ -290,6 +318,24 @@ describe('IncomingTransactionsController', function () { value: '0x0', }, }, + '0xfakeeip1559': { + blockNumber: '10', + hash: '0xfakeeip1559', + metamaskNetworkId: ROPSTEN_NETWORK_ID, + chainId: ROPSTEN_CHAIN_ID, + status: TRANSACTION_STATUSES.CONFIRMED, + time: 16000000000000000, + type: TRANSACTION_TYPES.INCOMING, + txParams: { + from: '0xfake', + gas: '0x0', + maxFeePerGas: '0xa', + maxPriorityFeePerGas: '0x1', + nonce: '0x64', + to: '0x0101', + value: '0x0', + }, + }, }, incomingTxLastFetchedBlockByChainId: { ...getNonEmptyInitState().incomingTxLastFetchedBlockByChainId, @@ -509,7 +555,11 @@ describe('IncomingTransactionsController', function () { 200, JSON.stringify({ status: '1', - result: [getFakeEtherscanTransaction(NEW_MOCK_SELECTED_ADDRESS)], + result: [ + getFakeEtherscanTransaction({ + toAddress: NEW_MOCK_SELECTED_ADDRESS, + }), + ], }), ); const updateStateStub = sinon.stub( @@ -586,7 +636,9 @@ describe('IncomingTransactionsController', function () { // reply with a valid request for any supported network, so that this test has every opportunity to fail nockEtherscanApiForAllChains({ status: '1', - result: [getFakeEtherscanTransaction(NEW_MOCK_SELECTED_ADDRESS)], + result: [ + getFakeEtherscanTransaction({ toAddress: NEW_MOCK_SELECTED_ADDRESS }), + ], }); const updateStateStub = sinon.stub( incomingTransactionsController.store, @@ -954,7 +1006,9 @@ describe('IncomingTransactionsController', function () { describe('_getNewIncomingTransactions', function () { const ADDRESS_TO_FETCH_FOR = '0xfakeaddress'; - const FETCHED_TX = getFakeEtherscanTransaction(ADDRESS_TO_FETCH_FOR); + const FETCHED_TX = getFakeEtherscanTransaction({ + toAddress: ADDRESS_TO_FETCH_FOR, + }); const mockFetch = sinon.stub().returns( Promise.resolve({ json: () => Promise.resolve({ status: '1', result: [FETCHED_TX] }), @@ -1212,5 +1266,53 @@ describe('IncomingTransactionsController', function () { type: TRANSACTION_TYPES.INCOMING, }); }); + + it('should return the expected data when the tx uses EIP-1559 fields', function () { + const incomingTransactionsController = new IncomingTransactionsController( + { + blockTracker: getMockBlockTracker(), + ...getMockNetworkControllerMethods(ROPSTEN_CHAIN_ID), + preferencesController: getMockPreferencesController(), + initState: getNonEmptyInitState(), + }, + ); + + const result = incomingTransactionsController._normalizeTxFromEtherscan( + { + timeStamp: '4444', + isError: '0', + blockNumber: 333, + from: '0xa', + gas: '11', + maxFeePerGas: '12', + maxPriorityFeePerGas: '1', + nonce: '13', + to: '0xe', + value: '15', + hash: '0xg', + }, + ROPSTEN_CHAIN_ID, + ); + + assert.deepStrictEqual(result, { + blockNumber: 333, + id: 54321, + metamaskNetworkId: ROPSTEN_NETWORK_ID, + chainId: ROPSTEN_CHAIN_ID, + status: TRANSACTION_STATUSES.CONFIRMED, + time: 4444000, + txParams: { + from: '0xa', + gas: '0xb', + maxFeePerGas: '0xc', + maxPriorityFeePerGas: '0x1', + nonce: '0xd', + to: '0xe', + value: '0xf', + }, + hash: '0xg', + type: TRANSACTION_TYPES.INCOMING, + }); + }); }); });