diff --git a/packages/test/v0.7/multiChainECDSAValidator.test.ts b/packages/test/v0.7/multiChainECDSAValidator.test.ts index bfd709b7..af48b1c1 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 SendUserOperationsParameters, + sendUserOperations +} from "@zerodev/multi-chain-ecdsa-validator/actions/sendUserOperations.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,60 +816,51 @@ 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: SendUserOperationsParameters[] = [ { - userOperations: [ - { ...sepoliaUserOp, chainId: sepolia.id }, - { - ...optimismSepoliaUserOp, - chainId: optimismSepolia.id - } - ] + ...userOps[0], + chainId: sepolia.id + }, + { + ...userOps[1], + chainId: optimismSepolia.id } - ) + ] - const sepoliaUserOpHash = - await sepoliaZerodevKernelClient.sendUserOperation({ - ...signedUserOps[0] - }) + const userOpHashes = await sendUserOperations(clients, userOpParams) + + console.log("userOpHashes", userOpHashes) + const sepoliaUserOpHash = userOpHashes[0] + const optimismSepoliaUserOpHash = userOpHashes[1] console.log("sepoliaUserOpHash", sepoliaUserOpHash) await sepoliaZerodevKernelClient.waitForUserOperationReceipt({ hash: sepoliaUserOpHash }) - const optimismSepoliaUserOpHash = - await optimismSepoliaZerodevKernelClient.sendUserOperation({ - ...signedUserOps[1] - }) - console.log("optimismSepoliaUserOpHash", optimismSepoliaUserOpHash) await optimismSepoliaZerodevKernelClient.waitForUserOperationReceipt( { @@ -963,55 +958,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: SendUserOperationsParameters[] = [ + { + ...userOps[0], + chainId: sepolia.id + }, + { + ...userOps[1], + chainId: optimismSepolia.id + } + ] - const sepoliaUserOpHash = - await sepoliaZerodevKernelClient.sendUserOperation({ - ...signedEnableUserOps[0] - }) + const userOpHashes = await sendUserOperations(clients, userOpParams) + + console.log("userOpHashes", userOpHashes) + + const sepoliaUserOpHash = userOpHashes[0] console.log("sepoliaUserOpHash", sepoliaUserOpHash) await sepoliaZerodevKernelClient.waitForUserOperationReceipt({ hash: sepoliaUserOpHash }) - const optimismSepoliaUserOpHash = - await optimismSepoliaZerodevKernelClient.sendUserOperation({ - ...signedEnableUserOps[1] - }) + const optimismSepoliaUserOpHash = userOpHashes[1] 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/actions/sendUserOperations.ts b/plugins/multi-chain-ecdsa/actions/sendUserOperations.ts new file mode 100644 index 00000000..9c38363e --- /dev/null +++ b/plugins/multi-chain-ecdsa/actions/sendUserOperations.ts @@ -0,0 +1,344 @@ +import { + AccountNotFoundError, + type Action, + type KernelSmartAccountImplementation, + KernelV3AccountAbi, + getEncodedPluginsData, + isPluginInitialized +} from "@zerodev/sdk" +import { MerkleTree } from "merkletreejs" +import { + type Chain, + type Client, + type Hash, + type Hex, + type Transport, + concatHex, + encodeAbiParameters, + getAbiItem, + hashTypedData, + keccak256, + toFunctionSelector, + zeroAddress +} from "viem" +import { + type PrepareUserOperationParameters, + type SendUserOperationParameters, + type SmartAccount, + type UserOperation, + getUserOperationHash, + prepareUserOperation, + sendUserOperation +} from "viem/account-abstraction" +import { parseAccount } from "viem/accounts" +import { getAction } from "viem/utils" + +export type SendUserOperationsParameters< + account extends SmartAccount | undefined = SmartAccount | undefined, + accountOverride extends SmartAccount | undefined = SmartAccount | undefined, + calls extends readonly unknown[] = readonly unknown[] +> = SendUserOperationParameters & { + chainId: number +} + +export async function sendUserOperations< + account extends SmartAccount | undefined, + chain extends Chain | undefined, + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] +>( + clients: Client[], + args_: SendUserOperationsParameters[] +): 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 SendUserOperationsParameters[] + 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 await Promise.all( + userOperations.map(async (userOperation, index) => { + return await getAction( + clients[index], + sendUserOperation, + "sendUserOperation" + )(userOperation) + }) + ) + } + // 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 await Promise.all( + userOperations.map(async (userOperation, index) => { + return await getAction( + clients[index], + sendUserOperation, + "sendUserOperation" + )(userOperation) + }) + ) + } + } + // 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 await Promise.all( + signedMultiUserOps.map(async (userOp, index) => { + return await getAction( + clients[index], + sendUserOperation, + "sendUserOperation" + )({ ...userOp }) + }) + ) +} diff --git a/plugins/multi-chain-ecdsa/index.ts b/plugins/multi-chain-ecdsa/index.ts index f2eed9f4..c891e72c 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 { + sendUserOperations, + type SendUserOperationsParameters +} from "./actions/sendUserOperations.js" + export { ecdsaGetMultiUserOpDummySignature } from "./utils/ecdsaGetMultiUserOpDummySignature.js" export { type MultiChainUserOpConfigForEnable, diff --git a/plugins/multi-chain-web-authn/actions/sendUserOperations.ts b/plugins/multi-chain-web-authn/actions/sendUserOperations.ts new file mode 100644 index 00000000..98ba9103 --- /dev/null +++ b/plugins/multi-chain-web-authn/actions/sendUserOperations.ts @@ -0,0 +1,375 @@ +import { + AccountNotFoundError, + type Action, + type KernelSmartAccountImplementation, + KernelV3AccountAbi, + getEncodedPluginsData, + isPluginInitialized +} from "@zerodev/sdk" +import { MerkleTree } from "merkletreejs" +import { + type Chain, + type Client, + type Hash, + type Hex, + type Transport, + concatHex, + encodeAbiParameters, + getAbiItem, + hashMessage, + hashTypedData, + keccak256, + toFunctionSelector, + zeroAddress +} from "viem" +import { + type PrepareUserOperationParameters, + type SendUserOperationParameters, + type SmartAccount, + type UserOperation, + getUserOperationHash, + prepareUserOperation, + sendUserOperation +} from "viem/account-abstraction" +import { parseAccount } from "viem/accounts" +import { getAction } from "viem/utils" + +export type SendUserOperationsParameters< + account extends SmartAccount | undefined = SmartAccount | undefined, + accountOverride extends SmartAccount | undefined = SmartAccount | undefined, + calls extends readonly unknown[] = readonly unknown[] +> = SendUserOperationParameters & { + chainId: number +} + +export async function sendUserOperations< + account extends SmartAccount | undefined, + chain extends Chain | undefined, + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] +>( + clients: Client[], + args_: SendUserOperationsParameters[] +): 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 SendUserOperationsParameters[] + 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 await Promise.all( + userOperations.map(async (userOperation, index) => { + return await getAction( + clients[index], + sendUserOperation, + "sendUserOperation" + )(userOperation) + }) + ) + } + // 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 await Promise.all( + userOperations.map(async (userOperation, index) => { + return await getAction( + clients[index], + sendUserOperation, + "sendUserOperation" + )(userOperation) + }) + ) + } + } + // 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 await Promise.all( + signedMultiUserOps.map(async (userOp, index) => { + return await getAction( + clients[index], + sendUserOperation, + "sendUserOperation" + )({ ...userOp }) + }) + ) +} diff --git a/plugins/multi-chain-web-authn/index.ts b/plugins/multi-chain-web-authn/index.ts index d3ddc8ba..4fabe03d 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 SendUserOperationsParameters, + sendUserOperations +} from "./actions/sendUserOperations.js" export { type SignUserOperationsParameters, type SignUserOperationsRequest,