Skip to content

Commit

Permalink
feat: Adding nonce spaces for session activity requests. (#2498)
Browse files Browse the repository at this point in the history
  • Loading branch information
shirren authored Jan 16, 2025
1 parent cf5c967 commit 05351cb
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 5 deletions.
2 changes: 2 additions & 0 deletions packages/passport/sdk/src/zkEvm/sendTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const sendTransaction = async ({
guardianClient,
zkEvmAddress,
flow,
nonceSpace,
}: EthSendTransactionParams): Promise<string> => {
const transactionRequest = params[0];

Expand All @@ -23,6 +24,7 @@ export const sendTransaction = async ({
relayerClient,
zkEvmAddress,
flow,
nonceSpace,
});

const { hash } = await pollRelayerTransaction(relayerClient, relayerId, flow);
Expand Down
7 changes: 5 additions & 2 deletions packages/passport/sdk/src/zkEvm/transactionHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type TransactionParams = {
relayerClient: RelayerClient;
zkEvmAddress: string;
flow: Flow;
nonceSpace?: BigNumber;
};

export type EjectionTransactionParams = Pick<TransactionParams, 'ethSigner' | 'zkEvmAddress' | 'flow'>;
Expand Down Expand Up @@ -64,13 +65,13 @@ const getFeeOption = async (
/**
* Prepares the meta transactions array to be signed by estimating the fee and
* getting the nonce from the smart wallet.
*
*/
const buildMetaTransactions = async (
transactionRequest: TransactionRequest,
rpcProvider: StaticJsonRpcProvider,
relayerClient: RelayerClient,
zkevmAddress: string,
nonceSpace?: BigNumber,
): Promise<[MetaTransaction, ...MetaTransaction[]]> => {
if (!transactionRequest.to) {
throw new JsonRpcError(
Expand All @@ -89,7 +90,7 @@ const buildMetaTransactions = async (

// Estimate the fee and get the nonce from the smart wallet
const [nonce, feeOption] = await Promise.all([
getNonce(rpcProvider, zkevmAddress),
getNonce(rpcProvider, zkevmAddress, nonceSpace),
getFeeOption(metaTransaction, zkevmAddress, relayerClient),
]);

Expand Down Expand Up @@ -166,6 +167,7 @@ export const prepareAndSignTransaction = async ({
relayerClient,
zkEvmAddress,
flow,
nonceSpace,
}: TransactionParams & { transactionRequest: TransactionRequest }) => {
const { chainId } = await rpcProvider.detectNetwork();
const chainIdBigNumber = BigNumber.from(chainId);
Expand All @@ -176,6 +178,7 @@ export const prepareAndSignTransaction = async ({
rpcProvider,
relayerClient,
zkEvmAddress,
nonceSpace,
);
flow.addEvent('endBuildMetaTransactions');

Expand Down
31 changes: 31 additions & 0 deletions packages/passport/sdk/src/zkEvm/walletHelpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
import { StaticJsonRpcProvider } from '@ethersproject/providers';
import {
getNonce, signMetaTransactions, signAndPackTypedData, packSignatures,
coerceNonceSpace,
encodeNonce,
} from './walletHelpers';
import { TypedDataPayload } from './types';

Expand Down Expand Up @@ -121,6 +123,34 @@ describe('signAndPackTypedData', () => {
});
});

describe('coerceNonceSpace', () => {
describe('with no space', () => {
it('should default to 0', () => {
expect(coerceNonceSpace()).toEqual(BigNumber.from(0));
});
});

describe('with space', () => {
it('should return the space', () => {
expect(coerceNonceSpace(BigNumber.from(12345))).toEqual(BigNumber.from(12345));
});
});
});

describe('encodeNonce', () => {
describe('with no space', () => {
it('should not left shift the nonce', () => {
expect(encodeNonce(BigNumber.from(0), BigNumber.from(1))).toEqual(BigNumber.from(1));
});
});

describe('with space', () => {
it('should left shift the nonce by 96 bits', () => {
expect(encodeNonce(BigNumber.from(1), BigNumber.from(0))).toEqual(BigNumber.from('0x01000000000000000000000000'));
});
});
});

describe('getNonce', () => {
const rpcProvider = {} as StaticJsonRpcProvider;
const nonceMock = jest.fn();
Expand All @@ -129,6 +159,7 @@ describe('getNonce', () => {
jest.resetAllMocks();
(Contract as unknown as jest.Mock).mockImplementation(() => ({
nonce: nonceMock,
readNonce: nonceMock,
}));
});

Expand Down
33 changes: 31 additions & 2 deletions packages/passport/sdk/src/zkEvm/walletHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
constants,
utils,
errors,
ethers,
} from 'ethers';
import { walletContracts } from '@0xsequence/abi';
import { StaticJsonRpcProvider } from '@ethersproject/providers';
Expand Down Expand Up @@ -55,19 +56,47 @@ export const encodedTransactions = (
[normalisedTransactions],
);

/**
* This helper function is used to coerce the type <BigNumber | undefined> to BigNumber for the
* getNonce function above.
* @param {BigNumber} nonceSpace - An unsigned 256 bit value that can be used to encode a nonce into a distinct space.
* @returns {BigNumber} The passed in nonceSpace or instead initialises the nonce to 0.
*/
export const coerceNonceSpace = (nonceSpace?: BigNumber): BigNumber => nonceSpace || BigNumber.from(0);

/**
* This helper function is used to encode the nonce into a 256 bit value where the space is encoded into
* the first 160 bits, and the nonce the remaining 96 bits.
* @param {BigNumber} nonceSpace - An unsigned 256 bit value that can be used to encode a nonce into a distinct space.
* @param nonce {BigNumber} nonce - Sequential number starting at 0, and incrementing in single steps e.g. 0,1,2,...
* @returns {BigNumber} The encoded value where the space is left shifted 96 bits, and the nonce is in the first 96 bits.
*/
export const encodeNonce = (nonceSpace: BigNumber, nonce: BigNumber): BigNumber => {
const shiftedSpace = BigNumber.from(nonceSpace).mul(ethers.constants.Two.pow(96));
return BigNumber.from(nonce).add(shiftedSpace);
};

/**
* When we retrieve a nonce for a smart contract wallet we can retrieve the nonce in a given 256 bit space.
* Nonces in each 256 bit space need to be sequential per wallet address. Nonces across 256 bit spaces per
* wallet address do not. This function overload can be used to invoke transactions in parallel per smart
* contract wallet if required.
*/
export const getNonce = async (
rpcProvider: StaticJsonRpcProvider,
smartContractWalletAddress: string,
nonceSpace?: BigNumber,
): Promise<BigNumber> => {
try {
const contract = new Contract(
smartContractWalletAddress,
walletContracts.mainModule.abi,
rpcProvider,
);
const result = await contract.nonce();
const space: BigNumber = coerceNonceSpace(nonceSpace); // Default nonce space is 0
const result = await contract.readNonce(space);
if (result instanceof BigNumber) {
return result;
return encodeNonce(space, result);
}
} catch (error) {
if (error instanceof Error
Expand Down
8 changes: 7 additions & 1 deletion packages/passport/sdk/src/zkEvm/zkEvmProvider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { StaticJsonRpcProvider, Web3Provider } from '@ethersproject/providers';
import { MultiRollupApiClients } from '@imtbl/generated-clients';
import { Signer } from '@ethersproject/abstract-signer';
import { utils } from 'ethers';
import { BigNumber, utils } from 'ethers';
import {
Flow, identify, trackError, trackFlow,
} from '@imtbl/metrics';
Expand Down Expand Up @@ -193,6 +193,11 @@ export class ZkEvmProvider implements Provider {
}

async #callSessionActivity(zkEvmAddress: string, clientId?: string) {
// SessionActivity requests are processed in nonce space 1, where as all
// other sendTransaction requests are processed in nonce space 0. This means
// we can submit a session activity request per SCW in parallel without a SCW
// INVALID_NONCE error.
const nonceSpace: BigNumber = BigNumber.from(1);
const sendTransactionClosure = async (params: Array<any>, flow: Flow) => {
const ethSigner = await this.#getSigner();
return await sendTransaction({
Expand All @@ -203,6 +208,7 @@ export class ZkEvmProvider implements Provider {
relayerClient: this.#relayerClient,
zkEvmAddress,
flow,
nonceSpace,
});
};
this.#passportEventEmitter.emit(PassportEvents.ACCOUNTS_REQUESTED, {
Expand Down

0 comments on commit 05351cb

Please sign in to comment.