diff --git a/package.json b/package.json index 10f2e72b..ade7ce8d 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,8 @@ "@ethereumjs/tx": "^5.2.1", "@ethereumjs/util": "^9.0.2", "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/transactions": "^5.7.0", "@metamask/base-controller": "^7.0.1", "@metamask/controller-utils": "^11.0.0", "@metamask/eth-json-rpc-provider": "^4.1.6", diff --git a/src/SmartTransactionsController.test.ts b/src/SmartTransactionsController.test.ts index b0e2bf91..1693560e 100644 --- a/src/SmartTransactionsController.test.ts +++ b/src/SmartTransactionsController.test.ts @@ -39,11 +39,6 @@ import type { SmartTransaction, UnsignedTransaction, Hex } from './types'; import { SmartTransactionStatuses, ClientId } from './types'; import * as utils from './utils'; -jest.mock('@ethersproject/bytes', () => ({ - ...jest.requireActual('@ethersproject/bytes'), - hexlify: (str: string) => `0x${str}`, -})); - jest.mock('@metamask/eth-query', () => { const EthQuery = jest.requireActual('@metamask/eth-query'); return class FakeEthQuery extends EthQuery { @@ -730,7 +725,7 @@ describe('SmartTransactionsController', () => { it('should acquire nonce for Swap transactions only', async () => { // Create a mock for getNonceLock const mockGetNonceLock = jest.fn().mockResolvedValue({ - nextNonce: 'nextNonce', + nextNonce: 42, nonceDetails: { test: 'details' }, releaseLock: jest.fn(), }); @@ -840,7 +835,7 @@ describe('SmartTransactionsController', () => { ][0]; // Verify nonce was set correctly on the txParams in the created transaction - expect(createdSmartTransaction.txParams.nonce).toBe('0x42'); // 42 as a hex string + expect(createdSmartTransaction.txParams.nonce).toBe('0x2a'); // 42 in hex // Verify transaction type is set to 'swap' by default expect(createdSmartTransaction.type).toBe('swap'); @@ -2652,7 +2647,7 @@ async function withController( messenger, clientId: ClientId.Mobile, getNonceLock: jest.fn().mockResolvedValue({ - nextNonce: 'nextNonce', + nextNonce: 42, releaseLock: jest.fn(), }), confirmExternalTransaction: jest.fn(), diff --git a/src/utils.test.ts b/src/utils.test.ts index 5d06b53f..42a8aa46 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -1,3 +1,5 @@ +import { arrayify, hexlify } from '@ethersproject/bytes'; +import { keccak256 } from '@ethersproject/keccak256'; import { ChainId, NetworkType } from '@metamask/controller-utils'; import { type TransactionMeta, @@ -372,7 +374,14 @@ describe('src/utils.js', () => { it('throws an error with an incorrect signed transaction', () => { expect(() => { utils.getTxHash('0x0302b75dfb9fd9eb34056af0'); - }).toThrow('kzg instance required to instantiate blob tx'); + }).toThrow('unsupported transaction type: 3'); + }); + + it('computes hash for type 4 transaction', () => { + const type4TxHex = '0x04010203040506070809'; + const expectedHash = hexlify(keccak256(arrayify(type4TxHex))); + const txHash = utils.getTxHash(type4TxHex); + expect(txHash).toBe(expectedHash); }); }); diff --git a/src/utils.ts b/src/utils.ts index 1fd34882..cabdc178 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,6 @@ -import { TransactionFactory } from '@ethereumjs/tx'; -import { bytesToHex } from '@ethereumjs/util'; -import { hexlify } from '@ethersproject/bytes'; +import { arrayify, hexlify } from '@ethersproject/bytes'; +import { keccak256 } from '@ethersproject/keccak256'; +import { parse } from '@ethersproject/transactions'; import type { TransactionMeta } from '@metamask/transaction-controller'; import { TransactionStatus } from '@metamask/transaction-controller'; import { BigNumber } from 'bignumber.js'; @@ -224,15 +224,23 @@ export const incrementNonceInHex = (nonceInHex: string): string => { return hexlify(Number(nonceInDec) + 1); }; +const isType4Transaction = (signedTxHex: string) => { + return typeof signedTxHex === 'string' && signedTxHex.startsWith('0x04'); +}; + export const getTxHash = (signedTxHex: any) => { if (!signedTxHex) { return ''; } - const txHashBytes = TransactionFactory.fromSerializedData( - // eslint-disable-next-line no-restricted-globals - Buffer.from(signedTxHex.slice(2), 'hex'), - ).hash(); - return bytesToHex(txHashBytes); + try { + const parsed = parse(signedTxHex); + return parsed?.hash ?? ''; + } catch (error) { + if (isType4Transaction(signedTxHex)) { + return hexlify(keccak256(arrayify(signedTxHex))); + } + throw error; + } }; export const getSmartTransactionMetricsProperties = ( diff --git a/yarn.lock b/yarn.lock index 5ecc57c3..d24e59ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -665,6 +665,15 @@ __metadata: languageName: node linkType: hard +"@ethersproject/bytes@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/bytes@npm:5.8.0" + dependencies: + "@ethersproject/logger": ^5.8.0 + checksum: 507e8ef1f1559590b4e78e3392a2b16090e96fb1091e0b08d3a8491df65976b313c29cdb412594454f68f9f04d5f77ea5a400b489d80a3e46a608156ef31b251 + languageName: node + linkType: hard + "@ethersproject/constants@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/constants@npm:5.7.0" @@ -719,6 +728,16 @@ __metadata: languageName: node linkType: hard +"@ethersproject/keccak256@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/keccak256@npm:5.8.0" + dependencies: + "@ethersproject/bytes": ^5.8.0 + js-sha3: 0.8.0 + checksum: af3621d2b18af6c8f5181dacad91e1f6da4e8a6065668b20e4c24684bdb130b31e45e0d4dbaed86d4f1314d01358aa119f05be541b696e455424c47849d81913 + languageName: node + linkType: hard + "@ethersproject/logger@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/logger@npm:5.7.0" @@ -726,6 +745,13 @@ __metadata: languageName: node linkType: hard +"@ethersproject/logger@npm:^5.8.0": + version: 5.8.0 + resolution: "@ethersproject/logger@npm:5.8.0" + checksum: 6249885a7fd4a5806e4c8700b76ffcc8f1ff00d71f31aa717716a89fa6b391de19fbb0cb5ae2560b9f57ec0c2e8e0a11ebc2099124c73d3b42bc58e3eedc41d1 + languageName: node + linkType: hard + "@ethersproject/networks@npm:^5.7.0": version: 5.7.1 resolution: "@ethersproject/networks@npm:5.7.1" @@ -1660,6 +1686,8 @@ __metadata: "@ethereumjs/tx": ^5.2.1 "@ethereumjs/util": ^9.0.2 "@ethersproject/bytes": ^5.7.0 + "@ethersproject/keccak256": ^5.8.0 + "@ethersproject/transactions": ^5.7.0 "@lavamoat/allow-scripts": ^3.2.1 "@lavamoat/preinstall-always-fail": ^2.1.0 "@metamask/auto-changelog": ^3.1.0