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, }, ], }), 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(); diff --git a/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts b/packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts index eb657c08df..85b5d5e21d 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, }, @@ -171,7 +170,6 @@ describe('transactionHelpers', () => { revertOnError: true, to: transactionRequest.to, value: '0x00', - nonce: expect.any(BigInt), }), ]), }), @@ -206,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), }), ]), }), @@ -225,17 +221,15 @@ 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), }), ]), - expect.any(BigInt), - expect.any(BigInt), + nonce, + chainId, zkEvmAddress, ethSigner, ); diff --git a/packages/passport/sdk/src/zkEvm/transactionHelpers.ts b/packages/passport/sdk/src/zkEvm/transactionHelpers.ts index d607da569a..fa02ff8308 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, @@ -11,7 +10,7 @@ import { getNonce, } from './walletHelpers'; import { RelayerClient } from './relayerClient'; -import GuardianClient, { convertBigNumberishToString } from '../guardian'; +import GuardianClient from '../guardian'; import { FeeOption, MetaTransaction, @@ -71,11 +70,10 @@ const getFeeOption = async ( */ const buildMetaTransactions = async ( transactionRequest: TransactionRequest, - rpcProvider: JsonRpcProvider, relayerClient: RelayerClient, zkevmAddress: string, - nonceSpace?: bigint, -): Promise<[MetaTransaction, ...MetaTransaction[]]> => { + flow: Flow, +): Promise => { if (!transactionRequest.to) { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, @@ -83,39 +81,27 @@ const buildMetaTransactions = async ( ); } - const metaTransaction: MetaTransaction = { + const metaTransactions: 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), - getFeeOption(metaTransaction, zkevmAddress, relayerClient), - ]); - - // Build the meta transactions array with a valid nonce and fee transaction - const metaTransactions: [MetaTransaction, ...MetaTransaction[]] = [ - { - ...metaTransaction, - nonce, - }, - ]; + const feeOption = await getFeeOption(metaTransactions[0], zkevmAddress, relayerClient); // 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, }); } + flow.addEvent('endBuildMetaTransactions'); return metaTransactions; }; @@ -162,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, @@ -173,30 +165,23 @@ export const prepareAndSignTransaction = async ({ nonceSpace, isBackgroundTransaction, }: TransactionParams & { transactionRequest: TransactionRequest }) => { - const { chainId } = await rpcProvider.getNetwork(); - const chainIdBigNumber = BigInt(chainId); - flow.addEvent('endDetectNetwork'); - - const metaTransactions = await buildMetaTransactions( - transactionRequest, - rpcProvider, - relayerClient, - zkEvmAddress, - nonceSpace, - ); - flow.addEvent('endBuildMetaTransactions'); - - const { nonce } = metaTransactions[0]; - if (typeof nonce === 'undefined') { - throw new Error('Failed to retrieve nonce from the smart wallet'); - } + const [metaTransactions, nonce, network] = await Promise.all([ + buildMetaTransactions( + transactionRequest, + relayerClient, + zkEvmAddress, + flow, + ), + getNonce(rpcProvider, zkEvmAddress, nonceSpace), + detectNetwork(rpcProvider, flow), + ]); // 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)), - nonce: convertBigNumberishToString(nonce), + chainId: getEip155ChainId(Number(network.chainId)), + nonce: nonce.toString(), metaTransactions, isBackgroundTransaction, }); @@ -209,7 +194,7 @@ export const prepareAndSignTransaction = async ({ const signed = await signMetaTransactions( metaTransactions, nonce, - chainIdBigNumber, + network.chainId, zkEvmAddress, ethSigner, ); @@ -230,7 +215,7 @@ export const prepareAndSignTransaction = async ({ const buildMetaTransactionForEjection = async ( transactionRequest: TransactionRequest, -): Promise<[MetaTransaction, ...MetaTransaction[]]> => { +): Promise => { if (!transactionRequest.to) { throw new JsonRpcError( RpcErrorCode.INVALID_PARAMS, @@ -238,29 +223,34 @@ const buildMetaTransactionForEjection = async ( ); } - if (typeof transactionRequest.nonce === 'undefined') { + const metaTransaction: MetaTransaction = { + to: transactionRequest.to.toString(), + data: transactionRequest.data, + value: transactionRequest.value, + revertOnError: true, + }; + + 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); +}; - if (!transactionRequest.chainId) { +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', ); } - - const metaTransaction: MetaTransaction = { - to: transactionRequest.to.toString(), - data: transactionRequest.data, - nonce: transactionRequest.nonce ?? undefined, - value: transactionRequest.value, - revertOnError: true, - }; - - return [metaTransaction]; + return BigInt(transactionRequest.chainId); }; export const prepareAndSignEjectionTransaction = async ({ @@ -269,15 +259,17 @@ export const prepareAndSignEjectionTransaction = async ({ zkEvmAddress, flow, }: EjectionTransactionParams & { transactionRequest: TransactionRequest }): Promise => { + const nonce = parseNonce(transactionRequest); + const chainId = parseChainId(transactionRequest); const metaTransaction = await buildMetaTransactionForEjection( transactionRequest, ); flow.addEvent('endBuildMetaTransactions'); const signedTransaction = await signMetaTransactions( - metaTransaction, - transactionRequest.nonce as BigNumberish, - BigInt(transactionRequest.chainId ?? 0), + [metaTransaction], + nonce, + chainId, zkEvmAddress, ethSigner, ); diff --git a/packages/passport/sdk/src/zkEvm/types.ts b/packages/passport/sdk/src/zkEvm/types.ts index b013685712..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; diff --git a/packages/passport/sdk/src/zkEvm/walletHelpers.test.ts b/packages/passport/sdk/src/zkEvm/walletHelpers.test.ts index 375650eff2..f49527aabb 100644 --- a/packages/passport/sdk/src/zkEvm/walletHelpers.test.ts +++ b/packages/passport/sdk/src/zkEvm/walletHelpers.test.ts @@ -31,13 +31,13 @@ describe('signMetaTransactions', () => { data: '0x', }, ]; - const nonce = 0; - const chainId = 1779; + const nonce = 0n; + const chainId = 1779n; const signature = await signMetaTransactions( transactions, nonce, - BigInt(chainId), + chainId, walletAddress, signer, ); diff --git a/packages/passport/sdk/src/zkEvm/walletHelpers.ts b/packages/passport/sdk/src/zkEvm/walletHelpers.ts index da5a9dcdc3..5d2c786ebc 100644 --- a/packages/passport/sdk/src/zkEvm/walletHelpers.ts +++ b/packages/passport/sdk/src/zkEvm/walletHelpers.ts @@ -114,7 +114,7 @@ export const encodeMessageSubDigest = (chainId: bigint, walletAddress: string, d export const signMetaTransactions = async ( metaTransactions: MetaTransaction[], - nonce: BigNumberish, + nonce: bigint, chainId: bigint, walletAddress: string, signer: Signer,