Skip to content

Commit a7af380

Browse files
authored
feat: Add support for type 4 transaction (#521)
1 parent 5df4775 commit a7af380

File tree

5 files changed

+59
-17
lines changed

5 files changed

+59
-17
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
"@ethereumjs/tx": "^5.2.1",
3131
"@ethereumjs/util": "^9.0.2",
3232
"@ethersproject/bytes": "^5.7.0",
33+
"@ethersproject/keccak256": "^5.8.0",
34+
"@ethersproject/transactions": "^5.7.0",
3335
"@metamask/base-controller": "^7.0.1",
3436
"@metamask/controller-utils": "^11.0.0",
3537
"@metamask/eth-json-rpc-provider": "^4.1.6",

src/SmartTransactionsController.test.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,6 @@ import type { SmartTransaction, UnsignedTransaction, Hex } from './types';
3939
import { SmartTransactionStatuses, ClientId } from './types';
4040
import * as utils from './utils';
4141

42-
jest.mock('@ethersproject/bytes', () => ({
43-
...jest.requireActual('@ethersproject/bytes'),
44-
hexlify: (str: string) => `0x${str}`,
45-
}));
46-
4742
jest.mock('@metamask/eth-query', () => {
4843
const EthQuery = jest.requireActual('@metamask/eth-query');
4944
return class FakeEthQuery extends EthQuery {
@@ -730,7 +725,7 @@ describe('SmartTransactionsController', () => {
730725
it('should acquire nonce for Swap transactions only', async () => {
731726
// Create a mock for getNonceLock
732727
const mockGetNonceLock = jest.fn().mockResolvedValue({
733-
nextNonce: 'nextNonce',
728+
nextNonce: 42,
734729
nonceDetails: { test: 'details' },
735730
releaseLock: jest.fn(),
736731
});
@@ -840,7 +835,7 @@ describe('SmartTransactionsController', () => {
840835
][0];
841836

842837
// Verify nonce was set correctly on the txParams in the created transaction
843-
expect(createdSmartTransaction.txParams.nonce).toBe('0x42'); // 42 as a hex string
838+
expect(createdSmartTransaction.txParams.nonce).toBe('0x2a'); // 42 in hex
844839

845840
// Verify transaction type is set to 'swap' by default
846841
expect(createdSmartTransaction.type).toBe('swap');
@@ -2652,7 +2647,7 @@ async function withController<ReturnValue>(
26522647
messenger,
26532648
clientId: ClientId.Mobile,
26542649
getNonceLock: jest.fn().mockResolvedValue({
2655-
nextNonce: 'nextNonce',
2650+
nextNonce: 42,
26562651
releaseLock: jest.fn(),
26572652
}),
26582653
confirmExternalTransaction: jest.fn(),

src/utils.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { arrayify, hexlify } from '@ethersproject/bytes';
2+
import { keccak256 } from '@ethersproject/keccak256';
13
import { ChainId, NetworkType } from '@metamask/controller-utils';
24
import {
35
type TransactionMeta,
@@ -372,7 +374,14 @@ describe('src/utils.js', () => {
372374
it('throws an error with an incorrect signed transaction', () => {
373375
expect(() => {
374376
utils.getTxHash('0x0302b75dfb9fd9eb34056af0');
375-
}).toThrow('kzg instance required to instantiate blob tx');
377+
}).toThrow('unsupported transaction type: 3');
378+
});
379+
380+
it('computes hash for type 4 transaction', () => {
381+
const type4TxHex = '0x04010203040506070809';
382+
const expectedHash = hexlify(keccak256(arrayify(type4TxHex)));
383+
const txHash = utils.getTxHash(type4TxHex);
384+
expect(txHash).toBe(expectedHash);
376385
});
377386
});
378387

src/utils.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { TransactionFactory } from '@ethereumjs/tx';
2-
import { bytesToHex } from '@ethereumjs/util';
3-
import { hexlify } from '@ethersproject/bytes';
1+
import { arrayify, hexlify } from '@ethersproject/bytes';
2+
import { keccak256 } from '@ethersproject/keccak256';
3+
import { parse } from '@ethersproject/transactions';
44
import type { TransactionMeta } from '@metamask/transaction-controller';
55
import { TransactionStatus } from '@metamask/transaction-controller';
66
import { BigNumber } from 'bignumber.js';
@@ -224,15 +224,23 @@ export const incrementNonceInHex = (nonceInHex: string): string => {
224224
return hexlify(Number(nonceInDec) + 1);
225225
};
226226

227+
const isType4Transaction = (signedTxHex: string) => {
228+
return typeof signedTxHex === 'string' && signedTxHex.startsWith('0x04');
229+
};
230+
227231
export const getTxHash = (signedTxHex: any) => {
228232
if (!signedTxHex) {
229233
return '';
230234
}
231-
const txHashBytes = TransactionFactory.fromSerializedData(
232-
// eslint-disable-next-line no-restricted-globals
233-
Buffer.from(signedTxHex.slice(2), 'hex'),
234-
).hash();
235-
return bytesToHex(txHashBytes);
235+
try {
236+
const parsed = parse(signedTxHex);
237+
return parsed?.hash ?? '';
238+
} catch (error) {
239+
if (isType4Transaction(signedTxHex)) {
240+
return hexlify(keccak256(arrayify(signedTxHex)));
241+
}
242+
throw error;
243+
}
236244
};
237245

238246
export const getSmartTransactionMetricsProperties = (

yarn.lock

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,15 @@ __metadata:
665665
languageName: node
666666
linkType: hard
667667

668+
"@ethersproject/bytes@npm:^5.8.0":
669+
version: 5.8.0
670+
resolution: "@ethersproject/bytes@npm:5.8.0"
671+
dependencies:
672+
"@ethersproject/logger": ^5.8.0
673+
checksum: 507e8ef1f1559590b4e78e3392a2b16090e96fb1091e0b08d3a8491df65976b313c29cdb412594454f68f9f04d5f77ea5a400b489d80a3e46a608156ef31b251
674+
languageName: node
675+
linkType: hard
676+
668677
"@ethersproject/constants@npm:^5.7.0":
669678
version: 5.7.0
670679
resolution: "@ethersproject/constants@npm:5.7.0"
@@ -719,13 +728,30 @@ __metadata:
719728
languageName: node
720729
linkType: hard
721730

731+
"@ethersproject/keccak256@npm:^5.8.0":
732+
version: 5.8.0
733+
resolution: "@ethersproject/keccak256@npm:5.8.0"
734+
dependencies:
735+
"@ethersproject/bytes": ^5.8.0
736+
js-sha3: 0.8.0
737+
checksum: af3621d2b18af6c8f5181dacad91e1f6da4e8a6065668b20e4c24684bdb130b31e45e0d4dbaed86d4f1314d01358aa119f05be541b696e455424c47849d81913
738+
languageName: node
739+
linkType: hard
740+
722741
"@ethersproject/logger@npm:^5.7.0":
723742
version: 5.7.0
724743
resolution: "@ethersproject/logger@npm:5.7.0"
725744
checksum: 075ab2f605f1fd0813f2e39c3308f77b44a67732b36e712d9bc085f22a84aac4da4f71b39bee50fe78da3e1c812673fadc41180c9970fe5e486e91ea17befe0d
726745
languageName: node
727746
linkType: hard
728747

748+
"@ethersproject/logger@npm:^5.8.0":
749+
version: 5.8.0
750+
resolution: "@ethersproject/logger@npm:5.8.0"
751+
checksum: 6249885a7fd4a5806e4c8700b76ffcc8f1ff00d71f31aa717716a89fa6b391de19fbb0cb5ae2560b9f57ec0c2e8e0a11ebc2099124c73d3b42bc58e3eedc41d1
752+
languageName: node
753+
linkType: hard
754+
729755
"@ethersproject/networks@npm:^5.7.0":
730756
version: 5.7.1
731757
resolution: "@ethersproject/networks@npm:5.7.1"
@@ -1660,6 +1686,8 @@ __metadata:
16601686
"@ethereumjs/tx": ^5.2.1
16611687
"@ethereumjs/util": ^9.0.2
16621688
"@ethersproject/bytes": ^5.7.0
1689+
"@ethersproject/keccak256": ^5.8.0
1690+
"@ethersproject/transactions": ^5.7.0
16631691
"@lavamoat/allow-scripts": ^3.2.1
16641692
"@lavamoat/preinstall-always-fail": ^2.1.0
16651693
"@metamask/auto-changelog": ^3.1.0

0 commit comments

Comments
 (0)