From d0774c8d8d4ad53c7dff8a8695d0e167c9fd3b74 Mon Sep 17 00:00:00 2001 From: Keith Broughton Date: Thu, 27 Feb 2025 11:14:00 +1100 Subject: [PATCH 01/17] Move test to the right place --- packages/passport/sdk/jest.setup.js | 11 +++- .../sdk/src/zkEvm/transactionHelpers.test.ts | 58 +++++++++++++------ .../passport/sdk/src/zkEvm/walletHelpers.ts | 7 +-- .../sdk/src/zkEvm/walletHelpersNoMock.test.ts | 11 ++++ 4 files changed, 63 insertions(+), 24 deletions(-) create mode 100644 packages/passport/sdk/src/zkEvm/walletHelpersNoMock.test.ts diff --git a/packages/passport/sdk/jest.setup.js b/packages/passport/sdk/jest.setup.js index 8e6b4b5927..e132c629ab 100644 --- a/packages/passport/sdk/jest.setup.js +++ b/packages/passport/sdk/jest.setup.js @@ -1,3 +1,12 @@ import { TextEncoder } from 'util'; -global.TextEncoder = TextEncoder; \ No newline at end of file +global.TextEncoder = TextEncoder; + +Object.defineProperty(Uint8Array, Symbol.hasInstance, { + value(potentialInstance) { + return this === Uint8Array + ? Object.prototype.toString.call(potentialInstance) === + '[object Uint8Array]' + : Uint8Array[Symbol.hasInstance].call(this, potentialInstance); + }, + }); \ No newline at end of file diff --git a/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts b/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts index 23803b3961..fef4d642ab 100644 --- a/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts +++ b/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts @@ -4,11 +4,14 @@ import { RelayerClient } from './relayerClient'; import GuardianClient from '../guardian'; import { FeeOption, MetaTransaction, RelayerTransactionStatus } from './types'; import { JsonRpcError, RpcErrorCode } from './JsonRpcError'; -import { pollRelayerTransaction, prepareAndSignTransaction } from './transactionHelpers'; +import { pollRelayerTransaction, prepareAndSignEjectionTransaction, prepareAndSignTransaction } from './transactionHelpers'; import * as walletHelpers from './walletHelpers'; import { retryWithDelay } from '../network/retry'; -jest.mock('./walletHelpers'); +jest.mock('./walletHelpers', () => ({ + __esModule: true, + ...jest.requireActual('./walletHelpers'), +})); jest.mock('../network/retry'); describe('transactionHelpers', () => { @@ -61,7 +64,7 @@ describe('transactionHelpers', () => { }); describe('prepareAndSignTransaction', () => { - const chainId = 123; + const chainId = 123n; const nonce = BigInt(5); const zkEvmAddress = '0x1234567890123456789012345678901234567890'; const transactionRequest = { @@ -108,15 +111,15 @@ describe('transactionHelpers', () => { beforeEach(() => { jest.resetAllMocks(); - (walletHelpers.getEip155ChainId as jest.Mock).mockReturnValue(`eip155:${chainId}`); - (walletHelpers.signMetaTransactions as jest.Mock).mockResolvedValue(signedTransactions); - (walletHelpers.getNonce as jest.Mock).mockResolvedValue(nonce); - (walletHelpers.getNormalisedTransactions as jest.Mock).mockReturnValue(metaTransactions); - (walletHelpers.encodedTransactions as jest.Mock).mockReturnValue('encodedTransactions123'); - (rpcProvider.getNetwork as jest.Mock).mockResolvedValue({ chainId }); - (relayerClient.imGetFeeOptions as jest.Mock).mockResolvedValue([imxFeeOption]); - (relayerClient.ethSendTransaction as jest.Mock).mockResolvedValue(relayerId); - (guardianClient.validateEVMTransaction as jest.Mock).mockResolvedValue(undefined); + jest.spyOn(walletHelpers, 'getEip155ChainId').mockReturnValue(`eip155:${chainId}`); + jest.spyOn(walletHelpers, 'signMetaTransactions').mockResolvedValue(signedTransactions); + jest.spyOn(walletHelpers, 'getNonce').mockResolvedValue(nonce); + jest.spyOn(walletHelpers, 'getNormalisedTransactions').mockReturnValue(metaTransactions as any); + jest.spyOn(walletHelpers, 'encodedTransactions').mockReturnValue('encodedTransactions123'); + jest.spyOn(rpcProvider, 'getNetwork').mockResolvedValue({ chainId } as any); + jest.spyOn(relayerClient, 'imGetFeeOptions').mockResolvedValue([imxFeeOption]); + jest.spyOn(relayerClient, 'ethSendTransaction').mockResolvedValue(relayerId); + jest.spyOn(guardianClient, 'validateEVMTransaction').mockResolvedValue(undefined); }); it('prepares and signs transaction correctly', async () => { @@ -284,26 +287,43 @@ describe('transactionHelpers', () => { flow, })).rejects.toThrow('Transaction send failed'); }); + }); + + describe('prepareAndSignEjectionTransaction', () => { + const chainId = 123; + + const transactionRequest = { + to: '0x1234567890123456789012345678901234567890', + data: '0x456', + value: '0x00', + chainId, + }; + + const zkEvmAddress = '0x1234567890123456789012345678901234567890'; + const ethSigner = {} as Signer; + const signedTransactions = 'signedTransactions123'; + + beforeEach(() => { + jest.resetAllMocks(); + jest.spyOn(walletHelpers, 'signMetaTransactions').mockResolvedValue(signedTransactions); + }); describe('when the nonce is 0', () => { it('prepares and signs transaction correctly', async () => { - const result = await prepareAndSignTransaction({ + const result = await prepareAndSignEjectionTransaction({ transactionRequest: { ...transactionRequest, nonce: 0, }, ethSigner, - rpcProvider, - guardianClient, - relayerClient, zkEvmAddress, flow, }); expect(result).toEqual({ - signedTransactions, - relayerId, - nonce, + chainId: 'eip155:123', + data: signedTransactions, + to: zkEvmAddress, }); }); }); diff --git a/packages/passport/sdk/src/zkEvm/walletHelpers.ts b/packages/passport/sdk/src/zkEvm/walletHelpers.ts index fdadf25894..16b2086615 100644 --- a/packages/passport/sdk/src/zkEvm/walletHelpers.ts +++ b/packages/passport/sdk/src/zkEvm/walletHelpers.ts @@ -89,12 +89,13 @@ export const getNonce = async ( rpcProvider, ); const space: bigint = coerceNonceSpace(nonceSpace); // Default nonce space is 0 - const result = await contract.readNonce(space); + const result = await contract.readNonce.staticCall(space); if (typeof result === 'bigint') { return encodeNonce(space, result); } + throw new Error('Unexpected result from contract.nonce() call.'); } catch (error) { - if (isError(error, 'UNKNOWN_ERROR') && isError(error.error, 'BAD_DATA')) { + if (isError(error, 'BAD_DATA')) { // The most likely reason for a BAD_DATA error is that the smart contract wallet // has not been deployed yet, so we should default to a nonce of 0. return BigInt(0); @@ -102,8 +103,6 @@ export const getNonce = async ( throw error; } - - throw new Error('Unexpected result from contract.nonce() call.'); }; export const encodeMessageSubDigest = (chainId: bigint, walletAddress: string, digest: string): string => ( diff --git a/packages/passport/sdk/src/zkEvm/walletHelpersNoMock.test.ts b/packages/passport/sdk/src/zkEvm/walletHelpersNoMock.test.ts new file mode 100644 index 0000000000..88e0322a4c --- /dev/null +++ b/packages/passport/sdk/src/zkEvm/walletHelpersNoMock.test.ts @@ -0,0 +1,11 @@ +import { JsonRpcProvider } from 'ethers'; +import { getNonce } from './walletHelpers'; + +describe('getNonce', () => { + it('should return the nonce for a smart contract wallet', async () => { + const rpcProvider = new JsonRpcProvider('https://rpc.testnet.immutable.com'); + const smartContractWalletAddress = '0x41da14174e0bc2c3f9358c0d0409092bbaa5c70d'; + const nonce = await getNonce(rpcProvider, smartContractWalletAddress); + expect(nonce).toBe(0n); + }, 10000); +}); From 926d7edd282f0ae5190ebb5a34b55d38e79c948a Mon Sep 17 00:00:00 2001 From: Keith Broughton Date: Thu, 27 Feb 2025 11:19:26 +1100 Subject: [PATCH 02/17] Fix the bug --- .../sdk/src/zkEvm/transactionHelpers.test.ts | 21 ++++++++++++++++++- .../sdk/src/zkEvm/transactionHelpers.ts | 4 ++-- packages/passport/sdk/src/zkEvm/types.ts | 2 +- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts b/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts index fef4d642ab..eb657c08df 100644 --- a/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts +++ b/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts @@ -111,7 +111,6 @@ describe('transactionHelpers', () => { beforeEach(() => { jest.resetAllMocks(); - jest.spyOn(walletHelpers, 'getEip155ChainId').mockReturnValue(`eip155:${chainId}`); jest.spyOn(walletHelpers, 'signMetaTransactions').mockResolvedValue(signedTransactions); jest.spyOn(walletHelpers, 'getNonce').mockResolvedValue(nonce); jest.spyOn(walletHelpers, 'getNormalisedTransactions').mockReturnValue(metaTransactions as any); @@ -242,6 +241,26 @@ describe('transactionHelpers', () => { ); }); + it('signs the transaction when the nonce is zero', async () => { + jest.spyOn(walletHelpers, 'getNonce').mockResolvedValue(0n); + + const result = await prepareAndSignTransaction({ + transactionRequest, + ethSigner, + rpcProvider, + guardianClient, + relayerClient, + zkEvmAddress, + flow, + }); + + expect(result).toEqual({ + signedTransactions, + relayerId, + nonce: 0n, + }); + }); + it('throws an error when validateEVMTransaction fails', async () => { (guardianClient.validateEVMTransaction as jest.Mock).mockRejectedValue(new Error('Validation failed')); diff --git a/packages/passport/sdk/src/zkEvm/transactionHelpers.ts b/packages/passport/sdk/src/zkEvm/transactionHelpers.ts index 6fcda98e52..d607da569a 100644 --- a/packages/passport/sdk/src/zkEvm/transactionHelpers.ts +++ b/packages/passport/sdk/src/zkEvm/transactionHelpers.ts @@ -187,7 +187,7 @@ export const prepareAndSignTransaction = async ({ flow.addEvent('endBuildMetaTransactions'); const { nonce } = metaTransactions[0]; - if (!nonce) { + if (typeof nonce === 'undefined') { throw new Error('Failed to retrieve nonce from the smart wallet'); } @@ -255,7 +255,7 @@ const buildMetaTransactionForEjection = async ( const metaTransaction: MetaTransaction = { to: transactionRequest.to.toString(), data: transactionRequest.data, - nonce: transactionRequest.nonce, + nonce: transactionRequest.nonce ?? undefined, value: transactionRequest.value, revertOnError: true, }; diff --git a/packages/passport/sdk/src/zkEvm/types.ts b/packages/passport/sdk/src/zkEvm/types.ts index 6a9ac86c72..b013685712 100644 --- a/packages/passport/sdk/src/zkEvm/types.ts +++ b/packages/passport/sdk/src/zkEvm/types.ts @@ -30,7 +30,7 @@ export interface MetaTransaction { to: string; value?: BigNumberish | null; data?: string | null; - nonce?: BigNumberish | null; + nonce?: BigNumberish; gasLimit?: BigNumberish; delegateCall?: boolean; revertOnError?: boolean; From 7e318b7de36b40e6925e08766495a0f656adbdf7 Mon Sep 17 00:00:00 2001 From: Keith Broughton Date: Thu, 27 Feb 2025 11:22:21 +1100 Subject: [PATCH 03/17] Add comment --- packages/passport/sdk/jest.setup.js | 5 ++++- .../sdk/src/zkEvm/walletHelpersNoMock.test.ts | 11 ----------- 2 files changed, 4 insertions(+), 12 deletions(-) delete mode 100644 packages/passport/sdk/src/zkEvm/walletHelpersNoMock.test.ts diff --git a/packages/passport/sdk/jest.setup.js b/packages/passport/sdk/jest.setup.js index e132c629ab..3e9ae4d55a 100644 --- a/packages/passport/sdk/jest.setup.js +++ b/packages/passport/sdk/jest.setup.js @@ -2,6 +2,9 @@ import { TextEncoder } from 'util'; global.TextEncoder = TextEncoder; +/** + * @see https://github.com/ethers-io/ethers.js/issues/4365 + */ Object.defineProperty(Uint8Array, Symbol.hasInstance, { value(potentialInstance) { return this === Uint8Array @@ -9,4 +12,4 @@ Object.defineProperty(Uint8Array, Symbol.hasInstance, { '[object Uint8Array]' : Uint8Array[Symbol.hasInstance].call(this, potentialInstance); }, - }); \ No newline at end of file + }); diff --git a/packages/passport/sdk/src/zkEvm/walletHelpersNoMock.test.ts b/packages/passport/sdk/src/zkEvm/walletHelpersNoMock.test.ts deleted file mode 100644 index 88e0322a4c..0000000000 --- a/packages/passport/sdk/src/zkEvm/walletHelpersNoMock.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { JsonRpcProvider } from 'ethers'; -import { getNonce } from './walletHelpers'; - -describe('getNonce', () => { - it('should return the nonce for a smart contract wallet', async () => { - const rpcProvider = new JsonRpcProvider('https://rpc.testnet.immutable.com'); - const smartContractWalletAddress = '0x41da14174e0bc2c3f9358c0d0409092bbaa5c70d'; - const nonce = await getNonce(rpcProvider, smartContractWalletAddress); - expect(nonce).toBe(0n); - }, 10000); -}); From bc24a1ce3b9237f837c9dcc8c1be6c3000602ba4 Mon Sep 17 00:00:00 2001 From: Keith Broughton Date: Thu, 27 Feb 2025 11:22:43 +1100 Subject: [PATCH 04/17] Add comment --- packages/passport/sdk/jest.setup.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/passport/sdk/jest.setup.js b/packages/passport/sdk/jest.setup.js index 3e9ae4d55a..2e10cd9913 100644 --- a/packages/passport/sdk/jest.setup.js +++ b/packages/passport/sdk/jest.setup.js @@ -3,6 +3,7 @@ import { TextEncoder } from 'util'; global.TextEncoder = TextEncoder; /** + * Required for ethers v6 * @see https://github.com/ethers-io/ethers.js/issues/4365 */ Object.defineProperty(Uint8Array, Symbol.hasInstance, { From 2d6494057c46f02ce0008dd9100fcc33842ae4ed Mon Sep 17 00:00:00 2001 From: Keith Broughton Date: Thu, 27 Feb 2025 11:32:02 +1100 Subject: [PATCH 05/17] Fix tests --- packages/passport/sdk/src/zkEvm/walletHelpers.test.ts | 3 +-- packages/passport/sdk/src/zkEvm/walletHelpers.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/passport/sdk/src/zkEvm/walletHelpers.test.ts b/packages/passport/sdk/src/zkEvm/walletHelpers.test.ts index 752187b64a..375650eff2 100644 --- a/packages/passport/sdk/src/zkEvm/walletHelpers.test.ts +++ b/packages/passport/sdk/src/zkEvm/walletHelpers.test.ts @@ -157,7 +157,6 @@ describe('getNonce', () => { beforeEach(() => { jest.resetAllMocks(); (Contract as unknown as jest.Mock).mockImplementation(() => ({ - nonce: nonceMock, readNonce: nonceMock, })); }); @@ -165,7 +164,7 @@ describe('getNonce', () => { describe('when an error is thrown', () => { describe('and the error is BAD_DATA', () => { it('should return 0', async () => { - const error = { code: 'UNKNOWN_ERROR', error: { code: 'BAD_DATA' } }; + const error = { code: 'BAD_DATA' }; nonceMock.mockRejectedValue(error); diff --git a/packages/passport/sdk/src/zkEvm/walletHelpers.ts b/packages/passport/sdk/src/zkEvm/walletHelpers.ts index 16b2086615..da5a9dcdc3 100644 --- a/packages/passport/sdk/src/zkEvm/walletHelpers.ts +++ b/packages/passport/sdk/src/zkEvm/walletHelpers.ts @@ -89,7 +89,7 @@ export const getNonce = async ( rpcProvider, ); const space: bigint = coerceNonceSpace(nonceSpace); // Default nonce space is 0 - const result = await contract.readNonce.staticCall(space); + const result = await contract.readNonce(space); if (typeof result === 'bigint') { return encodeNonce(space, result); } From b45464c6bff52f514042d75e95691a477484b0c6 Mon Sep 17 00:00:00 2001 From: Keith Broughton Date: Thu, 27 Feb 2025 12:10:17 +1100 Subject: [PATCH 06/17] NOJIRA: Try refactoring the nonce handling --- .../passport/sdk/src/zkEvm/transactionHelpers.ts | 16 +++++----------- packages/passport/sdk/src/zkEvm/types.ts | 2 +- .../passport/sdk/src/zkEvm/walletHelpers.test.ts | 3 +-- packages/passport/sdk/src/zkEvm/walletHelpers.ts | 2 +- 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/packages/passport/sdk/src/zkEvm/transactionHelpers.ts b/packages/passport/sdk/src/zkEvm/transactionHelpers.ts index d607da569a..dcb3b09c61 100644 --- a/packages/passport/sdk/src/zkEvm/transactionHelpers.ts +++ b/packages/passport/sdk/src/zkEvm/transactionHelpers.ts @@ -1,7 +1,6 @@ import { Flow } from '@imtbl/metrics'; import { Signer, TransactionRequest, JsonRpcProvider, - BigNumberish, } from 'ethers'; import { getEip155ChainId, @@ -187,9 +186,6 @@ export const prepareAndSignTransaction = async ({ flow.addEvent('endBuildMetaTransactions'); const { nonce } = metaTransactions[0]; - if (typeof nonce === 'undefined') { - throw new Error('Failed to retrieve nonce from the smart wallet'); - } // Parallelize the validation and signing of the transaction // without waiting for the validation to complete @@ -208,7 +204,6 @@ export const prepareAndSignTransaction = async ({ const signTransaction = async () => { const signed = await signMetaTransactions( metaTransactions, - nonce, chainIdBigNumber, zkEvmAddress, ethSigner, @@ -230,7 +225,7 @@ export const prepareAndSignTransaction = async ({ const buildMetaTransactionForEjection = async ( transactionRequest: TransactionRequest, -): Promise<[MetaTransaction, ...MetaTransaction[]]> => { +): Promise => { if (!transactionRequest.to) { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, @@ -238,7 +233,7 @@ const buildMetaTransactionForEjection = async ( ); } - if (typeof transactionRequest.nonce === 'undefined') { + if (typeof transactionRequest.nonce === 'undefined' || transactionRequest.nonce === null) { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, 'im_signEjectionTransaction requires a "nonce" field', @@ -255,12 +250,12 @@ const buildMetaTransactionForEjection = async ( const metaTransaction: MetaTransaction = { to: transactionRequest.to.toString(), data: transactionRequest.data, - nonce: transactionRequest.nonce ?? undefined, + nonce: transactionRequest.nonce, value: transactionRequest.value, revertOnError: true, }; - return [metaTransaction]; + return metaTransaction; }; export const prepareAndSignEjectionTransaction = async ({ @@ -275,8 +270,7 @@ export const prepareAndSignEjectionTransaction = async ({ flow.addEvent('endBuildMetaTransactions'); const signedTransaction = await signMetaTransactions( - metaTransaction, - transactionRequest.nonce as BigNumberish, + [metaTransaction], BigInt(transactionRequest.chainId ?? 0), zkEvmAddress, ethSigner, diff --git a/packages/passport/sdk/src/zkEvm/types.ts b/packages/passport/sdk/src/zkEvm/types.ts index b013685712..3904321638 100644 --- a/packages/passport/sdk/src/zkEvm/types.ts +++ b/packages/passport/sdk/src/zkEvm/types.ts @@ -30,7 +30,7 @@ export interface MetaTransaction { to: string; value?: BigNumberish | null; data?: string | null; - nonce?: BigNumberish; + nonce: BigNumberish; gasLimit?: BigNumberish; delegateCall?: boolean; revertOnError?: boolean; diff --git a/packages/passport/sdk/src/zkEvm/walletHelpers.test.ts b/packages/passport/sdk/src/zkEvm/walletHelpers.test.ts index 375650eff2..73688eb942 100644 --- a/packages/passport/sdk/src/zkEvm/walletHelpers.test.ts +++ b/packages/passport/sdk/src/zkEvm/walletHelpers.test.ts @@ -29,14 +29,13 @@ describe('signMetaTransactions', () => { to: '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', value: '500000000000000000', data: '0x', + nonce: 0, }, ]; - const nonce = 0; const chainId = 1779; const signature = await signMetaTransactions( transactions, - nonce, BigInt(chainId), walletAddress, signer, diff --git a/packages/passport/sdk/src/zkEvm/walletHelpers.ts b/packages/passport/sdk/src/zkEvm/walletHelpers.ts index da5a9dcdc3..866352a34e 100644 --- a/packages/passport/sdk/src/zkEvm/walletHelpers.ts +++ b/packages/passport/sdk/src/zkEvm/walletHelpers.ts @@ -114,12 +114,12 @@ export const encodeMessageSubDigest = (chainId: bigint, walletAddress: string, d export const signMetaTransactions = async ( metaTransactions: MetaTransaction[], - nonce: BigNumberish, chainId: bigint, walletAddress: string, signer: Signer, ): Promise => { const normalisedMetaTransactions = getNormalisedTransactions(metaTransactions); + const { nonce } = metaTransactions[0]; // Get the hash const digest = digestOfTransactionsAndNonce(nonce, normalisedMetaTransactions); From 19b37ddd357e8ac05fdc320857f02377483cd3e4 Mon Sep 17 00:00:00 2001 From: Keith Broughton Date: Thu, 27 Feb 2025 12:36:00 +1100 Subject: [PATCH 07/17] Fix test --- packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts b/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts index eb657c08df..f1229e3e92 100644 --- a/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts +++ b/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts @@ -234,8 +234,7 @@ describe('transactionHelpers', () => { nonce: expect.any(BigInt), }), ]), - expect.any(BigInt), - expect.any(BigInt), + chainId, zkEvmAddress, ethSigner, ); From cc400cd9eba4412712d03733b1a8936cd431e6a0 Mon Sep 17 00:00:00 2001 From: Keith Broughton Date: Thu, 27 Feb 2025 12:38:04 +1100 Subject: [PATCH 08/17] Small fix --- packages/passport/sdk/src/zkEvm/transactionHelpers.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/passport/sdk/src/zkEvm/transactionHelpers.ts b/packages/passport/sdk/src/zkEvm/transactionHelpers.ts index dcb3b09c61..48bcb681fc 100644 --- a/packages/passport/sdk/src/zkEvm/transactionHelpers.ts +++ b/packages/passport/sdk/src/zkEvm/transactionHelpers.ts @@ -173,7 +173,6 @@ export const prepareAndSignTransaction = async ({ isBackgroundTransaction, }: TransactionParams & { transactionRequest: TransactionRequest }) => { const { chainId } = await rpcProvider.getNetwork(); - const chainIdBigNumber = BigInt(chainId); flow.addEvent('endDetectNetwork'); const metaTransactions = await buildMetaTransactions( @@ -204,7 +203,7 @@ export const prepareAndSignTransaction = async ({ const signTransaction = async () => { const signed = await signMetaTransactions( metaTransactions, - chainIdBigNumber, + chainId, zkEvmAddress, ethSigner, ); From 0f1e7c22b3d90b1bfb305cd783d8db97df7f9507 Mon Sep 17 00:00:00 2001 From: Keith Broughton Date: Thu, 27 Feb 2025 12:38:39 +1100 Subject: [PATCH 09/17] Fix type --- packages/passport/sdk/src/zkEvm/transactionHelpers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/passport/sdk/src/zkEvm/transactionHelpers.ts b/packages/passport/sdk/src/zkEvm/transactionHelpers.ts index 48bcb681fc..8397c71e90 100644 --- a/packages/passport/sdk/src/zkEvm/transactionHelpers.ts +++ b/packages/passport/sdk/src/zkEvm/transactionHelpers.ts @@ -74,7 +74,7 @@ const buildMetaTransactions = async ( relayerClient: RelayerClient, zkevmAddress: string, nonceSpace?: bigint, -): Promise<[MetaTransaction, ...MetaTransaction[]]> => { +): Promise => { if (!transactionRequest.to) { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, @@ -97,7 +97,7 @@ const buildMetaTransactions = async ( ]); // Build the meta transactions array with a valid nonce and fee transaction - const metaTransactions: [MetaTransaction, ...MetaTransaction[]] = [ + const metaTransactions: MetaTransaction[] = [ { ...metaTransaction, nonce, From dd0e2de6bf74b73899f84a97600a24c1a5a6003c Mon Sep 17 00:00:00 2001 From: Keith Broughton Date: Thu, 27 Feb 2025 12:57:00 +1100 Subject: [PATCH 10/17] Just testing --- .../sdk/src/zkEvm/transactionHelpers.test.ts | 1 - .../sdk/src/zkEvm/transactionHelpers.ts | 17 +++-------------- packages/passport/sdk/src/zkEvm/types.ts | 1 - 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts b/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts index f1229e3e92..455268259c 100644 --- a/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts +++ b/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts @@ -77,7 +77,6 @@ describe('transactionHelpers', () => { { to: transactionRequest.to, data: transactionRequest.data, - nonce, value: transactionRequest.value, revertOnError: true, }, diff --git a/packages/passport/sdk/src/zkEvm/transactionHelpers.ts b/packages/passport/sdk/src/zkEvm/transactionHelpers.ts index 8397c71e90..774cd7b173 100644 --- a/packages/passport/sdk/src/zkEvm/transactionHelpers.ts +++ b/packages/passport/sdk/src/zkEvm/transactionHelpers.ts @@ -70,10 +70,8 @@ const getFeeOption = async ( */ const buildMetaTransactions = async ( transactionRequest: TransactionRequest, - rpcProvider: JsonRpcProvider, relayerClient: RelayerClient, zkevmAddress: string, - nonceSpace?: bigint, ): Promise => { if (!transactionRequest.to) { throw new JsonRpcError( @@ -85,30 +83,24 @@ const buildMetaTransactions = async ( const metaTransaction: MetaTransaction = { to: transactionRequest.to.toString(), data: transactionRequest.data, - nonce: BigInt(0), // NOTE: We don't need a valid nonce to estimate the fee value: transactionRequest.value, revertOnError: true, }; // Estimate the fee and get the nonce from the smart wallet - const [nonce, feeOption] = await Promise.all([ - getNonce(rpcProvider, zkevmAddress, nonceSpace), + const [feeOption] = await Promise.all([ getFeeOption(metaTransaction, zkevmAddress, relayerClient), ]); // Build the meta transactions array with a valid nonce and fee transaction const metaTransactions: MetaTransaction[] = [ - { - ...metaTransaction, - nonce, - }, + metaTransaction, ]; // Add a fee transaction if the fee is non-zero const feeValue = BigInt(feeOption.tokenPrice); if (feeValue !== BigInt(0)) { metaTransactions.push({ - nonce, to: feeOption.recipientAddress, value: feeValue, revertOnError: true, @@ -177,14 +169,12 @@ export const prepareAndSignTransaction = async ({ const metaTransactions = await buildMetaTransactions( transactionRequest, - rpcProvider, relayerClient, zkEvmAddress, - nonceSpace, ); flow.addEvent('endBuildMetaTransactions'); - const { nonce } = metaTransactions[0]; + const nonce = await getNonce(rpcProvider, zkEvmAddress, nonceSpace); // Parallelize the validation and signing of the transaction // without waiting for the validation to complete @@ -249,7 +239,6 @@ const buildMetaTransactionForEjection = async ( const metaTransaction: MetaTransaction = { to: transactionRequest.to.toString(), data: transactionRequest.data, - nonce: transactionRequest.nonce, value: transactionRequest.value, revertOnError: true, }; diff --git a/packages/passport/sdk/src/zkEvm/types.ts b/packages/passport/sdk/src/zkEvm/types.ts index 3904321638..b29c0699ff 100644 --- a/packages/passport/sdk/src/zkEvm/types.ts +++ b/packages/passport/sdk/src/zkEvm/types.ts @@ -30,7 +30,6 @@ export interface MetaTransaction { to: string; value?: BigNumberish | null; data?: string | null; - nonce: BigNumberish; gasLimit?: BigNumberish; delegateCall?: boolean; revertOnError?: boolean; From 17a074fd0aab02bd6c3dc9f8f6ddbade8c5524f7 Mon Sep 17 00:00:00 2001 From: Keith Broughton Date: Thu, 27 Feb 2025 13:02:44 +1100 Subject: [PATCH 11/17] . --- .../sdk/src/zkEvm/transactionHelpers.ts | 30 +++++++++++++++++-- .../sdk/src/zkEvm/walletHelpers.test.ts | 7 +++-- .../passport/sdk/src/zkEvm/walletHelpers.ts | 2 +- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/packages/passport/sdk/src/zkEvm/transactionHelpers.ts b/packages/passport/sdk/src/zkEvm/transactionHelpers.ts index 774cd7b173..2dd538c5b0 100644 --- a/packages/passport/sdk/src/zkEvm/transactionHelpers.ts +++ b/packages/passport/sdk/src/zkEvm/transactionHelpers.ts @@ -10,7 +10,7 @@ import { getNonce, } from './walletHelpers'; import { RelayerClient } from './relayerClient'; -import GuardianClient, { convertBigNumberishToString } from '../guardian'; +import GuardianClient from '../guardian'; import { FeeOption, MetaTransaction, @@ -181,7 +181,7 @@ export const prepareAndSignTransaction = async ({ const validateTransaction = async () => { await guardianClient.validateEVMTransaction({ chainId: getEip155ChainId(Number(chainId)), - nonce: convertBigNumberishToString(nonce), + nonce: nonce.toString(), metaTransactions, isBackgroundTransaction, }); @@ -193,6 +193,7 @@ export const prepareAndSignTransaction = async ({ const signTransaction = async () => { const signed = await signMetaTransactions( metaTransactions, + nonce, chainId, zkEvmAddress, ethSigner, @@ -246,12 +247,34 @@ const buildMetaTransactionForEjection = async ( return metaTransaction; }; +const parseNonce = (transactionRequest: TransactionRequest): bigint => { + if (typeof transactionRequest.nonce === 'undefined' || transactionRequest.nonce === null) { + throw new JsonRpcError( + RpcErrorCode.INVALID_PARAMS, + 'im_signEjectionTransaction requires a "nonce" field', + ); + } + return BigInt(transactionRequest.nonce); +}; + +const parseChainId = (transactionRequest: TransactionRequest): bigint => { + if (typeof transactionRequest.chainId === 'undefined' || transactionRequest.chainId === null) { + throw new JsonRpcError( + RpcErrorCode.INVALID_PARAMS, + 'im_signEjectionTransaction requires a "chainId" field', + ); + } + return BigInt(transactionRequest.chainId); +}; + export const prepareAndSignEjectionTransaction = async ({ transactionRequest, ethSigner, zkEvmAddress, flow, }: EjectionTransactionParams & { transactionRequest: TransactionRequest }): Promise => { + const nonce = parseNonce(transactionRequest); + const chainId = parseChainId(transactionRequest); const metaTransaction = await buildMetaTransactionForEjection( transactionRequest, ); @@ -259,7 +282,8 @@ export const prepareAndSignEjectionTransaction = async ({ const signedTransaction = await signMetaTransactions( [metaTransaction], - BigInt(transactionRequest.chainId ?? 0), + nonce, + chainId, zkEvmAddress, ethSigner, ); diff --git a/packages/passport/sdk/src/zkEvm/walletHelpers.test.ts b/packages/passport/sdk/src/zkEvm/walletHelpers.test.ts index 73688eb942..f49527aabb 100644 --- a/packages/passport/sdk/src/zkEvm/walletHelpers.test.ts +++ b/packages/passport/sdk/src/zkEvm/walletHelpers.test.ts @@ -29,14 +29,15 @@ describe('signMetaTransactions', () => { to: '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', value: '500000000000000000', data: '0x', - nonce: 0, }, ]; - const chainId = 1779; + const nonce = 0n; + const chainId = 1779n; const signature = await signMetaTransactions( transactions, - BigInt(chainId), + nonce, + chainId, walletAddress, signer, ); diff --git a/packages/passport/sdk/src/zkEvm/walletHelpers.ts b/packages/passport/sdk/src/zkEvm/walletHelpers.ts index 866352a34e..5d2c786ebc 100644 --- a/packages/passport/sdk/src/zkEvm/walletHelpers.ts +++ b/packages/passport/sdk/src/zkEvm/walletHelpers.ts @@ -114,12 +114,12 @@ export const encodeMessageSubDigest = (chainId: bigint, walletAddress: string, d export const signMetaTransactions = async ( metaTransactions: MetaTransaction[], + nonce: bigint, chainId: bigint, walletAddress: string, signer: Signer, ): Promise => { const normalisedMetaTransactions = getNormalisedTransactions(metaTransactions); - const { nonce } = metaTransactions[0]; // Get the hash const digest = digestOfTransactionsAndNonce(nonce, normalisedMetaTransactions); From 68454449e1fa4dab799c4342d470df65561b752d Mon Sep 17 00:00:00 2001 From: Keith Broughton Date: Thu, 27 Feb 2025 13:03:16 +1100 Subject: [PATCH 12/17] . --- .../passport/sdk/src/zkEvm/transactionHelpers.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/packages/passport/sdk/src/zkEvm/transactionHelpers.ts b/packages/passport/sdk/src/zkEvm/transactionHelpers.ts index 2dd538c5b0..5101746ef6 100644 --- a/packages/passport/sdk/src/zkEvm/transactionHelpers.ts +++ b/packages/passport/sdk/src/zkEvm/transactionHelpers.ts @@ -223,20 +223,6 @@ const buildMetaTransactionForEjection = async ( ); } - if (typeof transactionRequest.nonce === 'undefined' || transactionRequest.nonce === null) { - throw new JsonRpcError( - RpcErrorCode.INVALID_PARAMS, - 'im_signEjectionTransaction requires a "nonce" field', - ); - } - - if (!transactionRequest.chainId) { - throw new JsonRpcError( - RpcErrorCode.INVALID_PARAMS, - 'im_signEjectionTransaction requires a "chainId" field', - ); - } - const metaTransaction: MetaTransaction = { to: transactionRequest.to.toString(), data: transactionRequest.data, From c449213bfdaba5f9890c966a158102e5063d39a3 Mon Sep 17 00:00:00 2001 From: Keith Broughton Date: Thu, 27 Feb 2025 13:07:07 +1100 Subject: [PATCH 13/17] Fixes --- packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts b/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts index 455268259c..85b5d5e21d 100644 --- a/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts +++ b/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts @@ -170,7 +170,6 @@ describe('transactionHelpers', () => { revertOnError: true, to: transactionRequest.to, value: '0x00', - nonce: expect.any(BigInt), }), ]), }), @@ -205,13 +204,11 @@ describe('transactionHelpers', () => { revertOnError: true, to: transactionRequest.to, value: '0x00', - nonce: expect.any(BigInt), }), expect.objectContaining({ to: '0x7331', value: expect.any(BigInt), revertOnError: true, - nonce: expect.any(BigInt), }), ]), }), @@ -224,15 +221,14 @@ describe('transactionHelpers', () => { revertOnError: true, to: transactionRequest.to, value: '0x00', - nonce: expect.any(BigInt), }), expect.objectContaining({ to: '0x7331', value: expect.any(BigInt), revertOnError: true, - nonce: expect.any(BigInt), }), ]), + nonce, chainId, zkEvmAddress, ethSigner, From bad24667be0bffa81b6d8dbfcef3fce021b2a416 Mon Sep 17 00:00:00 2001 From: Keith Broughton Date: Thu, 27 Feb 2025 13:11:07 +1100 Subject: [PATCH 14/17] Fix --- packages/passport/sdk/src/guardian/index.test.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/passport/sdk/src/guardian/index.test.ts b/packages/passport/sdk/src/guardian/index.test.ts index f0aecd1168..9b73058fed 100644 --- a/packages/passport/sdk/src/guardian/index.test.ts +++ b/packages/passport/sdk/src/guardian/index.test.ts @@ -164,13 +164,11 @@ describe('Guardian', () => { revertOnError: true, to: mockUserZkEvm.zkEvm.ethAddress, value: '0x00', - nonce: 5, }, { revertOnError: true, to: '0x123', value: '0x', - nonce: 5, }, ], }), @@ -200,7 +198,6 @@ describe('Guardian', () => { revertOnError: true, to: mockUserZkEvm.zkEvm.ethAddress, value: '0x00', - nonce: 5, }, ], }); @@ -250,7 +247,6 @@ describe('Guardian', () => { revertOnError: true, to: mockUserZkEvm.zkEvm.ethAddress, value: '0x00', - nonce: 5, }, ], isBackgroundTransaction: true, @@ -302,7 +298,6 @@ describe('Guardian', () => { revertOnError: true, to: mockUserZkEvm.zkEvm.ethAddress, value: '0x00', - nonce: 1, }, ], isBackgroundTransaction: false, @@ -354,7 +349,6 @@ describe('Guardian', () => { revertOnError: true, to: mockUserZkEvm.zkEvm.ethAddress, value: '0x00', - nonce: 1, }, ], }); @@ -415,7 +409,6 @@ describe('Guardian', () => { revertOnError: true, to: mockUserZkEvm.zkEvm.ethAddress, value: '0x00', - nonce: 5, }, ], }); @@ -450,13 +443,11 @@ describe('Guardian', () => { revertOnError: true, to: mockUserZkEvm.zkEvm.ethAddress, value: '0x00', - nonce: 5, }, { revertOnError: true, to: '0x123', value: '0x00', - nonce: 5, }, ], }), From bcd0e8d2f111e6fcb19ad243a5a5acbf859d6b86 Mon Sep 17 00:00:00 2001 From: Keith Broughton Date: Thu, 27 Feb 2025 13:23:03 +1100 Subject: [PATCH 15/17] Parallel --- .../sdk/src/zkEvm/transactionHelpers.ts | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/packages/passport/sdk/src/zkEvm/transactionHelpers.ts b/packages/passport/sdk/src/zkEvm/transactionHelpers.ts index 5101746ef6..95470badfb 100644 --- a/packages/passport/sdk/src/zkEvm/transactionHelpers.ts +++ b/packages/passport/sdk/src/zkEvm/transactionHelpers.ts @@ -80,22 +80,15 @@ const buildMetaTransactions = async ( ); } - const metaTransaction: MetaTransaction = { + const metaTransactions: MetaTransaction[] = [{ to: transactionRequest.to.toString(), data: transactionRequest.data, value: transactionRequest.value, revertOnError: true, - }; + }]; // Estimate the fee and get the nonce from the smart wallet - const [feeOption] = await Promise.all([ - getFeeOption(metaTransaction, zkevmAddress, relayerClient), - ]); - - // Build the meta transactions array with a valid nonce and fee transaction - const metaTransactions: MetaTransaction[] = [ - metaTransaction, - ]; + const feeOption = await getFeeOption(metaTransactions[0], zkevmAddress, relayerClient); // Add a fee transaction if the fee is non-zero const feeValue = BigInt(feeOption.tokenPrice); @@ -164,23 +157,25 @@ export const prepareAndSignTransaction = async ({ nonceSpace, isBackgroundTransaction, }: TransactionParams & { transactionRequest: TransactionRequest }) => { - const { chainId } = await rpcProvider.getNetwork(); flow.addEvent('endDetectNetwork'); - const metaTransactions = await buildMetaTransactions( - transactionRequest, - relayerClient, - zkEvmAddress, - ); - flow.addEvent('endBuildMetaTransactions'); + const [metaTransactions, nonce, network] = await Promise.all([ + await buildMetaTransactions( + transactionRequest, + relayerClient, + zkEvmAddress, + ), + getNonce(rpcProvider, zkEvmAddress, nonceSpace), + rpcProvider.getNetwork(), + ]); - const nonce = await getNonce(rpcProvider, zkEvmAddress, nonceSpace); + flow.addEvent('endBuildMetaTransactions'); // Parallelize the validation and signing of the transaction // without waiting for the validation to complete const validateTransaction = async () => { await guardianClient.validateEVMTransaction({ - chainId: getEip155ChainId(Number(chainId)), + chainId: getEip155ChainId(Number(network.chainId)), nonce: nonce.toString(), metaTransactions, isBackgroundTransaction, @@ -194,7 +189,7 @@ export const prepareAndSignTransaction = async ({ const signed = await signMetaTransactions( metaTransactions, nonce, - chainId, + network.chainId, zkEvmAddress, ethSigner, ); From 7d7893f5c646e66315e0f41d9aaff395015c3709 Mon Sep 17 00:00:00 2001 From: Keith Broughton Date: Thu, 27 Feb 2025 13:25:40 +1100 Subject: [PATCH 16/17] Flows --- .../sdk/src/zkEvm/transactionHelpers.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/passport/sdk/src/zkEvm/transactionHelpers.ts b/packages/passport/sdk/src/zkEvm/transactionHelpers.ts index 95470badfb..fa02ff8308 100644 --- a/packages/passport/sdk/src/zkEvm/transactionHelpers.ts +++ b/packages/passport/sdk/src/zkEvm/transactionHelpers.ts @@ -72,6 +72,7 @@ const buildMetaTransactions = async ( transactionRequest: TransactionRequest, relayerClient: RelayerClient, zkevmAddress: string, + flow: Flow, ): Promise => { if (!transactionRequest.to) { throw new JsonRpcError( @@ -100,6 +101,7 @@ const buildMetaTransactions = async ( }); } + flow.addEvent('endBuildMetaTransactions'); return metaTransactions; }; @@ -146,6 +148,12 @@ export const pollRelayerTransaction = async ( return relayerTransaction; }; +const detectNetwork = async (rpcProvider: JsonRpcProvider, flow: Flow) => { + const network = await rpcProvider.getNetwork(); + flow.addEvent('endDetectNetwork'); + return network; +}; + export const prepareAndSignTransaction = async ({ transactionRequest, ethSigner, @@ -157,20 +165,17 @@ export const prepareAndSignTransaction = async ({ nonceSpace, isBackgroundTransaction, }: TransactionParams & { transactionRequest: TransactionRequest }) => { - flow.addEvent('endDetectNetwork'); - const [metaTransactions, nonce, network] = await Promise.all([ - await buildMetaTransactions( + buildMetaTransactions( transactionRequest, relayerClient, zkEvmAddress, + flow, ), getNonce(rpcProvider, zkEvmAddress, nonceSpace), - rpcProvider.getNetwork(), + detectNetwork(rpcProvider, flow), ]); - flow.addEvent('endBuildMetaTransactions'); - // Parallelize the validation and signing of the transaction // without waiting for the validation to complete const validateTransaction = async () => { From 553126402dac861acf264bb09051c034faff2e64 Mon Sep 17 00:00:00 2001 From: Keith Broughton Date: Thu, 27 Feb 2025 15:48:07 +1100 Subject: [PATCH 17/17] no exort --- packages/passport/sdk/src/guardian/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/passport/sdk/src/guardian/index.ts b/packages/passport/sdk/src/guardian/index.ts index d5fc6cebdf..2dbc07c5f5 100644 --- a/packages/passport/sdk/src/guardian/index.ts +++ b/packages/passport/sdk/src/guardian/index.ts @@ -41,7 +41,7 @@ type GuardianERC191MessageEvaluationParams = { const transactionRejectedCrossSdkBridgeError = 'Transaction requires confirmation but this functionality is not' + ' supported in this environment. Please contact Immutable support if you need to enable this feature.'; -export const convertBigNumberishToString = ( +const convertBigNumberishToString = ( value: BigNumberish, ): string => BigInt(value).toString();