diff --git a/packages/test/v0.7/multiChainECDSAValidator.test.ts b/packages/test/v0.7/multiChainECDSAValidator.test.ts index bfd709b7..cc54e676 100644 --- a/packages/test/v0.7/multiChainECDSAValidator.test.ts +++ b/packages/test/v0.7/multiChainECDSAValidator.test.ts @@ -6,6 +6,10 @@ import { signUserOperations } from "@zerodev/multi-chain-ecdsa-validator" import { toMultiChainECDSAValidator } from "@zerodev/multi-chain-ecdsa-validator" +import { + type PrepareAndSignUserOperationsParameters, + prepareAndSignUserOperations +} from "@zerodev/multi-chain-ecdsa-validator/actions/prepareAndSignUserOperations.js" import { EIP1271Abi, type KernelAccountClient, @@ -24,6 +28,7 @@ import { http, type Address, type Chain, + type Client, type GetContractReturnType, type Hex, type PrivateKeyAccount, @@ -94,7 +99,6 @@ const SEPOLIA_ZERODEV_RPC_URL = getBundlerRpc( const SEPOLIA_ZERODEV_PAYMASTER_RPC_URL = getPaymasterRpc( config["0.7"][sepolia.id].projectId ) - const OPTIMISM_SEPOLIA_ZERODEV_RPC_URL = getBundlerRpc( config["0.7"][optimismSepolia.id].projectId ) @@ -812,49 +816,52 @@ describe("MultiChainECDSAValidator", () => { paymaster: opSepoliaZeroDevPaymasterClient }) - const sepoliaUserOp = - await sepoliaZerodevKernelClient.prepareUserOperation({ - callData: - await sepoliaZerodevKernelClient.account.encodeCalls([ + const clients: Client[] = [ + { + ...sepoliaZerodevKernelClient + }, + { + ...optimismSepoliaZerodevKernelClient + } + ] + + const userOps = await Promise.all( + clients.map(async (client) => { + return { + callData: await client.account.encodeCalls([ { to: zeroAddress, value: BigInt(0), data: "0x" } ]) + } }) + ) - const optimismSepoliaUserOp = - await optimismSepoliaZerodevKernelClient.prepareUserOperation({ - callData: - await optimismSepoliaZerodevKernelClient.account.encodeCalls( - [ - { - to: zeroAddress, - value: BigInt(0), - data: "0x" - } - ] - ) - }) - - const signedUserOps = await signUserOperations( - sepoliaZerodevKernelClient, + const userOpParams: PrepareAndSignUserOperationsParameters[] = [ { - userOperations: [ - { ...sepoliaUserOp, chainId: sepolia.id }, - { - ...optimismSepoliaUserOp, - chainId: optimismSepolia.id - } - ] + ...userOps[0], + chainId: sepolia.id + }, + { + ...userOps[1], + chainId: optimismSepolia.id } + ] + + const signedUserOps = await prepareAndSignUserOperations( + clients, + userOpParams ) + const sepoliaUserOp = signedUserOps[0] + const optimismSepoliaUserOp = signedUserOps[1] + const sepoliaUserOpHash = - await sepoliaZerodevKernelClient.sendUserOperation({ - ...signedUserOps[0] - }) + await sepoliaZerodevKernelClient.sendUserOperation( + sepoliaUserOp + ) console.log("sepoliaUserOpHash", sepoliaUserOpHash) await sepoliaZerodevKernelClient.waitForUserOperationReceipt({ @@ -862,9 +869,9 @@ describe("MultiChainECDSAValidator", () => { }) const optimismSepoliaUserOpHash = - await optimismSepoliaZerodevKernelClient.sendUserOperation({ - ...signedUserOps[1] - }) + await optimismSepoliaZerodevKernelClient.sendUserOperation( + optimismSepoliaUserOp + ) console.log("optimismSepoliaUserOpHash", optimismSepoliaUserOpHash) await optimismSepoliaZerodevKernelClient.waitForUserOperationReceipt( @@ -963,45 +970,52 @@ describe("MultiChainECDSAValidator", () => { paymaster: opSepoliaZeroDevPaymasterClient }) - const sepoliaUserOp = - await sepoliaZerodevKernelClient.prepareUserOperation({ - callData: await sepoliaKernelAccount.encodeCalls([ - { - to: zeroAddress, - value: BigInt(0), - data: "0x" - } - ]) - }) + const clients: Client[] = [ + { + ...sepoliaZerodevKernelClient + }, + { + ...optimismSepoliaZerodevKernelClient + } + ] - const optimismSepoliaUserOp = - await optimismSepoliaZerodevKernelClient.prepareUserOperation({ - callData: await optimismSepoliaKernelAccount.encodeCalls([ - { - to: zeroAddress, - value: BigInt(0), - data: "0x" - } - ]) + const userOps = await Promise.all( + clients.map(async (client) => { + return { + callData: await client.account.encodeCalls([ + { + to: zeroAddress, + value: BigInt(0), + data: "0x" + } + ]) + } }) + ) - const signedEnableUserOps = await ecdsaSignUserOpsWithEnable({ - multiChainUserOpConfigsForEnable: [ - { - account: sepoliaKernelAccount, - userOp: sepoliaUserOp - }, - { - account: optimismSepoliaKernelAccount, - userOp: optimismSepoliaUserOp - } - ] - }) + const userOpParams: PrepareAndSignUserOperationsParameters[] = [ + { + ...userOps[0], + chainId: sepolia.id + }, + { + ...userOps[1], + chainId: optimismSepolia.id + } + ] + + const signedUserOps = await prepareAndSignUserOperations( + clients, + userOpParams + ) + + const sepoliaUserOp = signedUserOps[0] + const optimismSepoliaUserOp = signedUserOps[1] const sepoliaUserOpHash = - await sepoliaZerodevKernelClient.sendUserOperation({ - ...signedEnableUserOps[0] - }) + await sepoliaZerodevKernelClient.sendUserOperation( + sepoliaUserOp + ) console.log("sepoliaUserOpHash", sepoliaUserOpHash) await sepoliaZerodevKernelClient.waitForUserOperationReceipt({ @@ -1009,9 +1023,9 @@ describe("MultiChainECDSAValidator", () => { }) const optimismSepoliaUserOpHash = - await optimismSepoliaZerodevKernelClient.sendUserOperation({ - ...signedEnableUserOps[1] - }) + await optimismSepoliaZerodevKernelClient.sendUserOperation( + optimismSepoliaUserOp + ) console.log("optimismSepoliaUserOpHash", optimismSepoliaUserOpHash) await optimismSepoliaZerodevKernelClient.waitForUserOperationReceipt( diff --git a/packages/test/v0.7/utils/common.ts b/packages/test/v0.7/utils/common.ts index 35fc0bca..4b12366b 100644 --- a/packages/test/v0.7/utils/common.ts +++ b/packages/test/v0.7/utils/common.ts @@ -27,7 +27,7 @@ export const Test_ERC20Address = "0x3870419Ba2BBf0127060bCB37f69A1b1C090992B" const testingChain = allChains.sepolia.id export const kernelVersion = "0.3.1" export const index = 11111111111111111n // 432334375434333332434365532464445487823332432423423n -const DEFAULT_PROVIDER = "ALCHEMY" +const DEFAULT_PROVIDER = "PIMLICO" const projectId = config["0.7"][testingChain].projectId export const getEntryPoint = (): { diff --git a/plugins/multi-chain-ecdsa/CHANGELOG.md b/plugins/multi-chain-ecdsa/CHANGELOG.md index ad423c5c..eb69756d 100644 --- a/plugins/multi-chain-ecdsa/CHANGELOG.md +++ b/plugins/multi-chain-ecdsa/CHANGELOG.md @@ -1,5 +1,17 @@ # @zerodev/multi-chain-ecdsa +## 5.4.2 + +### Minor Changes + +- Change `sendUserOpeations` to `prepareAndSignUserOperations` + +## 5.4.1 + +### Minor Changes + +- - Add `sendUserOpeations` method + ## 5.4.0 ### Minor Changes diff --git a/plugins/multi-chain-ecdsa/actions/prepareAndSignUserOperations.ts b/plugins/multi-chain-ecdsa/actions/prepareAndSignUserOperations.ts new file mode 100644 index 00000000..5f0a7c15 --- /dev/null +++ b/plugins/multi-chain-ecdsa/actions/prepareAndSignUserOperations.ts @@ -0,0 +1,325 @@ +import { + AccountNotFoundError, + type Action, + type KernelSmartAccountImplementation, + KernelV3AccountAbi, + getEncodedPluginsData, + isPluginInitialized +} from "@zerodev/sdk" +import { MerkleTree } from "merkletreejs" +import { + type Chain, + type Client, + type Hex, + type Transport, + concatHex, + encodeAbiParameters, + getAbiItem, + hashTypedData, + keccak256, + toFunctionSelector, + zeroAddress +} from "viem" +import { + type PrepareUserOperationParameters, + type PrepareUserOperationReturnType, + type SendUserOperationParameters, + type SmartAccount, + type UserOperation, + getUserOperationHash, + prepareUserOperation +} from "viem/account-abstraction" +import { parseAccount } from "viem/accounts" +import { getAction } from "viem/utils" + +export type PrepareAndSignUserOperationsParameters< + account extends SmartAccount | undefined = SmartAccount | undefined, + accountOverride extends SmartAccount | undefined = SmartAccount | undefined, + calls extends readonly unknown[] = readonly unknown[] +> = SendUserOperationParameters & { + chainId: number +} + +export type SignUserOperationsReturnType = PrepareUserOperationReturnType[] + +export async function prepareAndSignUserOperations< + account extends SmartAccount | undefined, + chain extends Chain | undefined, + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] +>( + clients: Client[], + args_: PrepareAndSignUserOperationsParameters< + account, + accountOverride, + calls + >[] +): Promise { + if (clients.length < 2 && args_.length < 2) { + throw new Error("Should send more than 1 user operation") + } + if (clients.length !== args_.length) { + throw new Error("Number of clients and user operations do not match") + } + for (let i = 0; i < clients.length; i++) { + const client = clients[i] + const arg = args_[i] + + if (client.chain === undefined) { + throw new Error("client.chain is undefined, please provide a chain") + } + + if (client.chain.id !== arg.chainId) { + throw new Error( + `Chain ID mismatch at index ${i}: client.chainId (${client.chain.id}) !== args_.chainId (${arg.chainId})` + ) + } + } + + const args = args_ as PrepareAndSignUserOperationsParameters[] + const accounts_ = args.map( + (arg, index) => arg.account ?? clients[index].account + ) + if ( + !accounts_.every( + (account): account is SmartAccount => account !== undefined + ) + ) { + throw new AccountNotFoundError() + } + + const accounts = accounts_.map( + (account) => + parseAccount( + account + ) as SmartAccount + ) + + const _userOperations = args.map(({ chainId, ...userOp }) => userOp) + + const action: Action = { + selector: toFunctionSelector( + getAbiItem({ abi: KernelV3AccountAbi, name: "execute" }) + ), + address: zeroAddress + } + + const account = accounts[0] + + // check if regular validator exists + if (account.kernelPluginManager.regularValidator) { + const isPluginEnabledPerChains = await Promise.all( + accounts.map( + async (account, index) => + (await account.kernelPluginManager.isEnabled( + account.address, + action.selector + )) || + (await isPluginInitialized( + clients[index], + account.address, + account.kernelPluginManager.address + )) + ) + ) + + const allEnabled = isPluginEnabledPerChains.every((enabled) => enabled) + const noneEnabled = isPluginEnabledPerChains.every( + (enabled) => !enabled + ) + + if (!allEnabled && !noneEnabled) { + throw new Error( + "Plugins must be either all enabled or all disabled across chains." + ) + } + // if regular validators are not enabled, encode with enable signatures + if (noneEnabled) { + const dummySignatures = await Promise.all( + accounts.map(async (account, index) => { + return account.kernelPluginManager.regularValidator?.getStubSignature( + _userOperations[index] as UserOperation + ) + }) + ) + + for (const signature of dummySignatures) { + if (signature === undefined) { + throw new Error("Dummy signatures are undefined") + } + } + + const pluginEnableTypedDatas = await Promise.all( + accounts.map(async (account) => { + return account.kernelPluginManager.getPluginsEnableTypedData( + account.address + ) + }) + ) + + const leaves = pluginEnableTypedDatas.map((typedData) => { + return hashTypedData(typedData) + }) + + const merkleTree = new MerkleTree(leaves, keccak256, { + sortPairs: true + }) + + const merkleRoot = merkleTree.getHexRoot() as Hex + + const ecdsaSig = + await account.kernelPluginManager.sudoValidator?.signMessage({ + message: { + raw: merkleRoot + } + }) + + if (!ecdsaSig) { + throw new Error( + "No ecdsaSig, check if the sudo validator is multi-chain-ecdsa-validator" + ) + } + + const enableSigs = accounts.map((_, index) => { + const merkleProof = merkleTree.getHexProof( + leaves[index] + ) as Hex[] + const encodedMerkleProof = encodeAbiParameters( + [{ name: "proof", type: "bytes32[]" }], + [merkleProof] + ) + return concatHex([ecdsaSig, merkleRoot, encodedMerkleProof]) + }) + + const encodedDummySignatures = await Promise.all( + accounts.map(async (account, index) => { + return getEncodedPluginsData({ + enableSignature: enableSigs[index], + userOpSignature: dummySignatures[index] as Hex, + action, + enableData: + await account.kernelPluginManager.getEnableData( + account.address + ) + }) + }) + ) + + for (const [index, userOperation] of _userOperations.entries()) { + userOperation.signature = encodedDummySignatures[index] + } + + const userOperations = await Promise.all( + _userOperations.map(async (_userOperation, index) => { + return await getAction( + clients[index], + prepareUserOperation, + "prepareUserOperation" + )(_userOperation as PrepareUserOperationParameters) + }) + ) + + const encodedSignatures = await Promise.all( + userOperations.map(async (userOperation, index) => { + return await getEncodedPluginsData({ + enableSignature: enableSigs[index], + userOpSignature: await accounts[ + index + ].kernelPluginManager.signUserOperationWithActiveValidator( + userOperation as UserOperation + ), + action, + enableData: await accounts[ + index + ].kernelPluginManager.getEnableData(account.address) + }) + }) + ) + + userOperations.forEach((userOperation, index) => { + userOperation.signature = encodedSignatures[index] + }) + + return userOperations as SignUserOperationsReturnType + } + // if regular validators are enabled, use signUserOperationWithActiveValidator directly + if (allEnabled) { + const userOperations = await Promise.all( + _userOperations.map(async (_userOperation, index) => { + return await getAction( + clients[index], + prepareUserOperation, + "prepareUserOperation" + )(_userOperation as PrepareUserOperationParameters) + }) + ) + + const signatures = await Promise.all( + userOperations.map((userOperation, index) => + accounts[ + index + ].kernelPluginManager.signUserOperationWithActiveValidator( + userOperation as UserOperation + ) + ) + ) + + userOperations.forEach((userOperation, index) => { + userOperation.signature = signatures[index] + }) + + return userOperations as SignUserOperationsReturnType + } + } + // If regular validators do not exist, sign with multi-chain-ecdsa-validator + const userOperations = await Promise.all( + _userOperations.map(async (_userOperation, index) => { + return await getAction( + clients[index], + prepareUserOperation, + "prepareUserOperation" + )(_userOperation as PrepareUserOperationParameters) + }) + ) + + const userOpHashes = userOperations.map((userOp, index) => { + return getUserOperationHash({ + userOperation: { + ...userOp, + signature: "0x" + } as UserOperation, + entryPointAddress: account.entryPoint.address, + entryPointVersion: account.entryPoint.version, + chainId: args_[index].chainId + }) + }) + + const merkleTree = new MerkleTree(userOpHashes, keccak256, { + sortPairs: true + }) + + const merkleRoot = merkleTree.getHexRoot() as Hex + const ecdsaSig = await account.kernelPluginManager.signMessage({ + message: { + raw: merkleRoot + } + }) + + const encodeMerkleDataWithSig = (userOpHash: Hex) => { + const merkleProof = merkleTree.getHexProof(userOpHash) as Hex[] + const encodedMerkleProof = encodeAbiParameters( + [{ name: "proof", type: "bytes32[]" }], + [merkleProof] + ) + return concatHex([ecdsaSig, merkleRoot, encodedMerkleProof]) + } + + const signedMultiUserOps = userOperations.map((userOp, index) => { + return { + ...userOp, + signature: encodeMerkleDataWithSig(userOpHashes[index]) + } + }) + + return signedMultiUserOps as SignUserOperationsReturnType +} diff --git a/plugins/multi-chain-ecdsa/index.ts b/plugins/multi-chain-ecdsa/index.ts index f2eed9f4..bca38f6d 100644 --- a/plugins/multi-chain-ecdsa/index.ts +++ b/plugins/multi-chain-ecdsa/index.ts @@ -6,6 +6,11 @@ export { signUserOperations } from "./actions/index.js" +export { + prepareAndSignUserOperations, + type PrepareAndSignUserOperationsParameters +} from "./actions/prepareAndSignUserOperations.js" + export { ecdsaGetMultiUserOpDummySignature } from "./utils/ecdsaGetMultiUserOpDummySignature.js" export { type MultiChainUserOpConfigForEnable, diff --git a/plugins/multi-chain-ecdsa/package.json b/plugins/multi-chain-ecdsa/package.json index c495df8e..da402930 100644 --- a/plugins/multi-chain-ecdsa/package.json +++ b/plugins/multi-chain-ecdsa/package.json @@ -1,6 +1,6 @@ { "name": "@zerodev/multi-chain-ecdsa-validator", - "version": "5.4.0", + "version": "5.4.2", "author": "ZeroDev", "main": "./_cjs/index.js", "module": "./_esm/index.js", diff --git a/plugins/multi-chain-web-authn/CHANGELOG.md b/plugins/multi-chain-web-authn/CHANGELOG.md index 43f4757d..d5eb1353 100644 --- a/plugins/multi-chain-web-authn/CHANGELOG.md +++ b/plugins/multi-chain-web-authn/CHANGELOG.md @@ -1,5 +1,17 @@ # @zerodev/multi-chain-web-auth +## 5.4.3 + +### Minor Changes + +- Change `sendUserOpeations` to `prepareAndSignUserOperations` + +## 5.4.2 + +### Minor Changes + +- Add `sendUserOpeations` method + ## 5.4.1 ### Minor Changes diff --git a/plugins/multi-chain-web-authn/actions/prepareAndSignUserOperations.ts b/plugins/multi-chain-web-authn/actions/prepareAndSignUserOperations.ts new file mode 100644 index 00000000..620f6592 --- /dev/null +++ b/plugins/multi-chain-web-authn/actions/prepareAndSignUserOperations.ts @@ -0,0 +1,356 @@ +import { + AccountNotFoundError, + type Action, + type KernelSmartAccountImplementation, + KernelV3AccountAbi, + getEncodedPluginsData, + isPluginInitialized +} from "@zerodev/sdk" +import { MerkleTree } from "merkletreejs" +import { + type Chain, + type Client, + type Hex, + type Transport, + concatHex, + encodeAbiParameters, + getAbiItem, + hashMessage, + hashTypedData, + keccak256, + toFunctionSelector, + zeroAddress +} from "viem" +import { + type PrepareUserOperationParameters, + type PrepareUserOperationReturnType, + type SendUserOperationParameters, + type SmartAccount, + type UserOperation, + getUserOperationHash, + prepareUserOperation +} from "viem/account-abstraction" +import { parseAccount } from "viem/accounts" +import { getAction } from "viem/utils" + +export type PrepareAndSignUserOperationsParameters< + account extends SmartAccount | undefined = SmartAccount | undefined, + accountOverride extends SmartAccount | undefined = SmartAccount | undefined, + calls extends readonly unknown[] = readonly unknown[] +> = SendUserOperationParameters & { + chainId: number +} + +export type SignUserOperationsReturnType = PrepareUserOperationReturnType[] + +export async function prepareAndSignUserOperations< + account extends SmartAccount | undefined, + chain extends Chain | undefined, + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] +>( + clients: Client[], + args_: PrepareAndSignUserOperationsParameters< + account, + accountOverride, + calls + >[] +): Promise { + if (clients.length < 2 && args_.length < 2) { + throw new Error("Should send more than 1 user operation") + } + if (clients.length !== args_.length) { + throw new Error("Number of clients and user operations do not match") + } + for (let i = 0; i < clients.length; i++) { + const client = clients[i] + const arg = args_[i] + + if (client.chain === undefined) { + throw new Error("client.chain is undefined, please provide a chain") + } + + if (client.chain.id !== arg.chainId) { + throw new Error( + `Chain ID mismatch at index ${i}: client.chainId (${client.chain.id}) !== args_.chainId (${arg.chainId})` + ) + } + } + + const args = args_ as PrepareAndSignUserOperationsParameters[] + const accounts_ = args.map( + (arg, index) => arg.account ?? clients[index].account + ) + if ( + !accounts_.every( + (account): account is SmartAccount => account !== undefined + ) + ) { + throw new AccountNotFoundError() + } + + const accounts = accounts_.map( + (account) => + parseAccount( + account + ) as SmartAccount + ) + + const _userOperations = args.map(({ chainId, ...userOp }) => userOp) + + const action: Action = { + selector: toFunctionSelector( + getAbiItem({ abi: KernelV3AccountAbi, name: "execute" }) + ), + address: zeroAddress + } + + const account = accounts[0] + + // check if regular validator exists + if (account.kernelPluginManager.regularValidator) { + const isPluginEnabledPerChains = await Promise.all( + accounts.map( + async (account, index) => + (await account.kernelPluginManager.isEnabled( + account.address, + action.selector + )) || + (await isPluginInitialized( + clients[index], + account.address, + account.kernelPluginManager.address + )) + ) + ) + + const allEnabled = isPluginEnabledPerChains.every((enabled) => enabled) + const noneEnabled = isPluginEnabledPerChains.every( + (enabled) => !enabled + ) + + if (!allEnabled && !noneEnabled) { + throw new Error( + "Plugins must be either all enabled or all disabled across chains." + ) + } + // if regular validators are not enabled, encode with enable signatures + if (noneEnabled) { + const dummySignatures = await Promise.all( + accounts.map(async (account, index) => { + return account.kernelPluginManager.regularValidator?.getStubSignature( + _userOperations[index] as UserOperation + ) + }) + ) + + for (const signature of dummySignatures) { + if (signature === undefined) { + throw new Error("Dummy signatures are undefined") + } + } + + const pluginEnableTypedDatas = await Promise.all( + accounts.map(async (account) => { + return account.kernelPluginManager.getPluginsEnableTypedData( + account.address + ) + }) + ) + + const leaves = pluginEnableTypedDatas.map((typedData) => { + return hashTypedData(typedData) + }) + + const merkleTree = new MerkleTree(leaves, keccak256, { + sortPairs: true + }) + + const merkleRoot = merkleTree.getHexRoot() as Hex + const toEthSignedMessageHash = hashMessage({ raw: merkleRoot }) + + const passkeySig = + await account.kernelPluginManager.sudoValidator?.signMessage({ + message: { + raw: toEthSignedMessageHash + } + }) + + if (!passkeySig) { + throw new Error( + "No passkeySig, check if the sudo validator is multi-chain-web-authn-validator" + ) + } + + const enableSigs = accounts.map((_, index) => { + const merkleProof = merkleTree.getHexProof( + leaves[index] + ) as Hex[] + const encodedMerkleProof = encodeAbiParameters( + [{ name: "proof", type: "bytes32[]" }], + [merkleProof] + ) + const merkleData = concatHex([merkleRoot, encodedMerkleProof]) + return encodeAbiParameters( + [ + { + name: "merkleData", + type: "bytes" + }, + { + name: "signature", + type: "bytes" + } + ], + [merkleData, passkeySig] + ) + }) + + const encodedDummySignatures = await Promise.all( + accounts.map(async (account, index) => { + return getEncodedPluginsData({ + enableSignature: enableSigs[index], + userOpSignature: dummySignatures[index] as Hex, + action, + enableData: + await account.kernelPluginManager.getEnableData( + account.address + ) + }) + }) + ) + + _userOperations.forEach((userOperation, index) => { + userOperation.signature = encodedDummySignatures[index] + }) + + const userOperations = await Promise.all( + _userOperations.map(async (_userOperation, index) => { + return await getAction( + clients[index], + prepareUserOperation, + "prepareUserOperation" + )(_userOperation as PrepareUserOperationParameters) + }) + ) + + const encodedSignatures = await Promise.all( + userOperations.map(async (userOperation, index) => { + return await getEncodedPluginsData({ + enableSignature: enableSigs[index], + userOpSignature: await accounts[ + index + ].kernelPluginManager.signUserOperationWithActiveValidator( + userOperation as UserOperation + ), + action, + enableData: await accounts[ + index + ].kernelPluginManager.getEnableData(account.address) + }) + }) + ) + + userOperations.forEach((userOperation, index) => { + userOperation.signature = encodedSignatures[index] + }) + + return userOperations as SignUserOperationsReturnType + } + // if regular validators are enabled, use signUserOperationWithActiveValidator directly + if (allEnabled) { + const userOperations = await Promise.all( + _userOperations.map(async (_userOperation, index) => { + return await getAction( + clients[index], + prepareUserOperation, + "prepareUserOperation" + )(_userOperation as PrepareUserOperationParameters) + }) + ) + + const signatures = await Promise.all( + userOperations.map((userOperation, index) => + accounts[ + index + ].kernelPluginManager.signUserOperationWithActiveValidator( + userOperation as UserOperation + ) + ) + ) + + userOperations.forEach((userOperation, index) => { + userOperation.signature = signatures[index] + }) + + return userOperations as SignUserOperationsReturnType + } + } + // If regular validators do not exist, sign with multi-chain-ecdsa-validator + const userOperations = await Promise.all( + _userOperations.map(async (_userOperation, index) => { + return await getAction( + clients[index], + prepareUserOperation, + "prepareUserOperation" + )(_userOperation as PrepareUserOperationParameters) + }) + ) + + const userOpHashes = userOperations.map((userOp, index) => { + return getUserOperationHash({ + userOperation: { + ...userOp, + signature: "0x" + } as UserOperation, + entryPointAddress: account.entryPoint.address, + entryPointVersion: account.entryPoint.version, + chainId: args_[index].chainId + }) + }) + + const merkleTree = new MerkleTree(userOpHashes, keccak256, { + sortPairs: true + }) + + const merkleRoot = merkleTree.getHexRoot() as Hex + const toEthSignedMessageHash = hashMessage({ raw: merkleRoot }) + + const passkeySig = await account.kernelPluginManager.signMessage({ + message: { + raw: toEthSignedMessageHash + } + }) + + const encodeMerkleDataWithSig = (userOpHash: Hex) => { + const merkleProof = merkleTree.getHexProof(userOpHash) as Hex[] + + const encodedMerkleProof = encodeAbiParameters( + [{ name: "proof", type: "bytes32[]" }], + [merkleProof] + ) + const merkleData = concatHex([merkleRoot, encodedMerkleProof]) + return encodeAbiParameters( + [ + { + name: "merkleData", + type: "bytes" + }, + { + name: "signature", + type: "bytes" + } + ], + [merkleData, passkeySig] + ) + } + + const signedMultiUserOps = userOperations.map((userOp, index) => { + return { + ...userOp, + signature: encodeMerkleDataWithSig(userOpHashes[index]) + } + }) + + return signedMultiUserOps as SignUserOperationsReturnType +} diff --git a/plugins/multi-chain-web-authn/index.ts b/plugins/multi-chain-web-authn/index.ts index d3ddc8ba..1a974625 100644 --- a/plugins/multi-chain-web-authn/index.ts +++ b/plugins/multi-chain-web-authn/index.ts @@ -1,4 +1,8 @@ export { toMultiChainWebAuthnValidator } from "./toMultiChainWebAuthnValidator.js" +export { + type PrepareAndSignUserOperationsParameters, + prepareAndSignUserOperations +} from "./actions/prepareAndSignUserOperations.js" export { type SignUserOperationsParameters, type SignUserOperationsRequest, diff --git a/plugins/multi-chain-web-authn/package.json b/plugins/multi-chain-web-authn/package.json index 79051848..11a61a68 100644 --- a/plugins/multi-chain-web-authn/package.json +++ b/plugins/multi-chain-web-authn/package.json @@ -1,6 +1,6 @@ { "name": "@zerodev/multi-chain-web-authn-validator", - "version": "5.4.1", + "version": "5.4.3", "author": "ZeroDev", "main": "./_cjs/index.js", "module": "./_esm/index.js",