diff --git a/bun.lockb b/bun.lockb index ea7a390b..2b063456 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 5e33facb..8c1cfdcf 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,17 @@ # @zerodev/sdk +## 5.4.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + +## 5.4.0-beta.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + ## 5.3.26 ### Patch Changes diff --git a/packages/core/accounts/index.ts b/packages/core/accounts/index.ts index ee787b43..b8a315bc 100644 --- a/packages/core/accounts/index.ts +++ b/packages/core/accounts/index.ts @@ -1,11 +1,14 @@ export { createKernelAccount, - type KernelSmartAccount, + type CreateKernelAccountParameters, + type CreateKernelAccountReturnType, + type KernelSmartAccountImplementation, KERNEL_ADDRESSES } from "./kernel/createKernelAccount.js" export { createKernelAccountV1, - type KernelSmartAccountV1 + type CreateKernelAccountV1ReturnType, + type KernelSmartAccountV1Implementation } from "./kernel/v1/createKernelAccountV1.js" export { createKernelAccountV0_2 } from "./kernel/v2/createKernelAccountV0_2.js" export { KernelAccountV2Abi } from "./kernel/v2/abi/KernelAccountV2Abi.js" diff --git a/packages/core/accounts/kernel/createKernelAccount.ts b/packages/core/accounts/kernel/createKernelAccount.ts index 2c68174e..9a09e14b 100644 --- a/packages/core/accounts/kernel/createKernelAccount.ts +++ b/packages/core/accounts/kernel/createKernelAccount.ts @@ -1,29 +1,12 @@ -import { - getAccountNonce, - getEntryPointVersion, - getSenderAddress, - isSmartAccountDeployed -} from "permissionless" -import { - SignTransactionNotSupportedBySmartAccount, - type SmartAccount, - toSmartAccount -} from "permissionless/accounts" -import type { - ENTRYPOINT_ADDRESS_V06_TYPE, - ENTRYPOINT_ADDRESS_V07_TYPE, - EntryPoint -} from "permissionless/types" import { type Address, - type Chain, + type Assign, type Client, + type EncodeDeployDataParameters, type Hex, - type PublicActions, - type PublicRpcSchema, - type Transport, type TypedDataDefinition, concatHex, + createNonceManager, encodeFunctionData, getTypesForEIP712Domain, hashMessage, @@ -32,16 +15,33 @@ import { validateTypedData, zeroAddress } from "viem" +import { + type EntryPointVersion, + type SmartAccount, + type SmartAccountImplementation, + type UserOperation, + entryPoint06Abi, + entryPoint07Abi, + entryPoint07Address, + toSmartAccount +} from "viem/account-abstraction" +import { getChainId } from "viem/actions" +import { getAction } from "viem/utils" +import { + getAccountNonce, + getSenderAddress +} from "../../actions/public/index.js" import { KernelVersionToAddressesMap } from "../../constants.js" import type { + CallType, + EntryPointType, + GetEntryPointAbi, GetKernelVersion, - KernelEncodeCallDataArgs, KernelPluginManager, KernelPluginManagerParams } from "../../types/kernel.js" import { KERNEL_FEATURES, hasKernelFeature } from "../../utils.js" import { validateKernelVersionWithEntryPoint } from "../../utils.js" -import { parseFactoryAddressAndCallDataFromAccountInitCode } from "../utils/index.js" import { isKernelPluginManager, toKernelPluginManager @@ -58,39 +58,54 @@ import { encodeDeployCallData as encodeDeployCallDataV07 } from "./utils/account import { accountMetadata } from "./utils/common/accountMetadata.js" import { eip712WrapHash } from "./utils/common/eip712WrapHash.js" -export type KernelSmartAccount< - entryPoint extends EntryPoint, - transport extends Transport = Transport, - chain extends Chain | undefined = Chain | undefined -> = SmartAccount & { - kernelVersion: GetKernelVersion - kernelPluginManager: KernelPluginManager - getNonce: (customNonceKey?: bigint) => Promise - generateInitCode: () => Promise - encodeCallData: (args: KernelEncodeCallDataArgs) => Promise - encodeModuleInstallCallData: () => Promise -} +export type KernelSmartAccountImplementation< + entryPointVersion extends EntryPointVersion = "0.7" +> = Assign< + SmartAccountImplementation< + GetEntryPointAbi, + entryPointVersion + >, + { + sign: NonNullable + encodeCalls: ( + calls: Parameters[0], + callType?: CallType | undefined + ) => Promise + kernelVersion: GetKernelVersion + kernelPluginManager: KernelPluginManager + generateInitCode: () => Promise + encodeModuleInstallCallData: () => Promise + encodeDeployCallData: ({ + abi, + args, + bytecode + }: EncodeDeployDataParameters) => Promise + } +> + +export type CreateKernelAccountReturnType< + entryPointVersion extends EntryPointVersion = "0.7" +> = SmartAccount> export type CreateKernelAccountParameters< - entryPoint extends EntryPoint, - KernelVerion extends GetKernelVersion + entryPointVersion extends EntryPointVersion, + KernelVerion extends GetKernelVersion > = { plugins: | Omit< - KernelPluginManagerParams, + KernelPluginManagerParams, "entryPoint" | "kernelVersion" > - | KernelPluginManager - entryPoint: entryPoint + | KernelPluginManager + entryPoint: EntryPointType index?: bigint factoryAddress?: Address accountImplementationAddress?: Address metaFactoryAddress?: Address - deployedAccountAddress?: Address - kernelVersion: GetKernelVersion + address?: Address + kernelVersion: GetKernelVersion initConfig?: KernelVerion extends "0.3.1" ? Hex[] : never useMetaFactory?: boolean - chainId?: number } /** @@ -145,20 +160,21 @@ export const KERNEL_ADDRESSES: { FACTORY_STAKER: "0xd703aaE79538628d27099B8c4f621bE4CCd142d5" } -const getKernelInitData = async ({ - entryPoint: entryPointAddress, +const getKernelInitData = async ({ + entryPointVersion: _entryPointVersion, kernelPluginManager, initHook, kernelVersion, initConfig }: { - entryPoint: entryPoint - kernelPluginManager: KernelPluginManager + entryPointVersion: entryPointVersion + kernelPluginManager: KernelPluginManager initHook: boolean - kernelVersion: GetKernelVersion - initConfig?: GetKernelVersion extends "0.3.1" ? Hex[] : never + kernelVersion: GetKernelVersion + initConfig?: GetKernelVersion extends "0.3.1" + ? Hex[] + : never }) => { - const entryPointVersion = getEntryPointVersion(entryPointAddress) const { enableData, identifier, @@ -166,7 +182,7 @@ const getKernelInitData = async ({ initConfig: initConfig_ } = await kernelPluginManager.getValidatorInitData() - if (entryPointVersion === "v0.6") { + if (_entryPointVersion === "0.6") { return encodeFunctionData({ abi: KernelInitAbi, functionName: "initialize", @@ -214,12 +230,11 @@ const getKernelInitData = async ({ * @param accountImplementationAddress * @param ecdsaValidatorAddress */ -const getAccountInitCode = async ({ +const getAccountInitCode = async ({ index, factoryAddress, accountImplementationAddress, - metaFactoryAddress, - entryPoint: entryPointAddress, + entryPointVersion: _entryPointVersion, kernelPluginManager, initHook, kernelVersion, @@ -229,103 +244,51 @@ const getAccountInitCode = async ({ index: bigint factoryAddress: Address accountImplementationAddress: Address - metaFactoryAddress?: Address - entryPoint: entryPoint - kernelPluginManager: KernelPluginManager + entryPointVersion: entryPointVersion + kernelPluginManager: KernelPluginManager initHook: boolean - kernelVersion: GetKernelVersion - initConfig?: GetKernelVersion extends "0.3.1" ? Hex[] : never + kernelVersion: GetKernelVersion + initConfig?: GetKernelVersion extends "0.3.1" + ? Hex[] + : never useMetaFactory: boolean }): Promise => { // Build the account initialization data - const initialisationData = await getKernelInitData({ - entryPoint: entryPointAddress, + const initialisationData = await getKernelInitData({ + entryPointVersion: _entryPointVersion, kernelPluginManager, initHook, kernelVersion, initConfig }) - const entryPointVersion = getEntryPointVersion(entryPointAddress) // Build the account init code - if (entryPointVersion === "v0.6") { - return concatHex([ - factoryAddress, - encodeFunctionData({ - abi: createAccountAbi, - functionName: "createAccount", - args: [accountImplementationAddress, initialisationData, index] - }) as Hex - ]) + if (_entryPointVersion === "0.6") { + return encodeFunctionData({ + abi: createAccountAbi, + functionName: "createAccount", + args: [accountImplementationAddress, initialisationData, index] + }) } if (!useMetaFactory) { - return concatHex([ - factoryAddress, - encodeFunctionData({ - abi: KernelV3FactoryAbi, - functionName: "createAccount", - args: [initialisationData, toHex(index, { size: 32 })] - }) as Hex - ]) - } - - return concatHex([ - metaFactoryAddress ?? zeroAddress, - encodeFunctionData({ - abi: KernelFactoryStakerAbi, - functionName: "deployWithFactory", - args: [ - factoryAddress, - initialisationData, - toHex(index, { size: 32 }) - ] - }) - ]) -} - -/** - * Check the validity of an existing account address, or fetch the pre-deterministic account address for a kernel smart wallet - * @param client - * @param entryPoint - * @param initCodeProvider - */ -const getAccountAddress = async < - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined ->({ - client, - entryPoint: entryPointAddress, - initCodeProvider -}: { - client: Client - initCodeProvider: () => Promise - entryPoint: entryPoint -}): Promise
=> { - const entryPointVersion = getEntryPointVersion(entryPointAddress) - - // Find the init code for this account - const initCode = await initCodeProvider() - if (entryPointVersion === "v0.6") { - return getSenderAddress(client, { - initCode, - entryPoint: entryPointAddress as ENTRYPOINT_ADDRESS_V06_TYPE + return encodeFunctionData({ + abi: KernelV3FactoryAbi, + functionName: "createAccount", + args: [initialisationData, toHex(index, { size: 32 })] }) } - // Get the sender address based on the init code - return getSenderAddress(client, { - factory: parseFactoryAddressAndCallDataFromAccountInitCode(initCode)[0], - factoryData: - parseFactoryAddressAndCallDataFromAccountInitCode(initCode)[1], - entryPoint: entryPointAddress as ENTRYPOINT_ADDRESS_V07_TYPE + return encodeFunctionData({ + abi: KernelFactoryStakerAbi, + functionName: "deployWithFactory", + args: [factoryAddress, initialisationData, toHex(index, { size: 32 })] }) } -const getDefaultAddresses = ( - entryPointAddress: entryPoint, - kernelVersion: GetKernelVersion, +const getDefaultAddresses = ( + entryPointVersion: entryPointVersion, + kernelVersion: GetKernelVersion, { accountImplementationAddress, factoryAddress, @@ -336,7 +299,7 @@ const getDefaultAddresses = ( metaFactoryAddress?: Address } ) => { - validateKernelVersionWithEntryPoint(entryPointAddress, kernelVersion) + validateKernelVersionWithEntryPoint(entryPointVersion, kernelVersion) const addresses = KernelVersionToAddressesMap[kernelVersion] if (!addresses) { @@ -350,7 +313,8 @@ const getDefaultAddresses = ( accountImplementationAddress ?? addresses.accountImplementationAddress, factoryAddress: factoryAddress ?? addresses.factoryAddress, - metaFactoryAddress: metaFactoryAddress ?? addresses.metaFactoryAddress + metaFactoryAddress: + metaFactoryAddress ?? addresses.metaFactoryAddress ?? zeroAddress } } @@ -363,60 +327,62 @@ const getDefaultAddresses = ( * @param factoryAddress * @param accountImplementationAddress * @param ecdsaValidatorAddress - * @param deployedAccountAddress + * @param address */ export async function createKernelAccount< - entryPoint extends EntryPoint, - KernelVersion extends GetKernelVersion, - TTransport extends Transport, - TChain extends Chain | undefined + entryPointVersion extends EntryPointVersion, + KernelVersion extends GetKernelVersion >( - client: Client< - TTransport, - TChain, - undefined, - PublicRpcSchema, - PublicActions - >, + client: Client, { plugins, - entryPoint: entryPointAddress, + entryPoint, index = 0n, factoryAddress: _factoryAddress, accountImplementationAddress: _accountImplementationAddress, metaFactoryAddress: _metaFactoryAddress, - deployedAccountAddress, + address, kernelVersion, initConfig, - useMetaFactory = true, - chainId - }: CreateKernelAccountParameters -): Promise> { - const entryPointVersion = getEntryPointVersion(entryPointAddress) + useMetaFactory = true + }: CreateKernelAccountParameters +): Promise> { const { accountImplementationAddress, factoryAddress, metaFactoryAddress } = - getDefaultAddresses(entryPointAddress, kernelVersion, { + getDefaultAddresses(entryPoint.version, kernelVersion, { accountImplementationAddress: _accountImplementationAddress, factoryAddress: _factoryAddress, metaFactoryAddress: _metaFactoryAddress }) - const kernelPluginManager = isKernelPluginManager(plugins) + let chainId: number + + const getMemoizedChainId = async () => { + if (chainId) return chainId + chainId = client.chain + ? client.chain.id + : await getAction(client, getChainId, "getChainId")({}) + return chainId + } + + const kernelPluginManager = isKernelPluginManager( + plugins + ) ? plugins - : await toKernelPluginManager(client, { + : await toKernelPluginManager(client, { sudo: plugins.sudo, regular: plugins.regular, hook: plugins.hook, action: plugins.action, pluginEnableSignature: plugins.pluginEnableSignature, - entryPoint: entryPointAddress, + entryPoint, kernelVersion, - chainId + chainId: await getMemoizedChainId() }) // initHook flag is activated only if both the hook and sudo validator are given // if the hook is given with regular plugins, then consider it as a hook for regular plugins const initHook = Boolean( - isKernelPluginManager(plugins) + isKernelPluginManager(plugins) ? plugins.hook && plugins.getIdentifier() === plugins.sudoValidator?.getIdentifier() @@ -427,12 +393,11 @@ export async function createKernelAccount< const generateInitCode = async () => { if (!accountImplementationAddress || !factoryAddress) throw new Error("Missing account logic address or factory address") - return getAccountInitCode({ + return getAccountInitCode({ index, factoryAddress, accountImplementationAddress, - metaFactoryAddress, - entryPoint: entryPointAddress, + entryPointVersion: entryPoint.version, kernelPluginManager, initHook, kernelVersion, @@ -441,23 +406,39 @@ export async function createKernelAccount< }) } - // Fetch account address and chain id - const accountAddress = - deployedAccountAddress ?? - (await getAccountAddress({ - client, - entryPoint: entryPointAddress, - initCodeProvider: generateInitCode - })) - - if (!accountAddress) throw new Error("Account address not found") - - let smartAccountDeployed = await isSmartAccountDeployed( - client, - accountAddress - ) + const getFactoryArgs = async () => { + return { + factory: + entryPoint.version === "0.6" + ? factoryAddress + : metaFactoryAddress, + factoryData: await generateInitCode() + } + } - return { + // Fetch account address + let accountAddress = + address ?? + (await (async () => { + const { factory, factoryData } = await getFactoryArgs() + + // Get the sender address based on the init code + return await getSenderAddress(client, { + factory, + factoryData, + entryPointAddress: entryPoint.address + }) + })()) + + const _entryPoint = { + address: entryPoint?.address ?? entryPoint07Address, + abi: ((entryPoint?.version ?? "0.7") === "0.6" + ? entryPoint06Abi + : entryPoint07Abi) as GetEntryPointAbi, + version: entryPoint?.version ?? "0.7" + } as const + + return toSmartAccount>({ kernelVersion, kernelPluginManager, generateInitCode, @@ -466,197 +447,170 @@ export async function createKernelAccount< accountAddress ) }, - ...toSmartAccount({ - address: accountAddress, - publicKey: accountAddress, - source: "kernelSmartAccount", - client, - entryPoint: entryPointAddress, - // Encode the deploy call data - async encodeDeployCallData(_tx) { - if (entryPointVersion === "v0.6") { - return encodeDeployCallDataV06(_tx) - } - return encodeDeployCallDataV07(_tx) - }, - async encodeCallData(_tx) { - const tx = _tx as KernelEncodeCallDataArgs - if ( - !Array.isArray(tx) && - (!tx.callType || tx.callType === "call") && - tx.to.toLowerCase() === accountAddress.toLowerCase() - ) { - return tx.data - } - if (entryPointVersion === "v0.6") { - return encodeCallDataEpV06(tx) - } - - if (plugins.hook) { - return encodeCallDataEpV07(tx, true) - } - return encodeCallDataEpV07(tx) - }, - async getFactory() { - if (smartAccountDeployed) return undefined - - smartAccountDeployed = await isSmartAccountDeployed( - client, - accountAddress - ) - - if (smartAccountDeployed) return undefined - - const entryPointVersion = - getEntryPointVersion(entryPointAddress) - if (entryPointVersion === "v0.6") { - return factoryAddress - } - if (!useMetaFactory) { - return factoryAddress - } - return metaFactoryAddress - }, + nonceKeyManager: createNonceManager({ + source: { get: () => 0, set: () => {} } + }), + client, + entryPoint: _entryPoint, + getFactoryArgs, + async getAddress() { + if (accountAddress) return accountAddress - async getFactoryData() { - if (smartAccountDeployed) return undefined + const { factory, factoryData } = await getFactoryArgs() - smartAccountDeployed = await isSmartAccountDeployed( - client, - accountAddress - ) + // Get the sender address based on the init code + accountAddress = await getSenderAddress(client, { + factory, + factoryData, + entryPointAddress: entryPoint.address + }) - if (smartAccountDeployed) return undefined + return accountAddress + }, + // Encode the deploy call data + async encodeDeployCallData(_tx) { + if (entryPoint.version === "0.6") { + return encodeDeployCallDataV06(_tx) + } + return encodeDeployCallDataV07(_tx) + }, + async encodeCalls(calls, callType) { + if ( + calls.length === 1 && + (!callType || callType === "call") && + calls[0].to.toLowerCase() === accountAddress.toLowerCase() + ) { + return calls[0].data ?? "0x" + } + if (entryPoint.version === "0.6") { + return encodeCallDataEpV06(calls, callType) + } - return parseFactoryAddressAndCallDataFromAccountInitCode( - await generateInitCode() - )[1] - }, - async signMessage({ message }) { - const messageHash = hashMessage(message) - const { - name, - chainId: metadataChainId, - version - } = await accountMetadata( - client, - accountAddress, - kernelVersion, - chainId - ) - const wrappedMessageHash = await eip712WrapHash(messageHash, { - name, - chainId: Number(metadataChainId), - version, - verifyingContract: accountAddress - }) - const signature = await kernelPluginManager.signMessage({ - message: { raw: wrappedMessageHash } - }) - - if ( - !hasKernelFeature( - KERNEL_FEATURES.ERC1271_WITH_VALIDATOR, - version - ) - ) { - return signature - } - - return concatHex([ - kernelPluginManager.getIdentifier(), - signature - ]) - }, - async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() - }, - async signTypedData(typedData) { - const types = { - EIP712Domain: getTypesForEIP712Domain({ - domain: typedData.domain - }), - ...typedData.types - } - - // Need to do a runtime validation check on addresses, byte ranges, integer ranges, etc - // as we can't statically check this with TypeScript. - validateTypedData({ - domain: typedData.domain, - message: typedData.message, - primaryType: typedData.primaryType, - types: types - } as TypedDataDefinition) - - const typedHash = hashTypedData(typedData) - - const { - name, - chainId: metadataChainId, + if (plugins.hook) { + return encodeCallDataEpV07(calls, callType, true) + } + return encodeCallDataEpV07(calls, callType) + }, + async sign({ hash }) { + return this.signMessage({ message: hash }) + }, + async signMessage({ message }) { + const messageHash = hashMessage(message) + const { + name, + chainId: metadataChainId, + version + } = await accountMetadata( + client, + accountAddress, + kernelVersion, + chainId + ) + const wrappedMessageHash = await eip712WrapHash(messageHash, { + name, + chainId: Number(metadataChainId), + version, + verifyingContract: accountAddress + }) + const signature = await kernelPluginManager.signMessage({ + message: { raw: wrappedMessageHash } + }) + + if ( + !hasKernelFeature( + KERNEL_FEATURES.ERC1271_WITH_VALIDATOR, version - } = await accountMetadata( - client, - accountAddress, - kernelVersion, - chainId ) - const wrappedMessageHash = await eip712WrapHash(typedHash, { - name, - chainId: Number(metadataChainId), - version, - verifyingContract: accountAddress - }) - const signature = await kernelPluginManager.signMessage({ - message: { raw: wrappedMessageHash } - }) - if ( - !hasKernelFeature( - KERNEL_FEATURES.ERC1271_WITH_VALIDATOR, - version - ) - ) { - return signature - } - return concatHex([ - kernelPluginManager.getIdentifier(), - signature - ]) - }, - // Get the nonce of the smart account - async getNonce(customNonceKey?: bigint) { - const key = await kernelPluginManager.getNonceKey( - accountAddress, - customNonceKey - ) - return getAccountNonce(client, { - sender: accountAddress, - entryPoint: entryPointAddress, - key - }) - }, - - // Sign a user operation - async signUserOperation(userOperation) { - return kernelPluginManager.signUserOperation(userOperation) - }, - - // Get simple dummy signature - async getDummySignature(userOperation) { - return kernelPluginManager.getDummySignature(userOperation) - }, + ) { + return signature + } - // Encode the init code - async getInitCode() { - if (smartAccountDeployed) return "0x" + return concatHex([kernelPluginManager.getIdentifier(), signature]) + }, + async signTypedData(typedData) { + const { + message, + primaryType, + types: _types, + domain + } = typedData as TypedDataDefinition + const types = { + EIP712Domain: getTypesForEIP712Domain({ + domain: domain + }), + ..._types + } - smartAccountDeployed = await isSmartAccountDeployed( - client, - accountAddress + // Need to do a runtime validation check on addresses, byte ranges, integer ranges, etc + // as we can't statically check this with TypeScript. + validateTypedData({ + domain: domain, + message: message, + primaryType: primaryType, + types: types + }) + + const typedHash = hashTypedData(typedData) + + const { + name, + chainId: metadataChainId, + version + } = await accountMetadata( + client, + accountAddress, + kernelVersion, + chainId + ) + const wrappedMessageHash = await eip712WrapHash(typedHash, { + name, + chainId: Number(metadataChainId), + version, + verifyingContract: accountAddress + }) + const signature = await kernelPluginManager.signMessage({ + message: { raw: wrappedMessageHash } + }) + if ( + !hasKernelFeature( + KERNEL_FEATURES.ERC1271_WITH_VALIDATOR, + version ) - - if (smartAccountDeployed) return "0x" - return generateInitCode() + ) { + return signature } - }) - } + return concatHex([kernelPluginManager.getIdentifier(), signature]) + }, + // Get the nonce of the smart account + async getNonce(_args) { + const key = await kernelPluginManager.getNonceKey( + accountAddress, + _args?.key + ) + return getAccountNonce(client, { + address: accountAddress, + entryPointAddress: entryPoint.address, + key + }) + }, + async getStubSignature(userOperation) { + if (!userOperation) { + throw new Error("No user operation provided") + } + return kernelPluginManager.getStubSignature( + userOperation as UserOperation + ) + }, + + // Sign a user operation + async signUserOperation(parameters) { + const { chainId = await getMemoizedChainId(), ...userOperation } = + parameters + return kernelPluginManager.signUserOperation({ + ...userOperation, + sender: userOperation.sender ?? (await this.getAddress()), + chainId + }) + } + }) as Promise> } diff --git a/packages/core/accounts/kernel/utils/account/ep0_6/encodeCallData.ts b/packages/core/accounts/kernel/utils/account/ep0_6/encodeCallData.ts index 87c876e0..e9b23249 100644 --- a/packages/core/accounts/kernel/utils/account/ep0_6/encodeCallData.ts +++ b/packages/core/accounts/kernel/utils/account/ep0_6/encodeCallData.ts @@ -1,30 +1,40 @@ -import type { EntryPoint } from "permissionless/types" -import type { KernelSmartAccount } from "../../../createKernelAccount.js" +import type { EntryPointVersion } from "viem/account-abstraction" +import type { CallType } from "../../../../../types/kernel.js" +import type { KernelSmartAccountImplementation } from "../../../createKernelAccount.js" import { encodeExecuteBatchCall } from "../../ep0_6/encodeExecuteBatchCall.js" import { encodeExecuteDelegateCall } from "../../ep0_6/encodeExecuteDelegateCall.js" import { encodeExecuteSingleCall } from "../../ep0_6/encodeExecuteSingleCall.js" export const encodeCallData = async < - entryPoint extends EntryPoint = EntryPoint + entryPointVersion extends EntryPointVersion = EntryPointVersion >( - tx: Parameters["encodeCallData"]>[0] + calls: Parameters< + KernelSmartAccountImplementation["encodeCalls"] + >[0], + callType?: CallType | undefined ) => { - if (Array.isArray(tx)) { - if (tx.some((t) => t.callType === "delegatecall")) { + if (calls.length > 1) { + if (callType === "delegatecall") { throw new Error("Cannot batch delegatecall") } - return encodeExecuteBatchCall(tx) + return encodeExecuteBatchCall(calls) + } + + const call = calls.length === 0 ? undefined : calls[0] + + if (!call) { + throw new Error("No calls to encode") } // Default to `call` - if (!tx.callType || tx.callType === "call") { - return encodeExecuteSingleCall(tx) + if (!callType || callType === "call") { + return encodeExecuteSingleCall(call) } - if (tx.callType === "delegatecall") { + if (callType === "delegatecall") { return encodeExecuteDelegateCall({ - to: tx.to, - data: tx.data + to: call.to, + data: call.data }) } diff --git a/packages/core/accounts/kernel/utils/account/ep0_6/encodeDeployCallData.ts b/packages/core/accounts/kernel/utils/account/ep0_6/encodeDeployCallData.ts index 7ad93adf..792dafd6 100644 --- a/packages/core/accounts/kernel/utils/account/ep0_6/encodeDeployCallData.ts +++ b/packages/core/accounts/kernel/utils/account/ep0_6/encodeDeployCallData.ts @@ -1,13 +1,16 @@ -import type { toSmartAccount } from "permissionless/accounts" import { encodeDeployData } from "viem" +import type { EntryPointVersion } from "viem/account-abstraction" import { safeCreateCallAddress } from "../../../../../constants.js" +import type { KernelSmartAccountImplementation } from "../../../createKernelAccount.js" import { encodeSafeCreateCall } from "../../common/encodeSafeCreateCall.js" import { encodeExecuteDelegateCall } from "../../ep0_6/encodeExecuteDelegateCall.js" import type { DelegateCallArgs } from "../../types.js" -export const encodeDeployCallData = ( +export const encodeDeployCallData = < + entryPointVersion extends EntryPointVersion = EntryPointVersion +>( tx: Parameters< - Parameters[0]["encodeDeployCallData"] + KernelSmartAccountImplementation["encodeDeployCallData"] >[0] ) => { const deployCalldataArgs: DelegateCallArgs = { diff --git a/packages/core/accounts/kernel/utils/account/ep0_6/encodeModuleInstallCallData.ts b/packages/core/accounts/kernel/utils/account/ep0_6/encodeModuleInstallCallData.ts index 74d5b130..ddc6bf11 100644 --- a/packages/core/accounts/kernel/utils/account/ep0_6/encodeModuleInstallCallData.ts +++ b/packages/core/accounts/kernel/utils/account/ep0_6/encodeModuleInstallCallData.ts @@ -1,4 +1,3 @@ -import type { ENTRYPOINT_ADDRESS_V06_TYPE } from "permissionless/types/entrypoint" import { type Address, type Hex, encodeFunctionData } from "viem" import type { PluginInstallData } from "../../../../../types/kernel.js" import { KernelAccountAbi } from "../../../abi/KernelAccountAbi.js" @@ -14,22 +13,26 @@ export const encodeModuleInstallCallData = async ({ validator }: { accountAddress: Address -} & PluginInstallData): Promise => { - return encodeCallData({ - to: accountAddress, - value: 0n, - data: encodeFunctionData({ - abi: KernelAccountAbi, - functionName: "setExecution", - args: [ - selector, - executor, - validator, - validUntil, - validAfter, - enableData - ] - }), - callType: "call" - }) +} & PluginInstallData<"0.6">): Promise => { + return encodeCallData( + [ + { + to: accountAddress, + value: 0n, + data: encodeFunctionData({ + abi: KernelAccountAbi, + functionName: "setExecution", + args: [ + selector, + executor, + validator, + validUntil, + validAfter, + enableData + ] + }) + } + ], + "call" + ) } diff --git a/packages/core/accounts/kernel/utils/account/ep0_7/encodeCallData.ts b/packages/core/accounts/kernel/utils/account/ep0_7/encodeCallData.ts index 2122a111..a653303a 100644 --- a/packages/core/accounts/kernel/utils/account/ep0_7/encodeCallData.ts +++ b/packages/core/accounts/kernel/utils/account/ep0_7/encodeCallData.ts @@ -1,23 +1,27 @@ -import type { EntryPoint } from "permissionless/types" +import type { EntryPointVersion } from "viem/account-abstraction" import { EXEC_TYPE } from "../../../../../constants.js" -import type { KernelSmartAccount } from "../../../createKernelAccount.js" +import type { CallType } from "../../../../../types/kernel.js" +import type { KernelSmartAccountImplementation } from "../../../createKernelAccount.js" import { encodeExecuteBatchCall } from "../../ep0_7/encodeExecuteBatchCall.js" import { encodeExecuteDelegateCall } from "../../ep0_7/encodeExecuteDelegateCall.js" import { encodeExecuteSingleCall } from "../../ep0_7/encodeExecuteSingleCall.js" export const encodeCallData = async < - entryPoint extends EntryPoint = EntryPoint + entryPointVersion extends EntryPointVersion = EntryPointVersion >( - tx: Parameters["encodeCallData"]>[0], + calls: Parameters< + KernelSmartAccountImplementation["encodeCalls"] + >[0], + callType?: CallType | undefined, includeHooks?: boolean ) => { - if (Array.isArray(tx)) { - if (tx.some((t) => t.callType === "delegatecall")) { + if (calls.length > 1) { + if (callType === "delegatecall") { throw new Error("Cannot batch delegatecall") } // Encode a batched call return encodeExecuteBatchCall( - tx, + calls, { execType: EXEC_TYPE.DEFAULT }, @@ -25,10 +29,16 @@ export const encodeCallData = async < ) } + const call = calls.length === 0 ? undefined : calls[0] + + if (!call) { + throw new Error("No calls to encode") + } + // Default to `call` - if (!tx.callType || tx.callType === "call") { + if (!callType || callType === "call") { return encodeExecuteSingleCall( - tx, + call, { execType: EXEC_TYPE.DEFAULT }, @@ -36,9 +46,9 @@ export const encodeCallData = async < ) } - if (tx.callType === "delegatecall") { + if (callType === "delegatecall") { return encodeExecuteDelegateCall( - { to: tx.to, data: tx.data }, + { to: call.to, data: call.data }, { execType: EXEC_TYPE.DEFAULT }, diff --git a/packages/core/accounts/kernel/utils/account/ep0_7/encodeDeployCallData.ts b/packages/core/accounts/kernel/utils/account/ep0_7/encodeDeployCallData.ts index c457faac..16a72369 100644 --- a/packages/core/accounts/kernel/utils/account/ep0_7/encodeDeployCallData.ts +++ b/packages/core/accounts/kernel/utils/account/ep0_7/encodeDeployCallData.ts @@ -1,13 +1,16 @@ -import type { toSmartAccount } from "permissionless/accounts" import { encodeDeployData } from "viem" +import type { EntryPointVersion } from "viem/account-abstraction" import { EXEC_TYPE, safeCreateCallAddress } from "../../../../../constants.js" +import type { KernelSmartAccountImplementation } from "../../../createKernelAccount.js" import { encodeSafeCreateCall } from "../../common/encodeSafeCreateCall.js" import { encodeExecuteDelegateCall } from "../../ep0_7/encodeExecuteDelegateCall.js" import type { DelegateCallArgs } from "../../types.js" -export const encodeDeployCallData = ( +export const encodeDeployCallData = < + entryPointVersion extends EntryPointVersion = EntryPointVersion +>( tx: Parameters< - Parameters[0]["encodeDeployCallData"] + KernelSmartAccountImplementation["encodeDeployCallData"] >[0] ) => { const deployCalldataArgs: DelegateCallArgs = { diff --git a/packages/core/accounts/kernel/utils/common/accountMetadata.ts b/packages/core/accounts/kernel/utils/common/accountMetadata.ts index bb29e2a1..671ae061 100644 --- a/packages/core/accounts/kernel/utils/common/accountMetadata.ts +++ b/packages/core/accounts/kernel/utils/common/accountMetadata.ts @@ -1,8 +1,6 @@ import { type Address, - type Chain, type Client, - type Transport, decodeFunctionResult, encodeFunctionData, publicActions @@ -16,11 +14,8 @@ export type AccountMetadata = { version: string chainId: bigint } -export const accountMetadata = async < - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined ->( - client: Client, +export const accountMetadata = async ( + client: Client, accountAddress: Address, kernelVersion: KERNEL_VERSION_TYPE, chainId?: number diff --git a/packages/core/accounts/kernel/utils/common/getActionSelector.ts b/packages/core/accounts/kernel/utils/common/getActionSelector.ts index 4be53ae0..3bf70a0e 100644 --- a/packages/core/accounts/kernel/utils/common/getActionSelector.ts +++ b/packages/core/accounts/kernel/utils/common/getActionSelector.ts @@ -1,16 +1,16 @@ -import type { EntryPointVersion } from "permissionless/types/entrypoint" import { type Hex, getAbiItem, toFunctionSelector } from "viem" +import type { EntryPointVersion } from "viem/account-abstraction" import { KernelAccountAbi } from "../../abi/KernelAccountAbi.js" import { KernelV3AccountAbi } from "../../abi/kernel_v_3_0_0/KernelAccountAbi.js" export const getActionSelector = ( entryPointVersion: EntryPointVersion ): Hex => { - if (entryPointVersion === "v0.6") { + if (entryPointVersion === "0.6") { return toFunctionSelector( getAbiItem({ abi: KernelAccountAbi, name: "execute" }) ) - } else if (entryPointVersion === "v0.7") { + } else if (entryPointVersion === "0.7") { return toFunctionSelector( getAbiItem({ abi: KernelV3AccountAbi, name: "execute" }) ) diff --git a/packages/core/accounts/kernel/utils/ep0_6/encodeExecuteBatchCall.ts b/packages/core/accounts/kernel/utils/ep0_6/encodeExecuteBatchCall.ts index 7a4f1f49..f4f7cba7 100644 --- a/packages/core/accounts/kernel/utils/ep0_6/encodeExecuteBatchCall.ts +++ b/packages/core/accounts/kernel/utils/ep0_6/encodeExecuteBatchCall.ts @@ -2,7 +2,7 @@ import { encodeFunctionData } from "viem" import { KernelExecuteAbi } from "../../abi/KernelAccountAbi.js" import type { CallArgs } from "../types.js" -export const encodeExecuteBatchCall = (args: CallArgs[]) => { +export const encodeExecuteBatchCall = (args: readonly CallArgs[]) => { return encodeFunctionData({ abi: KernelExecuteAbi, functionName: "executeBatch", @@ -10,8 +10,8 @@ export const encodeExecuteBatchCall = (args: CallArgs[]) => { args.map((arg) => { return { to: arg.to, - value: arg.value, - data: arg.data + value: arg.value || 0n, + data: arg.data || "0x" } }) ] diff --git a/packages/core/accounts/kernel/utils/ep0_6/encodeExecuteDelegateCall.ts b/packages/core/accounts/kernel/utils/ep0_6/encodeExecuteDelegateCall.ts index d27ce726..f84a6c89 100644 --- a/packages/core/accounts/kernel/utils/ep0_6/encodeExecuteDelegateCall.ts +++ b/packages/core/accounts/kernel/utils/ep0_6/encodeExecuteDelegateCall.ts @@ -6,6 +6,6 @@ export const encodeExecuteDelegateCall = (args: DelegateCallArgs) => { return encodeFunctionData({ abi: KernelExecuteAbi, functionName: "executeDelegateCall", - args: [args.to, args.data] + args: [args.to, args.data || "0x"] }) } diff --git a/packages/core/accounts/kernel/utils/ep0_6/encodeExecuteSingleCall.ts b/packages/core/accounts/kernel/utils/ep0_6/encodeExecuteSingleCall.ts index d4edf604..274f9e18 100644 --- a/packages/core/accounts/kernel/utils/ep0_6/encodeExecuteSingleCall.ts +++ b/packages/core/accounts/kernel/utils/ep0_6/encodeExecuteSingleCall.ts @@ -6,6 +6,6 @@ export const encodeExecuteSingleCall = (args: CallArgs) => { return encodeFunctionData({ abi: KernelExecuteAbi, functionName: "execute", - args: [args.to, args.value, args.data, 0] + args: [args.to, args.value || 0n, args.data || "0x", 0] }) } diff --git a/packages/core/accounts/kernel/utils/ep0_7/encodeExecuteBatchCall.ts b/packages/core/accounts/kernel/utils/ep0_7/encodeExecuteBatchCall.ts index ab40843f..24779e18 100644 --- a/packages/core/accounts/kernel/utils/ep0_7/encodeExecuteBatchCall.ts +++ b/packages/core/accounts/kernel/utils/ep0_7/encodeExecuteBatchCall.ts @@ -7,7 +7,7 @@ import { } from "./encodeExecuteCall.js" export const encodeExecuteBatchCall = ( - args: CallArgs[], + args: readonly CallArgs[], options: Omit, includeHooks?: boolean ) => { @@ -36,8 +36,8 @@ export const encodeExecuteBatchCall = ( args.map((arg) => { return { target: arg.to, - value: arg.value, - callData: arg.data + value: arg.value || 0n, + callData: arg.data || "0x" } }) ] diff --git a/packages/core/accounts/kernel/utils/ep0_7/encodeExecuteCall.ts b/packages/core/accounts/kernel/utils/ep0_7/encodeExecuteCall.ts index 404ce2e5..ccccd3bd 100644 --- a/packages/core/accounts/kernel/utils/ep0_7/encodeExecuteCall.ts +++ b/packages/core/accounts/kernel/utils/ep0_7/encodeExecuteCall.ts @@ -33,9 +33,9 @@ export const encodeExecuteCall = ( calldata = concatHex([ args.to, options.callType !== CALL_TYPE.DELEGATE_CALL - ? toHex(args.value, { size: 32 }) + ? toHex(args.value || 0n, { size: 32 }) : "0x", // No value if delegate call - args.data + args.data || "0x" ]) } diff --git a/packages/core/accounts/kernel/utils/plugins/ep0_6/getEncodedPluginsData.ts b/packages/core/accounts/kernel/utils/plugins/ep0_6/getEncodedPluginsData.ts index 8b2413cc..17580dca 100644 --- a/packages/core/accounts/kernel/utils/plugins/ep0_6/getEncodedPluginsData.ts +++ b/packages/core/accounts/kernel/utils/plugins/ep0_6/getEncodedPluginsData.ts @@ -1,7 +1,3 @@ -import type { - ENTRYPOINT_ADDRESS_V06_TYPE, - EntryPoint -} from "permissionless/types/entrypoint" import { type Address, type Hex, concat, pad, toHex } from "viem" import { type PluginValidityData, @@ -9,9 +5,7 @@ import { } from "../../../../../types/index.js" import type { Kernel2_0_plugins } from "./getPluginsEnableTypedData.js" -export const getEncodedPluginsData = async < - entryPoint extends EntryPoint = ENTRYPOINT_ADDRESS_V06_TYPE ->({ +export const getEncodedPluginsData = async ({ accountAddress, enableSignature, action, @@ -21,7 +15,7 @@ export const getEncodedPluginsData = async < }: { accountAddress: Address enableSignature: Hex -} & Kernel2_0_plugins & +} & Kernel2_0_plugins & PluginValidityData) => { const enableData = await validator.getEnableData(accountAddress) const enableDataLength = enableData.length / 2 - 1 diff --git a/packages/core/accounts/kernel/utils/plugins/ep0_6/getPluginsEnableTypedData.ts b/packages/core/accounts/kernel/utils/plugins/ep0_6/getPluginsEnableTypedData.ts index 84adf872..7001502e 100644 --- a/packages/core/accounts/kernel/utils/plugins/ep0_6/getPluginsEnableTypedData.ts +++ b/packages/core/accounts/kernel/utils/plugins/ep0_6/getPluginsEnableTypedData.ts @@ -1,7 +1,3 @@ -import type { - ENTRYPOINT_ADDRESS_V06_TYPE, - EntryPoint -} from "permissionless/types/entrypoint" import { type Address, type CustomSource, @@ -16,13 +12,11 @@ import type { PluginValidityData } from "../../../../../types/index.js" -export type Kernel2_0_plugins = { - validator: KernelValidator +export type Kernel2_0_plugins = { + validator: KernelValidator action: Action } -export const getPluginsEnableTypedData = async < - entryPoint extends EntryPoint = ENTRYPOINT_ADDRESS_V06_TYPE ->({ +export const getPluginsEnableTypedData = async ({ accountAddress, chainId, kernelVersion, @@ -34,7 +28,7 @@ export const getPluginsEnableTypedData = async < accountAddress: Address chainId: number kernelVersion: string -} & Kernel2_0_plugins & +} & Kernel2_0_plugins & PluginValidityData): Promise< Parameters[0] > => { diff --git a/packages/core/accounts/kernel/utils/plugins/ep0_7/getPluginsEnableTypedData.ts b/packages/core/accounts/kernel/utils/plugins/ep0_7/getPluginsEnableTypedData.ts index 98663d41..8bac8adb 100644 --- a/packages/core/accounts/kernel/utils/plugins/ep0_7/getPluginsEnableTypedData.ts +++ b/packages/core/accounts/kernel/utils/plugins/ep0_7/getPluginsEnableTypedData.ts @@ -1,7 +1,3 @@ -import type { - ENTRYPOINT_ADDRESS_V07_TYPE, - EntryPoint -} from "permissionless/types/entrypoint" import { type Address, type CustomSource, @@ -15,9 +11,7 @@ import { CALL_TYPE, VALIDATOR_TYPE } from "../../../../../constants.js" import type { KernelValidatorHook } from "../../../../../types/kernel.js" import type { Kernel2_0_plugins } from "../ep0_6/getPluginsEnableTypedData.js" -export const getPluginsEnableTypedData = async < - entryPoint extends EntryPoint = ENTRYPOINT_ADDRESS_V07_TYPE ->({ +export const getPluginsEnableTypedData = async ({ accountAddress, chainId, kernelVersion, @@ -31,7 +25,7 @@ export const getPluginsEnableTypedData = async < kernelVersion: string validatorNonce: number hook?: KernelValidatorHook -} & Kernel2_0_plugins): Promise< +} & Kernel2_0_plugins): Promise< Parameters[0] > => { return { diff --git a/packages/core/accounts/kernel/utils/types.ts b/packages/core/accounts/kernel/utils/types.ts index b3ab286e..42462ca4 100644 --- a/packages/core/accounts/kernel/utils/types.ts +++ b/packages/core/accounts/kernel/utils/types.ts @@ -2,7 +2,7 @@ import type { Address, Hex } from "viem" export type CallArgs = { to: Address - data: Hex - value: bigint + data?: Hex + value?: bigint } export type DelegateCallArgs = Omit diff --git a/packages/core/accounts/kernel/v1/createKernelAccountV1.ts b/packages/core/accounts/kernel/v1/createKernelAccountV1.ts index 62ed7e84..d6a90ea8 100644 --- a/packages/core/accounts/kernel/v1/createKernelAccountV1.ts +++ b/packages/core/accounts/kernel/v1/createKernelAccountV1.ts @@ -1,55 +1,57 @@ import { - ENTRYPOINT_ADDRESS_V06, - getAccountNonce, - getEntryPointVersion, - getSenderAddress, - getUserOperationHash, - isSmartAccountDeployed -} from "permissionless" -import { - SignTransactionNotSupportedBySmartAccount, - type SmartAccountSigner -} from "permissionless/accounts" -import type { - ENTRYPOINT_ADDRESS_V06_TYPE, - EntryPoint -} from "permissionless/types" -import { + type Account, type Address, + type Assign, type Chain, type Client, - type Hash, + type EIP1193Provider, type Hex, type LocalAccount, - type PublicActions, - type PublicRpcSchema, + type OneOf, type Transport, type TypedData, type TypedDataDefinition, - concatHex, + type WalletClient, + createNonceManager, encodeFunctionData } from "viem" +import { + type SmartAccount, + type SmartAccountImplementation, + entryPoint06Abi, + entryPoint06Address, + getUserOperationHash, + toSmartAccount +} from "viem/account-abstraction" import { toAccount } from "viem/accounts" -import { getChainId, signMessage, signTypedData } from "viem/actions" -import type { KernelEncodeCallDataArgs } from "../../../types/kernel.js" -import { wrapSignatureWith6492 } from "../../utils/6492.js" -import { parseFactoryAddressAndCallDataFromAccountInitCode } from "../../utils/index.js" +import { getChainId, signMessage } from "viem/actions" +import { + getAccountNonce, + getSenderAddress +} from "../../../actions/public/index.js" +import type { CallType, GetEntryPointAbi } from "../../../types/kernel.js" import { MULTISEND_ADDRESS, encodeMultiSend, multiSendAbi } from "../../utils/multisend.js" -import type { KernelSmartAccount } from "../createKernelAccount.js" -export type KernelSmartAccountV1< - entryPoint extends EntryPoint, - transport extends Transport = Transport, - chain extends Chain | undefined = Chain | undefined -> = Omit< - KernelSmartAccount, - "kernelPluginManager" | "kernelVersion" +export type KernelSmartAccountV1Implementation = Assign< + SmartAccountImplementation, "0.6">, + { + sign: NonNullable + encodeCalls: ( + calls: Parameters[0], + callType?: CallType | undefined + ) => Promise + generateInitCode: () => Promise + encodeModuleInstallCallData: () => Promise + } > +export type CreateKernelAccountV1ReturnType = + SmartAccount + const createAccountAbi = [ { inputs: [ @@ -92,43 +94,33 @@ const KERNEL_V1_ADDRESSES: { ENTRYPOINT_V0_6: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" } -export async function createKernelAccountV1< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TSource extends string = string, - TAddress extends Address = Address ->( - client: Client< - TTransport, - TChain, - undefined, - PublicRpcSchema, - PublicActions - >, +export async function createKernelAccountV1( + client: Client, { signer, - entryPoint: entryPointAddress, + address, + entryPoint, index = 0n }: { - signer: SmartAccountSigner - entryPoint: entryPoint + signer: OneOf< + | EIP1193Provider + | WalletClient + | LocalAccount + > + entryPoint: { + address: Address + version: "0.6" + } + address?: Address index?: bigint } -): Promise> { - const entryPointVersion = getEntryPointVersion(entryPointAddress) - - if ( - entryPointVersion !== "v0.6" || - entryPointAddress !== ENTRYPOINT_ADDRESS_V06 - ) { - throw new Error("Only EntryPoint 0.6 is supported") - } - +): Promise { const viemSigner: LocalAccount = { ...signer, signTransaction: (_, __) => { - throw new SignTransactionNotSupportedBySmartAccount() + throw new Error( + "Smart account signer doesn't need to sign transactions" + ) } } as LocalAccount @@ -136,43 +128,45 @@ export async function createKernelAccountV1< const chainId = await getChainId(client) const generateInitCode = async (): Promise => { - return concatHex([ - KERNEL_V1_ADDRESSES.FACTORY_ADDRESS, - encodeFunctionData({ - abi: createAccountAbi, - functionName: "createAccount", - args: [signer.address, index] - }) - ]) as Hex + return encodeFunctionData({ + abi: createAccountAbi, + functionName: "createAccount", + args: [signer.address, index] + }) } - const initCode = await generateInitCode() - const accountAddress = await getSenderAddress( - client, - { - initCode, - entryPoint: entryPointAddress + const getFactoryArgs = async () => { + return { + factory: KERNEL_V1_ADDRESSES.FACTORY_ADDRESS, + factoryData: await generateInitCode() } - ) + } - if (!accountAddress) throw new Error("Account address not found") + // Fetch account address and chain id + let accountAddress = + address ?? + (await (async () => { + const { factory, factoryData } = await getFactoryArgs() + + // Get the sender address based on the init code + return await getSenderAddress(client, { + factory, + factoryData, + entryPointAddress: entryPoint.address + }) + })()) - let smartAccountDeployed = await isSmartAccountDeployed( - client, - accountAddress - ) + if (!accountAddress) throw new Error("Account address not found") const account = toAccount({ address: accountAddress, async signMessage({ message }) { - const [isDeployed, signature] = await Promise.all([ - isSmartAccountDeployed(client, accountAddress), - signer.signMessage({ message }) - ]) - return create6492Signature(isDeployed, signature) + return viemSigner.signMessage({ message }) }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new Error( + "Smart account signer doesn't need to sign transactions" + ) }, async signTypedData< const TTypedData extends TypedData | Record, @@ -180,94 +174,71 @@ export async function createKernelAccountV1< | keyof TTypedData | "EIP712Domain" = keyof TTypedData >(typedData: TypedDataDefinition) { - return signTypedData( - client, - { - account: viemSigner, - ...typedData - } - ) + return viemSigner.signTypedData(typedData) } }) - // const isAccountDeployed = async (): Promise => { - // const contractCode = await getBytecode(client, { - // address: accountAddress - // }) - - // return (contractCode?.length ?? 0) > 2 - // } - - const create6492Signature = async ( - isDeployed: boolean, - signature: Hash - ): Promise => { - if (isDeployed) { - return signature - } - - const [factoryAddress, factoryCalldata] = - parseFactoryAddressAndCallDataFromAccountInitCode( - await generateInitCode() - ) - - return wrapSignatureWith6492({ - factoryAddress, - factoryCalldata, - signature - }) + const _entryPoint = { + address: entryPoint?.address ?? entryPoint06Address, + abi: entryPoint06Abi, + version: entryPoint?.version ?? "0.6" } - return { + return toSmartAccount({ ...account, generateInitCode, encodeModuleInstallCallData: async () => { throw new Error("Not implemented") }, - client: client, - publicKey: accountAddress, - entryPoint: entryPointAddress, - source: "kernelSmartAccount", - async getFactory() { - if (smartAccountDeployed) return undefined - - smartAccountDeployed = await isSmartAccountDeployed( - client, - accountAddress - ) - - if (smartAccountDeployed) return undefined - - return KERNEL_V1_ADDRESSES.FACTORY_ADDRESS + nonceKeyManager: createNonceManager({ + source: { get: () => 0, set: () => {} } + }), + async sign({ hash }) { + return account.signMessage({ message: hash }) + }, + async signMessage({ message }) { + return account.signMessage({ message }) }, + async signTypedData< + const TTypedData extends TypedData | Record, + TPrimaryType extends + | keyof TTypedData + | "EIP712Domain" = keyof TTypedData + >(typedData: TypedDataDefinition) { + return viemSigner.signTypedData(typedData) + }, + async getAddress() { + if (accountAddress) return accountAddress - async getFactoryData() { - if (smartAccountDeployed) return undefined + const { factory, factoryData } = await getFactoryArgs() - smartAccountDeployed = await isSmartAccountDeployed( - client, - accountAddress - ) - - if (smartAccountDeployed) return undefined + // Get the sender address based on the init code + accountAddress = await getSenderAddress(client, { + factory, + factoryData, + entryPointAddress: entryPoint.address + }) - return parseFactoryAddressAndCallDataFromAccountInitCode( - await generateInitCode() - )[1] + return accountAddress }, + getFactoryArgs, + client: client, + entryPoint: _entryPoint, async getNonce() { return getAccountNonce(client, { - sender: accountAddress, - entryPoint: entryPointAddress + address: accountAddress, + entryPointAddress: entryPoint.address }) }, async signUserOperation(userOperation) { const hash = getUserOperationHash({ userOperation: { ...userOperation, + sender: userOperation.sender ?? (await this.getAddress()), signature: "0x" }, - entryPoint: entryPointAddress, + entryPointAddress: entryPoint.address, + entryPointVersion: entryPoint.version, chainId: chainId }) const signature = await signMessage(client, { @@ -276,26 +247,27 @@ export async function createKernelAccountV1< }) return signature }, - async getInitCode() { - if (smartAccountDeployed) return "0x" - - smartAccountDeployed = await isSmartAccountDeployed( - client, - accountAddress - ) - - if (smartAccountDeployed) return "0x" - return generateInitCode() - }, - async encodeCallData(_tx) { - const tx = _tx as KernelEncodeCallDataArgs - - if (Array.isArray(tx)) { + // async getInitCode() { + // if (smartAccountDeployed) return "0x" + + // smartAccountDeployed = await isSmartAccountDeployed( + // client, + // accountAddress + // ) + + // if (smartAccountDeployed) return "0x" + // return generateInitCode() + // }, + async encodeCalls(calls, callType): Promise { + if (calls.length > 1) { + if (callType === "delegatecall") { + throw new Error("Cannot batch delegatecall") + } // Encode a batched call using multiSend const multiSendCallData = encodeFunctionData({ abi: multiSendAbi, functionName: "multiSend", - args: [encodeMultiSend(tx)] + args: [encodeMultiSend(calls)] }) return encodeFunctionData({ @@ -305,34 +277,37 @@ export async function createKernelAccountV1< }) } + const call = calls.length === 0 ? undefined : calls[0] + + if (!call) { + throw new Error("No calls to encode") + } + // Default to `call` - if (!tx.callType || tx.callType === "call") { - if (tx.to.toLowerCase() === accountAddress.toLowerCase()) { - return tx.data + if (!callType || callType === "call") { + if (call.to.toLowerCase() === accountAddress.toLowerCase()) { + return call.data || "0x" } return encodeFunctionData({ abi: executeAndRevertAbi, functionName: "executeAndRevert", - args: [tx.to, tx.value || 0n, tx.data, 0n] + args: [call.to, call.value || 0n, call.data, 0n] }) } - if (tx.callType === "delegatecall") { + if (callType === "delegatecall") { return encodeFunctionData({ abi: executeAndRevertAbi, functionName: "executeAndRevert", - args: [tx.to, tx.value || 0n, tx.data, 1n] + args: [call.to, call.value || 0n, call.data, 1n] }) } throw new Error("Invalid call type") }, - async encodeDeployCallData() { - return "0x" - }, - async getDummySignature() { + async getStubSignature() { return "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c" } - } + }) } diff --git a/packages/core/accounts/kernel/v2/createKernelAccountV0_2.ts b/packages/core/accounts/kernel/v2/createKernelAccountV0_2.ts index 443273a8..0df963c2 100644 --- a/packages/core/accounts/kernel/v2/createKernelAccountV0_2.ts +++ b/packages/core/accounts/kernel/v2/createKernelAccountV0_2.ts @@ -1,43 +1,36 @@ -import { - getAccountNonce, - getEntryPointVersion, - getSenderAddress, - isSmartAccountDeployed -} from "permissionless" -import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" -import type { - ENTRYPOINT_ADDRESS_V06_TYPE, - EntryPoint -} from "permissionless/types" import { type Address, - type Chain, type Client, type EncodeDeployDataParameters, - type Hash, type Hex, - type PublicActions, - type PublicRpcSchema, - type Transport, type TypedDataDefinition, concatHex, + createNonceManager, encodeDeployData, encodeFunctionData, getTypesForEIP712Domain, - hashMessage, hashTypedData, parseAbi, validateTypedData } from "viem" +import { + type UserOperation, + entryPoint06Abi, + entryPoint06Address, + toSmartAccount +} from "viem/account-abstraction" import { toAccount } from "viem/accounts" +import { getChainId } from "viem/actions" +import { getAction } from "viem/utils" +import { + getAccountNonce, + getSenderAddress +} from "../../../actions/public/index.js" import type { GetKernelVersion, - KernelEncodeCallDataArgs, KernelPluginManager, KernelPluginManagerParams } from "../../../types/kernel.js" -import { wrapSignatureWith6492 } from "../../utils/6492.js" -import { parseFactoryAddressAndCallDataFromAccountInitCode } from "../../utils/index.js" import { MULTISEND_ADDRESS, encodeMultiSend, @@ -47,7 +40,10 @@ import { isKernelPluginManager, toKernelPluginManager } from "../../utils/toKernelPluginManager.js" -import type { KernelSmartAccount } from "../createKernelAccount.js" +import type { + CreateKernelAccountReturnType, + KernelSmartAccountImplementation +} from "../createKernelAccount.js" import { KernelAccountV2Abi } from "./abi/KernelAccountV2Abi.js" import { KernelFactoryV2Abi } from "./abi/KernelFactoryV2Abi.js" @@ -99,35 +95,6 @@ const getAccountInitCode = async ({ ]) } -/** - * Check the validity of an existing account address, or fetch the pre-deterministic account address for a kernel smart wallet - * @param client - * @param entryPoint - * @param initCodeProvider - */ -const getAccountAddress = async < - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined ->({ - client, - entryPoint: entryPointAddress, - initCodeProvider -}: { - client: Client - initCodeProvider: () => Promise - entryPoint: entryPoint -}): Promise
=> { - // Find the init code for this account - const initCode = await initCodeProvider() - - // Get the sender address based on the init code - return getSenderAddress(client, { - initCode, - entryPoint: entryPointAddress as ENTRYPOINT_ADDRESS_V06_TYPE - }) -} - /** * Build a kernel smart account from a private key, that use the ECDSA signer behind the scene * @param client @@ -138,43 +105,31 @@ const getAccountAddress = async < * @param ecdsaValidatorAddress * @param deployedAccountAddress */ -export async function createKernelAccountV0_2< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined ->( - client: Client< - TTransport, - TChain, - undefined, - PublicRpcSchema, - PublicActions - >, +export async function createKernelAccountV0_2( + client: Client, { plugins, - entryPoint: entryPointAddress, + entryPoint, index = 0n, factoryAddress = KERNEL_ADDRESSES.FACTORY_ADDRESS, - deployedAccountAddress + address }: { plugins: | Omit< - KernelPluginManagerParams, + KernelPluginManagerParams<"0.6">, "entryPoint" | "kernelVersion" > - | KernelPluginManager - entryPoint: entryPoint + | KernelPluginManager<"0.6"> + entryPoint: { + address: Address + version: "0.6" + } index?: bigint factoryAddress?: Address - deployedAccountAddress?: Address + address?: Address } -): Promise> { - const entryPointVersion = getEntryPointVersion(entryPointAddress) - - if (entryPointVersion !== "v0.6") { - throw new Error("Only EntryPoint 0.6 is supported") - } - const kernelPluginManager = isKernelPluginManager(plugins) +): Promise> { + const kernelPluginManager = isKernelPluginManager<"0.6">(plugins) ? plugins : await toKernelPluginManager(client, { sudo: plugins.sudo, @@ -182,7 +137,7 @@ export async function createKernelAccountV0_2< action: plugins.action, pluginEnableSignature: plugins.pluginEnableSignature, kernelVersion: "0.0.2", - entryPoint: entryPointAddress + entryPoint }) // Helper to generate the init code for the smart account const generateInitCode = async () => { @@ -196,97 +151,86 @@ export async function createKernelAccountV0_2< }) } - // Fetch account address and chain id - const accountAddress = - deployedAccountAddress ?? - (await getAccountAddress({ - client, - entryPoint: entryPointAddress, - initCodeProvider: generateInitCode - })) + const getFactoryArgs = async () => { + return { + factory: factoryAddress, + factoryData: await generateInitCode() + } + } - if (!accountAddress) throw new Error("Account address not found") + // Fetch account address + let accountAddress = + address ?? + (await (async () => { + const { factory, factoryData } = await getFactoryArgs() + + // Get the sender address based on the init code + return await getSenderAddress(client, { + factory, + factoryData, + entryPointAddress: entryPoint.address + }) + })()) // Build the EOA Signer const account = toAccount({ address: accountAddress, async signMessage({ message }) { - const messageHash = hashMessage(message) - const [isDeployed, signature] = await Promise.all([ - isSmartAccountDeployed(client, accountAddress), - kernelPluginManager.signMessage({ - message: { - raw: messageHash - } - }) - ]) - return create6492Signature(isDeployed, signature) + return kernelPluginManager.signMessage({ + message + }) }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new Error( + "Smart account signer doesn't need to sign transactions" + ) }, async signTypedData(typedData) { + const _typedData = typedData as TypedDataDefinition const types = { EIP712Domain: getTypesForEIP712Domain({ - domain: typedData.domain + domain: _typedData.domain }), - ...typedData.types + ..._typedData.types } // Need to do a runtime validation check on addresses, byte ranges, integer ranges, etc // as we can't statically check this with TypeScript. validateTypedData({ - domain: typedData.domain, - message: typedData.message, - primaryType: typedData.primaryType, + domain: _typedData.domain, + message: _typedData.message, + primaryType: _typedData.primaryType, types: types - } as TypedDataDefinition) + }) const typedHash = hashTypedData(typedData) - const [isDeployed, signature] = await Promise.all([ - isSmartAccountDeployed(client, accountAddress), - kernelPluginManager.signMessage({ - message: { - raw: typedHash - } - }) - ]) - return create6492Signature(isDeployed, signature) + return kernelPluginManager.signMessage({ + message: { + raw: typedHash + } + }) } }) - let smartAccountDeployed = await isSmartAccountDeployed( - client, - accountAddress - ) - - const create6492Signature = async ( - isDeployed: boolean, - signature: Hash - ): Promise => { - if (isDeployed) { - return signature - } - - const [factoryAddress, factoryCalldata] = - parseFactoryAddressAndCallDataFromAccountInitCode( - await generateInitCode() - ) - - return wrapSignatureWith6492({ - factoryAddress, - factoryCalldata, - signature - }) + const _entryPoint = { + address: entryPoint?.address ?? entryPoint06Address, + abi: entryPoint06Abi, + version: entryPoint?.version ?? "0.6" + } + let chainId: number + + const getMemoizedChainId = async () => { + if (chainId) return chainId + chainId = client.chain + ? client.chain.id + : await getAction(client, getChainId, "getChainId")({}) + return chainId } - return { - ...account, - kernelVersion: "0.0.2" as GetKernelVersion, + return toSmartAccount>({ + kernelVersion: "0.0.2" as GetKernelVersion<"0.6">, client: client, - publicKey: accountAddress, - entryPoint: entryPointAddress, - source: "kernelSmartAccount", + entryPoint: _entryPoint, kernelPluginManager, generateInitCode, encodeModuleInstallCallData: async () => { @@ -294,65 +238,56 @@ export async function createKernelAccountV0_2< accountAddress ) }, - async getFactory() { - if (smartAccountDeployed) return undefined - - smartAccountDeployed = await isSmartAccountDeployed( - client, - accountAddress - ) - - if (smartAccountDeployed) return undefined - - return factoryAddress + nonceKeyManager: createNonceManager({ + source: { get: () => 0, set: () => {} } + }), + async sign({ hash }) { + return account.signMessage({ message: hash }) }, + async signMessage(message) { + return account.signMessage(message) + }, + async signTypedData(typedData) { + return account.signTypedData(typedData) + }, + getFactoryArgs, + async getAddress() { + if (accountAddress) return accountAddress - async getFactoryData() { - if (smartAccountDeployed) return undefined - - smartAccountDeployed = await isSmartAccountDeployed( - client, - accountAddress - ) + const { factory, factoryData } = await getFactoryArgs() - if (smartAccountDeployed) return undefined + // Get the sender address based on the init code + accountAddress = await getSenderAddress(client, { + factory, + factoryData, + entryPointAddress: entryPoint.address + }) - return parseFactoryAddressAndCallDataFromAccountInitCode( - await generateInitCode() - )[1] + return accountAddress }, - // Get the nonce of the smart account - async getNonce(customNonceKey?: bigint) { + async getNonce(_args) { const key = await kernelPluginManager.getNonceKey( accountAddress, - customNonceKey + _args?.key ) return getAccountNonce(client, { - sender: accountAddress, - entryPoint: entryPointAddress, + address: accountAddress, + entryPointAddress: entryPoint.address, key }) }, // Sign a user operation - async signUserOperation(userOperation) { - return kernelPluginManager.signUserOperation(userOperation) - }, - - // Encode the init code - async getInitCode() { - if (smartAccountDeployed) return "0x" - - smartAccountDeployed = await isSmartAccountDeployed( - client, - accountAddress - ) - - if (smartAccountDeployed) return "0x" - return generateInitCode() + async signUserOperation(parameters) { + const { chainId = await getMemoizedChainId(), ...userOperation } = + parameters + return kernelPluginManager.signUserOperation({ + ...userOperation, + sender: userOperation.sender ?? (await this.getAddress()), + chainId + }) }, - // Encode the deploy call data async encodeDeployCallData(_tx) { return encodeFunctionData({ @@ -379,13 +314,12 @@ export async function createKernelAccountV0_2< }, // Encode a call - async encodeCallData(_tx) { - const tx = _tx as KernelEncodeCallDataArgs - if (Array.isArray(tx)) { + async encodeCalls(calls, callType): Promise { + if (calls.length > 1) { const multiSendCallData = encodeFunctionData({ abi: multiSendAbi, functionName: "multiSend", - args: [encodeMultiSend(tx)] + args: [encodeMultiSend(calls)] }) return encodeFunctionData({ abi: KernelAccountV2Abi, @@ -394,23 +328,29 @@ export async function createKernelAccountV0_2< }) } + const call = calls.length === 0 ? undefined : calls[0] + + if (!call) { + throw new Error("No calls to encode") + } + // Default to `call` - if (!tx.callType || tx.callType === "call") { - if (tx.to.toLowerCase() === accountAddress.toLowerCase()) { - return tx.data + if (!callType || callType === "call") { + if (call.to.toLowerCase() === accountAddress.toLowerCase()) { + return call.data || "0x" } return encodeFunctionData({ abi: KernelAccountV2Abi, functionName: "execute", - args: [tx.to, tx.value, tx.data, 0] + args: [call.to, call.value || 0n, call.data || "0x", 0] }) } - if (tx.callType === "delegatecall") { + if (callType === "delegatecall") { return encodeFunctionData({ abi: KernelAccountV2Abi, functionName: "execute", - args: [tx.to, 0n, tx.data, 1] + args: [call.to, 0n, call.data || "0x", 1] }) } @@ -418,8 +358,13 @@ export async function createKernelAccountV0_2< }, // Get simple dummy signature - async getDummySignature(userOperation) { - return kernelPluginManager.getDummySignature(userOperation) + async getStubSignature(userOperation) { + if (!userOperation) { + throw new Error("No user operation provided") + } + return kernelPluginManager.getStubSignature( + userOperation as UserOperation + ) } - } + }) } diff --git a/packages/core/accounts/utils/getCustomNonceKeyFromString.ts b/packages/core/accounts/utils/getCustomNonceKeyFromString.ts index 4f0981f7..3d8c35d7 100644 --- a/packages/core/accounts/utils/getCustomNonceKeyFromString.ts +++ b/packages/core/accounts/utils/getCustomNonceKeyFromString.ts @@ -1,6 +1,5 @@ -import { getEntryPointVersion } from "permissionless" -import type { EntryPoint } from "permissionless/types" import { keccak256, toHex } from "viem" +import type { EntryPointVersion } from "viem/account-abstraction" const hashAndTruncate = (input: string, byteSize: number): bigint => { const hash = keccak256(toHex(input)) @@ -10,11 +9,9 @@ const hashAndTruncate = (input: string, byteSize: number): bigint => { export const getCustomNonceKeyFromString = ( input: string, - entryPoint: EntryPoint + entryPointVersion: EntryPointVersion ) => { - const entryPointVersion = getEntryPointVersion(entryPoint) - - if (entryPointVersion === "v0.6") { + if (entryPointVersion === "0.6") { return hashAndTruncate(input, 24) // 24 bytes for v0.6 } return hashAndTruncate(input, 2) // 2 bytes for v0.7 diff --git a/packages/core/accounts/utils/index.ts b/packages/core/accounts/utils/index.ts index eb58e962..66c7b7bf 100644 --- a/packages/core/accounts/utils/index.ts +++ b/packages/core/accounts/utils/index.ts @@ -1,14 +1,5 @@ export { toKernelPluginManager } from "./toKernelPluginManager.js" -import type { Address, Hex } from "viem" export { verifyEIP6492Signature, universalValidatorByteCode } from "./6492.js" -export const parseFactoryAddressAndCallDataFromAccountInitCode = ( - initCode: Hex -): [Address, Hex] => { - const factoryAddress = `0x${initCode.substring(2, 42)}` as Address - const factoryCalldata = `0x${initCode.substring(42)}` as Hex - return [factoryAddress, factoryCalldata] -} - export { getCustomNonceKeyFromString } from "./getCustomNonceKeyFromString.js" diff --git a/packages/core/accounts/utils/multisend.ts b/packages/core/accounts/utils/multisend.ts index acaab166..df34e2fd 100644 --- a/packages/core/accounts/utils/multisend.ts +++ b/packages/core/accounts/utils/multisend.ts @@ -1,5 +1,6 @@ import { type Address, type Hex, encodePacked, toBytes } from "viem" -import type { CallType, KernelEncodeCallDataArgs } from "../../types/index.js" +import type { CallType } from "../../types/index.js" +import type { KernelSmartAccountImplementation } from "../kernel/createKernelAccount.js" export const MULTISEND_ADDRESS = "0x8ae01fcf7c655655ff2c6ef907b8b4718ab4e17c" @@ -31,7 +32,9 @@ const encodeCall = (call: { return encoded.slice(2) } -export const encodeMultiSend = (calls: KernelEncodeCallDataArgs): Hex => { +export const encodeMultiSend = ( + calls: Parameters["encodeCalls"]>[0] +): Hex => { if (!Array.isArray(calls)) { throw new Error("Invalid multiSend calls, should use an array of calls") } diff --git a/packages/core/accounts/utils/toKernelPluginManager.ts b/packages/core/accounts/utils/toKernelPluginManager.ts index 3f2ad0c7..b6e574d0 100644 --- a/packages/core/accounts/utils/toKernelPluginManager.ts +++ b/packages/core/accounts/utils/toKernelPluginManager.ts @@ -1,12 +1,8 @@ -import { getEntryPointVersion } from "permissionless" -import type { EntryPoint } from "permissionless/types/entrypoint" import { satisfies } from "semver" import { type Address, - type Chain, type Client, type Hex, - type Transport, concat, concatHex, maxUint16, @@ -15,6 +11,7 @@ import { toHex, zeroAddress } from "viem" +import type { EntryPointVersion } from "viem/account-abstraction" import { getChainId } from "viem/actions" import { encodeModuleInstallCallData as encodeModuleInstallCallDataEpV06 } from "../../accounts/kernel/utils/account/ep0_6/encodeModuleInstallCallData.js" import { @@ -36,19 +33,19 @@ import { getEncodedPluginsData as getEncodedPluginsDataV2 } from "../kernel/util import { getPluginsEnableTypedData as getPluginsEnableTypedDataV2 } from "../kernel/utils/plugins/ep0_7/getPluginsEnableTypedData.js" import { isPluginInitialized } from "../kernel/utils/plugins/ep0_7/isPluginInitialized.js" -export function isKernelPluginManager( +export function isKernelPluginManager< + entryPointVersion extends EntryPointVersion +>( // biome-ignore lint/suspicious/noExplicitAny: plugin: any -): plugin is KernelPluginManager { +): plugin is KernelPluginManager { return plugin.getPluginEnableSignature !== undefined } export async function toKernelPluginManager< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined + entryPointVersion extends EntryPointVersion >( - client: Client, + client: Client, { sudo, regular, @@ -58,11 +55,11 @@ export async function toKernelPluginManager< action, validAfter = 0, validUntil = 0, - entryPoint: entryPointAddress, + entryPoint, kernelVersion, chainId - }: KernelPluginManagerParams -): Promise> { + }: KernelPluginManagerParams +): Promise> { if ( (sudo && !satisfies(kernelVersion, sudo?.supportedKernelVersions)) || (regular && !satisfies(kernelVersion, regular?.supportedKernelVersions)) @@ -72,20 +69,19 @@ export async function toKernelPluginManager< ) } let pluginEnabled: boolean - const entryPointVersion = getEntryPointVersion(entryPointAddress) const activeValidator = regular || sudo if (!activeValidator) { throw new Error("One of `sudo` or `regular` validator must be set") } action = { - selector: action?.selector ?? getActionSelector(entryPointVersion), + selector: action?.selector ?? getActionSelector(entryPoint.version), address: action?.address ?? zeroAddress } if ( - entryPointVersion === "v0.7" && + entryPoint.version === "0.7" && (action.address.toLowerCase() !== zeroAddress.toLowerCase() || action.selector.toLowerCase() !== - getActionSelector(entryPointVersion).toLowerCase()) && + getActionSelector(entryPoint.version).toLowerCase()) && kernelVersion === "0.3.0" ) { action.hook = { @@ -105,7 +101,7 @@ export async function toKernelPluginManager< if (!action) { throw new Error("Action data must be set") } - if (entryPointVersion === "v0.6") { + if (entryPoint.version === "0.6") { if (regular) { if (pluginEnabled) { return ValidatorMode.plugin @@ -163,7 +159,7 @@ export async function toKernelPluginManager< throw new Error("Action data must be set") } if (!regular) throw new Error("regular validator not set") - if (entryPointVersion === "v0.6") { + if (entryPoint.version === "0.6") { return regular.isEnabled(accountAddress, selector) } const isEnabled = @@ -195,7 +191,7 @@ export async function toKernelPluginManager< chainId = client.chain?.id ?? (await getChainId(client)) } let ownerSig: Hex - if (entryPointVersion === "v0.6") { + if (entryPoint.version === "0.6") { const typeData = await getPluginsEnableTypedDataV1({ accountAddress, chainId, @@ -276,7 +272,7 @@ export async function toKernelPluginManager< throw new Error("Action data must be set") } if (!regular) throw new Error("regular validator not set") - if (entryPointVersion === "v0.6") { + if (entryPoint.version === "0.6") { return await encodeModuleInstallCallDataEpV06({ accountAddress, selector: action.selector, @@ -292,7 +288,7 @@ export async function toKernelPluginManager< signUserOperation: async (userOperation) => { const userOpSig = await activeValidator.signUserOperation(userOperation) - if (entryPointVersion === "v0.6") { + if (entryPoint.version === "0.6") { return concatHex([ await getSignatureData( userOperation.sender, @@ -317,10 +313,10 @@ export async function toKernelPluginManager< validAfter, validUntil }), - getDummySignature: async (userOperation) => { + getStubSignature: async (userOperation) => { const userOpSig = - await activeValidator.getDummySignature(userOperation) - if (entryPointVersion === "v0.6") { + await activeValidator.getStubSignature(userOperation) + if (entryPoint.version === "0.6") { return concatHex([ await getSignatureData( userOperation.sender, @@ -342,10 +338,10 @@ export async function toKernelPluginManager< if (!action) { throw new Error("Action data must be set") } - if (entryPointVersion === "v0.6") { + if (entryPoint.version === "0.6") { if (customNonceKey > maxUint192) { throw new Error( - "Custom nonce key must be equal or less than maxUint192 for v0.6" + "Custom nonce key must be equal or less than maxUint192 for 0.6" ) } diff --git a/packages/core/actions/account-client/changeSudoValidator.ts b/packages/core/actions/account-client/changeSudoValidator.ts index 1f61d5ae..8fead522 100644 --- a/packages/core/actions/account-client/changeSudoValidator.ts +++ b/packages/core/actions/account-client/changeSudoValidator.ts @@ -1,22 +1,25 @@ -import { - type SendUserOperationParameters, - sendUserOperation -} from "permissionless/actions/smartAccount" -import type { EntryPoint, Prettify } from "permissionless/types" -import { - AccountOrClientNotFoundError, - parseAccount -} from "permissionless/utils" import { type Chain, type Client, type Hash, type Hex, + type Prettify, type Transport, zeroAddress } from "viem" -import { concatHex, encodeFunctionData, getAction, pad } from "viem/utils" -import type { KernelSmartAccount } from "../../accounts/index.js" +import { + type SendUserOperationParameters, + type SmartAccount, + sendUserOperation +} from "viem/account-abstraction" +import { + concatHex, + encodeFunctionData, + getAction, + pad, + parseAccount +} from "viem/utils" +import type { KernelSmartAccountImplementation } from "../../accounts/index.js" import { KernelV3_1AccountAbi } from "../../accounts/kernel/abi/kernel_v_3_1/KernelAccountAbi.js" import { KERNEL_V3_0, @@ -24,72 +27,43 @@ import { KernelVersionToAddressesMap, VALIDATOR_TYPE } from "../../constants.js" +import { AccountNotFoundError } from "../../errors/index.js" import type { KernelValidator, KernelValidatorHook } from "../../types/kernel.js" -type OptionalUserOperationParameters< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount - | undefined -> = Omit< - SendUserOperationParameters, - "userOperation" -> & { - userOperation?: SendUserOperationParameters< - entryPoint, - TTransport, - TChain, - TAccount - >["userOperation"] -} - export type ChangeSudoValidatorParameters< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount - | undefined + account extends SmartAccount | undefined, + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] > = Prettify< - OptionalUserOperationParameters< - entryPoint, - TTransport, - TChain, - TAccount - > & { - sudoValidator: KernelValidator + Partial> & { + sudoValidator: KernelValidator hook?: KernelValidatorHook } > export async function changeSudoValidator< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount - | undefined + account extends SmartAccount | undefined, + chain extends Chain | undefined, + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] >( - client: Client, + client: Client, args: Prettify< - ChangeSudoValidatorParameters + ChangeSudoValidatorParameters > ): Promise { const { account: account_ = client.account, sudoValidator, hook } = args - if (!account_) throw new AccountOrClientNotFoundError() + if (!account_) + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/sendTransaction" + }) - const account = parseAccount(account_) as KernelSmartAccount + const account = parseAccount( + account_ + ) as SmartAccount let rootValidatorId: Hex if ( @@ -122,29 +96,31 @@ export async function changeSudoValidator< if (account.kernelVersion === KERNEL_V3_0) { return await getAction( client, - sendUserOperation, + sendUserOperation, "sendUserOperation" )({ ...args, - userOperation: { - callData: await account.encodeCallData({ - to: KernelVersionToAddressesMap[KERNEL_V3_1] - .accountImplementationAddress, - value: 0n, - data: encodeFunctionData({ - abi: KernelV3_1AccountAbi, - functionName: "changeRootValidator", - args: [rootValidatorId, hookId, validatorData, hookData] - }), - callType: "delegatecall" - }) - } - } as SendUserOperationParameters< - entryPoint, - TTransport, - TChain, - TAccount - >) + callData: await account.encodeCalls( + [ + { + to: KernelVersionToAddressesMap[KERNEL_V3_1] + .accountImplementationAddress, + value: 0n, + data: encodeFunctionData({ + abi: KernelV3_1AccountAbi, + functionName: "changeRootValidator", + args: [ + rootValidatorId, + hookId, + validatorData, + hookData + ] + }) + } + ], + "delegatecall" + ) + } as SendUserOperationParameters) } /** @@ -152,12 +128,12 @@ export async function changeSudoValidator< */ return await getAction( client, - sendUserOperation, + sendUserOperation, "sendUserOperation" )({ ...args, - userOperation: { - callData: await account.encodeCallData({ + callData: await account.encodeCalls([ + { to: account.address, value: 0n, data: encodeFunctionData({ @@ -165,7 +141,7 @@ export async function changeSudoValidator< functionName: "changeRootValidator", args: [rootValidatorId, hookId, validatorData, hookData] }) - }) - } - } as SendUserOperationParameters) + } + ]) + } as SendUserOperationParameters) } diff --git a/packages/core/actions/account-client/getKernelV3ModuleCurrentNonce.ts b/packages/core/actions/account-client/getKernelV3ModuleCurrentNonce.ts index ddb7a385..cb14d3a5 100644 --- a/packages/core/actions/account-client/getKernelV3ModuleCurrentNonce.ts +++ b/packages/core/actions/account-client/getKernelV3ModuleCurrentNonce.ts @@ -1,67 +1,28 @@ -import type { SendTransactionWithPaymasterParameters } from "permissionless/actions/smartAccount" -import type { EntryPoint, Prettify } from "permissionless/types" -import { - AccountOrClientNotFoundError, - parseAccount -} from "permissionless/utils" import type { Chain, Client, Transport } from "viem" +import type { SmartAccount } from "viem/account-abstraction" import { readContract } from "viem/actions" -import { getAction } from "viem/utils" -import type { KernelSmartAccount } from "../../accounts/index.js" +import { getAction, parseAccount } from "viem/utils" import { KernelV3AccountAbi } from "../../accounts/kernel/abi/kernel_v_3_0_0/KernelAccountAbi.js" - -export type GetKernelV3ModuleCurrentNonceParameters< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount - | undefined, - TChainOverride extends Chain | undefined = Chain | undefined -> = Prettify< - SendTransactionWithPaymasterParameters< - entryPoint, - TTransport, - TChain, - TAccount, - TChainOverride - > -> +import { AccountNotFoundError } from "../../errors/index.js" export async function getKernelV3ModuleCurrentNonce< - entryPoint extends EntryPoint, TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount - | undefined, - TChainOverride extends Chain | undefined = Chain | undefined ->( - client: Client, - args: Prettify< - GetKernelV3ModuleCurrentNonceParameters< - entryPoint, - TTransport, - TChain, - TAccount, - TChainOverride - > - > -): Promise { - const { account: account_ = client.account } = args - if (!account_) throw new AccountOrClientNotFoundError() + TAccount extends SmartAccount | undefined = SmartAccount | undefined +>(client: Client): Promise { + const account_ = client.account + if (!account_) + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/sendTransaction" + }) - const account = parseAccount(account_) as KernelSmartAccount + const account = parseAccount(account_) as SmartAccount try { const nonce = await getAction( client, readContract, - "sendTransaction" + "readContract" )({ abi: KernelV3AccountAbi, address: account.address, diff --git a/packages/core/actions/account-client/invalidateNonce.ts b/packages/core/actions/account-client/invalidateNonce.ts index 56c4af2b..2e2382ee 100644 --- a/packages/core/actions/account-client/invalidateNonce.ts +++ b/packages/core/actions/account-client/invalidateNonce.ts @@ -1,86 +1,57 @@ +import type { Chain, Client, Hash, Prettify, Transport } from "viem" import { - type SendTransactionWithPaymasterParameters, - sendTransaction -} from "permissionless/actions/smartAccount" -import type { EntryPoint, Prettify } from "permissionless/types" -import { - AccountOrClientNotFoundError, - parseAccount -} from "permissionless/utils" -import type { Chain, Client, Hash, Transport } from "viem" -import { encodeFunctionData, getAction } from "viem/utils" -import type { KernelSmartAccount } from "../../accounts/index.js" + type SendUserOperationParameters, + type SmartAccount, + sendUserOperation +} from "viem/account-abstraction" +import { encodeFunctionData, getAction, parseAccount } from "viem/utils" import { KernelV3AccountAbi } from "../../accounts/kernel/abi/kernel_v_3_0_0/KernelAccountAbi.js" +import { AccountNotFoundError } from "../../errors/index.js" export type InvalidateNonceParameters< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount - | undefined, - TChainOverride extends Chain | undefined = Chain | undefined + account extends SmartAccount | undefined, + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] > = Prettify< - SendTransactionWithPaymasterParameters< - entryPoint, - TTransport, - TChain, - TAccount, - TChainOverride - > & { + Partial> & { nonceToSet: number } > export async function invalidateNonce< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount - | undefined, - TChainOverride extends Chain | undefined = Chain | undefined + account extends SmartAccount | undefined, + chain extends Chain | undefined, + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] >( - client: Client, - args: Prettify< - InvalidateNonceParameters< - entryPoint, - TTransport, - TChain, - TAccount, - TChainOverride - > - > + client: Client, + args: Prettify> ): Promise { - const { account: account_ = client.account, middleware, nonceToSet } = args - if (!account_) throw new AccountOrClientNotFoundError() + const { account: account_ = client.account, nonceToSet } = args + if (!account_) + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/sendTransaction" + }) - const account = parseAccount(account_) as KernelSmartAccount + const account = parseAccount(account_) as SmartAccount return await getAction( client, - sendTransaction< - TTransport, - TChain, - TAccount, - entryPoint, - TChainOverride - >, - "sendTransaction" + sendUserOperation, + "sendUserOperation" )({ ...args, - to: account.address, - data: encodeFunctionData({ - abi: KernelV3AccountAbi, - functionName: "invalidateNonce", - args: [nonceToSet] - }), - value: 0n, - account, - middleware - }) + calls: [ + { + to: account.address, + data: encodeFunctionData({ + abi: KernelV3AccountAbi, + functionName: "invalidateNonce", + args: [nonceToSet] + }), + value: 0n + } + ], + account + } as SendUserOperationParameters) } diff --git a/packages/core/actions/account-client/sendTransaction.ts b/packages/core/actions/account-client/sendTransaction.ts new file mode 100644 index 00000000..d10b31d0 --- /dev/null +++ b/packages/core/actions/account-client/sendTransaction.ts @@ -0,0 +1,134 @@ +// Copied from: https://github.com/pimlicolabs/permissionless.js/blob/main/packages/permissionless/actions/smartAccount/sendTransaction.ts + +import type { + Chain, + Client, + Hash, + SendTransactionParameters, + Transport +} from "viem" +import { + type SendUserOperationParameters, + type SmartAccount, + sendUserOperation, + waitForUserOperationReceipt +} from "viem/account-abstraction" +import { getAction, parseAccount } from "viem/utils" +import { AccountNotFoundError } from "../../errors" + +/** + * Creates, signs, and sends a new transaction to the network. + * This function also allows you to sponsor this transaction if sender is a smartAccount + * + * - Docs: https://viem.sh/docs/actions/wallet/sendTransaction.html + * - Examples: https://stackblitz.com/github/wagmi-dev/viem/tree/main/examples/transactions/sending-transactions + * - JSON-RPC Methods: + * - JSON-RPC Accounts: [`eth_sendTransaction`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendtransaction) + * - Local Accounts: [`eth_sendRawTransaction`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendrawtransaction) + * + * @param client - Client to use + * @param parameters - {@link SendTransactionParameters} + * @returns The [Transaction](https://viem.sh/docs/glossary/terms.html#transaction) hash. + * + * @example + * import { createWalletClient, custom } from 'viem' + * import { mainnet } from 'viem/chains' + * import { sendTransaction } from 'viem/wallet' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const hash = await sendTransaction(client, { + * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + * to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + * value: 1000000000000000000n, + * }) + * + * @example + * // Account Hoisting + * import { createWalletClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { mainnet } from 'viem/chains' + * import { sendTransaction } from 'viem/wallet' + * + * const client = createWalletClient({ + * account: privateKeyToAccount('0x…'), + * chain: mainnet, + * transport: http(), + * }) + * const hash = await sendTransaction(client, { + * to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + * value: 1000000000000000000n, + * }) + */ +export async function sendTransaction< + account extends SmartAccount | undefined, + chain extends Chain | undefined, + accountOverride extends SmartAccount | undefined = undefined, + chainOverride extends Chain | undefined = Chain | undefined, + calls extends readonly unknown[] = readonly unknown[] +>( + client: Client, + args: + | SendTransactionParameters + | SendUserOperationParameters +): Promise { + let userOpHash: Hash + + if ("to" in args) { + const { + account: account_ = client.account, + data, + maxFeePerGas, + maxPriorityFeePerGas, + to, + value, + nonce + } = args + + if (!account_) { + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/sendTransaction" + }) + } + + const account = parseAccount(account_) as SmartAccount + + if (!to) throw new Error("Missing to address") + + userOpHash = await getAction( + client, + sendUserOperation, + "sendUserOperation" + )({ + calls: [ + { + to, + value: value || BigInt(0), + data: data || "0x" + } + ], + account, + maxFeePerGas, + maxPriorityFeePerGas, + nonce: nonce ? BigInt(nonce) : undefined + }) + } else { + userOpHash = await getAction( + client, + sendUserOperation, + "sendUserOperation" + )({ ...args } as SendUserOperationParameters) + } + + const userOperationReceipt = await getAction( + client, + waitForUserOperationReceipt, + "waitForUserOperationReceipt" + )({ + hash: userOpHash + }) + + return userOperationReceipt?.receipt.transactionHash +} diff --git a/packages/core/actions/account-client/signMessage.ts b/packages/core/actions/account-client/signMessage.ts new file mode 100644 index 00000000..c7743ac3 --- /dev/null +++ b/packages/core/actions/account-client/signMessage.ts @@ -0,0 +1,75 @@ +// Copied from: https://github.com/pimlicolabs/permissionless.js/blob/main/packages/permissionless/actions/smartAccount/signMessage.ts + +import type { + Chain, + Client, + SignMessageParameters, + SignMessageReturnType, + Transport +} from "viem" +import type { SmartAccount } from "viem/account-abstraction" +import { parseAccount } from "viem/utils" +import { AccountNotFoundError } from "../../errors" + +/** + * Calculates an Ethereum-specific signature in [EIP-191 format](https://eips.ethereum.org/EIPS/eip-191): `keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))`. + * + * - Docs: https://viem.sh/docs/actions/wallet/signMessage.html + * - JSON-RPC Methods: + * - JSON-RPC Accounts: [`personal_sign`](https://docs.metamask.io/guide/signing-data.html#personal-sign) + * - Local Accounts: Signs locally. No JSON-RPC request. + * + * With the calculated signature, you can: + * - use [`verifyMessage`](https://viem.sh/docs/utilities/verifyMessage.html) to verify the signature, + * - use [`recoverMessageAddress`](https://viem.sh/docs/utilities/recoverMessageAddress.html) to recover the signing address from a signature. + * + * @param client - Client to use + * @param parameters - {@link SignMessageParameters} + * @returns The signed message. {@link SignMessageReturnType} + * + * @example + * import { createWalletClient, custom } from 'viem' + * import { mainnet } from 'viem/chains' + * import { signMessage } from 'viem/wallet' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const signature = await signMessage(client, { + * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + * message: 'hello world', + * }) + * + * @example + * // Account Hoisting + * import { createWalletClient, custom } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { mainnet } from 'viem/chains' + * import { signMessage } from 'viem/wallet' + * + * const client = createWalletClient({ + * account: privateKeyToAccount('0x…'), + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const signature = await signMessage(client, { + * message: 'hello world', + * }) + */ +export async function signMessage( + client: Client, + { + account: account_ = client.account, + message + }: SignMessageParameters +): Promise { + if (!account_) + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/signMessage" + }) + + const account = parseAccount(account_) as SmartAccount + + return account.signMessage({ message }) +} diff --git a/packages/core/actions/account-client/signTypedData.ts b/packages/core/actions/account-client/signTypedData.ts new file mode 100644 index 00000000..03d90e28 --- /dev/null +++ b/packages/core/actions/account-client/signTypedData.ts @@ -0,0 +1,159 @@ +// Copied from: https://github.com/pimlicolabs/permissionless.js/blob/main/packages/permissionless/actions/smartAccount/signTypedData.ts + +import { + type Chain, + type Client, + type SignTypedDataParameters, + type SignTypedDataReturnType, + type Transport, + type TypedData, + type TypedDataDefinition, + type TypedDataDomain, + getTypesForEIP712Domain, + validateTypedData +} from "viem" +import type { SmartAccount } from "viem/account-abstraction" +import { parseAccount } from "viem/utils" +import { AccountNotFoundError } from "../../errors" + +/** + * Signs typed data and calculates an Ethereum-specific signature in [https://eips.ethereum.org/EIPS/eip-712](https://eips.ethereum.org/EIPS/eip-712): `sign(keccak256("\x19\x01" ‖ domainSeparator ‖ hashStruct(message)))` + * + * - Docs: https://viem.sh/docs/actions/wallet/signTypedData.html + * - JSON-RPC Methods: + * - JSON-RPC Accounts: [`eth_signTypedData_v4`](https://docs.metamask.io/guide/signing-data.html#signtypeddata-v4) + * - Local Accounts: Signs locally. No JSON-RPC request. + * + * @param client - Client to use + * @param parameters - {@link SignTypedDataParameters} + * @returns The signed data. {@link SignTypedDataReturnType} + * + * @example + * import { createWalletClient, custom } from 'viem' + * import { mainnet } from 'viem/chains' + * import { signTypedData } from 'viem/wallet' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const signature = await signTypedData(client, { + * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + * domain: { + * name: 'Ether Mail', + * version: '1', + * chainId: 1, + * verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + * }, + * types: { + * Person: [ + * { name: 'name', type: 'string' }, + * { name: 'wallet', type: 'address' }, + * ], + * Mail: [ + * { name: 'from', type: 'Person' }, + * { name: 'to', type: 'Person' }, + * { name: 'contents', type: 'string' }, + * ], + * }, + * primaryType: 'Mail', + * message: { + * from: { + * name: 'Cow', + * wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + * }, + * to: { + * name: 'Bob', + * wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + * }, + * contents: 'Hello, Bob!', + * }, + * }) + * + * @example + * // Account Hoisting + * import { createWalletClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { mainnet } from 'viem/chains' + * import { signTypedData } from 'viem/wallet' + * + * const client = createWalletClient({ + * account: privateKeyToAccount('0x…'), + * chain: mainnet, + * transport: http(), + * }) + * const signature = await signTypedData(client, { + * domain: { + * name: 'Ether Mail', + * version: '1', + * chainId: 1, + * verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + * }, + * types: { + * Person: [ + * { name: 'name', type: 'string' }, + * { name: 'wallet', type: 'address' }, + * ], + * Mail: [ + * { name: 'from', type: 'Person' }, + * { name: 'to', type: 'Person' }, + * { name: 'contents', type: 'string' }, + * ], + * }, + * primaryType: 'Mail', + * message: { + * from: { + * name: 'Cow', + * wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + * }, + * to: { + * name: 'Bob', + * wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + * }, + * contents: 'Hello, Bob!', + * }, + * }) + */ +export async function signTypedData< + const TTypedData extends TypedData | { [key: string]: unknown }, + TPrimaryType extends string, + TAccount extends SmartAccount | undefined = SmartAccount | undefined +>( + client: Client, + { + account: account_ = client.account, + domain, + message, + primaryType, + types: types_ + }: SignTypedDataParameters +): Promise { + if (!account_) { + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/signMessage" + }) + } + + const account = parseAccount(account_) as SmartAccount + + const types = { + EIP712Domain: getTypesForEIP712Domain({ domain } as { + domain: TypedDataDomain + }), + ...(types_ as TTypedData) + } + + validateTypedData({ + domain, + message, + primaryType, + types + } as TypedDataDefinition) + + return account.signTypedData({ + domain, + primaryType, + types, + message + } as TypedDataDefinition) +} diff --git a/packages/core/actions/account-client/signUserOperation.ts b/packages/core/actions/account-client/signUserOperation.ts index 6e950349..00e91442 100644 --- a/packages/core/actions/account-client/signUserOperation.ts +++ b/packages/core/actions/account-client/signUserOperation.ts @@ -1,80 +1,48 @@ -import type { SmartAccount } from "permissionless/accounts" -import { prepareUserOperationRequest } from "permissionless/actions/smartAccount" -import type { - PrepareUserOperationRequestParameters, - PrepareUserOperationRequestReturnType -} from "permissionless/actions/smartAccount" -import type { - EntryPoint, - GetEntryPointVersion, - Prettify, - UserOperation -} from "permissionless/types" -import { - AccountOrClientNotFoundError, - parseAccount -} from "permissionless/utils" import type { Chain, Client, Transport } from "viem" -import { getAction } from "viem/utils" +import { + type PrepareUserOperationParameters, + type PrepareUserOperationReturnType, + type SmartAccount, + type UserOperation, + prepareUserOperation +} from "viem/account-abstraction" +import { getAction, parseAccount } from "viem/utils" +import { AccountNotFoundError } from "../../errors" export type SignUserOperationParameters< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | SmartAccount - | undefined = - | SmartAccount - | undefined -> = PrepareUserOperationRequestParameters< - entryPoint, - TTransport, - TChain, - TAccount -> + account extends SmartAccount | undefined = SmartAccount | undefined, + accountOverride extends SmartAccount | undefined = SmartAccount | undefined, + calls extends readonly unknown[] = readonly unknown[] +> = PrepareUserOperationParameters -export type SignUserOperationReturnType = - PrepareUserOperationRequestReturnType +export type SignUserOperationReturnType = PrepareUserOperationReturnType export async function signUserOperation< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | SmartAccount - | undefined = - | SmartAccount - | undefined + account extends SmartAccount | undefined = SmartAccount | undefined, + chain extends Chain | undefined = Chain | undefined, + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] >( - client: Client, - args: Prettify< - SignUserOperationParameters - > -): Promise> { + client: Client, + args: SignUserOperationParameters +): Promise { const { account: account_ = client.account } = args - if (!account_) throw new AccountOrClientNotFoundError() + if (!account_) + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/sendTransaction" + }) - const account = parseAccount(account_) as SmartAccount< - entryPoint, - string, - TTransport, - TChain - > + const account = parseAccount(account_) as SmartAccount const userOperation = await getAction( client, - prepareUserOperationRequest, - "prepareUserOperationRequest" - )({ ...args, account } as PrepareUserOperationRequestParameters< - entryPoint, - TTransport, - TChain, - TAccount - >) + prepareUserOperation, + "prepareUserOperation" + )({ ...args, account } as PrepareUserOperationParameters) userOperation.signature = await account.signUserOperation( - userOperation as UserOperation> + userOperation as UserOperation ) - return userOperation as SignUserOperationReturnType + return userOperation } diff --git a/packages/core/actions/account-client/uninstallPlugin.ts b/packages/core/actions/account-client/uninstallPlugin.ts index 80ae58a3..646a3460 100644 --- a/packages/core/actions/account-client/uninstallPlugin.ts +++ b/packages/core/actions/account-client/uninstallPlugin.ts @@ -1,69 +1,49 @@ +import type { Chain, Client, Hash, Hex, Transport } from "viem" import { - type SendTransactionsWithPaymasterParameters, - sendTransactions -} from "permissionless/actions/smartAccount" -import type { EntryPoint, Prettify } from "permissionless/types" + type SendUserOperationParameters, + type SmartAccount, + sendUserOperation +} from "viem/account-abstraction" import { - AccountOrClientNotFoundError, + concatHex, + encodeFunctionData, + getAction, + pad, parseAccount -} from "permissionless/utils" -import type { Chain, Client, Hash, Hex, Transport } from "viem" -import { concatHex, encodeFunctionData, getAction, pad } from "viem/utils" -import type { KernelSmartAccount } from "../../accounts/index.js" +} from "viem/utils" import { KernelV3AccountAbi } from "../../accounts/kernel/abi/kernel_v_3_0_0/KernelAccountAbi.js" import { VALIDATOR_TYPE } from "../../constants.js" +import { AccountNotFoundError } from "../../errors/index.js" import type { KernelValidator, KernelValidatorHook } from "../../types/kernel.js" export type UninstallPluginParameters< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount - | undefined -> = Prettify< - Partial< - SendTransactionsWithPaymasterParameters< - entryPoint, - TTransport, - TChain, - TAccount - > - > & { - plugin: KernelValidator - hook?: KernelValidatorHook - } -> + account extends SmartAccount | undefined = SmartAccount | undefined, + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] +> = Partial> & { + plugin: KernelValidator + hook?: KernelValidatorHook +} export async function uninstallPlugin< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount - | undefined + account extends SmartAccount | undefined, + chain extends Chain | undefined, + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] >( - client: Client, - args: Prettify< - UninstallPluginParameters - > + client: Client, + args: UninstallPluginParameters ): Promise { - const { - account: account_ = client.account, - middleware, - plugin, - hook - } = args - if (!account_) throw new AccountOrClientNotFoundError() + const { account: account_ = client.account, plugin, hook } = args + if (!account_) + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/sendTransaction" + }) - const account = parseAccount(account_) as KernelSmartAccount + const account = parseAccount(account_) as SmartAccount let validatorId: Hex if ( @@ -87,11 +67,11 @@ export async function uninstallPlugin< const hookData = (await hook?.getEnableData(account.address)) ?? "0x" return await getAction( client, - sendTransactions, - "sendTransactions" + sendUserOperation, + "sendUserOperation" )({ ...args, - transactions: [ + calls: [ { to: account.address, data: encodeFunctionData({ @@ -102,7 +82,6 @@ export async function uninstallPlugin< value: 0n } ], - account, - middleware - }) + account + } as SendUserOperationParameters) } diff --git a/packages/core/actions/account-client/writeContract.ts b/packages/core/actions/account-client/writeContract.ts new file mode 100644 index 00000000..7cc64dc9 --- /dev/null +++ b/packages/core/actions/account-client/writeContract.ts @@ -0,0 +1,72 @@ +// Copied from: https://github.com/pimlicolabs/permissionless.js/blob/main/packages/permissionless/actions/smartAccount/writeContract.ts + +import { + type Abi, + type Chain, + type Client, + type ContractFunctionArgs, + type ContractFunctionName, + type EncodeFunctionDataParameters, + type Hash, + type SendTransactionParameters, + type Transport, + type WriteContractParameters, + encodeFunctionData +} from "viem" +import type { SmartAccount } from "viem/account-abstraction" +import { getAction } from "viem/utils" +import { sendTransaction } from "./sendTransaction.js" + +export async function writeContract< + TChain extends Chain | undefined, + TAccount extends SmartAccount | undefined, + const TAbi extends Abi | readonly unknown[], + TFunctionName extends ContractFunctionName< + TAbi, + "nonpayable" | "payable" + > = ContractFunctionName, + TArgs extends ContractFunctionArgs< + TAbi, + "nonpayable" | "payable", + TFunctionName + > = ContractFunctionArgs, + TChainOverride extends Chain | undefined = undefined +>( + client: Client, + { + abi, + address, + args, + dataSuffix, + functionName, + ...request + }: WriteContractParameters< + TAbi, + TFunctionName, + TArgs, + TChain, + TAccount, + TChainOverride + > +): Promise { + const data = encodeFunctionData({ + abi, + args, + functionName + } as EncodeFunctionDataParameters) + + const hash = await getAction( + client, + sendTransaction, + "sendTransaction" + )({ + data: `${data}${dataSuffix ? dataSuffix.replace("0x", "") : ""}`, + to: address, + ...request + } as unknown as SendTransactionParameters< + Chain | undefined, + TAccount, + undefined + >) + return hash +} diff --git a/packages/core/actions/index.ts b/packages/core/actions/index.ts index 2551f98a..60c1aaa9 100644 --- a/packages/core/actions/index.ts +++ b/packages/core/actions/index.ts @@ -20,6 +20,21 @@ export { type UninstallPluginParameters } from "./account-client/uninstallPlugin.js" +export { getKernelV3ModuleCurrentNonce } from "./account-client/getKernelV3ModuleCurrentNonce.js" + +export { + type InvalidateNonceParameters, + invalidateNonce +} from "./account-client/invalidateNonce.js" + +export { sendTransaction } from "./account-client/sendTransaction.js" + +export { signMessage } from "./account-client/signMessage.js" + +export { signTypedData } from "./account-client/signTypedData.js" + +export { writeContract } from "./account-client/writeContract.js" + export { changeSudoValidator, type ChangeSudoValidatorParameters @@ -31,8 +46,4 @@ export { type EstimateGasInERC20ReturnType } from "./paymaster/estimateGasInERC20.js" -export { - type SponsorUserOperationEip7677Parameters, - type SponsorUserOperationEip7677ReturnType, - sponsorUserOperationEip7677 -} from "./paymaster/sponsorUserOperationEip7677.js" +export * from "./public/index.js" diff --git a/packages/core/actions/paymaster/estimateGasInERC20.ts b/packages/core/actions/paymaster/estimateGasInERC20.ts index ca437f96..ab59b135 100644 --- a/packages/core/actions/paymaster/estimateGasInERC20.ts +++ b/packages/core/actions/paymaster/estimateGasInERC20.ts @@ -1,12 +1,14 @@ -import { ENTRYPOINT_ADDRESS_V06 } from "permissionless" -import type { EntryPoint } from "permissionless/types" -import type { UserOperation } from "permissionless/types/userOperation.js" -import { deepHexlify } from "permissionless/utils" import type { Address, Hex } from "viem" +import { + type EntryPointVersion, + type UserOperation, + entryPoint06Address +} from "viem/account-abstraction" import type { ZeroDevPaymasterClient } from "../../clients/paymasterClient.js" +import { deepHexlify } from "../../utils.js" export type EstimateGasInERC20Parameters = { - userOperation: UserOperation<"v0.6" | "v0.7"> + userOperation: UserOperation gasTokenAddress: Hex entryPoint: Address } @@ -24,8 +26,10 @@ export type EstimateGasInERC20ReturnType = { * Returns paymasterAndData & updated gas parameters required to sponsor a userOperation. */ -export const estimateGasInERC20 = async ( - client: ZeroDevPaymasterClient, +export const estimateGasInERC20 = async < + entryPointVersion extends EntryPointVersion +>( + client: ZeroDevPaymasterClient, args: EstimateGasInERC20Parameters ): Promise => { const response = await client.request({ @@ -38,7 +42,7 @@ export const estimateGasInERC20 = async ( initCode: args.userOperation.initCode || "0x" }, tokenAddress: args.gasTokenAddress, - entryPointAddress: args.entryPoint ?? ENTRYPOINT_ADDRESS_V06 + entryPointAddress: args.entryPoint ?? entryPoint06Address } ] }) diff --git a/packages/core/actions/paymaster/sponsorUserOperation.ts b/packages/core/actions/paymaster/sponsorUserOperation.ts index 56549d89..9fb0ffc5 100644 --- a/packages/core/actions/paymaster/sponsorUserOperation.ts +++ b/packages/core/actions/paymaster/sponsorUserOperation.ts @@ -1,77 +1,58 @@ -import { getEntryPointVersion } from "permissionless" -import type { PartialPick } from "permissionless/types" -import type { - ENTRYPOINT_ADDRESS_V06_TYPE, - EntryPoint -} from "permissionless/types/entrypoint" -import type { UserOperation } from "permissionless/types/userOperation.js" -import { deepHexlify } from "permissionless/utils" -import type { Address, Hex } from "viem" -import type { PartialBy } from "viem/types/utils" +import { type Address, type Hex, type Prettify, isAddressEqual } from "viem" +import { + type EntryPointVersion, + type GetPaymasterDataParameters, + type GetPaymasterStubDataReturnType, + entryPoint06Address +} from "viem/account-abstraction" import type { ZeroDevPaymasterClient } from "../../clients/paymasterClient.js" +import { deepHexlify } from "../../utils.js" -export type SponsorUserOperationParameters = { - userOperation: entryPoint extends ENTRYPOINT_ADDRESS_V06_TYPE - ? PartialBy< - UserOperation<"v0.6">, - "callGasLimit" | "preVerificationGas" | "verificationGasLimit" - > - : PartialBy< - UserOperation<"v0.7">, - | "callGasLimit" - | "preVerificationGas" - | "verificationGasLimit" - | "paymasterVerificationGasLimit" - | "paymasterPostOpGasLimit" - > - entryPoint: entryPoint +export type SponsorUserOperationParameters = { + userOperation: GetPaymasterDataParameters gasToken?: Hex shouldOverrideFee?: boolean shouldConsume?: boolean } -export type SponsorUserOperationReturnType = - entryPoint extends ENTRYPOINT_ADDRESS_V06_TYPE - ? Pick< - UserOperation<"v0.6">, - | "callGasLimit" - | "verificationGasLimit" - | "preVerificationGas" - | "paymasterAndData" - > & - PartialPick< - UserOperation<"v0.6">, - "maxFeePerGas" | "maxPriorityFeePerGas" - > - : Pick< - UserOperation<"v0.7">, - | "callGasLimit" - | "verificationGasLimit" - | "preVerificationGas" - | "paymaster" - | "paymasterVerificationGasLimit" - | "paymasterPostOpGasLimit" - | "paymasterData" - > & - PartialPick< - UserOperation<"v0.7">, - "maxFeePerGas" | "maxPriorityFeePerGas" - > +export type SponsorUserOperationReturnType = Prettify< + GetPaymasterStubDataReturnType & { + maxFeePerGas?: bigint | undefined + maxPriorityFeePerGas?: bigint | undefined + callGasLimit?: bigint | undefined + verificationGasLimit?: bigint | undefined + preVerificationGas?: bigint | undefined + } +> /** * Returns paymasterAndData & updated gas parameters required to sponsor a userOperation. */ -export const sponsorUserOperation = async ( - client: ZeroDevPaymasterClient, - args: SponsorUserOperationParameters -): Promise> => { +export const sponsorUserOperation = async < + entryPointVersion extends EntryPointVersion +>( + client: ZeroDevPaymasterClient, + args: SponsorUserOperationParameters +): Promise => { + const { + userOperation: { + chainId, + entryPointAddress, + context, + // @ts-ignore + calls, + // @ts-ignore + account, + ..._userOperation + } + } = args const response = await client.request({ method: "zd_sponsorUserOperation", params: [ { chainId: client.chain?.id as number, - userOp: deepHexlify(args.userOperation), - entryPointAddress: args.entryPoint, + userOp: deepHexlify(_userOperation), + entryPointAddress: entryPointAddress, gasTokenData: args.gasToken && { tokenAddress: args.gasToken }, @@ -80,19 +61,19 @@ export const sponsorUserOperation = async ( } ] }) - const entryPointVersion = getEntryPointVersion(args.entryPoint) - if (entryPointVersion === "v0.6") { + if (isAddressEqual(entryPointAddress, entryPoint06Address)) { return { paymasterAndData: response.paymasterAndData, preVerificationGas: BigInt(response.preVerificationGas), verificationGasLimit: BigInt(response.verificationGasLimit), callGasLimit: BigInt(response.callGasLimit), - maxFeePerGas: - response.maxFeePerGas && BigInt(response.maxFeePerGas), - maxPriorityFeePerGas: - response.maxPriorityFeePerGas && - BigInt(response.maxPriorityFeePerGas) - } as SponsorUserOperationReturnType + maxFeePerGas: response.maxFeePerGas + ? BigInt(response.maxFeePerGas) + : args.userOperation.maxFeePerGas, + maxPriorityFeePerGas: response.maxPriorityFeePerGas + ? BigInt(response.maxPriorityFeePerGas) + : args.userOperation.maxPriorityFeePerGas + } as SponsorUserOperationReturnType } const responseV07 = response as { preVerificationGas: Hex @@ -117,9 +98,11 @@ export const sponsorUserOperation = async ( ), paymasterPostOpGasLimit: BigInt(responseV07.paymasterPostOpGasLimit), paymasterData: responseV07.paymasterData, - maxFeePerGas: response.maxFeePerGas && BigInt(response.maxFeePerGas), - maxPriorityFeePerGas: - response.maxPriorityFeePerGas && - BigInt(response.maxPriorityFeePerGas) - } as SponsorUserOperationReturnType + maxFeePerGas: response.maxFeePerGas + ? BigInt(response.maxFeePerGas) + : args.userOperation.maxFeePerGas, + maxPriorityFeePerGas: response.maxPriorityFeePerGas + ? BigInt(response.maxPriorityFeePerGas) + : args.userOperation.maxPriorityFeePerGas + } as SponsorUserOperationReturnType } diff --git a/packages/core/actions/paymaster/sponsorUserOperationEip7677.ts b/packages/core/actions/paymaster/sponsorUserOperationEip7677.ts deleted file mode 100644 index 04cbbffe..00000000 --- a/packages/core/actions/paymaster/sponsorUserOperationEip7677.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { - type BundlerClient, - ENTRYPOINT_ADDRESS_V06, - type EstimateUserOperationGasParameters, - type EstimateUserOperationGasReturnType -} from "permissionless" -import { - type GetPaymasterDataParameters, - type GetPaymasterDataReturnType, - type GetPaymasterStubDataParameters, - type GetPaymasterStubDataReturnType, - paymasterActionsEip7677 -} from "permissionless/experimental" -import type { - ENTRYPOINT_ADDRESS_V06_TYPE, - ENTRYPOINT_ADDRESS_V07_TYPE, - EntryPoint, - PartialPick -} from "permissionless/types" -import type { StateOverrides } from "permissionless/types/bundler" -import type { UserOperation } from "permissionless/types/userOperation.js" -import type { PartialBy } from "viem/types/utils" -import type { ZeroDevPaymasterClient } from "../../clients/paymasterClient.js" - -export type SponsorUserOperationEip7677Parameters< - entryPoint extends EntryPoint -> = { - userOperation: entryPoint extends ENTRYPOINT_ADDRESS_V06_TYPE - ? PartialBy< - UserOperation<"v0.6">, - "callGasLimit" | "preVerificationGas" | "verificationGasLimit" - > - : PartialBy< - UserOperation<"v0.7">, - | "callGasLimit" - | "preVerificationGas" - | "verificationGasLimit" - | "paymasterVerificationGasLimit" - | "paymasterPostOpGasLimit" - > - entryPoint: entryPoint -} - -export type SponsorUserOperationEip7677ReturnType< - entryPoint extends EntryPoint -> = entryPoint extends ENTRYPOINT_ADDRESS_V06_TYPE - ? Pick< - UserOperation<"v0.6">, - | "callGasLimit" - | "verificationGasLimit" - | "preVerificationGas" - | "paymasterAndData" - > & - PartialPick< - UserOperation<"v0.6">, - "maxFeePerGas" | "maxPriorityFeePerGas" - > - : Pick< - UserOperation<"v0.7">, - | "callGasLimit" - | "verificationGasLimit" - | "preVerificationGas" - | "paymaster" - | "paymasterVerificationGasLimit" - | "paymasterPostOpGasLimit" - | "paymasterData" - > & - PartialPick< - UserOperation<"v0.7">, - "maxFeePerGas" | "maxPriorityFeePerGas" - > - -/** - * Returns paymasterAndData & updated gas parameters required to sponsor a userOperation. - */ -export const sponsorUserOperationEip7677 = async < - entryPoint extends EntryPoint ->( - client: ZeroDevPaymasterClient, - args: SponsorUserOperationEip7677Parameters, - bundlerClient: BundlerClient, - stateOverrides?: StateOverrides -): Promise> => { - const { entryPoint: entryPointAddress, userOperation } = args - const chain = client.chain - const paymasterClient = client.extend( - paymasterActionsEip7677(entryPointAddress) - ) - const stubData = await paymasterClient.getPaymasterStubData({ - userOperation: userOperation as GetPaymasterStubDataParameters< - entryPoint, - typeof chain - >["userOperation"], - chain - }) - const stubUserOperation = { - ...userOperation, - ...stubData - } - - const gas = (await bundlerClient.estimateUserOperationGas( - { - userOperation: - stubUserOperation as EstimateUserOperationGasParameters["userOperation"] - }, - stateOverrides - )) as EstimateUserOperationGasReturnType - const userOperationWithGas = { - ...stubUserOperation, - callGasLimit: gas.callGasLimit, - verificationGasLimit: gas.verificationGasLimit, - preVerificationGas: gas.preVerificationGas - } as GetPaymasterDataParameters["userOperation"] - - const paymasterData = await paymasterClient.getPaymasterData({ - userOperation: userOperationWithGas, - chain - }) - - if (entryPointAddress === ENTRYPOINT_ADDRESS_V06) { - const paymasterDataV06 = - paymasterData as GetPaymasterDataReturnType - return { - callGasLimit: BigInt(gas.callGasLimit), - verificationGasLimit: BigInt(gas.verificationGasLimit), - preVerificationGas: BigInt(gas.preVerificationGas), - paymasterAndData: paymasterDataV06?.paymasterAndData, - maxFeePerGas: BigInt(userOperation.maxFeePerGas), - maxPriorityFeePerGas: BigInt(userOperation.maxPriorityFeePerGas) - } as SponsorUserOperationEip7677ReturnType - } - const stubDataV07 = - stubData as GetPaymasterStubDataReturnType - const paymasterDataV07 = - paymasterData as GetPaymasterDataReturnType - - return { - callGasLimit: BigInt(gas.callGasLimit), - verificationGasLimit: BigInt(gas.verificationGasLimit), - preVerificationGas: BigInt(gas.preVerificationGas), - paymaster: paymasterDataV07.paymaster, - paymasterData: paymasterDataV07.paymasterData, - paymasterVerificationGasLimit: - stubDataV07.paymasterVerificationGasLimit && - BigInt(stubDataV07.paymasterVerificationGasLimit), - paymasterPostOpGasLimit: - stubDataV07?.paymasterPostOpGasLimit && - BigInt(stubDataV07.paymasterPostOpGasLimit), - maxFeePerGas: BigInt(userOperation.maxFeePerGas), - maxPriorityFeePerGas: BigInt(userOperation.maxPriorityFeePerGas) - } as SponsorUserOperationEip7677ReturnType -} diff --git a/packages/core/actions/public/getAccountNonce.ts b/packages/core/actions/public/getAccountNonce.ts new file mode 100644 index 00000000..fad1e54e --- /dev/null +++ b/packages/core/actions/public/getAccountNonce.ts @@ -0,0 +1,77 @@ +// Copied from: https://github.com/pimlicolabs/permissionless.js/blob/main/packages/permissionless/actions/public/getAccountNonce.ts + +import type { Address, Client } from "viem" +import { readContract } from "viem/actions" +import { getAction } from "viem/utils" + +export type GetAccountNonceParams = { + address: Address + entryPointAddress: Address + key?: bigint +} + +/** + * Returns the nonce of the account with the entry point. + * + * - Docs: https://docs.pimlico.io/permissionless/reference/public-actions/getAccountNonce + * + * @param client {@link client} that you created using viem's createPublicClient. + * @param args {@link GetAccountNonceParams} address, entryPoint & key + * @returns bigint nonce + * + * @example + * import { createPublicClient } from "viem" + * import { getAccountNonce } from "permissionless/actions" + * + * const client = createPublicClient({ + * chain: goerli, + * transport: http("https://goerli.infura.io/v3/your-infura-key") + * }) + * + * const nonce = await getAccountNonce(client, { + * address, + * entryPoint, + * key + * }) + * + * // Return 0n + */ +export const getAccountNonce = async ( + client: Client, + args: GetAccountNonceParams +): Promise => { + const { address, entryPointAddress, key = BigInt(0) } = args + + return await getAction( + client, + readContract, + "readContract" + )({ + address: entryPointAddress, + abi: [ + { + inputs: [ + { + name: "sender", + type: "address" + }, + { + name: "key", + type: "uint192" + } + ], + name: "getNonce", + outputs: [ + { + name: "nonce", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + } + ], + functionName: "getNonce", + args: [address, key] + }) +} diff --git a/packages/core/actions/public/getSenderAddress.ts b/packages/core/actions/public/getSenderAddress.ts new file mode 100644 index 00000000..aab6f1fb --- /dev/null +++ b/packages/core/actions/public/getSenderAddress.ts @@ -0,0 +1,276 @@ +// Copied from: https://github.com/pimlicolabs/permissionless.js/blob/main/packages/permissionless/actions/public/getSenderAddress.ts + +import { + type Address, + BaseError, + type Client, + type ContractFunctionExecutionErrorType, + ContractFunctionRevertedError, + type Hex, + InvalidInputRpcError, + type OneOf, + type Prettify, + RawContractError, + RpcRequestError, + UnknownRpcError, + concat, + decodeErrorResult +} from "viem" + +import { simulateContract } from "viem/actions" +import { getAction } from "viem/utils" + +export type GetSenderAddressParams = OneOf< + | { + initCode: Hex + entryPointAddress: Address + factory?: never + factoryData?: never + } + | { + entryPointAddress: Address + factory: Address + factoryData: Hex + initCode?: never + } +> + +export class InvalidEntryPointError extends BaseError { + override name = "InvalidEntryPointError" + + constructor({ + cause, + entryPointAddress + }: { cause?: BaseError; entryPointAddress?: Address } = {}) { + super( + `The entry point address (\`entryPoint\`${ + entryPointAddress ? ` = ${entryPointAddress}` : "" + }) is not a valid entry point. getSenderAddress did not revert with a SenderAddressResult error.`, + { + cause + } + ) + } +} + +/** + * Returns the address of the account that will be deployed with the given init code. + * + * - Docs: https://docs.pimlico.io/permissionless/reference/public-actions/getSenderAddress + * + * @param client {@link Client} that you created using viem's createPublicClient. + * @param args {@link GetSenderAddressParams} initCode & entryPoint + * @returns Sender's Address + * + * @example + * import { createPublicClient } from "viem" + * import { getSenderAddress } from "permissionless/actions" + * + * const publicClient = createPublicClient({ + * chain: goerli, + * transport: http("https://goerli.infura.io/v3/your-infura-key") + * }) + * + * const senderAddress = await getSenderAddress(publicClient, { + * initCode, + * entryPoint + * }) + * + * // Return '0x7a88a206ba40b37a8c07a2b5688cf8b287318b63' + */ +export const getSenderAddress = async ( + client: Client, + args: Prettify +): Promise
=> { + const { initCode, entryPointAddress, factory, factoryData } = args + + if (!initCode && !factory && !factoryData) { + throw new Error( + "Either `initCode` or `factory` and `factoryData` must be provided" + ) + } + + try { + await getAction( + client, + simulateContract, + "simulateContract" + )({ + address: entryPointAddress, + abi: [ + { + inputs: [ + { + internalType: "address", + name: "sender", + type: "address" + } + ], + name: "SenderAddressResult", + type: "error" + }, + { + inputs: [ + { + internalType: "bytes", + name: "initCode", + type: "bytes" + } + ], + name: "getSenderAddress", + outputs: [], + stateMutability: "nonpayable", + type: "function" + } + ], + functionName: "getSenderAddress", + args: [initCode || concat([factory as Hex, factoryData as Hex])] + }) + } catch (e) { + const revertError = (e as ContractFunctionExecutionErrorType).walk( + (err) => + err instanceof ContractFunctionRevertedError || + err instanceof RpcRequestError || + err instanceof InvalidInputRpcError || + err instanceof UnknownRpcError + ) + + if (!revertError) { + // biome-ignore lint/suspicious/noExplicitAny: + const cause = (e as ContractFunctionExecutionErrorType).cause as any + const errorName = cause?.data?.errorName ?? "" + if ( + errorName === "SenderAddressResult" && + cause?.data?.args && + cause?.data?.args[0] + ) { + return cause.data?.args[0] as Address + } + } + + if (revertError instanceof ContractFunctionRevertedError) { + const errorName = revertError.data?.errorName ?? "" + if ( + errorName === "SenderAddressResult" && + revertError.data?.args && + revertError.data?.args[0] + ) { + return revertError.data?.args[0] as Address + } + } + + if (revertError instanceof RpcRequestError) { + const hexStringRegex = /0x[a-fA-F0-9]+/ + // biome-ignore lint/suspicious/noExplicitAny: + const match = (revertError as unknown as any).cause.data.match( + hexStringRegex + ) + + if (!match) { + throw new Error( + "Failed to parse revert bytes from RPC response" + ) + } + + const data: Hex = match[0] + + const error = decodeErrorResult({ + abi: [ + { + inputs: [ + { + internalType: "address", + name: "sender", + type: "address" + } + ], + name: "SenderAddressResult", + type: "error" + } + ], + data + }) + + return error.args[0] as Address + } + + if (revertError instanceof InvalidInputRpcError) { + const { data: data_ } = ( + e instanceof RawContractError + ? e + : e instanceof BaseError + ? e.walk((err) => "data" in (err as Error)) || e.walk() + : {} + ) as RawContractError + + const data = typeof data_ === "string" ? data_ : data_?.data + + if (data === undefined) { + throw new Error( + "Failed to parse revert bytes from RPC response" + ) + } + + const error = decodeErrorResult({ + abi: [ + { + inputs: [ + { + internalType: "address", + name: "sender", + type: "address" + } + ], + name: "SenderAddressResult", + type: "error" + } + ], + data + }) + + return error.args[0] as Address + } + + if (revertError instanceof UnknownRpcError) { + const parsedBody = JSON.parse( + // biome-ignore lint/suspicious/noExplicitAny: + (revertError as unknown as any).cause.body + ) + const revertData = parsedBody.error.data + + const hexStringRegex = /0x[a-fA-F0-9]+/ + const match = revertData.match(hexStringRegex) + + if (!match) { + throw new Error( + "Failed to parse revert bytes from RPC response" + ) + } + + const data: Hex = match[0] + + const error = decodeErrorResult({ + abi: [ + { + inputs: [ + { + internalType: "address", + name: "sender", + type: "address" + } + ], + name: "SenderAddressResult", + type: "error" + } + ], + data + }) + + return error.args[0] as Address + } + + throw e + } + + throw new InvalidEntryPointError({ entryPointAddress }) +} diff --git a/packages/core/actions/public/index.ts b/packages/core/actions/public/index.ts new file mode 100644 index 00000000..05290c43 --- /dev/null +++ b/packages/core/actions/public/index.ts @@ -0,0 +1,10 @@ +export { + type GetAccountNonceParams, + getAccountNonce +} from "./getAccountNonce.js" +export { + type GetSenderAddressParams, + type InvalidEntryPointError, + getSenderAddress +} from "./getSenderAddress.js" +export { isSmartAccountDeployed } from "./isSmartAccountDeployed.js" diff --git a/packages/core/actions/public/isSmartAccountDeployed.ts b/packages/core/actions/public/isSmartAccountDeployed.ts new file mode 100644 index 00000000..d8783ed7 --- /dev/null +++ b/packages/core/actions/public/isSmartAccountDeployed.ts @@ -0,0 +1,18 @@ +import type { Address, Client } from "viem" +import { getCode } from "viem/actions" +import { getAction } from "viem/utils" + +export const isSmartAccountDeployed = async ( + client: Client, + address: Address +): Promise => { + const code = await getAction( + client, + getCode, + "getCode" + )({ + address + }) + const deployed = Boolean(code) + return deployed +} diff --git a/packages/core/clients/decorators/kernel.ts b/packages/core/clients/decorators/kernel.ts index b2ac801a..eca1eff6 100644 --- a/packages/core/clients/decorators/kernel.ts +++ b/packages/core/clients/decorators/kernel.ts @@ -1,17 +1,17 @@ -import { - type BundlerClient, - type SmartAccountActions, - smartAccountActions -} from "permissionless" -import type { Middleware } from "permissionless/actions/smartAccount" -import type { EntryPoint, Prettify } from "permissionless/types" -import type { StateOverrides } from "permissionless/types/bundler" -import type { Chain, Client, Hash, Transport } from "viem" -import type { KernelSmartAccount } from "../../accounts/index.js" -import { - type GetKernelV3ModuleCurrentNonceParameters, - getKernelV3ModuleCurrentNonce -} from "../../actions/account-client/getKernelV3ModuleCurrentNonce.js" +import type { + Abi, + Chain, + Client, + ContractFunctionArgs, + ContractFunctionName, + Hash, + Prettify, + Transport, + TypedData, + WriteContractParameters +} from "viem" +import type { EntryPointVersion, SmartAccount } from "viem/account-abstraction" +import { getKernelV3ModuleCurrentNonce } from "../../actions/account-client/getKernelV3ModuleCurrentNonce.js" import { type GetUserOperationGasPriceReturnType, getUserOperationGasPrice @@ -20,18 +20,18 @@ import { type InvalidateNonceParameters, invalidateNonce } from "../../actions/account-client/invalidateNonce.js" +import { sendTransaction } from "../../actions/account-client/sendTransaction.js" +import { signMessage } from "../../actions/account-client/signMessage.js" +import { signTypedData } from "../../actions/account-client/signTypedData.js" +import { writeContract } from "../../actions/account-client/writeContract.js" import type { ChangeSudoValidatorParameters, - SignUserOperationParameters, SignUserOperationReturnType, - SponsorUserOperationEip7677Parameters, - SponsorUserOperationEip7677ReturnType, UninstallPluginParameters } from "../../actions/index.js" import { changeSudoValidator, signUserOperation, - sponsorUserOperationEip7677, uninstallPlugin } from "../../actions/index.js" import { @@ -46,80 +46,58 @@ import { } from "../../actions/paymaster/sponsorUserOperation.js" import type { ZeroDevPaymasterClient } from "../paymasterClient.js" -export type ZeroDevPaymasterClientActions = { +export type ZeroDevPaymasterClientActions = { /** * Returns paymasterAndData & updated gas parameters required to sponsor a userOperation. */ sponsorUserOperation: ( - args: SponsorUserOperationParameters - ) => Promise> + args: SponsorUserOperationParameters + ) => Promise estimateGasInERC20: ( args: EstimateGasInERC20Parameters ) => Promise - sponsorUserOperationEip7677: ( - args: SponsorUserOperationEip7677Parameters, - bundlerClient: BundlerClient, - stateOverrides?: StateOverrides - ) => Promise> } export const zerodevPaymasterActions = - (entryPointAddress: entryPoint) => - (client: Client): ZeroDevPaymasterClientActions => ({ - sponsorUserOperation: async ( - args: Omit, "entryPoint"> - ) => - sponsorUserOperation( - client as ZeroDevPaymasterClient, + () => + (client: Client): ZeroDevPaymasterClientActions => ({ + sponsorUserOperation: async (args: SponsorUserOperationParameters) => + sponsorUserOperation( + client as ZeroDevPaymasterClient, { - ...args, - entryPoint: entryPointAddress + ...args } ), estimateGasInERC20: async (args: EstimateGasInERC20Parameters) => estimateGasInERC20( - client as ZeroDevPaymasterClient, + client as ZeroDevPaymasterClient, args - ), - sponsorUserOperationEip7677: async ( - args: SponsorUserOperationEip7677Parameters, - bundlerClient: BundlerClient, - stateOverrides?: StateOverrides - ) => - sponsorUserOperationEip7677( - client as ZeroDevPaymasterClient, - args, - bundlerClient, - stateOverrides ) }) export type KernelAccountClientActions< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, - TSmartAccount extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount - | undefined -> = SmartAccountActions & { + TSmartAccount extends SmartAccount | undefined = SmartAccount | undefined +> = { /** * Signs a user operation with the given transport, chain, and smart account. * * @param args - Parameters for the signUserOperation function * @returns A promise that resolves to the result of the signUserOperation function */ - signUserOperation: ( + signUserOperation: < + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] + >( args: Parameters< typeof signUserOperation< - entryPoint, - TTransport, + TSmartAccount, TChain, - TSmartAccount + accountOverride, + calls > >[1] - ) => Promise> + ) => Promise /** * Returns the live gas prices that you can use to send a user operation. * @@ -136,24 +114,24 @@ export type KernelAccountClientActions< * @param args - {@link UninstallPermissionParameters} * @returns The [Transaction](https://viem.sh/docs/glossary/terms.html#transaction) hash. {@link SendTransactionReturnType} */ - uninstallPlugin: ( - args: UninstallPluginParameters< - entryPoint, - TTransport, - TChain, - TSmartAccount - > + uninstallPlugin: < + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] + >( + args: UninstallPluginParameters ) => Promise /** * Creates, signs, and sends a user operation to change sudo validator to the network. * This function also allows you to sponsor this transaction if sender is a smartAccount */ - changeSudoValidator: ( + changeSudoValidator: < + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] + >( args: ChangeSudoValidatorParameters< - entryPoint, - TTransport, - TChain, - TSmartAccount + TSmartAccount, + accountOverride, + calls > ) => Promise @@ -166,116 +144,330 @@ export type KernelAccountClientActions< * @returns The [Transaction](https://viem.sh/docs/glossary/terms.html#transaction) hash. {@link SendTransactionReturnType} */ invalidateNonce: < - TChainOverride extends Chain | undefined = Chain | undefined + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] >( - args: InvalidateNonceParameters< - entryPoint, - TTransport, - TChain, - TSmartAccount, - TChainOverride - > + args: InvalidateNonceParameters ) => Promise /** * Creates, signs, and sends a transaction to fetch KernelV3 module nonce to the network. * This function also allows you to sponsor this transaction if sender is a smartAccount * * - * @param args - {@link GetKernelV3ModuleCurrentNonceParameters} * @returns nonce */ - getKernelV3ModuleCurrentNonce: < - TChainOverride extends Chain | undefined = Chain | undefined + getKernelV3ModuleCurrentNonce: () => Promise + /** + * Creates, signs, and sends a new transaction to the network. + * This function also allows you to sponsor this transaction if sender is a smartAccount + * + * - Docs: https://viem.sh/docs/actions/wallet/sendTransaction.html + * - Examples: https://stackblitz.com/github/wagmi-dev/viem/tree/main/examples/transactions/sending-transactions + * - JSON-RPC Methods: + * - JSON-RPC Accounts: [`eth_sendTransaction`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendtransaction) + * - Local Accounts: [`eth_sendRawTransaction`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendrawtransaction) + * + * @param args - {@link SendTransactionParameters} + * @returns The [Transaction](https://viem.sh/docs/glossary/terms.html#transaction) hash. {@link SendTransactionReturnType} + * + * @example + * import { createWalletClient, custom } from 'viem' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const hash = await client.sendTransaction({ + * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + * to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + * value: 1000000000000000000n, + * }) + * + * @example + * // Account Hoisting + * import { createWalletClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * account: privateKeyToAccount('0x…'), + * chain: mainnet, + * transport: http(), + * }) + * const hash = await client.sendTransaction({ + * to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + * value: 1000000000000000000n, + * }) + */ + sendTransaction: < + TChainOverride extends Chain | undefined = undefined, + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] >( - args: GetKernelV3ModuleCurrentNonceParameters< - entryPoint, - TTransport, + args: Parameters< + typeof sendTransaction< + TSmartAccount, + TChain, + accountOverride, + TChainOverride, + calls + > + >[1] + ) => Promise + /** + * Calculates an Ethereum-specific signature in [EIP-191 format](https://eips.ethereum.org/EIPS/eip-191): `keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))`. + * + * - Docs: https://viem.sh/docs/actions/wallet/signMessage.html + * - JSON-RPC Methods: + * - JSON-RPC Accounts: [`personal_sign`](https://docs.metamask.io/guide/signing-data.html#personal-sign) + * - Local Accounts: Signs locally. No JSON-RPC request. + * + * With the calculated signature, you can: + * - use [`verifyMessage`](https://viem.sh/docs/utilities/verifyMessage.html) to verify the signature, + * - use [`recoverMessageAddress`](https://viem.sh/docs/utilities/recoverMessageAddress.html) to recover the signing address from a signature. + * + * @param args - {@link SignMessageParameters} + * @returns The signed message. {@link SignMessageReturnType} + * + * @example + * import { createWalletClient, custom } from 'viem' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const signature = await client.signMessage({ + * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + * message: 'hello world', + * }) + * + * @example + * // Account Hoisting + * import { createWalletClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * account: privateKeyToAccount('0x…'), + * chain: mainnet, + * transport: http(), + * }) + * const signature = await client.signMessage({ + * message: 'hello world', + * }) + */ + signMessage: ( + args: Parameters>[1] + ) => ReturnType> + /** + * Signs typed data and calculates an Ethereum-specific signature in [EIP-191 format](https://eips.ethereum.org/EIPS/eip-191): `keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))`. + * + * - Docs: https://viem.sh/docs/actions/wallet/signTypedData.html + * - JSON-RPC Methods: + * - JSON-RPC Accounts: [`eth_signTypedData_v4`](https://docs.metamask.io/guide/signing-data.html#signtypeddata-v4) + * - Local Accounts: Signs locally. No JSON-RPC request. + * + * @param client - Client to use + * @param args - {@link SignTypedDataParameters} + * @returns The signed data. {@link SignTypedDataReturnType} + * + * @example + * import { createWalletClient, custom } from 'viem' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const signature = await client.signTypedData({ + * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + * domain: { + * name: 'Ether Mail', + * version: '1', + * chainId: 1, + * verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + * }, + * types: { + * Person: [ + * { name: 'name', type: 'string' }, + * { name: 'wallet', type: 'address' }, + * ], + * Mail: [ + * { name: 'from', type: 'Person' }, + * { name: 'to', type: 'Person' }, + * { name: 'contents', type: 'string' }, + * ], + * }, + * primaryType: 'Mail', + * message: { + * from: { + * name: 'Cow', + * wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + * }, + * to: { + * name: 'Bob', + * wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + * }, + * contents: 'Hello, Bob!', + * }, + * }) + * + * @example + * // Account Hoisting + * import { createWalletClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * account: privateKeyToAccount('0x…'), + * chain: mainnet, + * transport: http(), + * }) + * const signature = await client.signTypedData({ + * domain: { + * name: 'Ether Mail', + * version: '1', + * chainId: 1, + * verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + * }, + * types: { + * Person: [ + * { name: 'name', type: 'string' }, + * { name: 'wallet', type: 'address' }, + * ], + * Mail: [ + * { name: 'from', type: 'Person' }, + * { name: 'to', type: 'Person' }, + * { name: 'contents', type: 'string' }, + * ], + * }, + * primaryType: 'Mail', + * message: { + * from: { + * name: 'Cow', + * wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + * }, + * to: { + * name: 'Bob', + * wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + * }, + * contents: 'Hello, Bob!', + * }, + * }) + */ + signTypedData: < + const TTypedData extends TypedData | { [key: string]: unknown }, + TPrimaryType extends string + >( + args: Parameters< + typeof signTypedData + >[1] + ) => ReturnType< + typeof signTypedData + > + /** + * Executes a write function on a contract. + * This function also allows you to sponsor this transaction if sender is a smartAccount + * + * - Docs: https://viem.sh/docs/contract/writeContract.html + * - Examples: https://stackblitz.com/github/wagmi-dev/viem/tree/main/examples/contracts/writing-to-contracts + * + * A "write" function on a Solidity contract modifies the state of the blockchain. These types of functions require gas to be executed, and hence a [Transaction](https://viem.sh/docs/glossary/terms.html) is needed to be broadcast in order to change the state. + * + * Internally, uses a [Wallet Client](https://viem.sh/docs/clients/wallet.html) to call the [`sendTransaction` action](https://viem.sh/docs/actions/wallet/sendTransaction.html) with [ABI-encoded `data`](https://viem.sh/docs/contract/encodeFunctionData.html). + * + * __Warning: The `write` internally sends a transaction – it does not validate if the contract write will succeed (the contract may throw an error). It is highly recommended to [simulate the contract write with `contract.simulate`](https://viem.sh/docs/contract/writeContract.html#usage) before you execute it.__ + * + * @param args - {@link WriteContractParameters} + * @returns A [Transaction Hash](https://viem.sh/docs/glossary/terms.html#hash). {@link WriteContractReturnType} + * + * @example + * import { createWalletClient, custom, parseAbi } from 'viem' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const hash = await client.writeContract({ + * address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + * abi: parseAbi(['function mint(uint32 tokenId) nonpayable']), + * functionName: 'mint', + * args: [69420], + * }) + * + * @example + * // With Validation + * import { createWalletClient, custom, parseAbi } from 'viem' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const { request } = await client.simulateContract({ + * address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + * abi: parseAbi(['function mint(uint32 tokenId) nonpayable']), + * functionName: 'mint', + * args: [69420], + * } + * const hash = await client.writeContract(request) + */ + writeContract: < + const TAbi extends Abi | readonly unknown[], + TFunctionName extends ContractFunctionName< + TAbi, + "nonpayable" | "payable" + > = ContractFunctionName, + TArgs extends ContractFunctionArgs< + TAbi, + "nonpayable" | "payable", + TFunctionName + > = ContractFunctionArgs, + TChainOverride extends Chain | undefined = undefined + >( + args: WriteContractParameters< + TAbi, + TFunctionName, + TArgs, + TChain, + TSmartAccount, + TChainOverride + > + ) => ReturnType< + typeof writeContract< TChain, TSmartAccount, + TAbi, + TFunctionName, + TArgs, TChainOverride > - ) => Promise + > } -export function kernelAccountClientActions({ - middleware -}: Middleware) { +export function kernelAccountClientActions() { return < - TTransport extends Transport, TChain extends Chain | undefined = Chain | undefined, - TSmartAccount extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount + TSmartAccount extends SmartAccount | undefined = + | SmartAccount | undefined >( - client: Client - ): KernelAccountClientActions< - entryPoint, - TTransport, - TChain, - TSmartAccount - > => ({ - ...smartAccountActions({ middleware })(client), - signUserOperation: (args) => - signUserOperation( - client, - { - ...args, - middleware - } as SignUserOperationParameters< - entryPoint, - TTransport, - TChain, - TSmartAccount - > - ), + client: Client + ): KernelAccountClientActions => ({ + signUserOperation: (args) => signUserOperation(client, args), getUserOperationGasPrice: async () => getUserOperationGasPrice(client), - uninstallPlugin: async (args) => - uninstallPlugin( - client, - { - ...args, - middleware - } as UninstallPluginParameters< - entryPoint, - TTransport, - TChain, - TSmartAccount - > - ), - changeSudoValidator: async (args) => - changeSudoValidator( - client, - { - ...args, - middleware - } as ChangeSudoValidatorParameters< - entryPoint, - TTransport, - TChain, - TSmartAccount - > - ), - invalidateNonce: async (args) => - invalidateNonce(client, { - ...args, - middleware - } as InvalidateNonceParameters< - entryPoint, - TTransport, - TChain, - TSmartAccount - >), - getKernelV3ModuleCurrentNonce: async (args) => - getKernelV3ModuleCurrentNonce(client, { - ...args, - middleware - } as GetKernelV3ModuleCurrentNonceParameters< - entryPoint, - TTransport, - TChain, - TSmartAccount - >) + uninstallPlugin: async (args) => uninstallPlugin(client, args), + changeSudoValidator: async (args) => changeSudoValidator(client, args), + invalidateNonce: async (args) => invalidateNonce(client, args), + getKernelV3ModuleCurrentNonce: async () => + getKernelV3ModuleCurrentNonce(client), + // biome-ignore lint/suspicious/noExplicitAny: + sendTransaction: (args) => sendTransaction(client, args as any), + signMessage: (args) => signMessage(client, args), + signTypedData: (args) => signTypedData(client, args), + writeContract: (args) => writeContract(client, args) }) } diff --git a/packages/core/clients/fallbackKernelAccountClient.ts b/packages/core/clients/fallbackKernelAccountClient.ts index 5d604ece..fd6ec884 100644 --- a/packages/core/clients/fallbackKernelAccountClient.ts +++ b/packages/core/clients/fallbackKernelAccountClient.ts @@ -1,21 +1,19 @@ -import type { EntryPoint } from "permissionless/types" -import type { Chain, Transport } from "viem" -import type { KernelSmartAccount } from "../accounts/index.js" +import type { Chain, Client, RpcSchema, Transport } from "viem" +import type { SmartAccount } from "viem/account-abstraction" import type { KernelAccountClient } from "./kernelAccountClient.js" export const createFallbackKernelAccountClient = < - TEntryPoint extends EntryPoint, - TTransport extends Transport, - TChain extends Chain | undefined, - TSmartAccount extends - | KernelSmartAccount - | undefined + transport extends Transport, + chain extends Chain | undefined = undefined, + account extends SmartAccount | undefined = undefined, + client extends Client | undefined = undefined, + rpcSchema extends RpcSchema | undefined = undefined >( clients: Array< - KernelAccountClient + KernelAccountClient >, onError?: (error: Error, clientUrl: string) => Promise -): KernelAccountClient => { +): KernelAccountClient => { // Function to create a fallback method for a given property. // This method will try each client in sequence until one succeeds or all fail. function createFallbackMethod(prop: PropertyKey) { diff --git a/packages/core/clients/kernelAccountClient.ts b/packages/core/clients/kernelAccountClient.ts index 7f5c16a4..f6a82963 100644 --- a/packages/core/clients/kernelAccountClient.ts +++ b/packages/core/clients/kernelAccountClient.ts @@ -1,139 +1,177 @@ -import { getEntryPointVersion } from "permissionless" -import type { SmartAccount } from "permissionless/accounts" -import type { Middleware } from "permissionless/actions/smartAccount" -import type { EntryPoint, Prettify } from "permissionless/types" -import type { BundlerRpcSchema } from "permissionless/types/bundler" import { - http, + type BundlerRpcSchema, type Chain, type Client, type ClientConfig, + type EstimateFeesPerGasReturnType, + type Prettify, + type RpcSchema, type Transport, createClient } from "viem" -import type { KernelSmartAccount } from "../accounts/index.js" -import { getUserOperationGasPrice } from "../actions/account-client/getUserOperationGasPrice.js" +import { + type BundlerActions, + type BundlerClientConfig, + type PaymasterActions, + type PrepareUserOperationParameters, + type SmartAccount, + type UserOperationRequest, + bundlerActions, + type prepareUserOperation as viemPrepareUserOperation +} from "viem/account-abstraction" import { type KernelAccountClientActions, kernelAccountClientActions } from "./decorators/kernel.js" -import { isProviderSet, setPimlicoAsProvider } from "./utils.js" export type KernelAccountClient< - entryPoint extends EntryPoint, transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined, - account extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount - | undefined -> = Client< - transport, - chain, - account, - BundlerRpcSchema, - KernelAccountClientActions -> + account extends SmartAccount | undefined = SmartAccount | undefined, + client extends Client | undefined = Client | undefined, + rpcSchema extends RpcSchema | undefined = undefined +> = Prettify< + Client< + transport, + chain extends Chain + ? chain + : // biome-ignore lint/suspicious/noExplicitAny: + client extends Client + ? chain + : undefined, + account, + rpcSchema extends RpcSchema + ? [...BundlerRpcSchema, ...rpcSchema] + : BundlerRpcSchema, + BundlerActions & KernelAccountClientActions + > +> & { + client: client + paymaster: BundlerClientConfig["paymaster"] | undefined + paymasterContext: BundlerClientConfig["paymasterContext"] | undefined + userOperation: BundlerClientConfig["userOperation"] | undefined +} export type SmartAccountClientConfig< - entryPoint extends EntryPoint, transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined, - account extends - | SmartAccount - | undefined = - | SmartAccount - | undefined + account extends SmartAccount | undefined = SmartAccount | undefined, + client extends Client | undefined = Client | undefined, + rpcSchema extends RpcSchema | undefined = undefined > = Prettify< Pick< - ClientConfig, - "cacheTime" | "chain" | "key" | "name" | "pollingInterval" - > & { - account?: account - bundlerTransport: Transport - } & Middleware & { - entryPoint: entryPoint - } -> - -export const createKernelAccountClient = < - TTransport extends Transport, - TChain extends Chain | undefined, - TEntryPoint extends EntryPoint, - TSmartAccount extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount + ClientConfig, + | "account" + | "cacheTime" + | "chain" + | "key" + | "name" + | "pollingInterval" + | "rpcSchema" + > +> & { + bundlerTransport: transport + /** Client that points to an Execution RPC URL. */ + client?: client | Client | undefined + /** Paymaster configuration. */ + paymaster?: + | true + | { + /** Retrieves paymaster-related User Operation properties to be used for sending the User Operation. */ + getPaymasterData?: + | PaymasterActions["getPaymasterData"] + | undefined + /** Retrieves paymaster-related User Operation properties to be used for gas estimation. */ + getPaymasterStubData?: + | PaymasterActions["getPaymasterStubData"] + | undefined + } + | undefined + /** Paymaster context to pass to `getPaymasterData` and `getPaymasterStubData` calls. */ + paymasterContext?: unknown + /** User Operation configuration. */ + userOperation?: + | { + /** Prepares fee properties for the User Operation request. */ + estimateFeesPerGas?: + | ((parameters: { + account: account | SmartAccount + bundlerClient: Client + userOperation: UserOperationRequest + }) => Promise>) + | undefined + /** Prepare User Operation configuration. */ + prepareUserOperation?: typeof viemPrepareUserOperation | undefined + } | undefined +} + +export function createKernelAccountClient< + transport extends Transport, + chain extends Chain | undefined = undefined, + account extends SmartAccount | undefined = undefined, + client extends Client | undefined = undefined, + rpcSchema extends RpcSchema | undefined = undefined >( parameters: SmartAccountClientConfig< - TEntryPoint, - TTransport, - TChain, - TSmartAccount + transport, + chain, + account, + client, + rpcSchema > -): KernelAccountClient => { +): KernelAccountClient + +export function createKernelAccountClient( + parameters: SmartAccountClientConfig +): KernelAccountClient { const { + client: client_, key = "Account", name = "Kernel Account Client", + paymaster, + paymasterContext, bundlerTransport, - entryPoint + userOperation } = parameters - const entryPointVersion = getEntryPointVersion(entryPoint) - const shouldIncludePimlicoProvider = - bundlerTransport({}).config.key === "http" && - entryPointVersion === "v0.7" - const client = createClient({ - ...parameters, - key, - name, - transport: (opts) => { - let _bundlerTransport = bundlerTransport({ - ...opts, - timeout: bundlerTransport({}).config.timeout, - retryCount: 0 - }) - if ( - !shouldIncludePimlicoProvider || - isProviderSet(_bundlerTransport.value?.url, "ALCHEMY") || - isProviderSet(_bundlerTransport.value?.url, "ZERODEV") || - isProviderSet(_bundlerTransport.value?.url, "THIRDWEB") || - isProviderSet(_bundlerTransport.value?.url, "CONDUIT") - ) - return _bundlerTransport - _bundlerTransport = http( - setPimlicoAsProvider(_bundlerTransport.value?.url) - )({ - ...opts, - timeout: bundlerTransport({}).config.timeout, - retryCount: 0 - }) - return _bundlerTransport - }, - type: "kernelAccountClient" - }) + const client = Object.assign( + createClient({ + ...parameters, + chain: parameters.chain ?? client_?.chain, + transport: bundlerTransport, + key, + name, + type: "kernelAccountClient" + }), + { client: client_, paymaster, paymasterContext, userOperation } + ) - let middleware = parameters.middleware + if (parameters.userOperation?.prepareUserOperation) { + const customPrepareUserOp = + parameters.userOperation.prepareUserOperation - if ( - (!middleware || - (typeof middleware !== "function" && !middleware.gasPrice)) && - client.transport?.url && - (isProviderSet(client.transport.url, "PIMLICO") || - isProviderSet(client.transport.url, "THIRDWEB") || - isProviderSet(client.transport.url, "CONDUIT")) - ) { - const gasPrice = () => getUserOperationGasPrice(client) - middleware = { - ...middleware, - gasPrice - } + return client + .extend(bundlerActions) + .extend((client) => ({ + prepareUserOperation: ( + args: PrepareUserOperationParameters + ) => { + return customPrepareUserOp(client, args) + } + })) + .extend(bundlerActions) + .extend((client) => ({ + prepareUserOperation: ( + args: PrepareUserOperationParameters + ) => { + return customPrepareUserOp(client, args) + } + })) + .extend(kernelAccountClientActions()) as KernelAccountClient } - return client.extend( - kernelAccountClientActions({ - middleware - }) - ) as KernelAccountClient + + return client + .extend(bundlerActions) + .extend(kernelAccountClientActions()) as KernelAccountClient } diff --git a/packages/core/clients/paymasterClient.ts b/packages/core/clients/paymasterClient.ts index 4e824621..afc812db 100644 --- a/packages/core/clients/paymasterClient.ts +++ b/packages/core/clients/paymasterClient.ts @@ -1,27 +1,65 @@ -import { getEntryPointVersion } from "permissionless" -import type { EntryPoint } from "permissionless/types/entrypoint" import { - http, - type Account, type Chain, type Client, + type ClientConfig, + type Prettify, type PublicClientConfig, + type RpcSchema, type Transport, createClient } from "viem" +import { + type PaymasterActions, + type SmartAccount, + paymasterActions +} from "viem/account-abstraction" import type { ZeroDevPaymasterRpcSchema } from "../types/kernel.js" import { type ZeroDevPaymasterClientActions, zerodevPaymasterActions } from "./decorators/kernel.js" -import { isProviderSet, setPimlicoAsProvider } from "./utils.js" -export type ZeroDevPaymasterClient = Client< - Transport, - Chain | undefined, - Account | undefined, - ZeroDevPaymasterRpcSchema, - ZeroDevPaymasterClientActions +export type ZeroDevPaymasterClient< + entryPointVersion extends "0.6" | "0.7" = "0.7", + transport extends Transport = Transport, + chain extends Chain | undefined = Chain | undefined, + account extends SmartAccount | undefined = SmartAccount | undefined, + client extends Client | undefined = Client | undefined, + rpcSchema extends RpcSchema | undefined = undefined +> = Prettify< + Client< + transport, + chain extends Chain + ? chain + : // biome-ignore lint/suspicious/noExplicitAny: We need any to infer the chain type + client extends Client + ? chain + : undefined, + account, + rpcSchema extends RpcSchema + ? [...ZeroDevPaymasterRpcSchema, ...rpcSchema] + : ZeroDevPaymasterRpcSchema, + PaymasterActions & ZeroDevPaymasterClientActions + > +> + +export type ZeroDevPaymasterClientConfig< + transport extends Transport = Transport, + chain extends Chain | undefined = Chain | undefined, + account extends SmartAccount | undefined = SmartAccount | undefined, + rpcSchema extends RpcSchema | undefined = undefined +> = Prettify< + Pick< + ClientConfig, + | "account" + | "cacheTime" + | "chain" + | "key" + | "name" + | "pollingInterval" + | "rpcSchema" + | "transport" + > > /** * Creates a ZeroDev-specific Paymaster Client with a given [Transport](https://viem.sh/docs/clients/intro.html) configured for a [Chain](https://viem.sh/docs/clients/chains.html). @@ -40,48 +78,25 @@ export type ZeroDevPaymasterClient = Client< * transport: http(`https://rpc.zerodev.app/api/v2/paymaster/${projectId}`), * }) */ -export const createZeroDevPaymasterClient = < - entryPoint extends EntryPoint, - transport extends Transport, - chain extends Chain | undefined = undefined ->( - parameters: PublicClientConfig & { - entryPoint: entryPoint - } -): ZeroDevPaymasterClient => { +export const createZeroDevPaymasterClient = ( + parameters: ZeroDevPaymasterClientConfig +): ZeroDevPaymasterClient => { const { key = "public", name = "ZeroDev Paymaster Client", - entryPoint: entryPointAddress, transport } = parameters - const entryPointVersion = getEntryPointVersion(entryPointAddress) - const shouldIncludePimlicoProvider = - transport({}).config.key === "http" && entryPointVersion === "v0.7" const client = createClient({ ...parameters, transport: (opts) => { - let _transport = transport({ - ...opts, - retryCount: 0 - }) - if ( - !shouldIncludePimlicoProvider || - isProviderSet(_transport.value?.url, "ALCHEMY") || - isProviderSet(_transport.value?.url, "ZERODEV") || - isProviderSet(_transport.value?.url, "THIRDWEB") || - isProviderSet(_transport.value?.url, "CONDUIT") - ) - return _transport - _transport = http(setPimlicoAsProvider(_transport.value?.url))({ + return transport({ ...opts, retryCount: 0 }) - return _transport }, key, name, type: "zerodevPaymasterClient" }) - return client.extend(zerodevPaymasterActions(parameters.entryPoint)) + return client.extend(paymasterActions).extend(zerodevPaymasterActions()) } diff --git a/packages/core/constants.ts b/packages/core/constants.ts index da051ee0..52019892 100644 --- a/packages/core/constants.ts +++ b/packages/core/constants.ts @@ -1,5 +1,11 @@ import { type Address, type Hex, zeroAddress } from "viem" +import { + type EntryPointVersion, + entryPoint06Address, + entryPoint07Address +} from "viem/account-abstraction" import type { + EntryPointType, KERNEL_V2_VERSION_TYPE, KERNEL_V3_VERSION_TYPE, KERNEL_VERSION_TYPE @@ -112,3 +118,17 @@ export const KernelFactoryToInitCodeHashMap: { [key: Address]: Hex } = { "0x6723b44Abeec4E71eBE3232BD5B455805baDD22f": "0x6fe6e6ea30eddce942b9618033ab8429f9ddac594053bec8a6744fffc71976e2" } + +export const getEntryPoint = ( + entryPointVersion: TEntryPointVersion +): EntryPointType => { + if (entryPointVersion === "0.6") + return { + address: entryPoint06Address, + version: entryPointVersion + } + return { + address: entryPoint07Address, + version: entryPointVersion + } +} diff --git a/packages/core/errors/index.ts b/packages/core/errors/index.ts new file mode 100644 index 00000000..e332b308 --- /dev/null +++ b/packages/core/errors/index.ts @@ -0,0 +1,25 @@ +import { BaseError } from "viem" + +export class AccountNotFoundError extends BaseError { + constructor({ docsPath }: { docsPath?: string | undefined } = {}) { + super( + [ + "Could not find an Account to execute with this Action.", + "Please provide an Account with the `account` argument on the Action, or by supplying an `account` to the Client." + ].join("\n"), + { + docsPath, + docsSlug: "account", + name: "AccountNotFoundError" + } + ) + } +} + +export class SignTransactionNotSupportedBySmartAccountError extends BaseError { + constructor() { + super("Smart account signer doesn't need to sign transactions", { + name: "SignTransactionNotSupportedError" + }) + } +} diff --git a/packages/core/gasTokenAddresses.ts b/packages/core/gasTokenAddresses.ts index c0c44de7..25198984 100644 --- a/packages/core/gasTokenAddresses.ts +++ b/packages/core/gasTokenAddresses.ts @@ -306,7 +306,7 @@ export type TokenSymbolsMap = { 84532: "6TEST" 421613: "USDC" | "6TEST" 421614: "6TEST" - 11155111: "6TEST" + 11155111: "6TEST" | "USDC" 11155420: "6TEST" } @@ -662,7 +662,8 @@ export const gasTokenAddresses: { "6TEST": "0x3870419Ba2BBf0127060bCB37f69A1b1C090992B" }, 11155111: { - "6TEST": "0x3870419Ba2BBf0127060bCB37f69A1b1C090992B" + "6TEST": "0x3870419Ba2BBf0127060bCB37f69A1b1C090992B", + USDC: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238" }, 11155420: { "6TEST": "0x3870419Ba2BBf0127060bCB37f69A1b1C090992B" diff --git a/packages/core/index.ts b/packages/core/index.ts index 1e0116df..d3925368 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -2,8 +2,11 @@ export { createKernelAccount, createKernelAccountV0_2, createKernelAccountV1, - type KernelSmartAccountV1, - type KernelSmartAccount, + type CreateKernelAccountParameters, + type CreateKernelAccountReturnType, + type CreateKernelAccountV1ReturnType, + type KernelSmartAccountImplementation, + type KernelSmartAccountV1Implementation, KERNEL_ADDRESSES, addressToEmptyAccount, EIP1271Abi, @@ -63,3 +66,5 @@ export { getEncodedPluginsData } from "./accounts/kernel/utils/plugins/ep0_7/get export { isProviderSet, setPimlicoAsProvider } from "./clients/utils.js" export { getUserOperationGasPrice } from "./actions/account-client/getUserOperationGasPrice.js" export { isPluginInitialized } from "./accounts/kernel/utils/plugins/ep0_7/isPluginInitialized.js" +export * from "./errors/index.js" +export * from "./utils/index.js" diff --git a/packages/core/package.json b/packages/core/package.json index 67351dc9..412adca0 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@zerodev/sdk", - "version": "5.3.26", + "version": "5.4.0", "author": "ZeroDev", "main": "./_cjs/index.js", "module": "./_esm/index.js", @@ -101,8 +101,7 @@ } }, "peerDependencies": { - "viem": ">=2.16.3 <2.18.0", - "permissionless": ">=0.1.44 <=0.1.45" + "viem": "^2.21.40" }, "dependencies": { "semver": "^7.6.0" diff --git a/packages/core/providers/KernelEIP1193Provider.ts b/packages/core/providers/KernelEIP1193Provider.ts index cf22643a..d6bfd231 100644 --- a/packages/core/providers/KernelEIP1193Provider.ts +++ b/packages/core/providers/KernelEIP1193Provider.ts @@ -1,19 +1,16 @@ import { EventEmitter } from "events" -import type { KernelAccountClient } from "@zerodev/sdk" -import type { EntryPoint } from "permissionless/types" import type { EIP1193Parameters, EIP1193RequestFn, Hash, SendTransactionParameters } from "viem" +import type { KernelAccountClient } from "../clients/kernelAccountClient.js" -export class KernelEIP1193Provider< - entryPoint extends EntryPoint -> extends EventEmitter { - private kernelClient: KernelAccountClient +export class KernelEIP1193Provider extends EventEmitter { + private kernelClient: KernelAccountClient - constructor(kernelClient: KernelAccountClient) { + constructor(kernelClient: KernelAccountClient) { super() this.kernelClient = kernelClient } diff --git a/packages/core/types/index.ts b/packages/core/types/index.ts index 3598d17d..c9264f49 100644 --- a/packages/core/types/index.ts +++ b/packages/core/types/index.ts @@ -12,9 +12,15 @@ export type { KERNEL_V2_VERSION_TYPE, KERNEL_V3_VERSION_TYPE, KERNEL_VERSION_TYPE, - GetKernelVersion + GetKernelVersion, + GetEntryPointAbi, + EntryPointType } from "./kernel.js" export { ValidatorMode } from "./kernel.js" +export type { Signer } from "./utils.js" + export type WithRequired = Required> + +export type PartialPick = Partial> diff --git a/packages/core/types/kernel.ts b/packages/core/types/kernel.ts index bb385d6c..48112b60 100644 --- a/packages/core/types/kernel.ts +++ b/packages/core/types/kernel.ts @@ -1,30 +1,28 @@ +import type { Address, CustomSource, Hex, LocalAccount, PartialBy } from "viem" import type { - ENTRYPOINT_ADDRESS_V06_TYPE, - EntryPoint -} from "permissionless/types" -import type { GetEntryPointVersion } from "permissionless/types/entrypoint" -import type { + EntryPointVersion, UserOperation, - UserOperationWithBigIntAsHex -} from "permissionless/types/userOperation" -import type { Address, CustomSource, Hex, LocalAccount } from "viem" -import type { PartialBy } from "viem/types/utils" + entryPoint06Abi, + entryPoint07Abi +} from "viem/account-abstraction" import type { VALIDATOR_TYPE } from "../constants.js" -export type ZeroDevPaymasterRpcSchema = [ +export type ZeroDevPaymasterRpcSchema< + entryPointVersion extends EntryPointVersion +> = [ { Method: "zd_sponsorUserOperation" Parameters: [ { chainId: number - userOp: GetEntryPointVersion extends "v0.6" + userOp: entryPointVersion extends "0.6" ? PartialBy< - UserOperationWithBigIntAsHex<"v0.6">, + UserOperation<"0.6">, | "callGasLimit" | "preVerificationGas" | "verificationGasLimit" > : PartialBy< - UserOperationWithBigIntAsHex<"v0.7">, + UserOperation<"0.7">, | "callGasLimit" | "preVerificationGas" | "verificationGasLimit" @@ -40,7 +38,7 @@ export type ZeroDevPaymasterRpcSchema = [ shouldConsume?: boolean } ] - ReturnType: GetEntryPointVersion extends "v0.6" + ReturnType: entryPointVersion extends "0.6" ? { paymasterAndData: Hex preVerificationGas: Hex @@ -81,7 +79,7 @@ export type ZeroDevPaymasterRpcSchema = [ Parameters: [ { chainId: number - userOp: UserOperationWithBigIntAsHex<"v0.6"> + userOp: UserOperation<"0.6"> entryPointAddress: Address tokenAddress: Address } @@ -121,28 +119,28 @@ export type ValidatorType = Extract< "PERMISSION" | "SECONDARY" > -export type KernelValidator< - entryPoint extends EntryPoint, - Name extends string = string -> = LocalAccount & { - validatorType: ValidatorType - supportedKernelVersions: string - getNonceKey: ( - accountAddress?: Address, - customNonceKey?: bigint - ) => Promise - getDummySignature( - userOperation: UserOperation>, - pluginEnableSignature?: Hex - ): Promise - signUserOperation: ( - userOperation: UserOperation>, - pluginEnableSignature?: Hex - ) => Promise - getEnableData(accountAddress?: Address): Promise - isEnabled(accountAddress: Address, selector: Hex): Promise - getIdentifier: () => Hex -} +export type KernelValidator = + LocalAccount & { + validatorType: ValidatorType + supportedKernelVersions: string + getNonceKey: ( + accountAddress?: Address, + customNonceKey?: bigint + ) => Promise + getStubSignature( + userOperation: UserOperation, + pluginEnableSignature?: Hex + ): Promise + signUserOperation: ( + userOperation: UserOperation & { + chainId?: number | undefined + }, + pluginEnableSignature?: Hex + ) => Promise + getEnableData(accountAddress?: Address): Promise + isEnabled(accountAddress: Address, selector: Hex): Promise + getIdentifier: () => Hex + } export type KernelValidatorHook = { getEnableData(accountAddress?: Address): Promise @@ -156,10 +154,10 @@ export type ValidatorInitData = { initConfig?: Hex[] } -export type KernelPluginManager = - KernelValidator & { - sudoValidator?: KernelValidator - regularValidator?: KernelValidator +export type KernelPluginManager = + KernelValidator & { + sudoValidator?: KernelValidator + regularValidator?: KernelValidator hook?: KernelValidatorHook getPluginEnableSignature(accountAddress: Address): Promise getValidatorInitData(): Promise @@ -171,12 +169,12 @@ export type KernelPluginManager = accountAddress: Address ) => Promise[0]> signUserOperationWithActiveValidator: ( - userOperation: UserOperation> + userOperation: UserOperation ) => Promise } -export type PluginInstallData = - entryPoint extends ENTRYPOINT_ADDRESS_V06_TYPE +export type PluginInstallData = + entryPointVersion extends "0.6" ? { selector: Hex executor: Address @@ -188,14 +186,16 @@ export type PluginInstallData = : // TODO: Add support for EP v0.7 never -export type KernelPluginManagerParams = { - sudo?: KernelValidator - regular?: KernelValidator +export type KernelPluginManagerParams< + entryPointVersion extends EntryPointVersion +> = { + sudo?: KernelValidator + regular?: KernelValidator hook?: KernelValidatorHook pluginEnableSignature?: Hex validatorInitData?: ValidatorInitData action?: Action - entryPoint: entryPoint + entryPoint: EntryPointType kernelVersion: KERNEL_VERSION_TYPE chainId?: number } & Partial @@ -223,19 +223,17 @@ export enum ValidatorMode { export type CallType = "call" | "delegatecall" -export type KernelEncodeCallDataArgs = - | { - to: Address - value: bigint - data: Hex - callType: CallType | undefined - } - | { - to: Address - value: bigint - data: Hex - callType: CallType | undefined - }[] +export type KernelEncodeCallDataArgs = { + to: Address + value: bigint + data: Hex + callType: CallType | undefined +}[] + +export type GetEntryPointAbi = + entryPointVersion extends "0.6" + ? typeof entryPoint06Abi + : typeof entryPoint07Abi export type Execution = { target: Address @@ -251,7 +249,12 @@ export type KERNEL_VERSION_TYPE = | KERNEL_V2_VERSION_TYPE | KERNEL_V3_VERSION_TYPE -export type GetKernelVersion = - entryPoint extends ENTRYPOINT_ADDRESS_V06_TYPE +export type GetKernelVersion = + entryPointVersion extends "0.6" ? KERNEL_V2_VERSION_TYPE : KERNEL_V3_VERSION_TYPE + +export type EntryPointType = { + address: Address + version: entryPointVersion +} diff --git a/packages/core/types/utils.ts b/packages/core/types/utils.ts new file mode 100644 index 00000000..44cf62b3 --- /dev/null +++ b/packages/core/types/utils.ts @@ -0,0 +1,15 @@ +import type { + Account, + Chain, + EIP1193Provider, + LocalAccount, + OneOf, + Transport, + WalletClient +} from "viem" + +export type Signer = OneOf< + | EIP1193Provider + | WalletClient + | LocalAccount +> diff --git a/packages/core/utils.ts b/packages/core/utils.ts index ae3d6ff7..db006348 100644 --- a/packages/core/utils.ts +++ b/packages/core/utils.ts @@ -1,5 +1,3 @@ -import { ENTRYPOINT_ADDRESS_V06, ENTRYPOINT_ADDRESS_V07 } from "permissionless" -import type { EntryPoint } from "permissionless/types/entrypoint" import { satisfies } from "semver" import { type Address, @@ -10,11 +8,13 @@ import { hexToSignature, isHex, pad, - signatureToHex + signatureToHex, + toHex } from "viem" +import type { EntryPointVersion } from "viem/account-abstraction" import type { ZeroDevPaymasterClient } from "./clients/paymasterClient.js" import type { CALL_TYPE, EXEC_TYPE } from "./constants.js" -import type { GetKernelVersion } from "./types/kernel.js" +import type { EntryPointType, GetKernelVersion } from "./types/kernel.js" export enum KERNEL_FEATURES { ERC1271_SIG_WRAPPER = "ERC1271_SIG_WRAPPER", @@ -40,9 +40,9 @@ export const hasKernelFeature = ( } export const getERC20PaymasterApproveCall = async < - entryPoint extends EntryPoint + entryPointVersion extends EntryPointVersion >( - client: ZeroDevPaymasterClient, + client: ZeroDevPaymasterClient, { gasToken, approveAmount, @@ -50,7 +50,7 @@ export const getERC20PaymasterApproveCall = async < }: { gasToken: Address approveAmount: bigint - entryPoint: Address + entryPoint: EntryPointType } ): Promise<{ to: Address; value: bigint; data: Hex }> => { const response = await client.request({ @@ -58,7 +58,7 @@ export const getERC20PaymasterApproveCall = async < params: [ { chainId: client.chain?.id as number, - entryPointAddress: entryPoint + entryPointAddress: entryPoint.address } ] }) @@ -106,16 +106,15 @@ export const getExecMode = ({ } export const validateKernelVersionWithEntryPoint = < - entryPoint extends EntryPoint + entryPointVersion extends EntryPointVersion >( - entryPointAddress: entryPoint, - kernelVersion: GetKernelVersion + entryPointVersion: EntryPointVersion, + kernelVersion: GetKernelVersion ) => { if ( - (entryPointAddress === ENTRYPOINT_ADDRESS_V06 && + (entryPointVersion === "0.6" && !satisfies(kernelVersion, ">=0.2.2 || <=0.2.4")) || - (entryPointAddress === ENTRYPOINT_ADDRESS_V07 && - !satisfies(kernelVersion, ">=0.3.0")) + (entryPointVersion === "0.7" && !satisfies(kernelVersion, ">=0.3.0")) ) { throw new Error( "KernelVersion should be >= 0.2.2 and <= 0.2.4 for EntryPointV0.6 and >= 0.3.0 for EntryPointV0.7" @@ -126,3 +125,32 @@ export const validateKernelVersionWithEntryPoint = < export const satisfiesRange = (version: string, range: string): boolean => { return satisfies(version, range) } + +// biome-ignore lint/suspicious/noExplicitAny: it's a recursive function, so it's hard to type +export function deepHexlify(obj: any): any { + if (typeof obj === "function") { + return undefined + } + if (obj == null || typeof obj === "string" || typeof obj === "boolean") { + return obj + } + + if (typeof obj === "bigint") { + return toHex(obj) + } + + if (obj._isBigNumber != null || typeof obj !== "object") { + return toHex(obj).replace(/^0x0/, "0x") + } + if (Array.isArray(obj)) { + return obj.map((member) => deepHexlify(member)) + } + return Object.keys(obj).reduce( + // biome-ignore lint/suspicious/noExplicitAny: it's a recursive function, so it's hard to type + (set: any, key: string) => { + set[key] = deepHexlify(obj[key]) + return set + }, + {} + ) +} diff --git a/packages/core/utils/index.ts b/packages/core/utils/index.ts new file mode 100644 index 00000000..586ef3b7 --- /dev/null +++ b/packages/core/utils/index.ts @@ -0,0 +1 @@ +export * from "./toSigner.js" diff --git a/packages/core/utils/toSigner.ts b/packages/core/utils/toSigner.ts new file mode 100644 index 00000000..14442a51 --- /dev/null +++ b/packages/core/utils/toSigner.ts @@ -0,0 +1,106 @@ +// Copied from: https://github.com/pimlicolabs/permissionless.js/blob/main/packages/permissionless/utils/toOwner.ts +import { + type Account, + type Address, + type Chain, + type EIP1193Provider, + type EIP1193RequestFn, + type EIP1474Methods, + type LocalAccount, + type Transport, + type TypedData, + type TypedDataDefinition, + type WalletClient, + createWalletClient, + custom +} from "viem" +import { toAccount } from "viem/accounts" + +import { signMessage, signTypedData } from "viem/actions" +import type { Signer } from "../types" + +export async function toSigner({ + signer, + address +}: { + signer: Signer + address?: Address +}): Promise { + if ("type" in signer && signer.type === "local") { + return signer as LocalAccount + } + + let walletClient: + | WalletClient + | undefined = undefined + + if ("request" in signer) { + if (!address) { + try { + ;[address] = await ( + signer.request as EIP1193RequestFn + )({ + method: "eth_requestAccounts" + }) + } catch { + ;[address] = await ( + signer.request as EIP1193RequestFn + )({ + method: "eth_accounts" + }) + } + } + if (!address) { + // For TS to be happy + throw new Error("address is required") + } + walletClient = createWalletClient({ + account: address, + transport: custom(signer as EIP1193Provider) + }) + } + + if (!walletClient) { + walletClient = signer as WalletClient< + Transport, + Chain | undefined, + Account + > + } + + return toAccount({ + address: walletClient.account.address, + async signMessage({ message }) { + return signMessage( + walletClient as WalletClient< + Transport, + Chain | undefined, + Account + >, + { message } + ) + }, + async signTypedData(typedData) { + const { primaryType, domain, message, types } = + typedData as TypedDataDefinition + return signTypedData( + walletClient as WalletClient< + Transport, + Chain | undefined, + Account + >, + { + primaryType, + domain, + message, + types + } + ) + }, + async signTransaction(_) { + throw new Error( + "Smart account signer doesn't need to sign transactions" + ) + } + }) as LocalAccount +} diff --git a/packages/test/config.ts b/packages/test/config.ts index 186f1a87..56beb8e6 100644 --- a/packages/test/config.ts +++ b/packages/test/config.ts @@ -1,5 +1,4 @@ -import type { EntryPointVersion } from "permissionless/types" -import type { Chain } from "viem" +import type { EntryPointVersion } from "viem/account-abstraction" import { baseSepolia, optimismSepolia, @@ -17,7 +16,7 @@ export const config: { } } } = { - "v0.6": { + "0.6": { [sepolia.id]: { rpcUrl: process.env.RPC_URL_SEPOLIA || "", bundlerUrl: process.env.ZERODEV_BUNDLER_RPC_HOST_EPV07 || "", @@ -25,7 +24,7 @@ export const config: { projectId: process.env.ZERODEV_PROJECT_ID_SEPOLIA || "" } }, - "v0.7": { + "0.7": { [sepolia.id]: { rpcUrl: process.env.RPC_URL_SEPOLIA || "", bundlerUrl: process.env.ZERODEV_BUNDLER_RPC_HOST_EPV07 || "", diff --git a/packages/test/ecdsaKernelAccount.test.ts b/packages/test/ecdsaKernelAccount.test.ts index 7b1d15ef..99a068e4 100644 --- a/packages/test/ecdsaKernelAccount.test.ts +++ b/packages/test/ecdsaKernelAccount.test.ts @@ -9,7 +9,7 @@ import { constants, EIP1271Abi, type KernelAccountClient, - type KernelSmartAccount, + type KernelSmartAccountImplementation, createKernelAccount, getCustomNonceKeyFromString, getERC20PaymasterApproveCall, @@ -18,9 +18,6 @@ import { import { gasTokenAddresses } from "@zerodev/sdk" import dotenv from "dotenv" import { ethers } from "ethers" -import { type BundlerClient, ENTRYPOINT_ADDRESS_V06 } from "permissionless" -import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" -import type { ENTRYPOINT_ADDRESS_V06_TYPE } from "permissionless/types" import { type Address, type Chain, @@ -38,6 +35,10 @@ import { parseEther, zeroAddress } from "viem" +import { + type SmartAccount, + entryPoint06Address +} from "viem/account-abstraction" import { privateKeyToAccount } from "viem/accounts" import { sepolia } from "viem/chains" import { EntryPointAbi } from "./abis/EntryPoint.js" @@ -48,19 +49,19 @@ import { TOKEN_ACTION_ADDRESS, config } from "./config.js" import { Test_ERC20Address, findUserOperationEvent, - getEcdsaKernelAccountWithRandomSigner, getEntryPoint, - getKernelAccountClient, - getKernelBundlerClient, getPublicClient, - getSignerToEcdsaKernelAccount, - getZeroDevERC20PaymasterClient, getZeroDevPaymasterClient, index, kernelVersion, + mintToAccount, waitForNonceUpdate -} from "./utils.js" -import { mintToAccount } from "./v0.7/utils.js" +} from "./utils_0_6/common.js" +import { + getEcdsaKernelAccountWithRandomSigner, + getKernelAccountClient, + getSignerToEcdsaKernelAccount +} from "./utils_0_6/ecdsaUtils.js" dotenv.config() @@ -97,15 +98,13 @@ const TX_HASH_REGEX = /^0x[0-9a-fA-F]{64}$/ const TEST_TIMEOUT = 1000000 describe("ECDSA kernel Account", () => { - let account: KernelSmartAccount + let account: SmartAccount> let ownerAccount: PrivateKeyAccount let publicClient: PublicClient - let bundlerClient: BundlerClient let kernelClient: KernelAccountClient< - ENTRYPOINT_ADDRESS_V06_TYPE, Transport, Chain, - KernelSmartAccount + SmartAccount> > let greeterContract: GetContractReturnType< typeof GreeterAbi, @@ -121,18 +120,10 @@ describe("ECDSA kernel Account", () => { ownerAccount = privateKeyToAccount(ownerPrivateKey as Hex) account = await getSignerToEcdsaKernelAccount() publicClient = await getPublicClient() - bundlerClient = getKernelBundlerClient() + const zerodevPaymaster = getZeroDevPaymasterClient() kernelClient = await getKernelAccountClient({ account, - middleware: { - sponsorUserOperation: async ({ userOperation, entryPoint }) => { - const zerodevPaymaster = getZeroDevPaymasterClient() - return zerodevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint - }) - } - } + paymaster: zerodevPaymaster }) greeterContract = getContract({ abi: GreeterAbi, @@ -151,7 +142,7 @@ describe("ECDSA kernel Account", () => { test("getKernelAddressFromECDSA util should return valid account address", async () => { const generatedAccountAddress = await getKernelAddressFromECDSA({ - entryPointAddress: ENTRYPOINT_ADDRESS_V06, + entryPoint: getEntryPoint(), kernelVersion, eoaAddress: ownerAccount.address, index: index, @@ -166,15 +157,15 @@ describe("ECDSA kernel Account", () => { expect(account.address).toEqual(generatedAccountAddress) }) - test("Account should throw when trying to sign a transaction", async () => { - await expect(async () => { - await account.signTransaction({ - to: zeroAddress, - value: 0n, - data: "0x" - }) - }).toThrow(new SignTransactionNotSupportedBySmartAccount()) - }) + // test("Account should throw when trying to sign a transaction", async () => { + // await expect(async () => { + // await account.signTransaction({ + // to: zeroAddress, + // value: 0n, + // data: "0x" + // }) + // }).toThrow(new SignTransactionNotSupportedBySmartAccount()) + // }) test( "Should validate message signatures for undeployed accounts (6492)", @@ -200,7 +191,7 @@ describe("ECDSA kernel Account", () => { message, signature: signature, provider: new ethers.providers.JsonRpcProvider( - config["v0.6"][sepolia.id].rpcUrl + config["0.6"][sepolia.id].rpcUrl ) }) expect(ambireResult).toBeTrue() @@ -265,7 +256,7 @@ describe("ECDSA kernel Account", () => { }, signature: signature, provider: new ethers.providers.JsonRpcProvider( - config["v0.6"][sepolia.id].rpcUrl + config["0.6"][sepolia.id].rpcUrl ) }) expect(ambireResult).toBeTrue() @@ -291,7 +282,7 @@ describe("ECDSA kernel Account", () => { message, signature: response, provider: new ethers.providers.JsonRpcProvider( - config["v0.6"][sepolia.id].rpcUrl + config["0.6"][sepolia.id].rpcUrl ) }) expect(ambireResult).toBeTrue() @@ -364,10 +355,13 @@ describe("ECDSA kernel Account", () => { test( "Client deploy contract", async () => { - const response = await kernelClient.deployContract({ + const callData = await kernelClient.account.encodeDeployCallData({ abi: GreeterAbi, bytecode: GreeterBytecode }) + const response = await kernelClient.sendTransaction({ + callData + }) expect(response).toBeString() expect(response).toHaveLength(TX_HASH_LENGTH) @@ -386,8 +380,8 @@ describe("ECDSA kernel Account", () => { test( "Smart account client send multiple transactions", async () => { - const response = await kernelClient.sendTransactions({ - transactions: [ + const response = await kernelClient.sendTransaction({ + calls: [ { to: zeroAddress, value: 0n, @@ -446,8 +440,8 @@ describe("ECDSA kernel Account", () => { "Client signs and then sends UserOp with paymaster", async () => { const userOp = await kernelClient.signUserOperation({ - userOperation: { - callData: await kernelClient.account.encodeCallData({ + callData: await kernelClient.account.encodeCalls([ + { to: process.env.GREETER_ADDRESS as Address, value: 0n, data: encodeFunctionData({ @@ -455,16 +449,16 @@ describe("ECDSA kernel Account", () => { functionName: "setGreeting", args: ["hello world"] }) - }) - } + } + ]) }) expect(userOp.signature).not.toBe("0x") - const userOpHash = await bundlerClient.sendUserOperation({ - userOperation: userOp + const userOpHash = await kernelClient.sendUserOperation({ + ...userOp }) expect(userOpHash).toHaveLength(66) - await bundlerClient.waitForUserOperationReceipt({ + await kernelClient.waitForUserOperationReceipt({ hash: userOpHash }) @@ -486,28 +480,28 @@ describe("ECDSA kernel Account", () => { amountToMint ) const userOpHash = await kernelClient.sendUserOperation({ - userOperation: { - callData: await kernelClient.account.encodeCallData({ - to: TOKEN_ACTION_ADDRESS, - value: 0n, - data: encodeFunctionData({ - abi: TokenActionsAbi, - functionName: "transferERC20Action", - args: [ - Test_ERC20Address, - amountToTransfer, - "0xA02CDdFa44B8C01b4257F54ac1c43F75801E8175" - ] - }), - callType: "delegatecall" - }) - } + callData: await kernelClient.account.encodeCalls( + [ + { + to: TOKEN_ACTION_ADDRESS, + value: 0n, + data: encodeFunctionData({ + abi: TokenActionsAbi, + functionName: "transferERC20Action", + args: [ + Test_ERC20Address, + amountToTransfer, + "0xA02CDdFa44B8C01b4257F54ac1c43F75801E8175" + ] + }) + } + ], + "delegatecall" + ) + }) + const transaction = await kernelClient.waitForUserOperationReceipt({ + hash: userOpHash }) - const transaction = await bundlerClient.waitForUserOperationReceipt( - { - hash: userOpHash - } - ) console.log( "transferTransactionHash", `https://sepolia.etherscan.io/tx/${transaction.receipt.transactionHash}` @@ -546,24 +540,24 @@ describe("ECDSA kernel Account", () => { async () => { const customNonceKey = getCustomNonceKeyFromString( "Hello, World!", - ENTRYPOINT_ADDRESS_V06 + "0.6" ) - const nonce = await account.getNonce(customNonceKey) + const nonce = await account.getNonce({ key: customNonceKey }) const userOpHash = await kernelClient.sendUserOperation({ - userOperation: { - callData: await kernelClient.account.encodeCallData({ + callData: await kernelClient.account.encodeCalls([ + { to: zeroAddress, value: 0n, data: "0x" - }), - nonce - } + } + ]), + nonce }) expect(userOpHash).toHaveLength(66) - await bundlerClient.waitForUserOperationReceipt({ + await kernelClient.waitForUserOperationReceipt({ hash: userOpHash }) }, @@ -600,26 +594,14 @@ describe("ECDSA kernel Account", () => { const publicClient = await getPublicClient() - const bundlerClient = getKernelBundlerClient() - + const zerodevPaymaster = getZeroDevPaymasterClient() const kernelClient = await getKernelAccountClient({ account, - middleware: { - sponsorUserOperation: async ({ - entryPoint: _entryPoint, - userOperation - }) => { - const zerodevPaymaster = getZeroDevPaymasterClient() - return zerodevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zerodevPaymaster }) - const response = await kernelClient.sendTransactions({ - transactions: [ + const response = await kernelClient.sendTransaction({ + calls: [ { to: zeroAddress, value: 0n, @@ -655,7 +637,7 @@ describe("ECDSA kernel Account", () => { if (event.eventName === "UserOperationEvent") { eventFound = true const userOperation = - await bundlerClient.getUserOperationByHash({ + await kernelClient.getUserOperation({ hash: event.args.userOpHash }) expect( @@ -677,48 +659,37 @@ describe("ECDSA kernel Account", () => { const publicClient = await getPublicClient() - const bundlerClient = getKernelBundlerClient() - + const zerodevPaymaster = getZeroDevPaymasterClient() const kernelClient = await getKernelAccountClient({ account, - middleware: { - sponsorUserOperation: async ({ - entryPoint, - userOperation - }) => { - const zerodevPaymaster = - getZeroDevERC20PaymasterClient() - return zerodevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint, - gasToken: gasTokenAddresses[sepolia.id]["6TEST"] - }) - } - } + paymaster: zerodevPaymaster }) - const pmClient = await getZeroDevERC20PaymasterClient() - const response = await kernelClient.sendTransactions({ - transactions: [ - { - to: gasTokenAddresses[sepolia.id]["6TEST"], - data: encodeFunctionData({ - abi: TEST_ERC20Abi, - functionName: "mint", - args: [account.address, parseEther("0.9")] - }), - value: 0n - }, - await getERC20PaymasterApproveCall(pmClient, { - gasToken: gasTokenAddresses[sepolia.id]["6TEST"], - approveAmount: parseEther("0.9") + const response = await kernelClient.sendTransaction({ + calls: [ + // { + // to: gasTokenAddresses[sepolia.id].USDC, + // data: encodeFunctionData({ + // abi: TEST_ERC20Abi, + // functionName: "mint", + // args: [account.address, parseEther("0.9")] + // }), + // value: 0n + // }, + await getERC20PaymasterApproveCall(zerodevPaymaster, { + gasToken: gasTokenAddresses[sepolia.id].USDC, + approveAmount: parseEther("0.9"), + entryPoint: getEntryPoint() }), { to: zeroAddress, value: 0n, data: "0x" } - ] + ], + paymasterContext: { + token: gasTokenAddresses[sepolia.id].USDC + } }) console.log( @@ -765,7 +736,7 @@ describe("ECDSA kernel Account", () => { `https://jiffyscan.xyz/userOpHash/${event.args.userOpHash}?network=sepolia/` ) const userOperation = - await bundlerClient.getUserOperationByHash({ + await kernelClient.getUserOperation({ hash: event.args.userOpHash }) expect( @@ -787,20 +758,10 @@ describe("ECDSA kernel Account", () => { const initialEcdsaSmartAccount = await getSignerToEcdsaKernelAccount() const publicClient = await getPublicClient() + const zerodevPaymaster = getZeroDevPaymasterClient() const kernelClient = await getKernelAccountClient({ account: initialEcdsaSmartAccount, - middleware: { - sponsorUserOperation: async ({ - entryPoint, - userOperation - }) => { - const zerodevPaymaster = getZeroDevPaymasterClient() - return zerodevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint - }) - } - } + paymaster: zerodevPaymaster }) // Send an initial tx to deploy the account @@ -834,7 +795,7 @@ describe("ECDSA kernel Account", () => { plugins: { sudo: ecdsaValidatorPlugin }, - deployedAccountAddress, + address: deployedAccountAddress, index } ) diff --git a/packages/test/modularPermissionKernelAccount.test.ts b/packages/test/modularPermissionKernelAccount.test.ts index 8ab7ae70..744204fc 100644 --- a/packages/test/modularPermissionKernelAccount.test.ts +++ b/packages/test/modularPermissionKernelAccount.test.ts @@ -1,7 +1,10 @@ // @ts-expect-error import { beforeAll, describe, expect, test } from "bun:test" -import type { KernelAccountClient, KernelSmartAccount } from "@zerodev/sdk" -import type { ENTRYPOINT_ADDRESS_V06_TYPE } from "permissionless/_types/types" +import type { + KernelAccountClient, + KernelSmartAccountImplementation, + ZeroDevPaymasterClient +} from "@zerodev/sdk" import { type Address, type Chain, @@ -13,6 +16,7 @@ import { pad, zeroAddress } from "viem" +import type { SmartAccount } from "viem/account-abstraction" import { privateKeyToAccount } from "viem/accounts" import { toGasPolicy } from "../../plugins/modularPermission/policies/toGasPolicy" import { @@ -27,7 +31,7 @@ import { getSignerToEcdsaKernelAccount, getSignerToModularPermissionKernelAccount, getZeroDevPaymasterClient -} from "./utils" +} from "./utils_0_6" const TEST_TIMEOUT = 1000000 @@ -38,11 +42,11 @@ describe("Modular Permission kernel Account", async () => { let owner: PrivateKeyAccount let ecdsaSmartAccountClient: KernelAccountClient< - ENTRYPOINT_ADDRESS_V06_TYPE, Transport, Chain, - KernelSmartAccount + SmartAccount> > + let zerodevPaymaster: ZeroDevPaymasterClient async function mintToAccount(amount: bigint) { const balanceBefore = await publicClient.readContract({ @@ -77,20 +81,12 @@ describe("Modular Permission kernel Account", async () => { publicClient = await getPublicClient() testPrivateKey = process.env.TEST_PRIVATE_KEY as Hex owner = privateKeyToAccount(testPrivateKey) + zerodevPaymaster = getZeroDevPaymasterClient() ecdsaSmartAccountClient = await getKernelAccountClient({ account: await getSignerToEcdsaKernelAccount(), - middleware: { - sponsorUserOperation: async ({ userOperation, entryPoint }) => { - const kernelPaymaster = getZeroDevPaymasterClient() - return kernelPaymaster.sponsorUserOperation({ - userOperation, - entryPoint - }) - } - } + paymaster: zerodevPaymaster }) - accountAddress = (await ecdsaSmartAccountClient.account - ?.address) as Address + accountAddress = ecdsaSmartAccountClient.account.address }) test( @@ -103,18 +99,7 @@ describe("Modular Permission kernel Account", async () => { maxGasAllowedInWei: 1000000000000000000n }) ]), - middleware: { - sponsorUserOperation: async ({ - userOperation, - entryPoint - }) => { - const kernelPaymaster = getZeroDevPaymasterClient() - return kernelPaymaster.sponsorUserOperation({ - userOperation, - entryPoint - }) - } - } + paymaster: zerodevPaymaster }) const txHash = @@ -168,18 +153,7 @@ describe("Modular Permission kernel Account", async () => { ] }) ]), - middleware: { - sponsorUserOperation: async ({ - userOperation, - entryPoint - }) => { - const kernelPaymaster = getZeroDevPaymasterClient() - return kernelPaymaster.sponsorUserOperation({ - userOperation, - entryPoint - }) - } - } + paymaster: zerodevPaymaster }) const txHash = @@ -215,18 +189,7 @@ describe("Modular Permission kernel Account", async () => { ] }) ]), - middleware: { - sponsorUserOperation: async ({ - userOperation, - entryPoint - }) => { - const kernelPaymaster = getZeroDevPaymasterClient() - return kernelPaymaster.sponsorUserOperation({ - userOperation, - entryPoint - }) - } - } + paymaster: zerodevPaymaster }) const txHash = diff --git a/packages/test/package.json b/packages/test/package.json index 21b76c39..32902f3e 100644 --- a/packages/test/package.json +++ b/packages/test/package.json @@ -12,11 +12,10 @@ }, "dependencies": { "dotenv": "^16.3.1", - "viem": ">=2.16.3 <2.18.0", + "viem": "^2.21.40", "@zerodev/sdk": "workspace:*", "@zerodev/ecdsa-validator": "workspace:*", "@zerodev/session-key": "workspace:*", - "@zerodev/modular-permission": "workspace:*", - "permissionless": ">=0.1.44 <=0.1.45" + "@zerodev/modular-permission": "workspace:*" } } diff --git a/packages/test/recoveryKernelAccount.test.ts b/packages/test/recoveryKernelAccount.test.ts index 3d38b2c5..233b0f66 100644 --- a/packages/test/recoveryKernelAccount.test.ts +++ b/packages/test/recoveryKernelAccount.test.ts @@ -1,15 +1,18 @@ // @ts-expect-error import { beforeAll, describe, expect, test } from "bun:test" -import type { KernelAccountClient, KernelSmartAccount } from "@zerodev/sdk" +import type { + KernelAccountClient, + KernelSmartAccountImplementation, + ZeroDevPaymasterClient +} from "@zerodev/sdk" import dotenv from "dotenv" -import { type BundlerClient, bundlerActions } from "permissionless" -import type { ENTRYPOINT_ADDRESS_V06_TYPE } from "permissionless/types/entrypoint.js" import { type Chain, type PublicClient, type Transport, zeroAddress } from "viem" +import type { SmartAccount } from "viem/account-abstraction" import { generatePrivateKey } from "viem/accounts" import { getEcdsaKernelAccountWithPrivateKey, @@ -18,7 +21,7 @@ import { getPublicClient, getRecoveryKernelAccount, getZeroDevPaymasterClient -} from "./utils.js" +} from "./utils_0_6" dotenv.config() @@ -51,16 +54,15 @@ const ETHEREUM_ADDRESS_REGEX = /^0x[0-9a-fA-F]{40}$/ const TEST_TIMEOUT = 1000000 describe("Recovery kernel Account", () => { - let ownerAccount: KernelSmartAccount - let recoveryAccount: KernelSmartAccount + let ownerAccount: SmartAccount> + let recoveryAccount: SmartAccount> let publicClient: PublicClient - let bundlerClient: BundlerClient let ownerKernelClient: KernelAccountClient< - ENTRYPOINT_ADDRESS_V06_TYPE, Transport, Chain, - KernelSmartAccount + SmartAccount> > + let zeroDevPaymaster: ZeroDevPaymasterClient beforeAll(async () => { publicClient = await getPublicClient() @@ -68,23 +70,12 @@ describe("Recovery kernel Account", () => { ownerAccount = await getEcdsaKernelAccountWithPrivateKey(ownerPrivateKey) + zeroDevPaymaster = getZeroDevPaymasterClient() recoveryAccount = await getRecoveryKernelAccount(ownerAccount.address) ownerKernelClient = await getKernelAccountClient({ account: ownerAccount, - middleware: { - sponsorUserOperation: async ({ userOperation, entryPoint }) => { - const zerodevPaymaster = getZeroDevPaymasterClient() - return zerodevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint - }) - } - } + paymaster: zeroDevPaymaster }) - - bundlerClient = ownerKernelClient.extend( - bundlerActions(getEntryPoint()) - ) }) test("Account address should be a valid Ethereum address", async () => { @@ -106,17 +97,14 @@ describe("Recovery kernel Account", () => { txHash ) const userOpHash = await ownerKernelClient.sendUserOperation({ - userOperation: { - callData: - await recoveryAccount.encodeModuleInstallCallData() - } + callData: await recoveryAccount.encodeModuleInstallCallData() }) console.log("userOpHash:", userOpHash) expect(userOpHash).toHaveLength(66) const transactionReceipt = - await bundlerClient.waitForUserOperationReceipt({ + await ownerKernelClient.waitForUserOperationReceipt({ hash: userOpHash }) console.log( diff --git a/packages/test/sessionKeyKernelAccount.test.ts b/packages/test/sessionKeyKernelAccount.test.ts index 9961c3e6..7d65b839 100644 --- a/packages/test/sessionKeyKernelAccount.test.ts +++ b/packages/test/sessionKeyKernelAccount.test.ts @@ -3,19 +3,18 @@ import { beforeAll, describe, expect, test } from "bun:test" import { KernelAccountAbi, type KernelAccountClient, - type KernelSmartAccount + type KernelSmartAccountImplementation, + type ZeroDevPaymasterClient } from "@zerodev/sdk" import { Operation, ParamOperator, - type SessionKeyPlugin, anyPaymaster, deserializeSessionKeyAccount, deserializeSessionKeyAccountParams, serializeSessionKeyAccount, signerToSessionKeyValidator } from "@zerodev/session-key" -import type { ENTRYPOINT_ADDRESS_V06_TYPE } from "permissionless/types/entrypoint.js" import { http, type Address, @@ -27,12 +26,12 @@ import { createPublicClient, encodeFunctionData, getAbiItem, - getFunctionSelector, pad, parseEther, toFunctionSelector, zeroAddress } from "viem" +import type { SmartAccount } from "viem/account-abstraction" import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" import { sepolia } from "viem/chains" import { TEST_ERC20Abi } from "./abis/Test_ERC20Abi.js" @@ -42,20 +41,19 @@ import { Test_ERC20Address, getEntryPoint, getKernelAccountClient, - getKernelBundlerClient, getPublicClient, getSessionKeyToSessionKeyKernelAccount, getSignerToEcdsaKernelAccount, getSignerToSessionKeyKernelAccount, getZeroDevPaymasterClient, kernelVersion -} from "./utils.js" +} from "./utils_0_6" describe("Session Key kernel Account", async () => { let publicClient: PublicClient const client = await createPublicClient({ chain: sepolia, - transport: http(config["v0.6"][sepolia.id].rpcUrl as string) + transport: http(config["0.6"][sepolia.id].rpcUrl as string) }) const executeBatchSelector = toFunctionSelector( getAbiItem({ @@ -73,17 +71,16 @@ describe("Session Key kernel Account", async () => { let owner: PrivateKeyAccount let accountAddress: Address let ecdsaSmartAccountClient: KernelAccountClient< - ENTRYPOINT_ADDRESS_V06_TYPE, Transport, Chain, - KernelSmartAccount + SmartAccount> > let sessionKeySmartAccountClient: KernelAccountClient< - ENTRYPOINT_ADDRESS_V06_TYPE, Transport, Chain, - KernelSmartAccount + SmartAccount> > + let zerodevPaymaster: ZeroDevPaymasterClient async function mintToAccount(amount: bigint) { const balanceBefore = await client.readContract({ @@ -119,33 +116,17 @@ describe("Session Key kernel Account", async () => { publicClient = await getPublicClient() testPrivateKey = process.env.TEST_PRIVATE_KEY as Hex owner = privateKeyToAccount(testPrivateKey) + zerodevPaymaster = getZeroDevPaymasterClient() sessionKeySmartAccountClient = await getKernelAccountClient({ account: await getSignerToSessionKeyKernelAccount(), - middleware: { - sponsorUserOperation: async ({ userOperation, entryPoint }) => { - const kernelPaymaster = getZeroDevPaymasterClient() - return kernelPaymaster.sponsorUserOperation({ - userOperation, - entryPoint - }) - } - } + paymaster: zerodevPaymaster }) - accountAddress = (await sessionKeySmartAccountClient.account - ?.address) as Address + accountAddress = sessionKeySmartAccountClient.account.address ecdsaSmartAccountClient = await getKernelAccountClient({ account: await getSignerToEcdsaKernelAccount(), - middleware: { - sponsorUserOperation: async ({ userOperation, entryPoint }) => { - const kernelPaymaster = getZeroDevPaymasterClient() - return kernelPaymaster.sponsorUserOperation({ - userOperation, - entryPoint - }) - } - } + paymaster: zerodevPaymaster }) }) @@ -205,15 +186,7 @@ describe("Session Key kernel Account", async () => { selector: transfer20ActionSelector } ), - middleware: { - sponsorUserOperation: async ({ userOperation, entryPoint }) => { - const kernelPaymaster = getZeroDevPaymasterClient() - return kernelPaymaster.sponsorUserOperation({ - userOperation, - entryPoint - }) - } - } + paymaster: zerodevPaymaster }) const amountToTransfer = 10000n @@ -231,18 +204,15 @@ describe("Session Key kernel Account", async () => { }) const userOpHash = await _sessionKeySmartAccountClient.sendUserOperation({ - userOperation: { - callData: transferData - } + callData: transferData }) console.log( "jiffyScanLink:", `https://jiffyscan.xyz/userOpHash/${userOpHash}?network=sepolia/` ) - const bundlerClient = getKernelBundlerClient() const { receipt: { transactionHash: transferTransactionHash } - } = await bundlerClient.waitForUserOperationReceipt({ + } = await _sessionKeySmartAccountClient.waitForUserOperationReceipt({ hash: userOpHash }) @@ -276,15 +246,7 @@ describe("Session Key kernel Account", async () => { const _sessionKeySmartAccountClient = await getKernelAccountClient({ account: await getSessionKeyToSessionKeyKernelAccount(sessionKeyPlugin), - middleware: { - sponsorUserOperation: async ({ userOperation, entryPoint }) => { - const kernelPaymaster = getZeroDevPaymasterClient() - return kernelPaymaster.sponsorUserOperation({ - userOperation, - entryPoint - }) - } - } + paymaster: zerodevPaymaster }) const amountToTransfer = 10000n @@ -354,15 +316,7 @@ describe("Session Key kernel Account", async () => { const _sessionKeySmartAccountClient = await getKernelAccountClient({ account: await getSessionKeyToSessionKeyKernelAccount(sessionKeyPlugin), - middleware: { - sponsorUserOperation: async ({ userOperation, entryPoint }) => { - const kernelPaymaster = getZeroDevPaymasterClient() - return kernelPaymaster.sponsorUserOperation({ - userOperation, - entryPoint - }) - } - } + paymaster: zerodevPaymaster }) const amountToTransfer = 10000n @@ -442,15 +396,7 @@ describe("Session Key kernel Account", async () => { address: zeroAddress } ), - middleware: { - sponsorUserOperation: async ({ userOperation, entryPoint }) => { - const kernelPaymaster = getZeroDevPaymasterClient() - return kernelPaymaster.sponsorUserOperation({ - userOperation, - entryPoint - }) - } - } + paymaster: zerodevPaymaster }) const amountToTransfer = 10000n @@ -478,8 +424,8 @@ describe("Session Key kernel Account", async () => { args: [accountAddress, owner.address] }) const transferTransactionHash = - await _sessionKeySmartAccountClient.sendTransactions({ - transactions: [ + await _sessionKeySmartAccountClient.sendTransaction({ + calls: [ { to: Test_ERC20Address, data: increaseAllowanceData, @@ -537,15 +483,7 @@ describe("Session Key kernel Account", async () => { const _sessionKeySmartAccountClient = await getKernelAccountClient({ account: await getSessionKeyToSessionKeyKernelAccount(sessionKeyPlugin), - middleware: { - sponsorUserOperation: async ({ userOperation, entryPoint }) => { - const kernelPaymaster = getZeroDevPaymasterClient() - return kernelPaymaster.sponsorUserOperation({ - userOperation, - entryPoint - }) - } - } + paymaster: zerodevPaymaster }) const amountToTransfer = parseEther("0.0000000001") @@ -602,15 +540,7 @@ describe("Session Key kernel Account", async () => { kernelVersion, serializedSessionKeyAccountParams ), - middleware: { - sponsorUserOperation: async ({ userOperation, entryPoint }) => { - const kernelPaymaster = getZeroDevPaymasterClient() - return kernelPaymaster.sponsorUserOperation({ - userOperation, - entryPoint - }) - } - } + paymaster: zerodevPaymaster }) const amountToTransfer = parseEther("0.000001") @@ -762,15 +692,7 @@ describe("Session Key kernel Account", async () => { ) } ), - middleware: { - sponsorUserOperation: async ({ userOperation, entryPoint }) => { - const kernelPaymaster = getZeroDevPaymasterClient() - return kernelPaymaster.sponsorUserOperation({ - userOperation, - entryPoint - }) - } - } + paymaster: zerodevPaymaster }) const amountToTransfer = 10000n @@ -788,26 +710,25 @@ describe("Session Key kernel Account", async () => { }) const userOpHash = await _sessionKeySmartAccountClient.sendUserOperation({ - userOperation: { - callData: - await _sessionKeySmartAccountClient.account.encodeCallData( + callData: + await _sessionKeySmartAccountClient.account.encodeCalls( + [ { to: TOKEN_ACTION_ADDRESS, data: transferData, - value: 0n, - callType: "delegatecall" + value: 0n } - ) - } + ], + "delegatecall" + ) }) console.log( "jiffyScanLink:", `https://jiffyscan.xyz/userOpHash/${userOpHash}?network=sepolia/` ) - const bundlerClient = getKernelBundlerClient() const { receipt: { transactionHash: transferTransactionHash } - } = await bundlerClient.waitForUserOperationReceipt({ + } = await _sessionKeySmartAccountClient.waitForUserOperationReceipt({ hash: userOpHash }) diff --git a/packages/test/utils.ts b/packages/test/utils.ts index 7619f336..fc8b5e89 100644 --- a/packages/test/utils.ts +++ b/packages/test/utils.ts @@ -24,20 +24,6 @@ import { } from "@zerodev/session-key" import { createWeightedECDSAValidator } from "@zerodev/weighted-ecdsa-validator" import { getRecoveryAction } from "@zerodev/weighted-ecdsa-validator/constants.js" -import { - type BundlerClient, - ENTRYPOINT_ADDRESS_V06, - createBundlerClient -} from "permissionless" -import { - type SmartAccount, - signerToSimpleSmartAccount -} from "permissionless/accounts" -import type { Middleware } from "permissionless/actions/smartAccount" -import type { - ENTRYPOINT_ADDRESS_V06_TYPE, - EntryPoint -} from "permissionless/types" import { http, type AbiItem, @@ -574,8 +560,7 @@ export const getZeroDevPaymasterClient = () => { return createZeroDevPaymasterClient({ chain: chain, - transport: http(getPaymasterRpc()), - entryPoint: getEntryPoint() + transport: http(getPaymasterRpc()) }) } diff --git a/packages/test/utils_0_6/common.ts b/packages/test/utils_0_6/common.ts new file mode 100644 index 00000000..2b3d6c89 --- /dev/null +++ b/packages/test/utils_0_6/common.ts @@ -0,0 +1,199 @@ +import { + type KernelAccountClient, + type KernelSmartAccountImplementation, + createZeroDevPaymasterClient +} from "@zerodev/sdk" +import { + http, + type Address, + type Chain, + type Log, + type PublicClient, + type Transport, + createPublicClient, + decodeEventLog, + encodeFunctionData +} from "viem" +import { + type SmartAccount, + entryPoint06Address +} from "viem/account-abstraction" +import * as allChains from "viem/chains" +import { EntryPointAbi } from "../abis/EntryPoint" +import { TEST_ERC20Abi } from "../abis/Test_ERC20Abi" +import { config } from "../config" + +export const Test_ERC20Address = "0x3870419Ba2BBf0127060bCB37f69A1b1C090992B" +const testingChain = allChains.sepolia.id +export const kernelVersion = "0.2.4" +export const index = 11111111111111111n // 432334375434333332434365532464445487823332432423423n +const DEFAULT_PROVIDER = "ALCHEMY" +const projectId = config["0.6"][testingChain].projectId + +export const getEntryPoint = (): { + address: Address + version: "0.6" +} => { + return { address: entryPoint06Address, version: "0.6" } +} + +export const getTestingChain = (chainId?: number): Chain => { + const _chainId = chainId ?? testingChain + const chain = Object.values(allChains).find((c) => c.id === _chainId) + if (!chain) { + throw new Error(`Chain ${testingChain} not found`) + } + return chain +} + +export const getPublicClient = async (chain?: number) => { + const rpcUrl = config["0.6"][chain ?? testingChain].rpcUrl + if (!rpcUrl) { + throw new Error("RPC_URL environment variable not set") + } + + const publicClient = createPublicClient({ + transport: http(rpcUrl), + chain: getTestingChain(chain) + }) + + const chainId = await publicClient.getChainId() + + return publicClient +} + +export const getZeroDevPaymasterClient = () => { + if (!process.env.ZERODEV_PAYMASTER_RPC_HOST) + throw new Error( + "ZERODEV_PAYMASTER_RPC_HOST environment variable not set" + ) + if (!projectId) + throw new Error("ZERODEV_PROJECT_ID environment variable not set") + + const chain = getTestingChain() + + return createZeroDevPaymasterClient({ + chain: chain, + transport: http(getPaymasterRpc()) + }) +} + +export const getBundlerRpc = (_projectId?: string): string => { + const zeroDevProjectId = _projectId ?? projectId + const zeroDevBundlerRpcHost = config["0.6"][testingChain].bundlerUrl + if (!zeroDevProjectId || !zeroDevBundlerRpcHost) { + throw new Error( + "ZERODEV_PROJECT_ID and ZERODEV_BUNDLER_RPC_HOST environment variables must be set" + ) + } + + return `${zeroDevBundlerRpcHost}/${zeroDevProjectId}?provider=${DEFAULT_PROVIDER}` +} + +export const getPaymasterRpc = (_projectId?: string): string => { + const zeroDevProjectId = _projectId ?? projectId + const zeroDevPaymasterRpcHost = process.env.ZERODEV_PAYMASTER_RPC_HOST + if (!zeroDevProjectId || !zeroDevPaymasterRpcHost) { + throw new Error( + "ZERODEV_PROJECT_ID and ZERODEV_PAYMASTER_RPC_HOST environment variables must be set" + ) + } + + return `${zeroDevPaymasterRpcHost}/${zeroDevProjectId}?provider=${DEFAULT_PROVIDER}` +} + +export const findUserOperationEvent = (logs: Log[]): boolean => { + return logs.some((log) => { + try { + const event = decodeEventLog({ + abi: EntryPointAbi, + ...log + }) + return event.eventName === "UserOperationEvent" + } catch { + return false + } + }) +} + +export const getUserOperationEvent = (logs: Log[]) => { + for (const log of logs) { + try { + const event = decodeEventLog({ + abi: EntryPointAbi, + ...log + }) + console.log("event", event) + if (event.eventName === "UserOperationEvent") return event + } catch {} + } +} + +export async function mintToAccount( + publicClient: PublicClient, + ecdsaSmartAccountClient: KernelAccountClient< + Transport, + Chain, + SmartAccount> + >, + target: Address, + amount: bigint +) { + const balanceBefore = await publicClient.readContract({ + abi: TEST_ERC20Abi, + address: Test_ERC20Address, + functionName: "balanceOf", + args: [target] + }) + + console.log("balanceBefore of account", balanceBefore) + + const amountToMint = balanceBefore > amount ? 0n : amount + + const mintData = encodeFunctionData({ + abi: TEST_ERC20Abi, + functionName: "mint", + args: [target, amountToMint] + }) + + if (amountToMint > 0n) { + const mintTransactionHash = + await ecdsaSmartAccountClient.sendTransaction({ + to: Test_ERC20Address, + data: mintData + }) + + const balanceAfter = await publicClient.readContract({ + abi: TEST_ERC20Abi, + address: Test_ERC20Address, + functionName: "balanceOf", + args: [target] + }) + + console.log("balanceAfter of account", balanceAfter) + + console.log( + "mintTransactionHash", + `https://sepolia.etherscan.io/tx/${mintTransactionHash}` + ) + } +} + +export const validateEnvironmentVariables = (envVars: string[]): void => { + const unsetEnvVars = envVars.filter((envVar) => !process.env[envVar]) + if (unsetEnvVars.length > 0) { + throw new Error( + `The following environment variables are not set: ${unsetEnvVars.join( + ", " + )}` + ) + } +} + +export const sleep = async (milliseconds: number): Promise => { + return new Promise((resolve) => setTimeout(resolve, milliseconds)) +} + +export const waitForNonceUpdate = async (): Promise => { + return sleep(10000) +} diff --git a/packages/test/utils_0_6/ecdsaUtils.ts b/packages/test/utils_0_6/ecdsaUtils.ts new file mode 100644 index 00000000..fa5b9b46 --- /dev/null +++ b/packages/test/utils_0_6/ecdsaUtils.ts @@ -0,0 +1,94 @@ +import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator" +import { + type KernelAccountClient, + type KernelSmartAccountImplementation, + createKernelAccount, + createKernelAccountClient +} from "@zerodev/sdk" +import { http, type Chain, type Hex, type Transport } from "viem" +import type { PaymasterActions, SmartAccount } from "viem/account-abstraction" +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" +import { + getBundlerRpc, + getEntryPoint, + getPublicClient, + getTestingChain, + index, + kernelVersion +} from "./common" + +export const getEcdsaKernelAccountWithRandomSigner = async (chain?: number) => { + return getEcdsaKernelAccountWithPrivateKey( + "0xdfbb0d855aafff58aa0ae92aa9d03e88562bad9befe209f5693db89b65cc4a9a" ?? + "0x3688628d97b817ee5e25dfce254ba4d87b5fd894449fce6c2acc60fdf98906de" ?? + generatePrivateKey(), + chain + ) +} + +export const getEcdsaKernelAccountWithPrivateKey = async ( + privateKey: Hex, + chain?: number +): Promise>> => { + if (!privateKey) { + throw new Error("privateKey cannot be empty") + } + + const publicClient = await getPublicClient(chain) + const signer = privateKeyToAccount(privateKey) + const ecdsaValidatorPlugin = await signerToEcdsaValidator(publicClient, { + entryPoint: getEntryPoint(), + signer: { ...signer, source: "local" as "local" | "external" }, + kernelVersion + }) + + return createKernelAccount(publicClient, { + entryPoint: getEntryPoint(), + plugins: { + sudo: ecdsaValidatorPlugin + }, + index, + kernelVersion + }) +} + +export const getSignerToEcdsaKernelAccount = async () => { + const privateKey = process.env.TEST_PRIVATE_KEY as Hex + if (!privateKey) { + throw new Error("TEST_PRIVATE_KEY environment variable not set") + } + + return getEcdsaKernelAccountWithPrivateKey(privateKey) +} + +export const getKernelAccountClient = async ({ + account, + paymaster +}: { + paymaster?: { + /** Retrieves paymaster-related User Operation properties to be used for sending the User Operation. */ + getPaymasterData?: PaymasterActions["getPaymasterData"] | undefined + /** Retrieves paymaster-related User Operation properties to be used for gas estimation. */ + getPaymasterStubData?: + | PaymasterActions["getPaymasterStubData"] + | undefined + } +} & { + account?: SmartAccount> +} = {}): Promise< + KernelAccountClient< + Transport, + Chain, + SmartAccount> + > +> => { + const chain = getTestingChain() + const resolvedAccount = account ?? (await getSignerToEcdsaKernelAccount()) + + return createKernelAccountClient({ + account: resolvedAccount, + chain, + bundlerTransport: http(getBundlerRpc(), { timeout: 100_000 }), + paymaster + }) +} diff --git a/packages/test/utils_0_6/index.ts b/packages/test/utils_0_6/index.ts new file mode 100644 index 00000000..1ccbb920 --- /dev/null +++ b/packages/test/utils_0_6/index.ts @@ -0,0 +1,6 @@ +export * from "./common" +export * from "./ecdsaUtils" +export * from "./sessionKeyUtils" +export * from "./modularPermissionUtils" +export * from "./weightedEcdsa" +export * from "./recovery" diff --git a/packages/test/utils_0_6/modularPermissionUtils.ts b/packages/test/utils_0_6/modularPermissionUtils.ts new file mode 100644 index 00000000..6b17ab7e --- /dev/null +++ b/packages/test/utils_0_6/modularPermissionUtils.ts @@ -0,0 +1,50 @@ +import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator" +import { createPermissionValidator } from "@zerodev/modular-permission" +import type { Policy } from "@zerodev/modular-permission/policies" +import { toECDSASigner } from "@zerodev/modular-permission/signers" +import type { KernelSmartAccountImplementation } from "@zerodev/sdk" +import { createKernelAccount } from "@zerodev/sdk" +import type { Hex } from "viem" +import type { SmartAccount } from "viem/account-abstraction" +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" +import { getEntryPoint, getPublicClient, index, kernelVersion } from "./common" + +export const getSignerToModularPermissionKernelAccount = async ( + policies: Policy[] +): Promise>> => { + const privateKey = process.env.TEST_PRIVATE_KEY as Hex + if (!privateKey) { + throw new Error("TEST_PRIVATE_KEY environment variable not set") + } + + const publicClient = await getPublicClient() + const signer = privateKeyToAccount(privateKey) + const sessionPrivateKey = generatePrivateKey() + const sessionKey = privateKeyToAccount(sessionPrivateKey) + const ecdsaValidatorPlugin = await signerToEcdsaValidator(publicClient, { + entryPoint: getEntryPoint(), + signer, + kernelVersion + }) + + const ecdsaModularSigner = toECDSASigner({ signer: sessionKey }) + const modularPermissionPlugin = await createPermissionValidator( + publicClient, + { + entryPoint: getEntryPoint(), + kernelVersion, + signer: ecdsaModularSigner, + policies + } + ) + + return await createKernelAccount(publicClient, { + entryPoint: getEntryPoint(), + plugins: { + regular: modularPermissionPlugin, + sudo: ecdsaValidatorPlugin + }, + index, + kernelVersion + }) +} diff --git a/packages/test/utils_0_6/recovery.ts b/packages/test/utils_0_6/recovery.ts new file mode 100644 index 00000000..25e91509 --- /dev/null +++ b/packages/test/utils_0_6/recovery.ts @@ -0,0 +1,38 @@ +import { createKernelAccount } from "@zerodev/sdk" +import { + createWeightedECDSAValidator, + getRecoveryAction +} from "@zerodev/weighted-ecdsa-validator" +import type { Address } from "viem" +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" +import { getEntryPoint, getPublicClient, index, kernelVersion } from "./common" + +export const getRecoveryKernelAccount = async ( + deployedAccountAddress: Address +) => { + const privateKey1 = generatePrivateKey() + const signer1 = privateKeyToAccount(privateKey1) + const recoveryPlugin = await createWeightedECDSAValidator( + await getPublicClient(), + { + entryPoint: getEntryPoint(), + kernelVersion, + config: { + threshold: 100, + delay: 0, + signers: [{ address: signer1.address, weight: 100 }] + }, + signers: [signer1] + } + ) + return await createKernelAccount(await getPublicClient(), { + entryPoint: getEntryPoint(), + address: deployedAccountAddress, + plugins: { + regular: recoveryPlugin, + action: getRecoveryAction(getEntryPoint().version) + }, + index, + kernelVersion + }) +} diff --git a/packages/test/utils_0_6/sessionKeyUtils.ts b/packages/test/utils_0_6/sessionKeyUtils.ts new file mode 100644 index 00000000..d879044b --- /dev/null +++ b/packages/test/utils_0_6/sessionKeyUtils.ts @@ -0,0 +1,113 @@ +import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator" +import type { Action, KernelSmartAccountImplementation } from "@zerodev/sdk" +import { addressToEmptyAccount, createKernelAccount } from "@zerodev/sdk" +import { + ParamOperator, + type SessionKeyPlugin, + deserializeSessionKeyAccount, + serializeSessionKeyAccount, + signerToSessionKeyValidator +} from "@zerodev/session-key" +import type { Hex } from "viem" +import type { SmartAccount } from "viem/account-abstraction" +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" +import { TEST_ERC20Abi } from "../abis/Test_ERC20Abi" +import { + Test_ERC20Address, + getEntryPoint, + getPublicClient, + index, + kernelVersion +} from "./common" + +export const getSessionKeyToSessionKeyKernelAccount = async ( + sessionKeyPlugin: SessionKeyPlugin, + action?: Action +): Promise>> => { + const privateKey = process.env.TEST_PRIVATE_KEY as Hex + if (!privateKey) { + throw new Error("TEST_PRIVATE_KEY environment variable not set") + } + + const publicClient = await getPublicClient() + const signer = privateKeyToAccount(privateKey) + const ecdsaValidatorPlugin = await signerToEcdsaValidator(publicClient, { + entryPoint: getEntryPoint(), + signer: { ...signer, source: "local" as "local" | "external" }, + kernelVersion + }) + + return await createKernelAccount(publicClient, { + entryPoint: getEntryPoint(), + plugins: { + regular: sessionKeyPlugin, + sudo: ecdsaValidatorPlugin, + action + }, + index, + kernelVersion + }) +} + +export const getSignerToSessionKeyKernelAccount = async (): Promise< + SmartAccount> +> => { + const privateKey = process.env.TEST_PRIVATE_KEY as Hex + if (!privateKey) { + throw new Error("TEST_PRIVATE_KEY environment variable not set") + } + + const publicClient = await getPublicClient() + const signer = privateKeyToAccount(privateKey) + const sessionPrivateKey = generatePrivateKey() + const sessionKey = privateKeyToAccount(sessionPrivateKey) + const sessionKeyEmptyAccount = addressToEmptyAccount(sessionKey.address) + const ecdsaValidatorPlugin = await signerToEcdsaValidator(publicClient, { + entryPoint: getEntryPoint(), + signer: { ...signer, source: "local" as "local" | "external" }, + kernelVersion + }) + + const sessionKeyPlugin = await signerToSessionKeyValidator(publicClient, { + entryPoint: getEntryPoint(), + kernelVersion, + signer: sessionKeyEmptyAccount, + validatorData: { + permissions: [ + { + target: Test_ERC20Address, + abi: TEST_ERC20Abi, + functionName: "transfer", + args: [ + { + operator: ParamOperator.EQUAL, + value: signer.address + }, + null + ] + } + ] + } + }) + + const account = await createKernelAccount(publicClient, { + entryPoint: getEntryPoint(), + plugins: { + regular: sessionKeyPlugin, + sudo: ecdsaValidatorPlugin + }, + index, + kernelVersion + }) + + const serializedSessionKeyAccountParams = + await serializeSessionKeyAccount(account) + + return await deserializeSessionKeyAccount( + publicClient, + getEntryPoint(), + kernelVersion, + serializedSessionKeyAccountParams, + sessionKey + ) +} diff --git a/packages/test/utils_0_6/weightedEcdsa.ts b/packages/test/utils_0_6/weightedEcdsa.ts new file mode 100644 index 00000000..dc60830b --- /dev/null +++ b/packages/test/utils_0_6/weightedEcdsa.ts @@ -0,0 +1,62 @@ +import { + type KernelSmartAccountImplementation, + type KernelValidator, + createKernelAccount +} from "@zerodev/sdk" +import { createWeightedECDSAValidator } from "@zerodev/weighted-ecdsa-validator" +import type { Hex } from "viem" +import type { SmartAccount } from "viem/account-abstraction" +import { privateKeyToAccount } from "viem/accounts" +import { getEntryPoint, getPublicClient, index, kernelVersion } from "./common" + +export const getSignersToWeightedEcdsaKernelAccount = async ( + plugin?: KernelValidator +): Promise>> => { + const privateKey1 = process.env.TEST_PRIVATE_KEY as Hex + const privateKey2 = process.env.TEST_PRIVATE_KEY2 as Hex + if (!privateKey1 || !privateKey2) { + throw new Error( + "TEST_PRIVATE_KEY and TEST_PRIVATE_KEY2 environment variables must be set" + ) + } + const publicClient = await getPublicClient() + const signer1 = privateKeyToAccount(privateKey1) + const signer2 = privateKeyToAccount(privateKey2) + const weigthedECDSAPlugin = await createWeightedECDSAValidator( + publicClient, + { + kernelVersion, + entryPoint: getEntryPoint(), + config: { + threshold: 100, + delay: 0, + signers: [ + { address: signer1.address, weight: 50 }, + { address: signer2.address, weight: 50 } + ] + }, + signers: [signer1, signer2] + } + ) + + if (plugin) { + return await createKernelAccount(publicClient, { + entryPoint: getEntryPoint(), + plugins: { + regular: plugin, + sudo: weigthedECDSAPlugin + }, + index, + kernelVersion + }) + } else { + return await createKernelAccount(publicClient, { + entryPoint: getEntryPoint(), + plugins: { + sudo: weigthedECDSAPlugin + }, + index, + kernelVersion + }) + } +} diff --git a/packages/test/v0.7/utils.ts b/packages/test/v0.7/_utils.ts similarity index 91% rename from packages/test/v0.7/utils.ts rename to packages/test/v0.7/_utils.ts index cc31431e..532d4d70 100644 --- a/packages/test/v0.7/utils.ts +++ b/packages/test/v0.7/_utils.ts @@ -1,7 +1,7 @@ import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator" import { type KernelAccountClient, - type KernelSmartAccount, + type KernelSmartAccountImplementation, createKernelAccount, createKernelAccountClient, createZeroDevPaymasterClient @@ -11,16 +11,6 @@ import { createWeightedECDSAValidator, getRecoveryAction } from "@zerodev/weighted-ecdsa-validator" -import { ENTRYPOINT_ADDRESS_V07, createBundlerClient } from "permissionless" -import type { Middleware } from "permissionless/actions/smartAccount" -import { - createPimlicoBundlerClient, - createPimlicoPaymasterClient -} from "permissionless/clients/pimlico" -import type { - ENTRYPOINT_ADDRESS_V07_TYPE, - EntryPoint -} from "permissionless/types" import { http, type Address, @@ -48,13 +38,18 @@ import type { Policy } from "../../../plugins/permission/types" import { EntryPointAbi } from "../abis/EntryPoint" import type { Action } from "@zerodev/sdk/types" -import type { SmartAccountSigner } from "permissionless/accounts" import { TEST_ERC20Abi } from "../abis/Test_ERC20Abi.js" import { config } from "../config.js" import { Test_ERC20Address } from "../utils.js" import { type RequestListener, createServer } from "http" import type { AddressInfo } from "net" +import { + type EntryPointVersion, + type PaymasterActions, + type SmartAccount, + entryPoint07Address +} from "viem/account-abstraction" import { getChainId } from "viem/actions" import { createSessionAccount } from "../../../plugins/multi-tenant-session-account/index.js" import type { Delegation } from "../../../plugins/multi-tenant-session-account/types.js" @@ -64,7 +59,7 @@ export const index = 11111111111111111n // 4323343754343333324343655324644454878 export const kernelVersion = "0.3.1" const DEFAULT_PROVIDER = "PIMLICO" const testingChain = allChains.sepolia.id -const projectId = config["v0.7"][testingChain].projectId +const projectId = config["0.7"][testingChain].projectId export const validateEnvironmentVariables = (envVars: string[]): void => { const unsetEnvVars = envVars.filter((envVar) => !process.env[envVar]) @@ -99,49 +94,11 @@ export const waitForNonceUpdate = async (): Promise => { return sleep(10000) } -export const getEntryPoint = (): ENTRYPOINT_ADDRESS_V07_TYPE => { - return ENTRYPOINT_ADDRESS_V07 -} - -export const getEcdsaKernelAccountWithRandomSigner = async ( - initConfig?: Hex[], - chain?: number -) => { - return getEcdsaKernelAccountWithPrivateKey( - "0xdfbb0d855aafff58aa0ae92aa9d03e88562bad9befe209f5693db89b65cc4a9a" ?? - "0x3688628d97b817ee5e25dfce254ba4d87b5fd894449fce6c2acc60fdf98906de" ?? - generatePrivateKey(), - initConfig, - chain - ) -} - -const getEcdsaKernelAccountWithPrivateKey = async ( - privateKey: Hex, - initConfig?: Hex[], - chain?: number -) => { - if (!privateKey) { - throw new Error("privateKey cannot be empty") - } - - const publicClient = await getPublicClient(chain) - const signer = privateKeyToAccount(privateKey) - const ecdsaValidatorPlugin = await signerToEcdsaValidator(publicClient, { - entryPoint: getEntryPoint(), - signer: { ...signer, source: "local" as "local" | "external" }, - kernelVersion - }) - - return createKernelAccount(publicClient, { - entryPoint: getEntryPoint(), - plugins: { - sudo: ecdsaValidatorPlugin - }, - index, - kernelVersion, - initConfig - }) +export const getEntryPoint = (): { + address: Address + version: EntryPointVersion +} => { + return { address: entryPoint07Address, version: "0.7" } } export const generateRandomBigIntIndex = (): bigint => { @@ -229,7 +186,7 @@ export const getPaymasterRpc = (_projectId?: string): string => { } export const getPublicClient = async (chain?: number) => { - const rpcUrl = config["v0.7"][chain ?? testingChain].rpcUrl + const rpcUrl = config["0.7"][chain ?? testingChain].rpcUrl if (!rpcUrl) { throw new Error("RPC_URL environment variable not set") } @@ -284,10 +241,25 @@ export const getTestingChain = (chainId?: number): Chain => { export const getKernelAccountClient = async ({ account, - middleware -}: Middleware & { - account?: KernelSmartAccount -} = {}) => { + paymaster +}: { + paymaster?: { + /** Retrieves paymaster-related User Operation properties to be used for sending the User Operation. */ + getPaymasterData?: PaymasterActions["getPaymasterData"] | undefined + /** Retrieves paymaster-related User Operation properties to be used for gas estimation. */ + getPaymasterStubData?: + | PaymasterActions["getPaymasterStubData"] + | undefined + } +} & { + account?: SmartAccount +} = {}): Promise< + KernelAccountClient< + Transport, + Chain, + SmartAccount + > +> => { const chain = getTestingChain() const resolvedAccount = account ?? (await getSignerToEcdsaKernelAccount()) @@ -295,8 +267,7 @@ export const getKernelAccountClient = async ({ account: resolvedAccount, chain, bundlerTransport: http(getBundlerRpc(), { timeout: 100_000 }), - middleware, - entryPoint: getEntryPoint() + paymaster }) } @@ -311,7 +282,7 @@ export const getSignerToEcdsaKernelAccount = async () => { export const getBundlerRpc = (_projectId?: string): string => { const zeroDevProjectId = _projectId ?? projectId - const zeroDevBundlerRpcHost = config["v0.7"][testingChain].bundlerUrl + const zeroDevBundlerRpcHost = config["0.7"][testingChain].bundlerUrl if (!zeroDevProjectId || !zeroDevBundlerRpcHost) { throw new Error( "ZERODEV_PROJECT_ID and ZERODEV_BUNDLER_RPC_HOST environment variables must be set" @@ -712,13 +683,12 @@ export const getSignerToPermissionKernelAccountAndPlugin = async ( } } -export async function mintToAccount( +export async function mintToAccount( publicClient: PublicClient, ecdsaSmartAccountClient: KernelAccountClient< - entryPoint, Transport, Chain, - KernelSmartAccount + SmartAccount >, target: Address, amount: bigint diff --git a/packages/test/v0.7/ecdsaKernelAccount.test.ts b/packages/test/v0.7/ecdsaKernelAccount.test.ts index 1a3519ac..3210e506 100644 --- a/packages/test/v0.7/ecdsaKernelAccount.test.ts +++ b/packages/test/v0.7/ecdsaKernelAccount.test.ts @@ -9,21 +9,13 @@ import { constants, EIP1271Abi, type KernelAccountClient, - type KernelSmartAccount, + type KernelSmartAccountImplementation, createKernelAccount, getCustomNonceKeyFromString, verifyEIP6492Signature } from "@zerodev/sdk" import dotenv from "dotenv" import { ethers } from "ethers" -import { - type BundlerClient, - ENTRYPOINT_ADDRESS_V07, - bundlerActions -} from "permissionless" -import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" -import type { PimlicoBundlerClient } from "permissionless/clients/pimlico" -import type { ENTRYPOINT_ADDRESS_V07_TYPE } from "permissionless/types" import { type Address, type Chain, @@ -32,9 +24,7 @@ import { type PrivateKeyAccount, type PublicClient, type Transport, - decodeAbiParameters, decodeEventLog, - decodeFunctionData, encodeFunctionData, erc20Abi, getContract, @@ -48,22 +38,30 @@ import { EntryPointAbi } from "../abis/EntryPoint.js" import { GreeterAbi, GreeterBytecode } from "../abis/Greeter.js" import { TokenActionsAbi } from "../abis/TokenActionsAbi.js" import { TOKEN_ACTION_ADDRESS, config } from "../config.js" -import { Test_ERC20Address } from "../utils.js" + import { + type BundlerClient, + type SmartAccount, + entryPoint07Address +} from "viem/account-abstraction" +import { + Test_ERC20Address, findUserOperationEvent, - getEcdsaKernelAccountWithRandomSigner, getEntryPoint, - getKernelAccountClient, - getPimlicoBundlerClient, getPublicClient, - getSignerToEcdsaKernelAccount, + getUserOperationEvent, getZeroDevPaymasterClient, index, kernelVersion, mintToAccount, validateEnvironmentVariables, waitForNonceUpdate -} from "./utils.js" +} from "./utils/common.js" +import { + getEcdsaKernelAccountWithRandomSigner, + getKernelAccountClient, + getSignerToEcdsaKernelAccount +} from "./utils/ecdsaUtils.js" dotenv.config() @@ -89,16 +87,13 @@ const TX_HASH_REGEX = /^0x[0-9a-fA-F]{64}$/ const TEST_TIMEOUT = 1000000 describe("ECDSA kernel Account", () => { - let account: KernelSmartAccount + let account: SmartAccount let ownerAccount: PrivateKeyAccount let publicClient: PublicClient - let bundlerClient: BundlerClient - let pimlicoBundlerClient: PimlicoBundlerClient let kernelClient: KernelAccountClient< - ENTRYPOINT_ADDRESS_V07_TYPE, Transport, Chain, - KernelSmartAccount + SmartAccount > let greeterContract: GetContractReturnType< typeof GreeterAbi, @@ -116,20 +111,18 @@ describe("ECDSA kernel Account", () => { account = await getSignerToEcdsaKernelAccount() owner = privateKeyToAccount(process.env.TEST_PRIVATE_KEY as Hex).address publicClient = await getPublicClient() - pimlicoBundlerClient = getPimlicoBundlerClient() + const zeroDevPaymaster = getZeroDevPaymasterClient() kernelClient = await getKernelAccountClient({ account, - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster + // paymaster: { + // getPaymasterData(parameters) { + // return zeroDevPaymaster.sponsorUserOperation({ + // userOperation: parameters + // }) + // } + // }, }) - bundlerClient = kernelClient.extend(bundlerActions(getEntryPoint())) greeterContract = getContract({ abi: GreeterAbi, address: process.env.GREETER_ADDRESS as Address, @@ -147,7 +140,7 @@ describe("ECDSA kernel Account", () => { test("getKernelAddressFromECDSA util should return valid account address", async () => { const generatedAccountAddress = await getKernelAddressFromECDSA({ - entryPointAddress: ENTRYPOINT_ADDRESS_V07, + entryPoint: { address: entryPoint07Address, version: "0.7" }, kernelVersion, eoaAddress: ownerAccount.address, index: index, @@ -162,15 +155,15 @@ describe("ECDSA kernel Account", () => { expect(account.address).toEqual(generatedAccountAddress) }) - test("Account should throw when trying to sign a transaction", async () => { - await expect(async () => { - await account.signTransaction({ - to: zeroAddress, - value: 0n, - data: "0x" - }) - }).toThrow(new SignTransactionNotSupportedBySmartAccount()) - }) + // test("Account should throw when trying to sign a transaction", async () => { + // await expect(async () => { + // await account.signTransaction({ + // to: zeroAddress, + // value: 0n, + // data: "0x" + // }) + // }).toThrow(new SignTransactionNotSupportedBySmartAccount()) + // }) test( "Should validate message signatures for undeployed accounts (6492)", @@ -196,7 +189,7 @@ describe("ECDSA kernel Account", () => { message, signature: signature, provider: new ethers.providers.JsonRpcProvider( - config["v0.7"][sepolia.id].rpcUrl + config["0.7"][sepolia.id].rpcUrl ) }) expect(ambireResult).toBeTrue() @@ -261,7 +254,7 @@ describe("ECDSA kernel Account", () => { }, signature: signature, provider: new ethers.providers.JsonRpcProvider( - config["v0.7"][sepolia.id].rpcUrl + config["0.7"][sepolia.id].rpcUrl ) }) expect(ambireResult).toBeTrue() @@ -274,9 +267,13 @@ describe("ECDSA kernel Account", () => { async () => { // to make sure kernel is deployed const tx = await kernelClient.sendTransaction({ - to: zeroAddress, - value: 0n, - data: "0x" + calls: [ + { + to: zeroAddress, + value: 0n, + data: "0x" + } + ] }) console.log("tx", tx) @@ -291,7 +288,7 @@ describe("ECDSA kernel Account", () => { message, signature: response, provider: new ethers.providers.JsonRpcProvider( - config["v0.7"][sepolia.id].rpcUrl + config["0.7"][sepolia.id].rpcUrl ) }) expect(ambireResult).toBeTrue() @@ -367,19 +364,17 @@ describe("ECDSA kernel Account", () => { "Client deploy contract", async () => { const response = await kernelClient.sendUserOperation({ - userOperation: { - callData: await kernelClient.account.encodeDeployCallData({ - abi: GreeterAbi, - bytecode: GreeterBytecode - }) - } + callData: await kernelClient.account.encodeDeployCallData({ + abi: GreeterAbi, + bytecode: GreeterBytecode + }) }) expect(response).toBeString() expect(response).toHaveLength(TX_HASH_LENGTH) expect(response).toMatch(TX_HASH_REGEX) - const rcpt = await bundlerClient.waitForUserOperationReceipt({ + const rcpt = await kernelClient.waitForUserOperationReceipt({ hash: response }) const transactionReceipt = @@ -395,8 +390,8 @@ describe("ECDSA kernel Account", () => { test( "Smart account client send multiple transactions", async () => { - const response = await kernelClient.sendTransactions({ - transactions: [ + const response = await kernelClient.sendTransaction({ + calls: [ { to: zeroAddress, value: 0n, @@ -456,36 +451,34 @@ describe("ECDSA kernel Account", () => { "Client signs and then sends UserOp with paymaster", async () => { const userOp = await kernelClient.signUserOperation({ - userOperation: { - callData: await kernelClient.account.encodeCallData([ - { - to: process.env.GREETER_ADDRESS as Address, - value: 0n, - data: encodeFunctionData({ - abi: GreeterAbi, - functionName: "setGreeting", - args: ["hello world"] - }) - }, - { - to: process.env.GREETER_ADDRESS as Address, - value: 0n, - data: encodeFunctionData({ - abi: GreeterAbi, - functionName: "setGreeting", - args: ["hello world 2"] - }) - } - ]) - } + callData: await kernelClient.account.encodeCalls([ + { + to: process.env.GREETER_ADDRESS as Address, + value: 0n, + data: encodeFunctionData({ + abi: GreeterAbi, + functionName: "setGreeting", + args: ["hello world"] + }) + }, + { + to: process.env.GREETER_ADDRESS as Address, + value: 0n, + data: encodeFunctionData({ + abi: GreeterAbi, + functionName: "setGreeting", + args: ["hello world 2"] + }) + } + ]) }) expect(userOp.signature).not.toBe("0x") const userOpHash = await kernelClient.sendUserOperation({ - userOperation: userOp + ...userOp }) expect(userOpHash).toHaveLength(66) - await bundlerClient.waitForUserOperationReceipt({ + await kernelClient.waitForUserOperationReceipt({ hash: userOpHash }) @@ -511,24 +504,28 @@ describe("ECDSA kernel Account", () => { amountToMint ) const userOpHash = await kernelClient.sendUserOperation({ - userOperation: { - callData: await kernelClient.account.encodeCallData({ - to: TOKEN_ACTION_ADDRESS, - value: 0n, - data: encodeFunctionData({ - abi: TokenActionsAbi, - functionName: "transferERC20Action", - args: [Test_ERC20Address, amountToTransfer, owner] - }), - callType: "delegatecall" - }) - } + callData: await kernelClient.account.encodeCalls( + [ + { + to: TOKEN_ACTION_ADDRESS, + value: 0n, + data: encodeFunctionData({ + abi: TokenActionsAbi, + functionName: "transferERC20Action", + args: [ + Test_ERC20Address, + amountToTransfer, + owner + ] + }) + } + ], + "delegatecall" + ) + }) + const transaction = await kernelClient.waitForUserOperationReceipt({ + hash: userOpHash }) - const transaction = await bundlerClient.waitForUserOperationReceipt( - { - hash: userOpHash - } - ) console.log( "transferTransactionHash", `https://sepolia.etherscan.io/tx/${transaction.receipt.transactionHash}` @@ -567,26 +564,30 @@ describe("ECDSA kernel Account", () => { async () => { const customNonceKey = getCustomNonceKeyFromString( "Hello, World!", - ENTRYPOINT_ADDRESS_V07 + "0.7" ) - const nonce = await account.getNonce(customNonceKey) + const nonce = await account.getNonce({ key: customNonceKey }) const userOpHash = await kernelClient.sendUserOperation({ - userOperation: { - callData: await kernelClient.account.encodeCallData({ + callData: await kernelClient.account.encodeCalls([ + { to: zeroAddress, value: 0n, data: "0x" - }), - nonce - } + } + ]), + nonce }) expect(userOpHash).toHaveLength(66) - await bundlerClient.waitForUserOperationReceipt({ + await kernelClient.waitForUserOperationReceipt({ + hash: userOpHash + }) + const res = await kernelClient.getUserOperation({ hash: userOpHash }) + expect(res.userOperation.nonce).toEqual(nonce) }, TEST_TIMEOUT ) @@ -619,22 +620,21 @@ describe("ECDSA kernel Account", () => { "Client send multiple Transactions with paymaster", async () => { const account = await getSignerToEcdsaKernelAccount() - + const zeroDevPaymaster = getZeroDevPaymasterClient() const kernelClient = await getKernelAccountClient({ account, - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster + // paymaster: { + // getPaymasterData(parameters) { + // return zeroDevPaymaster.sponsorUserOperation({ + // userOperation: parameters + // }) + // } + // } }) - const response = await kernelClient.sendTransactions({ - transactions: [ + const response = await kernelClient.sendTransaction({ + calls: [ { to: zeroAddress, value: 0n, @@ -669,7 +669,7 @@ describe("ECDSA kernel Account", () => { if (event.eventName === "UserOperationEvent") { eventFound = true const userOperation = - await bundlerClient.getUserOperationByHash({ + await kernelClient.getUserOperation({ hash: event.args.userOpHash }) expect( @@ -801,17 +801,17 @@ describe("ECDSA kernel Account", () => { async () => { const initialEcdsaSmartAccount = await getSignerToEcdsaKernelAccount() + const zeroDevPaymaster = getZeroDevPaymasterClient() const kernelClient = await getKernelAccountClient({ account: initialEcdsaSmartAccount, - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster + // paymaster: { + // getPaymasterData(parameters) { + // return zeroDevPaymaster.sponsorUserOperation({ + // userOperation: parameters + // }) + // } + // } }) // Send an initial tx to deploy the account @@ -844,7 +844,7 @@ describe("ECDSA kernel Account", () => { plugins: { sudo: ecdsaValidatorPlugin }, - deployedAccountAddress, + address: deployedAccountAddress, index, kernelVersion } diff --git a/packages/test/v0.7/multiChainECDSAValidator.test.ts b/packages/test/v0.7/multiChainECDSAValidator.test.ts index 5e11c714..bfd709b7 100644 --- a/packages/test/v0.7/multiChainECDSAValidator.test.ts +++ b/packages/test/v0.7/multiChainECDSAValidator.test.ts @@ -1,10 +1,15 @@ // @ts-expect-error import { beforeAll, describe, expect, test } from "bun:test" import { verifyMessage } from "@ambire/signature-validator" +import { + ecdsaSignUserOpsWithEnable, + signUserOperations +} from "@zerodev/multi-chain-ecdsa-validator" +import { toMultiChainECDSAValidator } from "@zerodev/multi-chain-ecdsa-validator" import { EIP1271Abi, type KernelAccountClient, - type KernelSmartAccount, + type KernelSmartAccountImplementation, type KernelValidator, type ZeroDevPaymasterClient, addressToEmptyAccount, @@ -15,17 +20,6 @@ import { verifyEIP6492Signature } from "@zerodev/sdk" import { ethers } from "ethers" -import { - type BundlerClient, - ENTRYPOINT_ADDRESS_V07, - bundlerActions, - deepHexlify -} from "permissionless" -import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" -import type { - ENTRYPOINT_ADDRESS_V07_TYPE, - EntryPoint -} from "permissionless/types/entrypoint" import { http, type Address, @@ -42,19 +36,12 @@ import { getContract, hashMessage, hashTypedData, + parseEther, zeroAddress } from "viem" +import type { SmartAccount } from "viem/account-abstraction" import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" import { optimismSepolia, sepolia } from "viem/chains" -import { ValidatorType } from "../../../plugins/multichain/actions/type.js" -import { ecdsaPrepareMultiUserOpRequest } from "../../../plugins/multichain/ecdsa/ecdsaPrepareMultiUserOpRequest.js" -import { ecdsaSignUserOps } from "../../../plugins/multichain/ecdsa/ecdsaSignUserOps.js" -import { ecdsaSignUserOpsWithEnable } from "../../../plugins/multichain/ecdsa/ecdsaSignUserOpsWithEnable.js" -import { toMultiChainECDSAValidator } from "../../../plugins/multichain/ecdsa/toMultiChainECDSAValidator.js" -import { - type KernelMultiChainClient, - createKernelMultiChainClient -} from "../../../plugins/multichain/multiChainClient.js" import { deserializePermissionAccount } from "../../../plugins/permission/deserializePermissionAccount.js" import { toSudoPolicy } from "../../../plugins/permission/policies/index.js" import { serializeMultiChainPermissionAccounts } from "../../../plugins/permission/serializeMultiChainPermissionAccounts.js" @@ -64,28 +51,31 @@ import { EntryPointAbi } from "../abis/EntryPoint.js" import { GreeterAbi, GreeterBytecode } from "../abis/Greeter.js" import { TokenActionsAbi } from "../abis/TokenActionsAbi.js" import { TOKEN_ACTION_ADDRESS, config } from "../config.js" -import { Test_ERC20Address } from "../utils.js" import { + Test_ERC20Address, findUserOperationEvent, + getBundlerRpc, getEntryPoint, + getPaymasterRpc, + getPublicClient, kernelVersion, mintToAccount, validateEnvironmentVariables, waitForNonceUpdate -} from "./utils.js" +} from "./utils" -const requiredEnvVars = [ - "TEST_PRIVATE_KEY", - "GREETER_ADDRESS", - "SEPOLIA_RPC_URL", - "OPTIMISM_SEPOLIA_RPC_URL", - "SEPOLIA_ZERODEV_RPC_URL", - "SEPOLIA_ZERODEV_PAYMASTER_RPC_URL", - "OPTIMISM_SEPOLIA_ZERODEV_RPC_URL", - "OPTIMISM_SEPOLIA_ZERODEV_PAYMASTER_RPC_URL" -] +// const requiredEnvVars = [ +// "TEST_PRIVATE_KEY", +// "GREETER_ADDRESS", +// "SEPOLIA_RPC_URL", +// "OPTIMISM_SEPOLIA_RPC_URL", +// "SEPOLIA_ZERODEV_RPC_URL", +// "SEPOLIA_ZERODEV_PAYMASTER_RPC_URL", +// "OPTIMISM_SEPOLIA_ZERODEV_RPC_URL", +// "OPTIMISM_SEPOLIA_ZERODEV_PAYMASTER_RPC_URL" +// ] -validateEnvironmentVariables(requiredEnvVars) +// validateEnvironmentVariables(requiredEnvVars) const ETHEREUM_ADDRESS_LENGTH = 42 const ETHEREUM_ADDRESS_REGEX = /^0x[0-9a-fA-F]{40}$/ @@ -95,70 +85,70 @@ const TX_HASH_LENGTH = 66 const TX_HASH_REGEX = /^0x[0-9a-fA-F]{64}$/ const TEST_TIMEOUT = 1000000 -const SEPOLIA_RPC_URL = process.env.SEPOLIA_RPC_URL -const OPTIMISM_SEPOLIA_RPC_URL = process.env.OPTIMISM_SEPOLIA_RPC_URL +const SEPOLIA_RPC_URL = process.env.RPC_URL_SEPOLIA +const OPTIMISM_SEPOLIA_RPC_URL = process.env.RPC_URL_OP_SEPOLIA -const SEPOLIA_ZERODEV_RPC_URL = process.env.SEPOLIA_ZERODEV_RPC_URL -const SEPOLIA_ZERODEV_PAYMASTER_RPC_URL = - process.env.SEPOLIA_ZERODEV_PAYMASTER_RPC_URL +const SEPOLIA_ZERODEV_RPC_URL = getBundlerRpc( + config["0.7"][sepolia.id].projectId +) +const SEPOLIA_ZERODEV_PAYMASTER_RPC_URL = getPaymasterRpc( + config["0.7"][sepolia.id].projectId +) -const OPTIMISM_SEPOLIA_ZERODEV_RPC_URL = - process.env.OPTIMISM_SEPOLIA_ZERODEV_RPC_URL -const OPTIMISM_SEPOLIA_ZERODEV_PAYMASTER_RPC_URL = - process.env.OPTIMISM_SEPOLIA_ZERODEV_PAYMASTER_RPC_URL +const OPTIMISM_SEPOLIA_ZERODEV_RPC_URL = getBundlerRpc( + config["0.7"][optimismSepolia.id].projectId +) +const OPTIMISM_SEPOLIA_ZERODEV_PAYMASTER_RPC_URL = getPaymasterRpc( + config["0.7"][optimismSepolia.id].projectId +) const PRIVATE_KEY = process.env.TEST_PRIVATE_KEY describe("MultiChainECDSAValidator", () => { - let sepoliaPublicClient: PublicClient - let optimismSepoliaPublicClient: PublicClient + let sepoliaPublicClient: PublicClient + let optimismSepoliaPublicClient: PublicClient< + Transport, + typeof optimismSepolia + > - let sepoliaZeroDevPaymasterClient: ZeroDevPaymasterClient - let opSepoliaZeroDevPaymasterClient: ZeroDevPaymasterClient + let sepoliaZeroDevPaymasterClient: ZeroDevPaymasterClient + let opSepoliaZeroDevPaymasterClient: ZeroDevPaymasterClient let signer: PrivateKeyAccount - let sepoliaMultiChainECDSAValidatorPlugin: KernelValidator< - ENTRYPOINT_ADDRESS_V07_TYPE, - string - > - let optimismsepoliaMultiChainECDSAValidatorPlugin: KernelValidator< - ENTRYPOINT_ADDRESS_V07_TYPE, - string - > + let sepoliaMultiChainECDSAValidatorPlugin: KernelValidator<"MultiChainECDSAValidator"> + let optimismsepoliaMultiChainECDSAValidatorPlugin: KernelValidator<"MultiChainECDSAValidator"> - let account: KernelSmartAccount + let account: SmartAccount let kernelClient: KernelAccountClient< - ENTRYPOINT_ADDRESS_V07_TYPE, Transport, Chain, - KernelSmartAccount + SmartAccount > - let bundlerClient: BundlerClient let greeterContract: GetContractReturnType< typeof GreeterAbi, - typeof multiChainKernelClient, + typeof kernelClient, Address > let owner: Address beforeAll(async () => { sepoliaPublicClient = createPublicClient({ - transport: http(SEPOLIA_RPC_URL) + transport: http(SEPOLIA_RPC_URL), + chain: sepolia }) optimismSepoliaPublicClient = createPublicClient({ - transport: http(OPTIMISM_SEPOLIA_RPC_URL) + transport: http(OPTIMISM_SEPOLIA_RPC_URL), + chain: optimismSepolia }) sepoliaZeroDevPaymasterClient = createZeroDevPaymasterClient({ chain: sepolia, - transport: http(SEPOLIA_ZERODEV_PAYMASTER_RPC_URL), - entryPoint: getEntryPoint() + transport: http(SEPOLIA_ZERODEV_PAYMASTER_RPC_URL) }) opSepoliaZeroDevPaymasterClient = createZeroDevPaymasterClient({ chain: optimismSepolia, - transport: http(OPTIMISM_SEPOLIA_ZERODEV_PAYMASTER_RPC_URL), - entryPoint: getEntryPoint() + transport: http(OPTIMISM_SEPOLIA_ZERODEV_PAYMASTER_RPC_URL) }) signer = privateKeyToAccount(PRIVATE_KEY as Hex) @@ -167,13 +157,15 @@ describe("MultiChainECDSAValidator", () => { await toMultiChainECDSAValidator(sepoliaPublicClient, { entryPoint: getEntryPoint(), kernelVersion, - signer + signer, + multiChainIds: [sepolia.id, optimismSepolia.id] }) optimismsepoliaMultiChainECDSAValidatorPlugin = await toMultiChainECDSAValidator(optimismSepoliaPublicClient, { entryPoint: getEntryPoint(), kernelVersion, - signer + signer, + multiChainIds: [sepolia.id, optimismSepolia.id] }) account = await createKernelAccount(sepoliaPublicClient, { @@ -184,28 +176,17 @@ describe("MultiChainECDSAValidator", () => { } }) - multiChainKernelClient = createKernelMultiChainClient({ + kernelClient = createKernelAccountClient({ account: account, chain: sepolia, bundlerTransport: http(SEPOLIA_ZERODEV_RPC_URL), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - return sepoliaZeroDevPaymasterClient.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - }, - entryPoint: getEntryPoint() + paymaster: sepoliaZeroDevPaymasterClient }) - bundlerClient = multiChainKernelClient.extend( - bundlerActions(getEntryPoint()) - ) greeterContract = getContract({ abi: GreeterAbi, address: process.env.GREETER_ADDRESS as Address, - client: multiChainKernelClient + client: kernelClient }) owner = privateKeyToAccount(process.env.TEST_PRIVATE_KEY as Hex).address }) @@ -218,16 +199,6 @@ describe("MultiChainECDSAValidator", () => { console.log("account.address: ", account.address) }) - test("Account should throw when trying to sign a transaction", async () => { - await expect(async () => { - await account.signTransaction({ - to: zeroAddress, - value: 0n, - data: "0x" - }) - }).toThrow(new SignTransactionNotSupportedBySmartAccount()) - }) - test( "Should validate message signatures for undeployed accounts (6492)", async () => { @@ -264,7 +235,7 @@ describe("MultiChainECDSAValidator", () => { message, signature: signature, provider: new ethers.providers.JsonRpcProvider( - config["v0.7"][sepolia.id].rpcUrl + config["0.7"][sepolia.id].rpcUrl ) }) expect(ambireResult).toBeTrue() @@ -341,7 +312,7 @@ describe("MultiChainECDSAValidator", () => { }, signature: signature, provider: new ethers.providers.JsonRpcProvider( - config["v0.7"][sepolia.id].rpcUrl + config["0.7"][sepolia.id].rpcUrl ) }) expect(ambireResult).toBeTrue() @@ -353,7 +324,7 @@ describe("MultiChainECDSAValidator", () => { "Client signMessage should return a valid signature", async () => { // to make sure kernel is deployed - const tx = await multiChainKernelClient.sendTransaction({ + const tx = await kernelClient.sendTransaction({ to: zeroAddress, value: 0n, data: "0x" @@ -361,7 +332,7 @@ describe("MultiChainECDSAValidator", () => { console.log("tx", tx) const message = "hello world" - const response = await multiChainKernelClient.signMessage({ + const response = await kernelClient.signMessage({ message }) console.log("hashMessage(message)", hashMessage(message)) @@ -371,7 +342,7 @@ describe("MultiChainECDSAValidator", () => { message, signature: response, provider: new ethers.providers.JsonRpcProvider( - config["v0.7"][sepolia.id].rpcUrl + config["0.7"][sepolia.id].rpcUrl ) }) expect(ambireResult).toBeTrue() @@ -396,7 +367,7 @@ describe("MultiChainECDSAValidator", () => { "Client signMessage should return a valid signature", async () => { // to make sure kernel is deployed - const tx = await multiChainKernelClient.sendTransaction({ + const tx = await kernelClient.sendTransaction({ to: zeroAddress, value: 0n, data: "0x" @@ -404,7 +375,7 @@ describe("MultiChainECDSAValidator", () => { console.log("tx", tx) const message = "hello world" - const response = await multiChainKernelClient.signMessage({ + const response = await kernelClient.signMessage({ message }) console.log("hashMessage(message)", hashMessage(message)) @@ -414,7 +385,7 @@ describe("MultiChainECDSAValidator", () => { message, signature: response, provider: new ethers.providers.JsonRpcProvider( - config["v0.7"][sepolia.id].rpcUrl + config["0.7"][sepolia.id].rpcUrl ) }) expect(ambireResult).toBeTrue() @@ -465,7 +436,7 @@ describe("MultiChainECDSAValidator", () => { message }) - const response = await multiChainKernelClient.signTypedData({ + const response = await kernelClient.signTypedData({ domain, primaryType, types, @@ -489,23 +460,18 @@ describe("MultiChainECDSAValidator", () => { test( "Client deploy contract", async () => { - const response = await multiChainKernelClient.sendUserOperation({ - userOperation: { - callData: - await multiChainKernelClient.account.encodeDeployCallData( - { - abi: GreeterAbi, - bytecode: GreeterBytecode - } - ) - } + const response = await kernelClient.sendUserOperation({ + callData: await kernelClient.account.encodeDeployCallData({ + abi: GreeterAbi, + bytecode: GreeterBytecode + }) }) expect(response).toBeString() expect(response).toHaveLength(TX_HASH_LENGTH) expect(response).toMatch(TX_HASH_REGEX) - const rcpt = await bundlerClient.waitForUserOperationReceipt({ + const rcpt = await kernelClient.waitForUserOperationReceipt({ hash: response }) const transactionReceipt = @@ -521,8 +487,8 @@ describe("MultiChainECDSAValidator", () => { test( "Smart account client send multiple transactions", async () => { - const response = await multiChainKernelClient.sendTransactions({ - transactions: [ + const response = await kernelClient.sendTransaction({ + calls: [ { to: zeroAddress, value: 0n, @@ -581,38 +547,35 @@ describe("MultiChainECDSAValidator", () => { test( "Client signs and then sends UserOp with paymaster", async () => { - const userOp = await multiChainKernelClient.signUserOperation({ - userOperation: { - callData: - await multiChainKernelClient.account.encodeCallData([ - { - to: process.env.GREETER_ADDRESS as Address, - value: 0n, - data: encodeFunctionData({ - abi: GreeterAbi, - functionName: "setGreeting", - args: ["hello world"] - }) - }, - { - to: process.env.GREETER_ADDRESS as Address, - value: 0n, - data: encodeFunctionData({ - abi: GreeterAbi, - functionName: "setGreeting", - args: ["hello world 2"] - }) - } - ]) - } + const userOp = await kernelClient.signUserOperation({ + callData: await kernelClient.account.encodeCalls([ + { + to: process.env.GREETER_ADDRESS as Address, + value: 0n, + data: encodeFunctionData({ + abi: GreeterAbi, + functionName: "setGreeting", + args: ["hello world"] + }) + }, + { + to: process.env.GREETER_ADDRESS as Address, + value: 0n, + data: encodeFunctionData({ + abi: GreeterAbi, + functionName: "setGreeting", + args: ["hello world 2"] + }) + } + ]) }) expect(userOp.signature).not.toBe("0x") - const userOpHash = await multiChainKernelClient.sendUserOperation({ - userOperation: userOp + const userOpHash = await kernelClient.sendUserOperation({ + ...userOp }) expect(userOpHash).toHaveLength(66) - await bundlerClient.waitForUserOperationReceipt({ + await kernelClient.waitForUserOperationReceipt({ hash: userOpHash }) @@ -628,19 +591,19 @@ describe("MultiChainECDSAValidator", () => { test( "Client send UserOp with delegatecall", async () => { - const accountAddress = multiChainKernelClient.account.address - const amountToMint = 10000000n + const accountAddress = kernelClient.account.address + const amountToMint = parseEther("0.99999") const amountToTransfer = 4337n await mintToAccount( sepoliaPublicClient, - multiChainKernelClient, + kernelClient, accountAddress, amountToMint ) - const userOpHash = await multiChainKernelClient.sendUserOperation({ - userOperation: { - callData: - await multiChainKernelClient.account.encodeCallData({ + const userOpHash = await kernelClient.sendUserOperation({ + callData: await kernelClient.account.encodeCalls( + [ + { to: TOKEN_ACTION_ADDRESS, value: 0n, data: encodeFunctionData({ @@ -651,16 +614,15 @@ describe("MultiChainECDSAValidator", () => { amountToTransfer, owner ] - }), - callType: "delegatecall" - }) - } + }) + } + ], + "delegatecall" + ) + }) + const transaction = await kernelClient.waitForUserOperationReceipt({ + hash: userOpHash }) - const transaction = await bundlerClient.waitForUserOperationReceipt( - { - hash: userOpHash - } - ) console.log( "transferTransactionHash", `https://sepolia.etherscan.io/tx/${transaction.receipt.transactionHash}` @@ -699,25 +661,24 @@ describe("MultiChainECDSAValidator", () => { async () => { const customNonceKey = getCustomNonceKeyFromString( "Hello, World!", - ENTRYPOINT_ADDRESS_V07 + "0.7" ) - const nonce = await account.getNonce(customNonceKey) + const nonce = await account.getNonce({ key: customNonceKey }) - const userOpHash = await multiChainKernelClient.sendUserOperation({ - userOperation: { - callData: - await multiChainKernelClient.account.encodeCallData({ - to: zeroAddress, - value: 0n, - data: "0x" - }), - nonce - } + const userOpHash = await kernelClient.sendUserOperation({ + callData: await kernelClient.account.encodeCalls([ + { + to: zeroAddress, + value: 0n, + data: "0x" + } + ]), + nonce }) expect(userOpHash).toHaveLength(66) - await bundlerClient.waitForUserOperationReceipt({ + await kernelClient.waitForUserOperationReceipt({ hash: userOpHash }) }, @@ -727,12 +688,11 @@ describe("MultiChainECDSAValidator", () => { test( "Client send Transaction with paymaster", async () => { - const transactionHash = - await multiChainKernelClient.sendTransaction({ - to: zeroAddress, - value: 0n, - data: "0x" - }) + const transactionHash = await kernelClient.sendTransaction({ + to: zeroAddress, + value: 0n, + data: "0x" + }) console.log("transactionHash", transactionHash) expect(transactionHash).toBeString() @@ -752,8 +712,8 @@ describe("MultiChainECDSAValidator", () => { test( "Client send multiple Transactions with paymaster", async () => { - const response = await multiChainKernelClient.sendTransactions({ - transactions: [ + const response = await kernelClient.sendTransaction({ + calls: [ { to: zeroAddress, value: 0n, @@ -788,7 +748,7 @@ describe("MultiChainECDSAValidator", () => { if (event.eventName === "UserOperationEvent") { eventFound = true const userOperation = - await bundlerClient.getUserOperationByHash({ + await kernelClient.getUserOperation({ hash: event.args.userOpHash }) expect( @@ -837,117 +797,81 @@ describe("MultiChainECDSAValidator", () => { optimismSepoliaKernelAccount.address ) - const sepoliaZerodevKernelClient = createKernelMultiChainClient({ + const sepoliaZerodevKernelClient = createKernelAccountClient({ account: sepoliaKernelAccount, chain: sepolia, bundlerTransport: http(SEPOLIA_ZERODEV_RPC_URL), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - return sepoliaZeroDevPaymasterClient.sponsorUserOperation( - { - userOperation, - entryPoint: getEntryPoint() - } - ) - } - }, - entryPoint: getEntryPoint() + paymaster: sepoliaZeroDevPaymasterClient }) const optimismSepoliaZerodevKernelClient = - createKernelMultiChainClient({ + createKernelAccountClient({ account: optimismSepoliaKernelAccount, chain: optimismSepolia, bundlerTransport: http(OPTIMISM_SEPOLIA_ZERODEV_RPC_URL), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - return opSepoliaZeroDevPaymasterClient.sponsorUserOperation( - { - userOperation, - entryPoint: getEntryPoint() - } - ) - } - }, - entryPoint: getEntryPoint() + paymaster: opSepoliaZeroDevPaymasterClient }) const sepoliaUserOp = - await sepoliaZerodevKernelClient.prepareMultiUserOpRequest( - { - userOperation: { - callData: - await sepoliaZerodevKernelClient.account.encodeCallData( - { - to: zeroAddress, - value: BigInt(0), - data: "0x" - } - ) - } - }, - ValidatorType.ECDSA, - 2 - ) + await sepoliaZerodevKernelClient.prepareUserOperation({ + callData: + await sepoliaZerodevKernelClient.account.encodeCalls([ + { + to: zeroAddress, + value: BigInt(0), + data: "0x" + } + ]) + }) const optimismSepoliaUserOp = - await optimismSepoliaZerodevKernelClient.prepareMultiUserOpRequest( - { - userOperation: { - callData: - await optimismSepoliaZerodevKernelClient.account.encodeCallData( - { - to: zeroAddress, - value: BigInt(0), - data: "0x" - } - ) - } - }, - ValidatorType.ECDSA, - 2 - ) - - const signedUserOps = await ecdsaSignUserOps({ - account: sepoliaKernelAccount, - multiUserOps: [ - { userOperation: sepoliaUserOp, chainId: sepolia.id }, - { - userOperation: optimismSepoliaUserOp, - chainId: optimismSepolia.id - } - ], - entryPoint: getEntryPoint() - }) + await optimismSepoliaZerodevKernelClient.prepareUserOperation({ + callData: + await optimismSepoliaZerodevKernelClient.account.encodeCalls( + [ + { + to: zeroAddress, + value: BigInt(0), + data: "0x" + } + ] + ) + }) - const sepoliaBundlerClient = sepoliaZerodevKernelClient.extend( - bundlerActions(getEntryPoint()) + const signedUserOps = await signUserOperations( + sepoliaZerodevKernelClient, + { + userOperations: [ + { ...sepoliaUserOp, chainId: sepolia.id }, + { + ...optimismSepoliaUserOp, + chainId: optimismSepolia.id + } + ] + } ) - const optimismSepoliaBundlerClient = - optimismSepoliaZerodevKernelClient.extend( - bundlerActions(getEntryPoint()) - ) - const sepoliaUserOpHash = - await sepoliaBundlerClient.sendUserOperation({ - userOperation: signedUserOps[0] + await sepoliaZerodevKernelClient.sendUserOperation({ + ...signedUserOps[0] }) console.log("sepoliaUserOpHash", sepoliaUserOpHash) - await sepoliaBundlerClient.waitForUserOperationReceipt({ + await sepoliaZerodevKernelClient.waitForUserOperationReceipt({ hash: sepoliaUserOpHash }) const optimismSepoliaUserOpHash = - await optimismSepoliaBundlerClient.sendUserOperation({ - userOperation: signedUserOps[1] + await optimismSepoliaZerodevKernelClient.sendUserOperation({ + ...signedUserOps[1] }) console.log("optimismSepoliaUserOpHash", optimismSepoliaUserOpHash) - await optimismSepoliaBundlerClient.waitForUserOperationReceipt({ - hash: optimismSepoliaUserOpHash - }) + await optimismSepoliaZerodevKernelClient.waitForUserOperationReceipt( + { + hash: optimismSepoliaUserOpHash + } + ) }, TEST_TIMEOUT ) @@ -958,14 +882,14 @@ describe("MultiChainECDSAValidator", () => { const sepoliaSessionKeySigner = privateKeyToAccount( generatePrivateKey() ) - const sepoliaEcdsaModularSigner = toECDSASigner({ + const sepoliaEcdsaModularSigner = await toECDSASigner({ signer: sepoliaSessionKeySigner }) const optimismSepoliaSessionKeySigner = privateKeyToAccount( generatePrivateKey() ) - const optimismSepoliaEcdsaModularSigner = toECDSASigner({ + const optimismSepoliaEcdsaModularSigner = await toECDSASigner({ signer: optimismSepoliaSessionKeySigner }) @@ -1024,67 +948,42 @@ describe("MultiChainECDSAValidator", () => { optimismSepoliaKernelAccount.address ) - const sepoliaZerodevKernelClient = createKernelMultiChainClient({ + const sepoliaZerodevKernelClient = createKernelAccountClient({ account: sepoliaKernelAccount, chain: sepolia, bundlerTransport: http(SEPOLIA_ZERODEV_RPC_URL), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - return sepoliaZeroDevPaymasterClient.sponsorUserOperation( - { - userOperation, - entryPoint: getEntryPoint() - } - ) - } - }, - entryPoint: getEntryPoint() + paymaster: sepoliaZeroDevPaymasterClient }) const optimismSepoliaZerodevKernelClient = - createKernelMultiChainClient({ + createKernelAccountClient({ account: optimismSepoliaKernelAccount, chain: optimismSepolia, bundlerTransport: http(OPTIMISM_SEPOLIA_ZERODEV_RPC_URL), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - return opSepoliaZeroDevPaymasterClient.sponsorUserOperation( - { - userOperation, - entryPoint: getEntryPoint() - } - ) - } - }, - entryPoint: getEntryPoint() + paymaster: opSepoliaZeroDevPaymasterClient }) const sepoliaUserOp = - await sepoliaZerodevKernelClient.prepareUserOperationRequest({ - userOperation: { - callData: await sepoliaKernelAccount.encodeCallData({ + await sepoliaZerodevKernelClient.prepareUserOperation({ + callData: await sepoliaKernelAccount.encodeCalls([ + { to: zeroAddress, value: BigInt(0), data: "0x" - }) - } + } + ]) }) const optimismSepoliaUserOp = - await optimismSepoliaZerodevKernelClient.prepareUserOperationRequest( - { - userOperation: { - callData: - await optimismSepoliaKernelAccount.encodeCallData( - { - to: zeroAddress, - value: BigInt(0), - data: "0x" - } - ) + await optimismSepoliaZerodevKernelClient.prepareUserOperation({ + callData: await optimismSepoliaKernelAccount.encodeCalls([ + { + to: zeroAddress, + value: BigInt(0), + data: "0x" } - } - ) + ]) + }) const signedEnableUserOps = await ecdsaSignUserOpsWithEnable({ multiChainUserOpConfigsForEnable: [ @@ -1099,34 +998,27 @@ describe("MultiChainECDSAValidator", () => { ] }) - const sepoliaBundlerClient = sepoliaZerodevKernelClient.extend( - bundlerActions(getEntryPoint()) - ) - - const optimismSepoliaBundlerClient = - optimismSepoliaZerodevKernelClient.extend( - bundlerActions(getEntryPoint()) - ) - const sepoliaUserOpHash = - await sepoliaBundlerClient.sendUserOperation({ - userOperation: signedEnableUserOps[0] + await sepoliaZerodevKernelClient.sendUserOperation({ + ...signedEnableUserOps[0] }) console.log("sepoliaUserOpHash", sepoliaUserOpHash) - await sepoliaBundlerClient.waitForUserOperationReceipt({ + await sepoliaZerodevKernelClient.waitForUserOperationReceipt({ hash: sepoliaUserOpHash }) const optimismSepoliaUserOpHash = - await optimismSepoliaBundlerClient.sendUserOperation({ - userOperation: signedEnableUserOps[1] + await optimismSepoliaZerodevKernelClient.sendUserOperation({ + ...signedEnableUserOps[1] }) console.log("optimismSepoliaUserOpHash", optimismSepoliaUserOpHash) - await optimismSepoliaBundlerClient.waitForUserOperationReceipt({ - hash: optimismSepoliaUserOpHash - }) + await optimismSepoliaZerodevKernelClient.waitForUserOperationReceipt( + { + hash: optimismSepoliaUserOpHash + } + ) const sepoliaTxHashAfterEnable = await sepoliaZerodevKernelClient.sendTransaction({ @@ -1171,11 +1063,11 @@ describe("MultiChainECDSAValidator", () => { optimismSepoliaSessionKeyAccount.address ) - const sepoliaEmptySessionKeySigner = toECDSASigner({ + const sepoliaEmptySessionKeySigner = await toECDSASigner({ signer: sepoliaEmptyAccount }) - const optimismSepoliaEmptySessionKeySigner = toECDSASigner({ + const optimismSepoliaEmptySessionKeySigner = await toECDSASigner({ signer: optimismSepoliaEmptyAccount }) @@ -1247,11 +1139,11 @@ describe("MultiChainECDSAValidator", () => { ]) // get real session key signers - const sepoliaSessionKeySigner = toECDSASigner({ + const sepoliaSessionKeySigner = await toECDSASigner({ signer: sepoliaSessionKeyAccount }) - const optimismSepoliaSessionKeySigner = toECDSASigner({ + const optimismSepoliaSessionKeySigner = await toECDSASigner({ signer: optimismSepoliaSessionKeyAccount }) @@ -1279,17 +1171,7 @@ describe("MultiChainECDSAValidator", () => { account: deserializeSepoliaKernelAccount, chain: sepolia, bundlerTransport: http(SEPOLIA_ZERODEV_RPC_URL), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - return sepoliaZeroDevPaymasterClient.sponsorUserOperation( - { - userOperation, - entryPoint: getEntryPoint() - } - ) - } - }, - entryPoint: getEntryPoint() + paymaster: sepoliaZeroDevPaymasterClient }) const optimismSepoliaZerodevKernelClient = @@ -1297,17 +1179,7 @@ describe("MultiChainECDSAValidator", () => { account: deserializeOptimismSepoliaKernelAccount, chain: optimismSepolia, bundlerTransport: http(OPTIMISM_SEPOLIA_ZERODEV_RPC_URL), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - return opSepoliaZeroDevPaymasterClient.sponsorUserOperation( - { - userOperation, - entryPoint: getEntryPoint() - } - ) - } - }, - entryPoint: getEntryPoint() + paymaster: opSepoliaZeroDevPaymasterClient }) // send user ops. you don't need additional enables, since it already has the approvals with serialized account diff --git a/packages/test/v0.7/multiTenantSessionAccount.test.ts b/packages/test/v0.7/multiTenantSessionAccount.test.ts index af89c20e..62167c3a 100644 --- a/packages/test/v0.7/multiTenantSessionAccount.test.ts +++ b/packages/test/v0.7/multiTenantSessionAccount.test.ts @@ -3,23 +3,21 @@ import { beforeAll, describe, test } from "bun:test" import { CAB_V0_2_1, createKernelCABClient } from "@zerodev/cab" import { type KernelAccountClient, - type KernelSmartAccount, - KernelV3AccountAbi, - KernelV3_1AccountAbi, + type KernelSmartAccountImplementation, + type ZeroDevPaymasterClient, createKernelAccountClient, - createZeroDevPaymasterClient + type createZeroDevPaymasterClient } from "@zerodev/sdk" import { CAB_PAYMASTER_SERVER_URL, getInstallDMAsExecutorCallData } from "@zerodev/session-account" +import type { SessionAccountImplementation } from "@zerodev/session-account" import { type ENFORCER_VERSION, ParamCondition, toAllowedParamsEnforcer } from "@zerodev/session-account/enforcers" -import { type BundlerClient, bundlerActions } from "permissionless" -import type { ENTRYPOINT_ADDRESS_V07_TYPE } from "permissionless/types" import { http, type Address, @@ -27,27 +25,21 @@ import { type PublicClient, type Transport, createPublicClient, - decodeErrorResult, - decodeFunctionData, - encodeErrorResult, encodeFunctionData, parseEther, zeroAddress } from "viem" +import type { SmartAccount } from "viem/account-abstraction" import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" -import { baseSepolia, optimismSepolia, sepolia } from "viem/chains" -import type { - Caveat, - SessionAccount -} from "../../../plugins/multi-tenant-session-account" +import { optimismSepolia, sepolia } from "viem/chains" import { dmActionsEip7710 } from "../../../plugins/multi-tenant-session-account/clients" import { ROOT_AUTHORITY } from "../../../plugins/multi-tenant-session-account/constants" import { toCABPaymasterEnforcer } from "../../../plugins/multi-tenant-session-account/enforcers/cab-paymaster/toCABPaymasterEnforcer" import type { Delegation } from "../../../plugins/multi-tenant-session-account/types" import { TEST_ERC20Abi } from "../abis/Test_ERC20Abi" import { config } from "../config" -import { Test_ERC20Address } from "../utils" import { + Test_ERC20Address, getBundlerRpc, getEcdsaKernelAccountWithRandomSigner, getEntryPoint, @@ -61,29 +53,23 @@ import { const TEST_TIMEOUT = 1000000 -describe("Yi SubAccount", () => { +describe("7710 SessionAccount", () => { let publicClient: PublicClient - let mainDelegatorAccount: KernelSmartAccount< - ENTRYPOINT_ADDRESS_V07_TYPE, - Transport, - Chain - > - let bundlerClient: BundlerClient + let mainDelegatorAccount: SmartAccount let kernelClient: KernelAccountClient< - ENTRYPOINT_ADDRESS_V07_TYPE, Transport, Chain, - KernelSmartAccount + SmartAccount > - let sessionAccount: SessionAccount + let sessionAccount: SmartAccount let sessionAccountClient: KernelAccountClient< - ENTRYPOINT_ADDRESS_V07_TYPE, Transport, Chain, - KernelSmartAccount + SmartAccount > let delegations: Delegation[] const enforcerVersion: ENFORCER_VERSION = "v0_2" + let zeroDevPaymaster: ZeroDevPaymasterClient beforeAll(async () => { const ownerPrivateKey = process.env.TEST_PRIVATE_KEY @@ -91,18 +77,11 @@ describe("Yi SubAccount", () => { throw new Error("TEST_PRIVATE_KEY is not set") } publicClient = await getPublicClient() + zeroDevPaymaster = getZeroDevPaymasterClient() mainDelegatorAccount = await getEcdsaKernelAccountWithRandomSigner() kernelClient = await getKernelAccountClient({ account: mainDelegatorAccount, - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) const installTx = await kernelClient.sendTransaction({ @@ -111,8 +90,6 @@ describe("Yi SubAccount", () => { data: getInstallDMAsExecutorCallData() }) console.log({ installTx }) - - bundlerClient = kernelClient.extend(bundlerActions(getEntryPoint())) }) test( @@ -133,16 +110,7 @@ describe("Yi SubAccount", () => { ] const kernelClientDM = kernelClient.extend( - dmActionsEip7710< - ENTRYPOINT_ADDRESS_V07_TYPE, - Transport, - Chain, - KernelSmartAccount< - ENTRYPOINT_ADDRESS_V07_TYPE, - Transport, - Chain - > - >({ enforcerVersion }) + dmActionsEip7710({ enforcerVersion }) ) const mainDeleGatorSignature = await kernelClientDM.signDelegation({ @@ -150,40 +118,30 @@ describe("Yi SubAccount", () => { }) console.log({ mainDeleGatorSignature }) delegations[0].signature = mainDeleGatorSignature - const initCode = await mainDelegatorAccount.getInitCode() + const initCode = await mainDelegatorAccount.generateInitCode() sessionAccount = await getSessionAccount( delegations, privateSessionKey, initCode ) sessionAccountClient = await getKernelAccountClient({ - // @ts-ignore: fix return type error + // @ts-ignore account: sessionAccount, - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) const userOpHash = await sessionAccountClient.sendUserOperation({ - userOperation: { - callData: await sessionAccount.encodeCallData({ + callData: await sessionAccount.encodeCalls([ + { to: zeroAddress, data: "0x", value: 0n - }), - preVerificationGas: 84700n, - callGasLimit: 1273781n, - verificationGasLimit: 726789n - } - }) - const receipt = await bundlerClient.waitForUserOperationReceipt({ - hash: userOpHash + } + ]) }) + const receipt = + await sessionAccountClient.waitForUserOperationReceipt({ + hash: userOpHash + }) console.log( "transactionHash", `https://sepolia.etherscan.io/tx/${receipt.receipt.transactionHash}` @@ -227,16 +185,7 @@ describe("Yi SubAccount", () => { ] const kernelClientDM = kernelClient.extend( - dmActionsEip7710< - ENTRYPOINT_ADDRESS_V07_TYPE, - Transport, - Chain, - KernelSmartAccount< - ENTRYPOINT_ADDRESS_V07_TYPE, - Transport, - Chain - > - >({ enforcerVersion }) + dmActionsEip7710({ enforcerVersion }) ) const installDMAndDelegateHash = @@ -250,24 +199,16 @@ describe("Yi SubAccount", () => { `https://sepolia.etherscan.io/tx/${installDMAndDelegateHash}` ) - const initCode = await mainDelegatorAccount.getInitCode() + const initCode = await mainDelegatorAccount.generateInitCode() sessionAccount = await getSessionAccount( delegations, privateSessionKey, initCode ) sessionAccountClient = await getKernelAccountClient({ - // @ts-ignore: fix return type error + // @ts-ignore account: sessionAccount, - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) await mintToAccount( @@ -284,20 +225,18 @@ describe("Yi SubAccount", () => { args: [sessionKeyAccount.address, amountToTransfer] }) const userOpHash = await sessionAccountClient.sendUserOperation({ - userOperation: { - callData: await sessionAccount.encodeCallData({ + callData: await sessionAccount.encodeCalls([ + { to: Test_ERC20Address, data: transferData, value: 0n - }), - preVerificationGas: 84700n, - callGasLimit: 1273781n, - verificationGasLimit: 726789n - } - }) - const receipt = await bundlerClient.waitForUserOperationReceipt({ - hash: userOpHash + } + ]) }) + const receipt = + await sessionAccountClient.waitForUserOperationReceipt({ + hash: userOpHash + }) console.log( "transactionHash", `https://sepolia.etherscan.io/tx/${receipt.receipt.transactionHash}` @@ -306,247 +245,219 @@ describe("Yi SubAccount", () => { TEST_TIMEOUT ) - test( - "enable cab", - async () => { - const kernelCabClient = createKernelCABClient(kernelClient, { - transport: http(CAB_PAYMASTER_SERVER_URL), - entryPoint: getEntryPoint(), - chain: sepolia, - cabVersion: CAB_V0_2_1 - }) - console.log("kernelCabClient addr", kernelCabClient.account.address) - await kernelCabClient.enableCAB({ - tokens: [{ name: "6TEST", networks: [sepolia.id] }] - }) - // await mintToAccount( - // kernelClient.account.client as PublicClient, - // kernelClient, - // "0x066aB66D299600E006abD1af0d41AC872b77aeb6", - // parseEther("0.99999999999") - // ) - // await mintToAccount( - // kernelClient.account.client as PublicClient, - // kernelClient, - // "0x066aB66D299600E006abD1af0d41AC872b77aeb6", - // parseEther("0.99999999999") - // ) + // test( + // "enable cab", + // async () => { + // const kernelCabClient = createKernelCABClient(kernelClient, { + // transport: http(CAB_PAYMASTER_SERVER_URL), + // entryPoint: getEntryPoint(), + // chain: sepolia, + // cabVersion: CAB_V0_2_1 + // }) + // console.log("kernelCabClient addr", kernelCabClient.account.address) + // await kernelCabClient.enableCAB({ + // tokens: [{ name: "6TEST", networks: [sepolia.id] }] + // }) + // // await mintToAccount( + // // kernelClient.account.client as PublicClient, + // // kernelClient, + // // "0x066aB66D299600E006abD1af0d41AC872b77aeb6", + // // parseEther("0.99999999999") + // // ) + // // await mintToAccount( + // // kernelClient.account.client as PublicClient, + // // kernelClient, + // // "0x066aB66D299600E006abD1af0d41AC872b77aeb6", + // // parseEther("0.99999999999") + // // ) - const mainDelegatorAccountOPSepolia = - await getEcdsaKernelAccountWithRandomSigner( - undefined, - optimismSepolia.id - ) + // const mainDelegatorAccountOPSepolia = + // await getEcdsaKernelAccountWithRandomSigner( + // undefined, + // optimismSepolia.id + // ) - const kernelClientOPSepolia = createKernelAccountClient({ - account: mainDelegatorAccountOPSepolia, - chain: optimismSepolia, - bundlerTransport: http( - getBundlerRpc(config["v0.7"][optimismSepolia.id].projectId), - { timeout: 100_000 } - ), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = createZeroDevPaymasterClient({ - chain: optimismSepolia, - transport: http( - getPaymasterRpc( - config["v0.7"][optimismSepolia.id].projectId - ) - ), - entryPoint: getEntryPoint() - }) - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - }, - entryPoint: getEntryPoint() - }) + // const kernelClientOPSepolia = createKernelAccountClient({ + // account: mainDelegatorAccountOPSepolia, + // chain: optimismSepolia, + // bundlerTransport: http( + // getBundlerRpc(config["v0.7"][optimismSepolia.id].projectId), + // { timeout: 100_000 } + // ), + // middleware: { + // sponsorUserOperation: async ({ userOperation }) => { + // const zeroDevPaymaster = createZeroDevPaymasterClient({ + // chain: optimismSepolia, + // transport: http( + // getPaymasterRpc( + // config["v0.7"][optimismSepolia.id].projectId + // ) + // ), + // entryPoint: getEntryPoint() + // }) + // return zeroDevPaymaster.sponsorUserOperation({ + // userOperation, + // entryPoint: getEntryPoint() + // }) + // } + // }, + // entryPoint: getEntryPoint() + // }) - await mintToAccount( - kernelClientOPSepolia.account.client as PublicClient, - kernelClientOPSepolia, - kernelClientOPSepolia.account.address, - 100000000n - ) + // await mintToAccount( + // kernelClientOPSepolia.account.client as PublicClient, + // kernelClientOPSepolia, + // kernelClientOPSepolia.account.address, + // 100000000n + // ) - const kernelCabClientOPSepolia = createKernelCABClient( - kernelClientOPSepolia, - { - transport: http(CAB_PAYMASTER_SERVER_URL), - entryPoint: getEntryPoint(), - chain: optimismSepolia, - cabVersion: CAB_V0_2_1 - } - ) - console.log( - "kernelCabClientOPSepolia addr", - kernelCabClientOPSepolia.account.address - ) - await kernelCabClientOPSepolia.enableCAB({ - tokens: [{ name: "6TEST", networks: [optimismSepolia.id] }] - }) - while (true) { - const cabClient = createPublicClient({ - transport: http(CAB_PAYMASTER_SERVER_URL) - }) + // const kernelCabClientOPSepolia = createKernelCABClient( + // kernelClientOPSepolia, + // { + // transport: http(CAB_PAYMASTER_SERVER_URL), + // entryPoint: getEntryPoint(), + // chain: optimismSepolia, + // cabVersion: CAB_V0_2_1 + // } + // ) + // console.log( + // "kernelCabClientOPSepolia addr", + // kernelCabClientOPSepolia.account.address + // ) + // await kernelCabClientOPSepolia.enableCAB({ + // tokens: [{ name: "6TEST", networks: [optimismSepolia.id] }] + // }) + // while (true) { + // const cabClient = createPublicClient({ + // transport: http(CAB_PAYMASTER_SERVER_URL) + // }) - const cabBalance = await cabClient.request({ - // @ts-ignore - method: "pm_getCabAvailableRepayTokens", - params: [kernelCabClientOPSepolia.account.address] - }) - console.log("CAB balance:", cabBalance) - // @ts-ignore - if (cabBalance?.availableRepayTokens?.length) { - break - } - } - }, - TEST_TIMEOUT - ) - test( - "Send tx with cab caveat from kernelAccount through sessionAccount", - async () => { - const privateSessionKey = generatePrivateKey() - const sessionKeyAccount = privateKeyToAccount(privateSessionKey) - const allowedParamsCaveat = toAllowedParamsEnforcer({ - permissions: [ - { - abi: TEST_ERC20Abi, - target: Test_ERC20Address, - functionName: "transfer", - args: [ - { - condition: ParamCondition.EQUAL, - value: sessionKeyAccount.address - }, - null - ] - } - ] - }) - const cabCaveat = await toCABPaymasterEnforcer({ - accountAddress: mainDelegatorAccount.address, - enforcerVersion - }) - const caveats = [cabCaveat] + // const cabBalance = await cabClient.request({ + // // @ts-ignore + // method: "pm_getCabAvailableRepayTokens", + // params: [kernelCabClientOPSepolia.account.address] + // }) + // console.log("CAB balance:", cabBalance) + // // @ts-ignore + // if (cabBalance?.availableRepayTokens?.length) { + // break + // } + // } + // }, + // TEST_TIMEOUT + // ) + // test( + // "Send tx with cab caveat from kernelAccount through sessionAccount", + // async () => { + // const privateSessionKey = generatePrivateKey() + // const sessionKeyAccount = privateKeyToAccount(privateSessionKey) + // const allowedParamsCaveat = toAllowedParamsEnforcer({ + // permissions: [ + // { + // abi: TEST_ERC20Abi, + // target: Test_ERC20Address, + // functionName: "transfer", + // args: [ + // { + // condition: ParamCondition.EQUAL, + // value: sessionKeyAccount.address + // }, + // null + // ] + // } + // ] + // }) + // const cabCaveat = await toCABPaymasterEnforcer({ + // accountAddress: mainDelegatorAccount.address, + // enforcerVersion + // }) + // const caveats = [cabCaveat] - delegations = [ - { - delegator: mainDelegatorAccount.address, - delegate: sessionKeyAccount.address, - authority: ROOT_AUTHORITY, - caveats, - salt: 0n, - signature: "0x" - } - ] + // delegations = [ + // { + // delegator: mainDelegatorAccount.address, + // delegate: sessionKeyAccount.address, + // authority: ROOT_AUTHORITY, + // caveats, + // salt: 0n, + // signature: "0x" + // } + // ] - const kernelClientDM = kernelClient.extend( - dmActionsEip7710< - ENTRYPOINT_ADDRESS_V07_TYPE, - Transport, - Chain, - KernelSmartAccount< - ENTRYPOINT_ADDRESS_V07_TYPE, - Transport, - Chain - > - >({ enforcerVersion }) - ) + // const kernelClientDM = kernelClient.extend( + // dmActionsEip7710({ enforcerVersion }) + // ) - const mainDeleGatorSignature = await kernelClientDM.signDelegation({ - delegation: delegations[0] - }) - console.log({ mainDeleGatorSignature }) - delegations[0].signature = mainDeleGatorSignature - const initCode = await mainDelegatorAccount.getInitCode() - sessionAccount = await getSessionAccount( - delegations, - privateSessionKey, - initCode - ) - sessionAccountClient = await getKernelAccountClient({ - // @ts-ignore: fix return type error - account: sessionAccount, - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } - }) + // const mainDeleGatorSignature = await kernelClientDM.signDelegation({ + // delegation: delegations[0] + // }) + // console.log({ mainDeleGatorSignature }) + // delegations[0].signature = mainDeleGatorSignature + // const initCode = await mainDelegatorAccount.generateInitCode() + // sessionAccount = await getSessionAccount( + // delegations, + // privateSessionKey, + // initCode + // ) + // sessionAccountClient = await getKernelAccountClient({ + // // @ts-ignore: fix return type error + // account: sessionAccount, + // paymaster: zeroDevPaymaster + // }) - // await mintToAccount( - // kernelClient.account.client as PublicClient, - // kernelClient, - // kernelClient.account.address, - // 100000000n - // ) + // // await mintToAccount( + // // kernelClient.account.client as PublicClient, + // // kernelClient, + // // kernelClient.account.address, + // // 100000000n + // // ) - const amountToTransfer = 10000n - const transferData = encodeFunctionData({ - abi: TEST_ERC20Abi, - functionName: "transfer", - args: [sessionKeyAccount.address, amountToTransfer] - }) - const sessionAccountClientDM = sessionAccountClient.extend( - dmActionsEip7710< - ENTRYPOINT_ADDRESS_V07_TYPE, - Transport, - Chain, - KernelSmartAccount< - ENTRYPOINT_ADDRESS_V07_TYPE, - Transport, - Chain - > - >({ enforcerVersion }) - ) - const repayTokens = [ - { - address: - "0x3870419ba2bbf0127060bcb37f69a1b1c090992b" as Address, - chainId: optimismSepolia.id - } - ] - // const txHash = await sessionAccountClientDM.sendTransactionWithCAB({ - // to: Test_ERC20Address, - // data: transferData, - // value: 0n, - // repayTokens - // }) - const userOpHash = await sessionAccountClient.sendUserOperation({ - userOperation: { - callData: - await sessionAccountClientDM.encodeCallDataWithCAB({ - calls: [ - { - to: Test_ERC20Address, - data: transferData, - value: 0n - } - ], - repayTokens - }) - } - }) + // const amountToTransfer = 10000n + // const transferData = encodeFunctionData({ + // abi: TEST_ERC20Abi, + // functionName: "transfer", + // args: [sessionKeyAccount.address, amountToTransfer] + // }) + // const sessionAccountClientDM = sessionAccountClient.extend( + // dmActionsEip7710({ enforcerVersion }) + // ) + // const repayTokens = [ + // { + // address: + // "0x3870419ba2bbf0127060bcb37f69a1b1c090992b" as Address, + // chainId: optimismSepolia.id + // } + // ] + // // const txHash = await sessionAccountClientDM.sendTransactionWithCAB({ + // // to: Test_ERC20Address, + // // data: transferData, + // // value: 0n, + // // repayTokens + // // }) + // const userOpHash = await sessionAccountClient.sendUserOperation({ + // callData: await sessionAccountClientDM.encodeCallDataWithCAB({ + // calls: [ + // { + // to: Test_ERC20Address, + // data: transferData, + // value: 0n + // } + // ], + // repayTokens + // }) + // }) - const receipt = await bundlerClient.waitForUserOperationReceipt({ - hash: userOpHash, - timeout: TEST_TIMEOUT - }) + // const receipt = + // await sessionAccountClient.waitForUserOperationReceipt({ + // hash: userOpHash, + // timeout: TEST_TIMEOUT + // }) - console.log( - "transactionHash", - `https://sepolia.etherscan.io/tx/${receipt.receipt.transactionHash}` - ) - }, - TEST_TIMEOUT - ) + // console.log( + // "transactionHash", + // `https://sepolia.etherscan.io/tx/${receipt.receipt.transactionHash}` + // ) + // }, + // TEST_TIMEOUT + // ) }) diff --git a/packages/test/v0.7/permissionValidator.test.ts b/packages/test/v0.7/permissionValidator.test.ts index 7404cee9..e7ec0f09 100644 --- a/packages/test/v0.7/permissionValidator.test.ts +++ b/packages/test/v0.7/permissionValidator.test.ts @@ -4,14 +4,12 @@ import { verifyMessage } from "@ambire/signature-validator" import { EIP1271Abi, type KernelAccountClient, - type KernelSmartAccount, + type KernelSmartAccountImplementation, KernelV3AccountAbi, + type ZeroDevPaymasterClient, verifyEIP6492Signature } from "@zerodev/sdk" import { ethers } from "ethers" -import type { BundlerClient } from "permissionless" -import type { PimlicoBundlerClient } from "permissionless/clients/pimlico" -import type { ENTRYPOINT_ADDRESS_V07_TYPE } from "permissionless/types" import { type Address, type Chain, @@ -28,6 +26,7 @@ import { toFunctionSelector, zeroAddress } from "viem" +import type { SmartAccount } from "viem/account-abstraction" import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" import { sepolia } from "viem/chains" import { @@ -51,12 +50,10 @@ import { ParamCondition } from "../../../plugins/permission/policies/types" import { TEST_ERC20Abi } from "../abis/Test_ERC20Abi" import { TokenActionsAbi } from "../abis/TokenActionsAbi" import { TOKEN_ACTION_ADDRESS, config } from "../config" -import { Test_ERC20Address } from "../utils" import { + Test_ERC20Address, getEntryPoint, getKernelAccountClient, - getKernelBundlerClient, - getPimlicoBundlerClient, getPublicClient, getSessionKeySignerToPermissionKernelAccount, getSignerToEcdsaKernelAccount, @@ -78,23 +75,20 @@ const TX_HASH_REGEX = /^0x[0-9a-fA-F]{64}$/ const TEST_TIMEOUT = 1000000 describe("Permission kernel Account", () => { - let publicClient: PublicClient - let bundlerClient: BundlerClient + let publicClient: PublicClient let ecdsaSmartAccountClient: KernelAccountClient< - ENTRYPOINT_ADDRESS_V07_TYPE, Transport, Chain, - KernelSmartAccount + SmartAccount > - let pimlicoBundlerClient: PimlicoBundlerClient let owner: PrivateKeyAccount let gasPolicy: Policy let permissionSmartAccountClient: KernelAccountClient< - ENTRYPOINT_ADDRESS_V07_TYPE, Transport, Chain, - KernelSmartAccount + SmartAccount > + let zeroDevPaymaster: ZeroDevPaymasterClient async function mintToAccount(target: Address, amount: bigint) { const balanceBefore = await publicClient.readContract({ @@ -141,22 +135,13 @@ describe("Permission kernel Account", () => { const testPrivateKey = process.env.TEST_PRIVATE_KEY as Hex owner = privateKeyToAccount(testPrivateKey) publicClient = await getPublicClient() - bundlerClient = getKernelBundlerClient() - pimlicoBundlerClient = getPimlicoBundlerClient() const ecdsaAccount = await getSignerToEcdsaKernelAccount() + zeroDevPaymaster = getZeroDevPaymasterClient() ecdsaSmartAccountClient = await getKernelAccountClient({ account: ecdsaAccount, - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) gasPolicy = await toGasPolicy({ allowed: parseEther("10"), @@ -168,15 +153,7 @@ describe("Permission kernel Account", () => { permissionSmartAccountClient = await getKernelAccountClient({ account: await getSignerToRootPermissionKernelAccount([sudoPolicy]), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) }) @@ -215,7 +192,7 @@ describe("Permission kernel Account", () => { message, signature: signature, provider: new ethers.providers.JsonRpcProvider( - config["v0.7"][sepolia.id].rpcUrl + config["0.7"][sepolia.id].rpcUrl ) }) expect(ambireResult).toBeTrue() @@ -282,7 +259,7 @@ describe("Permission kernel Account", () => { }, signature: signature, provider: new ethers.providers.JsonRpcProvider( - config["v0.7"][sepolia.id].rpcUrl + config["0.7"][sepolia.id].rpcUrl ) }) expect(ambireResult).toBeTrue() @@ -312,7 +289,7 @@ describe("Permission kernel Account", () => { message, signature: response, provider: new ethers.providers.JsonRpcProvider( - config["v0.7"][sepolia.id].rpcUrl + config["0.7"][sepolia.id].rpcUrl ) }) expect(ambireResult).toBeTrue() @@ -392,15 +369,7 @@ describe("Permission kernel Account", () => { ]) const permissionSmartAccountClient = await getKernelAccountClient({ account: accountWithSudoAndRegular, - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) const response = await permissionSmartAccountClient.sendTransaction( @@ -418,25 +387,26 @@ describe("Permission kernel Account", () => { const permissionSmartAccountClientSudo = await getKernelAccountClient({ account: accountWithSudo, - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) - const response2 = + const response2UOHash = await permissionSmartAccountClientSudo.uninstallPlugin({ plugin }) - console.log("Uninstall Transaction hash:", response2) + const response2 = + await permissionSmartAccountClientSudo.waitForUserOperationReceipt( + { + hash: response2UOHash + } + ) + console.log( + "Uninstall Transaction hash:", + response2.receipt.transactionHash + ) const transactionReceipt = await publicClient.waitForTransactionReceipt({ - hash: response2 + hash: response2.receipt.transactionHash }) let policyAndSignerUninstalled = false @@ -463,22 +433,20 @@ describe("Permission kernel Account", () => { let errMsg = "" try { await permissionSmartAccountClient.sendUserOperation({ - userOperation: { - callData: - await permissionSmartAccountClient.account.encodeCallData( - { - to: zeroAddress, - value: 0n, - data: "0x" - } - ) - } + callData: + await permissionSmartAccountClient.account.encodeCalls([ + { + to: zeroAddress, + value: 0n, + data: "0x" + } + ]) }) } catch (error) { errMsg = error.message } expect(errMsg).toMatch( - "UserOperation reverted during simulation with reason: 0x756688fe" + "UserOperation reverted during simulation with reason: AA23 reverted 0x756688fe" ) }, TEST_TIMEOUT @@ -497,28 +465,12 @@ describe("Permission kernel Account", () => { const permissionSmartAccountClient = await getKernelAccountClient({ account: accountWithSudoAndRegular, - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) const permissionSmartAccountClientWithRegular = await getKernelAccountClient({ account: accountWithRegular, - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) const response = await permissionSmartAccountClient.sendTransaction( @@ -536,28 +488,27 @@ describe("Permission kernel Account", () => { const permissionSmartAccountClientSudo = await getKernelAccountClient({ account: accountWithSudo, - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) const currentNonce = - await permissionSmartAccountClientSudo.getKernelV3ModuleCurrentNonce( - {} - ) + await permissionSmartAccountClientSudo.getKernelV3ModuleCurrentNonce() console.log({ currentNonce }) - const response2 = + const response2UOHash = await permissionSmartAccountClientSudo.invalidateNonce({ nonceToSet: currentNonce + 1 }) - console.log("Invalidate nonce transaction hash:", response2) + const response2 = + await permissionSmartAccountClientSudo.waitForUserOperationReceipt( + { + hash: response2UOHash + } + ) + console.log( + "Invalidate nonce transaction hash:", + response2.receipt.transactionHash + ) let errMsg = "" try { @@ -571,7 +522,7 @@ describe("Permission kernel Account", () => { } console.log(errMsg) expect(errMsg).toMatch( - "UserOperation reverted during simulation with reason: 0x756688fe" + "UserOperation reverted during simulation with reason: AA23 reverted 0x756688fe" ) }, TEST_TIMEOUT @@ -588,15 +539,7 @@ describe("Permission kernel Account", () => { account: await getSignerToRootPermissionKernelAccount([ gasPolicy ]), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) console.log("Gas policy account") @@ -625,15 +568,7 @@ describe("Permission kernel Account", () => { await getSignerToRootPermissionWithSecondaryValidatorKernelAccount( [await toSudoPolicy({})] ), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) const response = await permissionSmartAccountClient.sendTransaction( @@ -661,15 +596,7 @@ describe("Permission kernel Account", () => { const permissionSmartAccountClient = await getKernelAccountClient({ account: await getSignerToPermissionKernelAccount([gasPolicy]), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) console.log("Gas policy account") @@ -704,15 +631,7 @@ describe("Permission kernel Account", () => { account: await getSignerToPermissionKernelAccount([ timestampPolicy ]), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) const response = await permissionSmartAccountClient.sendTransaction( @@ -742,15 +661,7 @@ describe("Permission kernel Account", () => { account: await getSignerToPermissionKernelAccount([ signaturePolicy ]), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) const response = await permissionSmartAccountClient.sendTransaction( @@ -784,15 +695,7 @@ describe("Permission kernel Account", () => { account: await getSignerToPermissionKernelAccount([ rateLimitPolicy ]), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) await sleep(2 * 5000) @@ -852,15 +755,7 @@ describe("Permission kernel Account", () => { const permissionSmartAccountClient = await getKernelAccountClient({ account: await getSignerToPermissionKernelAccount([callPolicy]), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) await mintToAccount( @@ -932,15 +827,7 @@ describe("Permission kernel Account", () => { const permissionSmartAccountClient = await getKernelAccountClient({ account: await getSignerToPermissionKernelAccount([callPolicy]), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) await mintToAccount( @@ -1014,15 +901,7 @@ describe("Permission kernel Account", () => { const permissionSmartAccountClient = await getKernelAccountClient({ account: await getSignerToPermissionKernelAccount([callPolicy]), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) await mintToAccount( @@ -1096,15 +975,7 @@ describe("Permission kernel Account", () => { const permissionSmartAccountClient = await getKernelAccountClient({ account: await getSignerToPermissionKernelAccount([callPolicy]), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) await mintToAccount( @@ -1176,15 +1047,7 @@ describe("Permission kernel Account", () => { const permissionSmartAccountClient = await getKernelAccountClient({ account: await getSignerToPermissionKernelAccount([callPolicy]), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) await mintToAccount( @@ -1276,18 +1139,7 @@ describe("Permission kernel Account", () => { const permissionSmartAccountClient = await getKernelAccountClient({ account: deserilizedAccount, - middleware: { - gasPrice: async () => - (await pimlicoBundlerClient.getUserOperationGasPrice()) - .fast, - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) await mintToAccount( @@ -1354,15 +1206,7 @@ describe("Permission kernel Account", () => { ) } ), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) await mintToAccount( diff --git a/packages/test/v0.7/recoveryKernelAccount.test.ts b/packages/test/v0.7/recoveryKernelAccount.test.ts index 45b8611b..e539e45b 100644 --- a/packages/test/v0.7/recoveryKernelAccount.test.ts +++ b/packages/test/v0.7/recoveryKernelAccount.test.ts @@ -1,10 +1,12 @@ // @ts-expect-error import { beforeAll, describe, expect, test } from "bun:test" import { getValidatorAddress } from "@zerodev/ecdsa-validator/index.js" -import type { KernelAccountClient, KernelSmartAccount } from "@zerodev/sdk" +import type { + KernelAccountClient, + KernelSmartAccountImplementation, + ZeroDevPaymasterClient +} from "@zerodev/sdk" import dotenv from "dotenv" -import { type BundlerClient, bundlerActions } from "permissionless" -import type { ENTRYPOINT_ADDRESS_V07_TYPE } from "permissionless/types/entrypoint.js" import { type Address, type Chain, @@ -15,6 +17,7 @@ import { parseAbi, zeroAddress } from "viem" +import type { SmartAccount } from "viem/account-abstraction" import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" import { getEntryPoint, @@ -24,7 +27,7 @@ import { getZeroDevPaymasterClient, kernelVersion, validateEnvironmentVariables -} from "./utils.js" +} from "./utils" dotenv.config() @@ -47,41 +50,27 @@ const TEST_TIMEOUT = 1000000 const recoveryExecutorFunction = "function doRecovery(address _validator, bytes calldata _data)" describe("Recovery kernel Account", () => { - let recoveryAccount: KernelSmartAccount - let publicClient: PublicClient - let bundlerClient: BundlerClient + let recoveryAccount: SmartAccount let recoveryKernelClient: KernelAccountClient< - ENTRYPOINT_ADDRESS_V07_TYPE, Transport, Chain, - KernelSmartAccount + SmartAccount > let accountAddress: Address let newSigner: PrivateKeyAccount + let zeroDevPaymaster: ZeroDevPaymasterClient beforeAll(async () => { - publicClient = await getPublicClient() const newSignerPrivateKey = generatePrivateKey() newSigner = privateKeyToAccount(newSignerPrivateKey) recoveryAccount = await getRecoveryKernelAccount() accountAddress = recoveryAccount.address + zeroDevPaymaster = getZeroDevPaymasterClient() recoveryKernelClient = await getKernelAccountClient({ account: recoveryAccount, - middleware: { - sponsorUserOperation: async ({ userOperation, entryPoint }) => { - const zerodevPaymaster = getZeroDevPaymasterClient() - return zerodevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint - }) - } - } + paymaster: zeroDevPaymaster }) - - bundlerClient = recoveryKernelClient.extend( - bundlerActions(getEntryPoint()) - ) }) test("Account address should be a valid Ethereum address", async () => { @@ -95,26 +84,21 @@ describe("Recovery kernel Account", () => { "Recover the account", async () => { const userOpHash = await recoveryKernelClient.sendUserOperation({ - userOperation: { - callData: encodeFunctionData({ - abi: parseAbi([recoveryExecutorFunction]), - functionName: "doRecovery", - args: [ - getValidatorAddress(getEntryPoint(), kernelVersion), - newSigner.address - ] - }), - preVerificationGas: 84700n, - callGasLimit: 1273781n, - verificationGasLimit: 726789n - } + callData: encodeFunctionData({ + abi: parseAbi([recoveryExecutorFunction]), + functionName: "doRecovery", + args: [ + getValidatorAddress(getEntryPoint(), kernelVersion), + newSigner.address + ] + }) }) console.log("userOpHash:", userOpHash) expect(userOpHash).toHaveLength(66) const transactionReceipt = - await bundlerClient.waitForUserOperationReceipt({ + await recoveryKernelClient.waitForUserOperationReceipt({ hash: userOpHash }) console.log( @@ -129,23 +113,21 @@ describe("Recovery kernel Account", () => { "Send the tx with new Signer", async () => { const userOpHash = await recoveryKernelClient.sendUserOperation({ - userOperation: { - callData: encodeFunctionData({ - abi: parseAbi([recoveryExecutorFunction]), - functionName: "doRecovery", - args: [ - getValidatorAddress(getEntryPoint(), kernelVersion), - newSigner.address - ] - }) - } + callData: encodeFunctionData({ + abi: parseAbi([recoveryExecutorFunction]), + functionName: "doRecovery", + args: [ + getValidatorAddress(getEntryPoint(), kernelVersion), + newSigner.address + ] + }) }) console.log("userOpHash:", userOpHash) expect(userOpHash).toHaveLength(66) const transactionReceipt = - await bundlerClient.waitForUserOperationReceipt({ + await recoveryKernelClient.waitForUserOperationReceipt({ hash: userOpHash }) console.log( diff --git a/packages/test/v0.7/remoteSigner.test.ts b/packages/test/v0.7/remoteSigner.test.ts index c4663c19..6615e100 100644 --- a/packages/test/v0.7/remoteSigner.test.ts +++ b/packages/test/v0.7/remoteSigner.test.ts @@ -4,13 +4,11 @@ import { verifyMessage } from "@ambire/signature-validator" import { EIP1271Abi, type KernelAccountClient, - type KernelSmartAccount, + type KernelSmartAccountImplementation, + type ZeroDevPaymasterClient, verifyEIP6492Signature } from "@zerodev/sdk" import { ethers } from "ethers" -import type { BundlerClient } from "permissionless" -import type { PimlicoBundlerClient } from "permissionless/clients/pimlico" -import type { ENTRYPOINT_ADDRESS_V07_TYPE } from "permissionless/types/entrypoint" import { type Address, type Chain, @@ -20,6 +18,8 @@ import { hashTypedData, zeroAddress } from "viem" +import type { SmartAccount } from "viem/account-abstraction" +import { sepolia } from "viem/chains" import { toSudoPolicy } from "../../../plugins/permission/policies" import { RemoteSignerMode, @@ -31,9 +31,7 @@ import { getEcdsaKernelAccountWithRemoteSigner, getEntryPoint, getKernelAccountClient, - getKernelBundlerClient, getPermissionKernelAccountWithRemoteSigner, - getPimlicoBundlerClient, getPublicClient, getZeroDevPaymasterClient } from "./utils" @@ -49,25 +47,20 @@ const TEST_TIMEOUT = 1000000 describe("Remote Signer", () => { let remoteSignerAddress: Address let publicClient: PublicClient - let bundlerClient: BundlerClient let ecdsaSmartAccountClient: KernelAccountClient< - ENTRYPOINT_ADDRESS_V07_TYPE, Transport, Chain, - KernelSmartAccount + SmartAccount > - let pimlicoBundlerClient: PimlicoBundlerClient let permissionSmartAccountClient: KernelAccountClient< - ENTRYPOINT_ADDRESS_V07_TYPE, Transport, Chain, - KernelSmartAccount + SmartAccount > + let zeroDevPaymaster: ZeroDevPaymasterClient beforeAll(async () => { publicClient = await getPublicClient() - bundlerClient = getKernelBundlerClient() - pimlicoBundlerClient = getPimlicoBundlerClient() const remoteSigner = await toRemoteSigner({ apiKey: process.env.ZERODEV_API_KEY as string, @@ -78,18 +71,10 @@ describe("Remote Signer", () => { const ecdsaAccount = await getEcdsaKernelAccountWithRemoteSigner(remoteSigner) - + zeroDevPaymaster = getZeroDevPaymasterClient() ecdsaSmartAccountClient = await getKernelAccountClient({ account: ecdsaAccount, - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) const sudoPolicy = await toSudoPolicy({}) @@ -98,15 +83,7 @@ describe("Remote Signer", () => { remoteSigner, [sudoPolicy] ), - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) }) @@ -150,7 +127,7 @@ describe("Remote Signer", () => { message, signature: signature, provider: new ethers.providers.JsonRpcProvider( - config["v0.7"][sepolia.id].rpcUrl + config["0.7"][sepolia.id].rpcUrl ) }) expect(ambireResult).toBeTrue() @@ -226,7 +203,7 @@ describe("Remote Signer", () => { }, signature: signature, provider: new ethers.providers.JsonRpcProvider( - config["v0.7"][sepolia.id].rpcUrl + config["0.7"][sepolia.id].rpcUrl ) }) expect(ambireResult).toBeTrue() @@ -256,7 +233,7 @@ describe("Remote Signer", () => { message, signature: response, provider: new ethers.providers.JsonRpcProvider( - config["v0.7"][sepolia.id].rpcUrl + config["0.7"][sepolia.id].rpcUrl ) }) expect(ambireResult).toBeTrue() diff --git a/packages/test/v0.7/spendingLimitHook.test.ts b/packages/test/v0.7/spendingLimitHook.test.ts index dd3b676b..b43413f6 100644 --- a/packages/test/v0.7/spendingLimitHook.test.ts +++ b/packages/test/v0.7/spendingLimitHook.test.ts @@ -1,77 +1,37 @@ // @ts-expect-error import { beforeAll, describe, expect, test } from "bun:test" -import { verifyMessage } from "@ambire/signature-validator" +import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator" +import { toSpendingLimitHook } from "@zerodev/hooks" import { - getKernelAddressFromECDSA, - signerToEcdsaValidator -} from "@zerodev/ecdsa-validator" -import { - constants, - EIP1271Abi, - KERNEL_ADDRESSES, type KernelAccountClient, - type KernelSmartAccount, - KernelV3ExecuteAbi, - createKernelAccount, - getCustomNonceKeyFromString, - verifyEIP6492Signature + type KernelSmartAccountImplementation, + type ZeroDevPaymasterClient, + createKernelAccount } from "@zerodev/sdk" import dotenv from "dotenv" -import { ethers } from "ethers" -import { - type BundlerClient, - ENTRYPOINT_ADDRESS_V07, - bundlerActions -} from "permissionless" -import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" -import type { PimlicoBundlerClient } from "permissionless/clients/pimlico.js" -import type { - ENTRYPOINT_ADDRESS_V07_TYPE, - EntryPoint -} from "permissionless/types/entrypoint.js" import { - type Address, type Chain, - type GetContractReturnType, - type Hex, type PrivateKeyAccount, type PublicClient, type Transport, - decodeEventLog, - decodeFunctionData, - encodeFunctionData, - erc20Abi, - getContract, - hashMessage, - hashTypedData, - zeroAddress + encodeFunctionData } from "viem" +import type { SmartAccount } from "viem/account-abstraction" import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" -import { toSpendingLimitHook } from "../../../plugins/hooks/toSpendingLimitHook.js" import { toSudoPolicy } from "../../../plugins/permission/policies/toSudoPolicy.js" import { toECDSASigner } from "../../../plugins/permission/signers/toECDSASigner.js" import { toPermissionValidator } from "../../../plugins/permission/toPermissionValidator.js" -import { EntryPointAbi } from "../abis/EntryPoint.js" -import { GreeterAbi, GreeterBytecode } from "../abis/Greeter.js" import { TEST_ERC20Abi } from "../abis/Test_ERC20Abi.js" -import { TokenActionsAbi } from "../abis/TokenActionsAbi.js" -import { TOKEN_ACTION_ADDRESS, config } from "../config.js" -import { Test_ERC20Address } from "../utils.js" import { - findUserOperationEvent, - getEcdsaKernelAccountWithRandomSigner, + Test_ERC20Address, getEntryPoint, getKernelAccountClient, - getPimlicoBundlerClient, getPublicClient, - getSignerToEcdsaKernelAccount, getZeroDevPaymasterClient, - index, kernelVersion, mintToAccount, - validateEnvironmentVariables, - waitForNonceUpdate -} from "./utils.js" + validateEnvironmentVariables +} from "./utils" dotenv.config() @@ -97,23 +57,22 @@ const TX_HASH_REGEX = /^0x[0-9a-fA-F]{64}$/ const TEST_TIMEOUT = 1000000 describe("Spending Limit Hook", () => { - let accountWithSudo: KernelSmartAccount - let accountWithRegular: KernelSmartAccount + let accountWithSudo: SmartAccount + let accountWithRegular: SmartAccount let ownerAccount1: PrivateKeyAccount let ownerAccount2: PrivateKeyAccount let publicClient: PublicClient let kernelClientWithSudo: KernelAccountClient< - ENTRYPOINT_ADDRESS_V07_TYPE, Transport, Chain, - KernelSmartAccount + SmartAccount > let kernelClientWithRegular: KernelAccountClient< - ENTRYPOINT_ADDRESS_V07_TYPE, Transport, Chain, - KernelSmartAccount + SmartAccount > + let zeroDevPaymaster: ZeroDevPaymasterClient beforeAll(async () => { // const ownerPrivateKey = process.env.TEST_PRIVATE_KEY @@ -182,30 +141,16 @@ describe("Spending Limit Hook", () => { console.log("accountWithSudo", accountWithSudo.address) console.log("accountWithRegular", accountWithRegular.address) + zeroDevPaymaster = getZeroDevPaymasterClient() + kernelClientWithSudo = await getKernelAccountClient({ account: accountWithSudo, - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) kernelClientWithRegular = await getKernelAccountClient({ account: accountWithRegular, - middleware: { - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) const amountToMint = 10000n diff --git a/packages/test/v0.7/utils/common.ts b/packages/test/v0.7/utils/common.ts new file mode 100644 index 00000000..35fc0bca --- /dev/null +++ b/packages/test/v0.7/utils/common.ts @@ -0,0 +1,200 @@ +import { + type KernelAccountClient, + type KernelSmartAccountImplementation, + createZeroDevPaymasterClient +} from "@zerodev/sdk" +import { + http, + type Address, + type Chain, + type Log, + type PublicClient, + type Transport, + createPublicClient, + decodeEventLog, + encodeFunctionData +} from "viem" +import { + type SmartAccount, + entryPoint07Address +} from "viem/account-abstraction" +import * as allChains from "viem/chains" +import { EntryPointAbi } from "../../abis/EntryPoint" +import { TEST_ERC20Abi } from "../../abis/Test_ERC20Abi" +import { config } from "../../config" + +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 projectId = config["0.7"][testingChain].projectId + +export const getEntryPoint = (): { + address: Address + version: "0.7" +} => { + return { address: entryPoint07Address, version: "0.7" } +} + +export const getTestingChain = (chainId?: number): Chain => { + const _chainId = chainId ?? testingChain + const chain = Object.values(allChains).find((c) => c.id === _chainId) + if (!chain) { + throw new Error(`Chain ${testingChain} not found`) + } + return chain +} + +export const getPublicClient = async (chain?: number) => { + const rpcUrl = config["0.7"][chain ?? testingChain].rpcUrl + if (!rpcUrl) { + throw new Error("RPC_URL environment variable not set") + } + + const publicClient = createPublicClient({ + transport: http(rpcUrl), + chain: getTestingChain(chain) + }) + + const chainId = await publicClient.getChainId() + + return publicClient +} + +export const getZeroDevPaymasterClient = () => { + if (!process.env.ZERODEV_PAYMASTER_RPC_HOST) + throw new Error( + "ZERODEV_PAYMASTER_RPC_HOST environment variable not set" + ) + if (!projectId) + throw new Error("ZERODEV_PROJECT_ID environment variable not set") + + const chain = getTestingChain() + + return createZeroDevPaymasterClient({ + chain: chain, + transport: http(getPaymasterRpc()) + }) +} + +export const getBundlerRpc = (_projectId?: string): string => { + const zeroDevProjectId = _projectId ?? projectId + const zeroDevBundlerRpcHost = config["0.7"][testingChain].bundlerUrl + if (!zeroDevProjectId || !zeroDevBundlerRpcHost) { + throw new Error( + "ZERODEV_PROJECT_ID and ZERODEV_BUNDLER_RPC_HOST environment variables must be set" + ) + } + + return `${zeroDevBundlerRpcHost}/${zeroDevProjectId}?provider=${DEFAULT_PROVIDER}` +} + +export const getPaymasterRpc = (_projectId?: string): string => { + const zeroDevProjectId = _projectId ?? projectId + const zeroDevPaymasterRpcHost = process.env.ZERODEV_PAYMASTER_RPC_HOST + if (!zeroDevProjectId || !zeroDevPaymasterRpcHost) { + throw new Error( + "ZERODEV_PROJECT_ID and ZERODEV_PAYMASTER_RPC_HOST environment variables must be set" + ) + } + + return `${zeroDevPaymasterRpcHost}/${zeroDevProjectId}?provider=${DEFAULT_PROVIDER}` +} + +export const findUserOperationEvent = (logs: Log[]): boolean => { + return logs.some((log) => { + try { + const event = decodeEventLog({ + abi: EntryPointAbi, + ...log + }) + return event.eventName === "UserOperationEvent" + } catch { + return false + } + }) +} + +export const getUserOperationEvent = (logs: Log[]) => { + for (const log of logs) { + try { + const event = decodeEventLog({ + abi: EntryPointAbi, + ...log + }) + console.log("event", event) + if (event.eventName === "UserOperationEvent") return event + } catch {} + } +} + +export async function mintToAccount( + publicClient: PublicClient, + ecdsaSmartAccountClient: KernelAccountClient< + Transport, + Chain, + SmartAccount + >, + target: Address, + amount: bigint +) { + const balanceBefore = await publicClient.readContract({ + abi: TEST_ERC20Abi, + address: Test_ERC20Address, + functionName: "balanceOf", + args: [target] + }) + + console.log("balanceBefore of account", balanceBefore) + + const amountToMint = balanceBefore > amount ? 0n : amount + + const mintData = encodeFunctionData({ + abi: TEST_ERC20Abi, + functionName: "mint", + args: [target, amountToMint] + }) + + if (amountToMint > 0n) { + const mintTransactionHash = + await ecdsaSmartAccountClient.sendTransaction({ + to: Test_ERC20Address, + data: mintData + }) + console.log({ mintTransactionHash }) + + const balanceAfter = await publicClient.readContract({ + abi: TEST_ERC20Abi, + address: Test_ERC20Address, + functionName: "balanceOf", + args: [target] + }) + + console.log("balanceAfter of account", balanceAfter) + + console.log( + "mintTransactionHash", + `https://sepolia.etherscan.io/tx/${mintTransactionHash}` + ) + } +} + +export const validateEnvironmentVariables = (envVars: string[]): void => { + const unsetEnvVars = envVars.filter((envVar) => !process.env[envVar]) + if (unsetEnvVars.length > 0) { + throw new Error( + `The following environment variables are not set: ${unsetEnvVars.join( + ", " + )}` + ) + } +} + +export const sleep = async (milliseconds: number): Promise => { + return new Promise((resolve) => setTimeout(resolve, milliseconds)) +} + +export const waitForNonceUpdate = async (): Promise => { + return sleep(10000) +} diff --git a/packages/test/v0.7/utils/ecdsaUtils.ts b/packages/test/v0.7/utils/ecdsaUtils.ts new file mode 100644 index 00000000..87ba18be --- /dev/null +++ b/packages/test/v0.7/utils/ecdsaUtils.ts @@ -0,0 +1,115 @@ +import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator" +import { + type KernelAccountClient, + type KernelSmartAccountImplementation, + createKernelAccount, + createKernelAccountClient, + getUserOperationGasPrice +} from "@zerodev/sdk" +import { http, type Chain, type Hex, type Transport } from "viem" +import { + type PaymasterActions, + type SmartAccount, + createBundlerClient +} from "viem/account-abstraction" +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" +import { + getBundlerRpc, + getEntryPoint, + getPublicClient, + getTestingChain, + index, + kernelVersion +} from "./common" + +export const getEcdsaKernelAccountWithRandomSigner = async ( + initConfig?: Hex[], + chain?: number +) => { + return getEcdsaKernelAccountWithPrivateKey( + "0xdfbb0d855aafff58aa0ae92aa9d03e88562bad9befe209f5693db89b65cc4a9a" ?? + "0x3688628d97b817ee5e25dfce254ba4d87b5fd894449fce6c2acc60fdf98906de" ?? + generatePrivateKey(), + initConfig, + chain + ) +} + +const getEcdsaKernelAccountWithPrivateKey = async ( + privateKey: Hex, + initConfig?: Hex[], + chain?: number +): Promise>> => { + if (!privateKey) { + throw new Error("privateKey cannot be empty") + } + + const publicClient = await getPublicClient(chain) + const signer = privateKeyToAccount(privateKey) + const ecdsaValidatorPlugin = await signerToEcdsaValidator(publicClient, { + entryPoint: getEntryPoint(), + signer: { ...signer, source: "local" as "local" | "external" }, + kernelVersion + }) + + return createKernelAccount(publicClient, { + entryPoint: getEntryPoint(), + plugins: { + sudo: ecdsaValidatorPlugin + }, + index, + kernelVersion, + initConfig + }) +} + +export const getSignerToEcdsaKernelAccount = async () => { + const privateKey = process.env.TEST_PRIVATE_KEY as Hex + if (!privateKey) { + throw new Error("TEST_PRIVATE_KEY environment variable not set") + } + + return getEcdsaKernelAccountWithPrivateKey(privateKey) +} + +export const getKernelAccountClient = async ({ + account, + paymaster +}: { + paymaster?: { + /** Retrieves paymaster-related User Operation properties to be used for sending the User Operation. */ + getPaymasterData?: PaymasterActions["getPaymasterData"] | undefined + /** Retrieves paymaster-related User Operation properties to be used for gas estimation. */ + getPaymasterStubData?: + | PaymasterActions["getPaymasterStubData"] + | undefined + } +} & { + account?: SmartAccount +} = {}): Promise< + KernelAccountClient< + Transport, + Chain, + SmartAccount + > +> => { + const chain = getTestingChain() + const resolvedAccount = account ?? (await getSignerToEcdsaKernelAccount()) + + const bundlerClient = createBundlerClient({ + transport: http(getBundlerRpc()), + chain + }) + + return createKernelAccountClient({ + account: resolvedAccount, + chain, + bundlerTransport: http(getBundlerRpc(), { timeout: 100_000 }), + paymaster, + userOperation: { + estimateFeesPerGas: async () => { + return getUserOperationGasPrice(bundlerClient) + } + } + }) +} diff --git a/packages/test/v0.7/utils/index.ts b/packages/test/v0.7/utils/index.ts new file mode 100644 index 00000000..68ad0f28 --- /dev/null +++ b/packages/test/v0.7/utils/index.ts @@ -0,0 +1,7 @@ +export * from "./common" +export * from "./ecdsaUtils" +export * from "./permissionUtils" +export * from "./session" +export * from "./weightedEcdsa" +export * from "./recovery" +export * from "./remoteSigner" diff --git a/packages/test/v0.7/utils/permissionUtils.ts b/packages/test/v0.7/utils/permissionUtils.ts new file mode 100644 index 00000000..f5670a88 --- /dev/null +++ b/packages/test/v0.7/utils/permissionUtils.ts @@ -0,0 +1,236 @@ +import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator" +import { type Policy, toPermissionValidator } from "@zerodev/permissions" +import { toECDSASigner } from "@zerodev/permissions/signers" +import type { Action, KernelSmartAccountImplementation } from "@zerodev/sdk" +import { createKernelAccount } from "@zerodev/sdk" +import { type Hex, toFunctionSelector, zeroAddress } from "viem" +import type { SmartAccount } from "viem/account-abstraction" +import { + type PrivateKeyAccount, + generatePrivateKey, + privateKeyToAccount +} from "viem/accounts" +import { getEntryPoint, getPublicClient, index, kernelVersion } from "./common" + +export const getSignerToPermissionKernelAccount = async ( + policies: Policy[], + action?: Action +): Promise> => { + const privateKey1 = process.env.TEST_PRIVATE_KEY as Hex + if (!privateKey1) { + throw new Error( + "TEST_PRIVATE_KEY and TEST_PRIVATE_KEY2 environment variables must be set" + ) + } + const publicClient = await getPublicClient() + const signer1 = privateKeyToAccount(generatePrivateKey()) + const ecdsaModularSigner = await toECDSASigner({ signer: signer1 }) + + const permissionPlugin = await toPermissionValidator(publicClient, { + entryPoint: getEntryPoint(), + kernelVersion, + signer: ecdsaModularSigner, + policies + }) + + const signer = privateKeyToAccount(privateKey1) + const ecdsaValidatorPlugin = await signerToEcdsaValidator(publicClient, { + entryPoint: getEntryPoint(), + signer: { ...signer, source: "local" as "local" | "external" }, + kernelVersion + }) + + return await createKernelAccount(publicClient, { + entryPoint: getEntryPoint(), + plugins: { + sudo: ecdsaValidatorPlugin, + regular: permissionPlugin, + action: action + }, + index, + kernelVersion + }) +} + +export const getSessionKeySignerToPermissionKernelAccount = async ( + policies: Policy[], + sessionKeySigner: PrivateKeyAccount +): Promise> => { + const privateKey1 = process.env.TEST_PRIVATE_KEY as Hex + if (!privateKey1) { + throw new Error( + "TEST_PRIVATE_KEY and TEST_PRIVATE_KEY2 environment variables must be set" + ) + } + const publicClient = await getPublicClient() + const ecdsaModularSigner = await toECDSASigner({ signer: sessionKeySigner }) + + const permissionPlugin = await toPermissionValidator(publicClient, { + entryPoint: getEntryPoint(), + kernelVersion, + signer: ecdsaModularSigner, + policies + }) + + const rootSigner = privateKeyToAccount(privateKey1) + const ecdsaValidatorPlugin = await signerToEcdsaValidator(publicClient, { + entryPoint: getEntryPoint(), + signer: { ...rootSigner, source: "local" as "local" | "external" }, + kernelVersion + }) + + return await createKernelAccount(publicClient, { + entryPoint: getEntryPoint(), + plugins: { + sudo: ecdsaValidatorPlugin, + regular: permissionPlugin + }, + index, + kernelVersion + }) +} + +export const getSignerToRootPermissionKernelAccount = async ( + policies: Policy[] +): Promise> => { + const publicClient = await getPublicClient() + const signer1 = privateKeyToAccount(generatePrivateKey()) + const ecdsaModularSigner = await toECDSASigner({ signer: signer1 }) + + const permissionPlugin = await toPermissionValidator(publicClient, { + entryPoint: getEntryPoint(), + kernelVersion, + signer: ecdsaModularSigner, + policies + }) + + return await createKernelAccount(publicClient, { + entryPoint: getEntryPoint(), + plugins: { + sudo: permissionPlugin + }, + index, + kernelVersion + }) +} + +export const getSignerToRootPermissionWithSecondaryValidatorKernelAccount = + async ( + policies: Policy[] + ): Promise> => { + const publicClient = await getPublicClient() + const signer1 = privateKeyToAccount(generatePrivateKey()) + const ecdsaModularSigner = await toECDSASigner({ signer: signer1 }) + + const permissionPlugin = await toPermissionValidator(publicClient, { + entryPoint: getEntryPoint(), + kernelVersion, + signer: ecdsaModularSigner, + policies + }) + + const privateKey2 = generatePrivateKey() + const signer2 = privateKeyToAccount(privateKey2) + const ecdsaModularSigner2 = await toECDSASigner({ signer: signer2 }) + const permissionSessionKeyPlugin = await toPermissionValidator( + publicClient, + { + entryPoint: getEntryPoint(), + kernelVersion, + signer: ecdsaModularSigner2, + policies + } + ) + + const account = await createKernelAccount(publicClient, { + entryPoint: getEntryPoint(), + plugins: { + sudo: permissionPlugin, + regular: permissionSessionKeyPlugin + }, + index, + kernelVersion + }) + return account + } + +export const getSignerToPermissionKernelAccountAndPlugin = async ( + policies: Policy[] +) => { + const publicClient = await getPublicClient() + const signer1 = privateKeyToAccount(generatePrivateKey()) + + const ecdsaPlugin = await signerToEcdsaValidator(publicClient, { + entryPoint: getEntryPoint(), + signer: signer1, + kernelVersion + }) + + const privateKey2 = generatePrivateKey() + const signer2 = privateKeyToAccount(privateKey2) + const ecdsaModularSigner2 = await toECDSASigner({ signer: signer2 }) + const permissionSessionKeyPlugin = await toPermissionValidator( + publicClient, + { + entryPoint: getEntryPoint(), + signer: ecdsaModularSigner2, + policies, + kernelVersion + } + ) + + const accountWithSudoAndRegular = await createKernelAccount(publicClient, { + entryPoint: getEntryPoint(), + plugins: { + sudo: ecdsaPlugin, + regular: permissionSessionKeyPlugin + }, + index, + kernelVersion + }) + const accountWithSudo = await createKernelAccount(publicClient, { + entryPoint: getEntryPoint(), + plugins: { + sudo: ecdsaPlugin + }, + index, + kernelVersion + }) + const accountWithRegular = await createKernelAccount(publicClient, { + entryPoint: getEntryPoint(), + plugins: { + regular: permissionSessionKeyPlugin + }, + address: accountWithSudo.address, + kernelVersion + }) + + const privateKey3 = generatePrivateKey() + const signer3 = privateKeyToAccount(privateKey3) + const ecdsaModularSigner3 = await toECDSASigner({ signer: signer3 }) + const permissionSessionKeyPlugin2 = await toPermissionValidator( + publicClient, + { + entryPoint: getEntryPoint(), + signer: ecdsaModularSigner3, + policies, + kernelVersion + } + ) + const accountWithSudoAndRegular2 = await createKernelAccount(publicClient, { + entryPoint: getEntryPoint(), + plugins: { + sudo: ecdsaPlugin, + regular: permissionSessionKeyPlugin2 + }, + index, + kernelVersion + }) + return { + accountWithSudoAndRegular, + accountWithSudo, + accountWithRegular, + accountWithSudoAndRegular2, + plugin: permissionSessionKeyPlugin + } +} diff --git a/packages/test/v0.7/utils/recovery.ts b/packages/test/v0.7/utils/recovery.ts new file mode 100644 index 00000000..9dd0f4cd --- /dev/null +++ b/packages/test/v0.7/utils/recovery.ts @@ -0,0 +1,45 @@ +import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator" +import { createKernelAccount } from "@zerodev/sdk" +import { + createWeightedECDSAValidator, + getRecoveryAction +} from "@zerodev/weighted-ecdsa-validator" +import type { Address } from "viem" +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" +import { getEntryPoint, getPublicClient, index, kernelVersion } from "./common" + +export const getRecoveryKernelAccount = async ( + deployedAccountAddress?: Address +) => { + const privateKey1 = generatePrivateKey() + const privateKey2 = generatePrivateKey() + const signer1 = privateKeyToAccount(privateKey1) + const signer2 = privateKeyToAccount(privateKey2) + const publicClient = await getPublicClient() + const ecdsaPlugin = await signerToEcdsaValidator(publicClient, { + entryPoint: getEntryPoint(), + signer: signer1, + kernelVersion + }) + const recoveryPlugin = await createWeightedECDSAValidator(publicClient, { + entryPoint: getEntryPoint(), + kernelVersion, + config: { + threshold: 100, + delay: 0, + signers: [{ address: signer2.address, weight: 100 }] + }, + signers: [signer2] + }) + return await createKernelAccount(await getPublicClient(), { + entryPoint: getEntryPoint(), + address: deployedAccountAddress, + plugins: { + sudo: ecdsaPlugin, + regular: recoveryPlugin, + action: getRecoveryAction(getEntryPoint().version) + }, + index, + kernelVersion + }) +} diff --git a/packages/test/v0.7/utils/remoteSigner.ts b/packages/test/v0.7/utils/remoteSigner.ts new file mode 100644 index 00000000..388823ba --- /dev/null +++ b/packages/test/v0.7/utils/remoteSigner.ts @@ -0,0 +1,53 @@ +import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator" +import { type Policy, toPermissionValidator } from "@zerodev/permissions" +import { toECDSASigner } from "@zerodev/permissions/signers" +import { + type KernelSmartAccountImplementation, + createKernelAccount +} from "@zerodev/sdk" +import type { LocalAccount } from "viem" +import type { SmartAccount } from "viem/account-abstraction" +import { getEntryPoint, getPublicClient, index, kernelVersion } from "./common" + +export const getEcdsaKernelAccountWithRemoteSigner = async ( + remoteSigner: LocalAccount +) => { + const publicClient = await getPublicClient() + const ecdsaValidatorPlugin = await signerToEcdsaValidator(publicClient, { + entryPoint: getEntryPoint(), + signer: remoteSigner, + kernelVersion + }) + + return createKernelAccount(publicClient, { + entryPoint: getEntryPoint(), + plugins: { + sudo: ecdsaValidatorPlugin + }, + index, + kernelVersion + }) +} + +export const getPermissionKernelAccountWithRemoteSigner = async ( + remoteSigner: LocalAccount, + policies: Policy[] +): Promise> => { + const publicClient = await getPublicClient() + const ecdsaSigner = await toECDSASigner({ signer: remoteSigner }) + const permissionPlugin = await toPermissionValidator(publicClient, { + entryPoint: getEntryPoint(), + kernelVersion, + signer: ecdsaSigner, + policies + }) + + return createKernelAccount(publicClient, { + entryPoint: getEntryPoint(), + plugins: { + sudo: permissionPlugin + }, + index, + kernelVersion + }) +} diff --git a/packages/test/v0.7/utils/session.ts b/packages/test/v0.7/utils/session.ts new file mode 100644 index 00000000..a997c3de --- /dev/null +++ b/packages/test/v0.7/utils/session.ts @@ -0,0 +1,20 @@ +import { type Delegation, createSessionAccount } from "@zerodev/session-account" +import type { Hex } from "viem" +import { privateKeyToAccount } from "viem/accounts" +import { getEntryPoint, getPublicClient } from "./common" + +export const getSessionAccount = async ( + delegations: Delegation[], + privateKey: Hex, + delegatorInitCode?: Hex +) => { + const sessionKeySigner = privateKeyToAccount(privateKey) + const publicClient = await getPublicClient() + + return createSessionAccount(publicClient, { + entryPoint: getEntryPoint(), + sessionKeySigner, + delegations, + delegatorInitCode + }) +} diff --git a/packages/test/v0.7/utils/weightedEcdsa.ts b/packages/test/v0.7/utils/weightedEcdsa.ts new file mode 100644 index 00000000..dcb4d840 --- /dev/null +++ b/packages/test/v0.7/utils/weightedEcdsa.ts @@ -0,0 +1,58 @@ +import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator" +import { + type KernelSmartAccountImplementation, + createKernelAccount +} from "@zerodev/sdk" +import { createWeightedECDSAValidator } from "@zerodev/weighted-ecdsa-validator" +import type { Hex } from "viem" +import type { SmartAccount } from "viem/account-abstraction" +import { privateKeyToAccount } from "viem/accounts" +import { getEntryPoint, getPublicClient, index, kernelVersion } from "./common" + +export const getSignersToWeightedEcdsaKernelAccount = async (): Promise< + SmartAccount +> => { + const privateKey1 = process.env.TEST_PRIVATE_KEY as Hex + const privateKey2 = process.env.TEST_PRIVATE_KEY2 as Hex + if (!privateKey1 || !privateKey2) { + throw new Error( + "TEST_PRIVATE_KEY and TEST_PRIVATE_KEY2 environment variables must be set" + ) + } + const publicClient = await getPublicClient() + const signer1 = privateKeyToAccount(privateKey1) + const signer2 = privateKeyToAccount(privateKey2) + const weightedECDSAPlugin = await createWeightedECDSAValidator( + publicClient, + { + entryPoint: getEntryPoint(), + kernelVersion, + config: { + threshold: 100, + delay: 0, + signers: [ + { address: signer1.address, weight: 50 }, + { address: signer2.address, weight: 50 } + ] + }, + signers: [signer1, signer2] + } + ) + + const signer = privateKeyToAccount(privateKey1) + const ecdsaValidatorPlugin = await signerToEcdsaValidator(publicClient, { + entryPoint: getEntryPoint(), + signer: { ...signer, source: "local" as "local" | "external" }, + kernelVersion + }) + + return await createKernelAccount(publicClient, { + entryPoint: getEntryPoint(), + plugins: { + sudo: ecdsaValidatorPlugin, + regular: weightedECDSAPlugin + }, + index, + kernelVersion + }) +} diff --git a/packages/test/v0.7/weightedECDSAValidator.test.ts b/packages/test/v0.7/weightedECDSAValidator.test.ts index 6abf356b..46f45b89 100644 --- a/packages/test/v0.7/weightedECDSAValidator.test.ts +++ b/packages/test/v0.7/weightedECDSAValidator.test.ts @@ -1,20 +1,20 @@ // @ts-expect-error import { beforeAll, describe, expect, test } from "bun:test" -import type { KernelAccountClient, KernelSmartAccount } from "@zerodev/sdk" -import type { BundlerClient } from "permissionless" -import type { PimlicoBundlerClient } from "permissionless/clients/pimlico" -import type { ENTRYPOINT_ADDRESS_V07_TYPE } from "permissionless/types/entrypoint" +import type { + KernelAccountClient, + KernelSmartAccountImplementation, + ZeroDevPaymasterClient +} from "@zerodev/sdk" import { type Chain, type PublicClient, type Transport, zeroAddress } from "viem" +import type { SmartAccount } from "viem/account-abstraction" import { getEntryPoint, getKernelAccountClient, - getKernelBundlerClient, - getPimlicoBundlerClient, getPublicClient, getSignersToWeightedEcdsaKernelAccount, getZeroDevPaymasterClient @@ -29,36 +29,22 @@ const TX_HASH_REGEX = /^0x[0-9a-fA-F]{64}$/ const TEST_TIMEOUT = 1000000 describe("Weighted ECDSA kernel Account", () => { - let account: KernelSmartAccount + let account: SmartAccount let publicClient: PublicClient - let bundlerClient: BundlerClient let kernelClient: KernelAccountClient< - ENTRYPOINT_ADDRESS_V07_TYPE, Transport, Chain, - KernelSmartAccount + SmartAccount > - let pimlicoBundlerClient: PimlicoBundlerClient + let zeroDevPaymaster: ZeroDevPaymasterClient beforeAll(async () => { account = await getSignersToWeightedEcdsaKernelAccount() publicClient = await getPublicClient() - bundlerClient = getKernelBundlerClient() - pimlicoBundlerClient = getPimlicoBundlerClient() + zeroDevPaymaster = getZeroDevPaymasterClient() kernelClient = await getKernelAccountClient({ account, - middleware: { - gasPrice: async () => - (await pimlicoBundlerClient.getUserOperationGasPrice()) - .fast, - sponsorUserOperation: async ({ userOperation }) => { - const zeroDevPaymaster = getZeroDevPaymasterClient() - return zeroDevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - } + paymaster: zeroDevPaymaster }) }) diff --git a/packages/test/v0.7/weightedValidator.test.ts b/packages/test/v0.7/weightedValidator.test.ts index 911fb4e1..a8a4e699 100644 --- a/packages/test/v0.7/weightedValidator.test.ts +++ b/packages/test/v0.7/weightedValidator.test.ts @@ -83,7 +83,7 @@ describe("weightedValidator", () => { }) const pKey = "0xd565cc0ff5dc317e52fb4e9be3c2d5cfd86734a98ffbb97f103e3bac009b30d9" - const someSigner = toStandaloneECDSASigner({ + const someSigner = await toStandaloneECDSASigner({ signer: privateKeyToAccount(pKey) }) const kernelVersion = KERNEL_V3_1 @@ -109,20 +109,15 @@ describe("weightedValidator", () => { console.log(`Account address: ${account.address}`) const paymasterClient = createZeroDevPaymasterClient({ - entryPoint, chain, transport: http(paymasterUrl) }) const client = createWeightedKernelAccountClient({ account, - entryPoint, chain, bundlerTransport: http(bundlerUrl), - middleware: { - sponsorUserOperation: - paymasterClient.sponsorUserOperation - } + paymaster: paymasterClient }) return client } @@ -131,33 +126,33 @@ describe("weightedValidator", () => { const client2 = await createWeightedAccountClient(ecdsaSigner2) const signature1 = await client1.approveUserOperation({ - userOperation: { - callData: await client1.account.encodeCallData({ + callData: await client1.account.encodeCalls([ + { to: zeroAddress, data: "0x", value: BigInt(0) - }) - } + } + ]) }) const signature2 = await client2.approveUserOperation({ - userOperation: { - callData: await client2.account.encodeCallData({ + calls: [ + { to: zeroAddress, data: "0x", value: BigInt(0) - }) - } + } + ] }) const userOpHash = await client2.sendUserOperationWithSignatures({ - userOperation: { - callData: await client1.account.encodeCallData({ + callData: await client1.account.encodeCalls([ + { to: zeroAddress, data: "0x", value: BigInt(0) - }) - }, + } + ]), signatures: [signature1, signature2] }) diff --git a/packages/test/weightedEcdsaKernelAccount.test.ts b/packages/test/weightedEcdsaKernelAccount.test.ts index dc9739be..7c73bf00 100644 --- a/packages/test/weightedEcdsaKernelAccount.test.ts +++ b/packages/test/weightedEcdsaKernelAccount.test.ts @@ -1,23 +1,13 @@ // @ts-expect-error import { beforeAll, describe, expect, test } from "bun:test" import { - KERNEL_ADDRESSES, type KernelAccountClient, - type KernelSmartAccount, + type KernelSmartAccountImplementation, + type ZeroDevPaymasterClient, getCustomNonceKeyFromString } from "@zerodev/sdk" import { signerToSessionKeyValidator } from "@zerodev/session-key" import dotenv from "dotenv" -import { - type BundlerClient, - ENTRYPOINT_ADDRESS_V06, - bundlerActions -} from "permissionless" -import { - SignTransactionNotSupportedBySmartAccount, - SmartAccount -} from "permissionless/accounts" -import type { ENTRYPOINT_ADDRESS_V06_TYPE } from "permissionless/types/entrypoint.js" import { type Address, type Chain, @@ -27,18 +17,19 @@ import { getContract, zeroAddress } from "viem" +import type { SmartAccount } from "viem/account-abstraction" import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" import { GreeterAbi, GreeterBytecode } from "./abis/Greeter.js" import { findUserOperationEvent, getEntryPoint, getKernelAccountClient, - getKernelBundlerClient, getPublicClient, getSignersToWeightedEcdsaKernelAccount, getZeroDevPaymasterClient, + kernelVersion, waitForNonceUpdate -} from "./utils.js" +} from "./utils_0_6" dotenv.config() @@ -75,31 +66,22 @@ const TX_HASH_REGEX = /^0x[0-9a-fA-F]{64}$/ const TEST_TIMEOUT = 1000000 describe("Weighted ECDSA kernel Account", () => { - let account: KernelSmartAccount + let account: SmartAccount> let publicClient: PublicClient - let bundlerClient: BundlerClient let kernelClient: KernelAccountClient< - ENTRYPOINT_ADDRESS_V06_TYPE, Transport, Chain, - KernelSmartAccount + SmartAccount> > + let zeroDevPaymaster: ZeroDevPaymasterClient beforeAll(async () => { account = await getSignersToWeightedEcdsaKernelAccount() publicClient = await getPublicClient() - bundlerClient = getKernelBundlerClient() + zeroDevPaymaster = getZeroDevPaymasterClient() kernelClient = await getKernelAccountClient({ account, - middleware: { - sponsorUserOperation: async ({ userOperation, entryPoint }) => { - const zerodevPaymaster = getZeroDevPaymasterClient() - return zerodevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint - }) - } - } + paymaster: zeroDevPaymaster }) }) @@ -109,16 +91,6 @@ describe("Weighted ECDSA kernel Account", () => { expect(account.address).toMatch(ETHEREUM_ADDRESS_REGEX) }) - test("Account should throw when trying to sign a transaction", async () => { - await expect(async () => { - await account.signTransaction({ - to: zeroAddress, - value: 0n, - data: "0x" - }) - }).toThrow(new SignTransactionNotSupportedBySmartAccount()) - }) - // test( // "Client signMessage should return a valid signature", // async () => { @@ -150,9 +122,11 @@ describe("Weighted ECDSA kernel Account", () => { test( "Client deploy contract", async () => { - const response = await kernelClient.deployContract({ - abi: GreeterAbi, - bytecode: GreeterBytecode + const response = await kernelClient.sendTransaction({ + callData: await kernelClient.account.encodeDeployCallData({ + abi: GreeterAbi, + bytecode: GreeterBytecode + }) }) expect(response).toBeString() @@ -173,8 +147,8 @@ describe("Weighted ECDSA kernel Account", () => { test( "Smart account client send multiple transactions", async () => { - const response = await kernelClient.sendTransactions({ - transactions: [ + const response = await kernelClient.sendTransaction({ + calls: [ { to: zeroAddress, value: 0n, @@ -226,8 +200,8 @@ describe("Weighted ECDSA kernel Account", () => { "Client signs and then sends UserOp with paymaster", async () => { const userOp = await kernelClient.signUserOperation({ - userOperation: { - callData: await kernelClient.account.encodeCallData({ + callData: await kernelClient.account.encodeCalls([ + { to: process.env.GREETER_ADDRESS as Address, value: 0n, data: encodeFunctionData({ @@ -235,19 +209,16 @@ describe("Weighted ECDSA kernel Account", () => { functionName: "setGreeting", args: ["hello world"] }) - }) - } + } + ]) }) expect(userOp.signature).not.toBe("0x") - const bundlerClient = kernelClient.extend( - bundlerActions(getEntryPoint()) - ) - const userOpHash = await bundlerClient.sendUserOperation({ - userOperation: userOp + const userOpHash = await kernelClient.sendUserOperation({ + ...userOp }) expect(userOpHash).toHaveLength(66) - await bundlerClient.waitForUserOperationReceipt({ + await kernelClient.waitForUserOperationReceipt({ hash: userOpHash }) @@ -260,18 +231,20 @@ describe("Weighted ECDSA kernel Account", () => { "Client send UserOp with delegatecall", async () => { const userOpHash = await kernelClient.sendUserOperation({ - userOperation: { - callData: await kernelClient.account.encodeCallData({ - to: zeroAddress, - value: 0n, - data: "0x", - callType: "delegatecall" - }) - } + callData: await kernelClient.account.encodeCalls( + [ + { + to: zeroAddress, + value: 0n, + data: "0x" + } + ], + "delegatecall" + ) }) expect(userOpHash).toHaveLength(66) - await bundlerClient.waitForUserOperationReceipt({ + await kernelClient.waitForUserOperationReceipt({ hash: userOpHash }) @@ -285,25 +258,25 @@ describe("Weighted ECDSA kernel Account", () => { async () => { const customNonceKey = getCustomNonceKeyFromString( "Hello, World!", - ENTRYPOINT_ADDRESS_V06 + "0.6" ) - const nonce = await account.getNonce(customNonceKey) + const nonce = await account.getNonce({ key: customNonceKey }) const userOpHash = await kernelClient.sendUserOperation({ - userOperation: { - callData: await kernelClient.account.encodeCallData({ + callData: await kernelClient.account.encodeCalls([ + { to: zeroAddress, value: 0n, data: "0x" - }), - nonce - } + } + ]), + nonce }) console.log("userOpHash", userOpHash) expect(userOpHash).toHaveLength(66) - await bundlerClient.waitForUserOperationReceipt({ + await kernelClient.waitForUserOperationReceipt({ hash: userOpHash }) }, @@ -345,7 +318,8 @@ describe("Weighted ECDSA kernel Account", () => { signer: sessionKeySigner, validatorData: { permissions: [] - } + }, + kernelVersion } ) @@ -353,33 +327,22 @@ describe("Weighted ECDSA kernel Account", () => { await getSignersToWeightedEcdsaKernelAccount(sessionKeyPlugin) const sessionKeyClient = await getKernelAccountClient({ account: sessionKeyAccount, - middleware: { - sponsorUserOperation: async ({ - userOperation, - entryPoint - }) => { - const zerodevPaymaster = getZeroDevPaymasterClient() - return zerodevPaymaster.sponsorUserOperation({ - userOperation, - entryPoint - }) - } - } + paymaster: zeroDevPaymaster }) const userOpHash = await sessionKeyClient.sendUserOperation({ - userOperation: { - callData: await sessionKeyAccount.encodeCallData({ + callData: await sessionKeyAccount.encodeCalls([ + { to: zeroAddress, value: 0n, data: "0x" - }) - } + } + ]) }) expect(userOpHash).toHaveLength(66) - await bundlerClient.waitForUserOperationReceipt({ + await kernelClient.waitForUserOperationReceipt({ hash: userOpHash }) }, diff --git a/plugins/ecdsa/CHANGELOG.md b/plugins/ecdsa/CHANGELOG.md index 8806dc6f..045c2900 100644 --- a/plugins/ecdsa/CHANGELOG.md +++ b/plugins/ecdsa/CHANGELOG.md @@ -1,5 +1,17 @@ # @zerodev/ecdsa-validator +## 5.4.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + +## 5.4.0-beta.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + ## 5.3.3 ### Patch Changes diff --git a/plugins/ecdsa/getAddress.ts b/plugins/ecdsa/getAddress.ts index e3e069dd..1dbbe8ec 100644 --- a/plugins/ecdsa/getAddress.ts +++ b/plugins/ecdsa/getAddress.ts @@ -5,13 +5,8 @@ import { KernelV3_1AccountAbi, validateKernelVersionWithEntryPoint } from "@zerodev/sdk" -import type { GetKernelVersion } from "@zerodev/sdk/types" +import type { EntryPointType, GetKernelVersion } from "@zerodev/sdk/types" import type { KERNEL_VERSION_TYPE } from "@zerodev/sdk/types" -import { getEntryPointVersion } from "permissionless" -import type { - ENTRYPOINT_ADDRESS_V06_TYPE, - EntryPoint -} from "permissionless/types/entrypoint" import { type Address, type Hex, @@ -26,17 +21,17 @@ import { toHex, zeroAddress } from "viem" +import type { EntryPointVersion } from "viem/account-abstraction" import { getValidatorAddress } from "./toECDSAValidatorPlugin.js" -const getInitCodeHash = async ( +const getInitCodeHash = async ( publicClient: PublicClient, - entryPointAddress: entryPoint, - kernelVersion: GetKernelVersion + entryPoint: EntryPointType, + kernelVersion: GetKernelVersion ): Promise => { - const entryPointVersion = getEntryPointVersion(entryPointAddress) - validateKernelVersionWithEntryPoint(entryPointAddress, kernelVersion) + validateKernelVersionWithEntryPoint(entryPoint.version, kernelVersion) const addresses = constants.KernelVersionToAddressesMap[kernelVersion] - if (entryPointVersion === "v0.6") { + if (entryPoint.version === "0.6") { return await initCodeHashV0_6(publicClient, addresses.factoryAddress) } return initCodeHashV0_7(addresses.accountImplementationAddress) @@ -146,33 +141,32 @@ const generateSaltForV07 = ( return keccak256(packedData) } -export type GetKernelAddressFromECDSAParams = { - entryPointAddress: entryPoint - kernelVersion: GetKernelVersion +export type GetKernelAddressFromECDSAParams< + entryPointVersion extends EntryPointVersion +> = { + entryPoint: EntryPointType + kernelVersion: GetKernelVersion eoaAddress: Address index: bigint - hookAddress?: entryPoint extends ENTRYPOINT_ADDRESS_V06_TYPE - ? never - : Address - hookData?: entryPoint extends ENTRYPOINT_ADDRESS_V06_TYPE ? never : Hex - initConfig?: entryPoint extends ENTRYPOINT_ADDRESS_V06_TYPE ? never : Hex[] + hookAddress?: entryPointVersion extends "0.6" ? never : Address + hookData?: entryPointVersion extends "0.6" ? never : Hex + initConfig?: entryPointVersion extends "0.6" ? never : Hex[] } & ( | { publicClient: PublicClient; initCodeHash?: never } | { publicClient?: never; initCodeHash: Hex } ) -export async function getKernelAddressFromECDSA( - params: GetKernelAddressFromECDSAParams -) { - const entryPointVersion = getEntryPointVersion(params.entryPointAddress) +export async function getKernelAddressFromECDSA< + entryPointVersion extends EntryPointVersion +>(params: GetKernelAddressFromECDSAParams) { validateKernelVersionWithEntryPoint( - params.entryPointAddress, + params.entryPoint.version, params.kernelVersion ) const kernelAddresses = constants.KernelVersionToAddressesMap[params.kernelVersion] const ecdsaValidatorAddress = getValidatorAddress( - params.entryPointAddress, + params.entryPoint, params.kernelVersion ) const bytecodeHash = await (async () => { @@ -182,14 +176,14 @@ export async function getKernelAddressFromECDSA( if ("publicClient" in params && params.publicClient) { return await getInitCodeHash( params.publicClient, - params.entryPointAddress, + params.entryPoint, params.kernelVersion ) } throw new Error("Either initCodeHash or publicClient must be provided") })() let salt: Hex - if (entryPointVersion === "v0.6") { + if (params.entryPoint.version === "0.6") { salt = generateSaltForV06( params.eoaAddress, params.index, diff --git a/plugins/ecdsa/package.json b/plugins/ecdsa/package.json index b1281af3..3ed35541 100644 --- a/plugins/ecdsa/package.json +++ b/plugins/ecdsa/package.json @@ -1,6 +1,6 @@ { "name": "@zerodev/ecdsa-validator", - "version": "5.3.3", + "version": "5.4.0", "author": "ZeroDev", "main": "./_cjs/index.js", "module": "./_esm/index.js", @@ -35,8 +35,7 @@ "lint:fix": "bun run lint --apply" }, "peerDependencies": { - "viem": ">=2.16.3 <2.18.0", - "@zerodev/sdk": "^5.2.1", - "permissionless": ">=0.1.44 <=0.1.45" + "viem": "^2.21.40", + "@zerodev/sdk": "^5.4.0" } } diff --git a/plugins/ecdsa/toECDSAValidatorPlugin.ts b/plugins/ecdsa/toECDSAValidatorPlugin.ts index eb0c73fd..6095f867 100644 --- a/plugins/ecdsa/toECDSAValidatorPlugin.ts +++ b/plugins/ecdsa/toECDSAValidatorPlugin.ts @@ -1,37 +1,36 @@ -import { validateKernelVersionWithEntryPoint } from "@zerodev/sdk" +import { toSigner, validateKernelVersionWithEntryPoint } from "@zerodev/sdk" import { satisfiesRange } from "@zerodev/sdk" -import type { GetKernelVersion, KernelValidator } from "@zerodev/sdk/types" -import type { TypedData } from "abitype" -import { type UserOperation, getUserOperationHash } from "permissionless" -import { - SignTransactionNotSupportedBySmartAccount, - type SmartAccountSigner -} from "permissionless/accounts" import type { - EntryPoint, - GetEntryPointVersion -} from "permissionless/types/entrypoint" + EntryPointType, + GetKernelVersion, + KernelValidator, + Signer +} from "@zerodev/sdk/types" +import type { TypedData } from "abitype" import { type Address, - type Chain, type Client, type Hex, - type LocalAccount, - type Transport, type TypedDataDefinition, zeroAddress } from "viem" +import { + type EntryPointVersion, + type UserOperation, + getUserOperationHash +} from "viem/account-abstraction" import { toAccount } from "viem/accounts" -import { signMessage, signTypedData } from "viem/actions" -import { getChainId } from "viem/actions" +import { getChainId, signMessage } from "viem/actions" import { kernelVersionRangeToValidator } from "./constants.js" -export const getValidatorAddress = ( - entryPointAddress: entryPoint, - kernelVersion: GetKernelVersion, +export const getValidatorAddress = < + entryPointVersion extends EntryPointVersion +>( + entryPoint: EntryPointType, + kernelVersion: GetKernelVersion, validatorAddress?: Address ): Address => { - validateKernelVersionWithEntryPoint(entryPointAddress, kernelVersion) + validateKernelVersionWithEntryPoint(entryPoint.version, kernelVersion) const ecdsaValidatorAddress = Object.entries( kernelVersionRangeToValidator ).find(([range]) => satisfiesRange(kernelVersion, range))?.[1] @@ -46,37 +45,27 @@ export const getValidatorAddress = ( } export async function signerToEcdsaValidator< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TSource extends string = "custom", - TAddress extends Address = Address + entryPointVersion extends EntryPointVersion >( - client: Client, + client: Client, { signer, - entryPoint: entryPointAddress, + entryPoint, kernelVersion, validatorAddress: _validatorAddress }: { - signer: SmartAccountSigner - entryPoint: entryPoint - kernelVersion: GetKernelVersion + signer: Signer + entryPoint: EntryPointType + kernelVersion: GetKernelVersion validatorAddress?: Address } -): Promise> { +): Promise> { const validatorAddress = getValidatorAddress( - entryPointAddress, + entryPoint, kernelVersion, _validatorAddress ) - // Get the private key related account - const viemSigner: LocalAccount = { - ...signer, - signTransaction: (_, __) => { - throw new SignTransactionNotSupportedBySmartAccount() - } - } as LocalAccount + const viemSigner = await toSigner({ signer }) // Fetch chain id const chainId = await getChainId(client) @@ -88,7 +77,9 @@ export async function signerToEcdsaValidator< return signMessage(client, { account: viemSigner, message }) }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new Error( + "Smart account signer doesn't need to sign transactions" + ) }, async signTypedData< const TTypedData extends TypedData | Record, @@ -96,13 +87,7 @@ export async function signerToEcdsaValidator< | keyof TTypedData | "EIP712Domain" = keyof TTypedData >(typedData: TypedDataDefinition) { - return signTypedData( - client, - { - account: viemSigner, - ...typedData - } - ) + return viemSigner.signTypedData(typedData) } }) @@ -126,15 +111,14 @@ export async function signerToEcdsaValidator< return 0n }, // Sign a user operation - async signUserOperation( - userOperation: UserOperation> - ) { - const hash = getUserOperationHash({ + async signUserOperation(userOperation) { + const hash = getUserOperationHash({ userOperation: { ...userOperation, signature: "0x" - }, - entryPoint: entryPointAddress, + } as UserOperation, + entryPointAddress: entryPoint.address, + entryPointVersion: entryPoint.version, chainId: chainId }) const signature = await signMessage(client, { @@ -145,7 +129,7 @@ export async function signerToEcdsaValidator< }, // Get simple dummy signature - async getDummySignature() { + async getStubSignature() { return "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c" }, diff --git a/plugins/hooks/CHANGELOG.md b/plugins/hooks/CHANGELOG.md index 3d3115ef..a021865f 100644 --- a/plugins/hooks/CHANGELOG.md +++ b/plugins/hooks/CHANGELOG.md @@ -1,5 +1,17 @@ # @zerodev/hooks +## 5.3.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + +## 5.3.0-beta.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + ## 5.2.5 ### Patch Changes diff --git a/plugins/hooks/package.json b/plugins/hooks/package.json index e6274888..11efcc4f 100644 --- a/plugins/hooks/package.json +++ b/plugins/hooks/package.json @@ -1,6 +1,6 @@ { "name": "@zerodev/hooks", - "version": "5.2.5", + "version": "5.3.0", "author": "ZeroDev", "main": "./_cjs/index.js", "module": "./_esm/index.js", @@ -35,8 +35,7 @@ "lint:fix": "bun run lint --apply" }, "peerDependencies": { - "viem": ">=2.16.3 <2.18.0", - "@zerodev/sdk": "^5.2.17", - "permissionless": ">=0.1.44 <=0.1.45" + "viem": "^2.21.40", + "@zerodev/sdk": "^5.4.0" } } diff --git a/plugins/modularPermission/CHANGELOG.md b/plugins/modularPermission/CHANGELOG.md index 8071a32a..0b69962d 100644 --- a/plugins/modularPermission/CHANGELOG.md +++ b/plugins/modularPermission/CHANGELOG.md @@ -1,5 +1,17 @@ # @zerodev/modular-permission +## 5.4.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + +## 5.4.0-beta.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + ## 5.3.4 ### Patch Changes diff --git a/plugins/modularPermission/deserializeModularPermissionAccount.ts b/plugins/modularPermission/deserializeModularPermissionAccount.ts index 295f9897..c971c6ee 100644 --- a/plugins/modularPermission/deserializeModularPermissionAccount.ts +++ b/plugins/modularPermission/deserializeModularPermissionAccount.ts @@ -1,10 +1,14 @@ import { KernelAccountAbi, createKernelAccount } from "@zerodev/sdk" import { KernelFactoryAbi } from "@zerodev/sdk" import { toKernelPluginManager } from "@zerodev/sdk/accounts" -import type { GetKernelVersion, ValidatorInitData } from "@zerodev/sdk/types" -import type { EntryPoint } from "permissionless/types" -import type { Hex } from "viem" +import type { + EntryPointType, + GetKernelVersion, + ValidatorInitData +} from "@zerodev/sdk/types" +import type { Client, Hex } from "viem" import { decodeFunctionData } from "viem" +import type { EntryPointVersion } from "viem/account-abstraction" import { privateKeyToAccount } from "viem/accounts" import { toGasPolicy, @@ -19,11 +23,11 @@ import { createPermissionValidator } from "./toModularPermissionValidatorPlugin. import { deserializeModularPermissionAccountParams } from "./utils.js" export const deserializeModularPermissionAccount = async < - entryPoint extends EntryPoint + entryPointVersion extends EntryPointVersion >( - client: Parameters[0], - entryPointAddress: entryPoint, - kernelVersion: GetKernelVersion, + client: Client, + entryPoint: EntryPointType, + kernelVersion: GetKernelVersion, modularPermissionAccountParams: string, modularSigner?: ModularSigner ) => { @@ -32,7 +36,7 @@ export const deserializeModularPermissionAccount = async < ) let signer: ModularSigner if (params.privateKey) - signer = toECDSASigner({ + signer = await toECDSASigner({ signer: privateKeyToAccount(params.privateKey) }) else if (modularSigner) signer = modularSigner @@ -47,7 +51,7 @@ export const deserializeModularPermissionAccount = async < ), validUntil: params.modularPermissionParams.validUntil || 0, validAfter: params.modularPermissionParams.validAfter || 0, - entryPoint: entryPointAddress, + entryPoint, kernelVersion }) @@ -60,23 +64,21 @@ export const deserializeModularPermissionAccount = async < pluginEnableSignature: params.enableSignature, validatorInitData, action: params.action, - entryPoint: entryPointAddress, + entryPoint, kernelVersion, ...params.validityData }) return createKernelAccount(client, { - entryPoint: entryPointAddress, + entryPoint, kernelVersion, plugins: kernelPluginManager, index, - deployedAccountAddress: params.accountParams.accountAddress + address: params.accountParams.accountAddress }) } -export const createPolicyFromParams = async ( - policy: Policy -) => { +export const createPolicyFromParams = async (policy: Policy) => { switch (policy.policyParams.type) { case "sudo": return await toSudoPolicy(policy.policyParams) @@ -96,7 +98,7 @@ export const decodeParamsFromInitCode = (initCode: Hex) => { let validatorInitData: ValidatorInitData | undefined const createAccountFunctionData = decodeFunctionData({ abi: KernelFactoryAbi, - data: `0x${initCode.slice(42)}` + data: initCode }) if (!createAccountFunctionData) throw new Error("Invalid initCode") if (createAccountFunctionData.functionName === "createAccount") { diff --git a/plugins/modularPermission/package.json b/plugins/modularPermission/package.json index 2e685d0a..70767edb 100644 --- a/plugins/modularPermission/package.json +++ b/plugins/modularPermission/package.json @@ -1,6 +1,6 @@ { "name": "@zerodev/modular-permission", - "version": "5.3.4", + "version": "5.4.0", "author": "ZeroDev", "main": "./_cjs/index.js", "module": "./_esm/index.js", @@ -56,8 +56,7 @@ "@simplewebauthn/browser": "^9.0.1" }, "peerDependencies": { - "viem": ">=2.16.3 <2.18.0", - "@zerodev/sdk": "^5.2.1", - "permissionless": ">=0.1.44 <=0.1.45" + "viem": "^2.21.40", + "@zerodev/sdk": "^5.4.0" } } diff --git a/plugins/modularPermission/policies/toGasPolicy.ts b/plugins/modularPermission/policies/toGasPolicy.ts index f262f732..9eede323 100644 --- a/plugins/modularPermission/policies/toGasPolicy.ts +++ b/plugins/modularPermission/policies/toGasPolicy.ts @@ -1,16 +1,15 @@ -import type { EntryPoint } from "permissionless/types/entrypoint" import { concatHex, encodeAbiParameters, zeroAddress } from "viem" import { PolicyFlags } from "../constants.js" import { GAS_POLICY_CONTRACT } from "../constants.js" import type { GasPolicyParams, Policy } from "./types.js" -export async function toGasPolicy({ +export async function toGasPolicy({ policyAddress = GAS_POLICY_CONTRACT, policyFlag = PolicyFlags.FOR_ALL_VALIDATION, maxGasAllowedInWei, enforcePaymaster = false, paymasterAddress = zeroAddress -}: GasPolicyParams): Promise> { +}: GasPolicyParams): Promise { return { getPolicyData: () => { return encodeAbiParameters( diff --git a/plugins/modularPermission/policies/toMerklePolicy.ts b/plugins/modularPermission/policies/toMerklePolicy.ts index 261830fb..3bc440cf 100644 --- a/plugins/modularPermission/policies/toMerklePolicy.ts +++ b/plugins/modularPermission/policies/toMerklePolicy.ts @@ -1,5 +1,4 @@ import { MerkleTree } from "merkletreejs" -import type { EntryPoint } from "permissionless/types/entrypoint" import { type Abi, type Hex, @@ -32,14 +31,13 @@ export enum Operation { } export async function toMerklePolicy< - entryPoint extends EntryPoint, TAbi extends Abi | readonly unknown[], TFunctionName extends string | undefined = string >({ policyAddress = MERKLE_POLICY_CONTRACT, policyFlag = PolicyFlags.FOR_ALL_VALIDATION, permissions = [] -}: MerklePolicyParams): Promise> { +}: MerklePolicyParams): Promise { const generatedPermissionParams = permissions?.map((perm) => getPermissionFromABI({ abi: perm.abi as Abi, diff --git a/plugins/modularPermission/policies/toSignaturePolicy.ts b/plugins/modularPermission/policies/toSignaturePolicy.ts index 94ecdd65..ddcc0c6d 100644 --- a/plugins/modularPermission/policies/toSignaturePolicy.ts +++ b/plugins/modularPermission/policies/toSignaturePolicy.ts @@ -1,13 +1,12 @@ -import type { EntryPoint } from "permissionless/types/entrypoint" import { concatHex, encodeAbiParameters } from "viem" import { PolicyFlags, SIGNATURE_POLICY_CONTRACT } from "../constants.js" import type { Policy, SignaturePolicyParams } from "./types.js" -export async function toSignaturePolicy({ +export async function toSignaturePolicy({ policyAddress = SIGNATURE_POLICY_CONTRACT, policyFlag = PolicyFlags.NOT_FOR_VALIDATE_USEROP, allowedRequestors -}: SignaturePolicyParams): Promise> { +}: SignaturePolicyParams): Promise { return { getPolicyData: () => { return encodeAbiParameters( diff --git a/plugins/modularPermission/policies/toSudoPolicy.ts b/plugins/modularPermission/policies/toSudoPolicy.ts index a8b8e21a..d5be4307 100644 --- a/plugins/modularPermission/policies/toSudoPolicy.ts +++ b/plugins/modularPermission/policies/toSudoPolicy.ts @@ -1,12 +1,11 @@ -import type { EntryPoint } from "permissionless/types/entrypoint" import { concatHex } from "viem" import { PolicyFlags, SUDO_POLICY_CONTRACT } from "../constants.js" import type { Policy, SudoPolicyParams } from "./types.js" -export async function toSudoPolicy({ +export async function toSudoPolicy({ policyAddress = SUDO_POLICY_CONTRACT, policyFlag = PolicyFlags.FOR_ALL_VALIDATION -}: SudoPolicyParams): Promise> { +}: SudoPolicyParams): Promise { return { getPolicyData: () => { return "0x" diff --git a/plugins/modularPermission/policies/types.ts b/plugins/modularPermission/policies/types.ts index a797f9d2..62492e4d 100644 --- a/plugins/modularPermission/policies/types.ts +++ b/plugins/modularPermission/policies/types.ts @@ -1,9 +1,5 @@ -import type { UserOperation } from "permissionless" -import type { - EntryPoint, - GetEntryPointVersion -} from "permissionless/types/entrypoint" import type { Abi, Address, Hex } from "viem" +import type { UserOperation } from "viem/account-abstraction" import type { PolicyFlags } from "../constants.js" import type { Permission } from "../types.js" @@ -31,11 +27,9 @@ export type GasPolicyParams = PolicyParams & { paymasterAddress?: Address } -export type Policy = { +export type Policy = { getPolicyData: () => Hex - getSignaturePolicyData: ( - userOperation: UserOperation> - ) => Hex + getSignaturePolicyData: (userOperation: UserOperation) => Hex getPolicyInfoInBytes: () => Hex // return params directly to serialize/deserialize Policy policyParams: diff --git a/plugins/modularPermission/serializeModularPermissionAccount.ts b/plugins/modularPermission/serializeModularPermissionAccount.ts index 75dc2e11..46335420 100644 --- a/plugins/modularPermission/serializeModularPermissionAccount.ts +++ b/plugins/modularPermission/serializeModularPermissionAccount.ts @@ -1,15 +1,15 @@ -import type { KernelSmartAccount } from "@zerodev/sdk" -import type { EntryPoint } from "permissionless/types" +import type { KernelSmartAccountImplementation } from "@zerodev/sdk" import type { Hex } from "viem" +import type { EntryPointVersion, SmartAccount } from "viem/account-abstraction" import { isModularPermissionValidatorPlugin, serializeModularPermissionAccountParams } from "./utils.js" export const serializeModularPermissionAccount = async < - entryPoint extends EntryPoint + entryPointVersion extends EntryPointVersion >( - account: KernelSmartAccount, + account: SmartAccount>, privateKey?: Hex ): Promise => { if (!isModularPermissionValidatorPlugin(account.kernelPluginManager)) @@ -23,7 +23,7 @@ export const serializeModularPermissionAccount = async < account.address ) const accountParams = { - initCode: await account.getInitCode(), + initCode: await account.generateInitCode(), accountAddress: account.address } diff --git a/plugins/modularPermission/signers/toECDSASigner.ts b/plugins/modularPermission/signers/toECDSASigner.ts index 1f4891b6..d5b61654 100644 --- a/plugins/modularPermission/signers/toECDSASigner.ts +++ b/plugins/modularPermission/signers/toECDSASigner.ts @@ -1,41 +1,29 @@ -import { constants, fixSignedData } from "@zerodev/sdk" +import { constants, fixSignedData, toSigner } from "@zerodev/sdk" +import type { Signer } from "@zerodev/sdk/types" import type { TypedData } from "abitype" -import { - SignTransactionNotSupportedBySmartAccount, - type SmartAccountSigner -} from "permissionless/accounts" -import type { Address, LocalAccount, TypedDataDefinition } from "viem" +import type { TypedDataDefinition } from "viem" import { toAccount } from "viem/accounts" import { ECDSA_SIGNER_CONTRACT } from "../constants.js" import type { ModularSigner, ModularSignerParams } from "./types.js" -export type ECDSAModularSignerParams< - TSource extends string = "custom", - TAddress extends Address = Address -> = ModularSignerParams & { - signer: SmartAccountSigner +export type ECDSAModularSignerParams = ModularSignerParams & { + signer: Signer } -export function toECDSASigner< - TSource extends string = "custom", - TAddress extends Address = Address ->({ +export async function toECDSASigner({ signer, signerContractAddress = ECDSA_SIGNER_CONTRACT -}: ECDSAModularSignerParams): ModularSigner { - const viemSigner: LocalAccount = { - ...signer, - signTransaction: (_, __) => { - throw new SignTransactionNotSupportedBySmartAccount() - } - } as LocalAccount +}: ECDSAModularSignerParams): Promise { + const viemSigner = await toSigner({ signer }) const account = toAccount({ address: viemSigner.address, async signMessage({ message }) { return fixSignedData(await viemSigner.signMessage({ message })) }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new Error( + "Smart account signer doesn't need to sign transactions" + ) }, async signTypedData< const TTypedData extends TypedData | Record, diff --git a/plugins/modularPermission/signers/toWebAuthnSigner.ts b/plugins/modularPermission/signers/toWebAuthnSigner.ts index d9676dec..3949e004 100644 --- a/plugins/modularPermission/signers/toWebAuthnSigner.ts +++ b/plugins/modularPermission/signers/toWebAuthnSigner.ts @@ -1,6 +1,5 @@ import type { PublicKeyCredentialRequestOptionsJSON } from "@simplewebauthn/typescript-types" import type { TypedData } from "abitype" -import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" import { type Chain, type Client, @@ -178,7 +177,9 @@ export const toWebAuthnSigner = async < return signMessageUsingWebAuthn(message) }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new Error( + "Smart account signer doesn't need to sign transactions" + ) }, async signTypedData< const TTypedData extends TypedData | Record, diff --git a/plugins/modularPermission/toModularPermissionValidatorPlugin.ts b/plugins/modularPermission/toModularPermissionValidatorPlugin.ts index 970d596e..25e52bb6 100644 --- a/plugins/modularPermission/toModularPermissionValidatorPlugin.ts +++ b/plugins/modularPermission/toModularPermissionValidatorPlugin.ts @@ -1,19 +1,20 @@ import { KernelAccountAbi } from "@zerodev/sdk" -import type { GetKernelVersion } from "@zerodev/sdk/types" -import { getEntryPointVersion, getUserOperationHash } from "permissionless" -import type { EntryPoint } from "permissionless/types/entrypoint" +import type { EntryPointType, GetKernelVersion } from "@zerodev/sdk/types" import { type Address, - type Chain, type Client, type Hex, - type Transport, concat, encodeAbiParameters, keccak256, pad, toHex } from "viem" +import { + type EntryPointVersion, + type UserOperation, + getUserOperationHash +} from "viem/account-abstraction" import { getChainId, readContract } from "viem/actions" import { getAction } from "viem/utils" import { ModularPermissionValidatorAbi } from "./abi/ModularPermissionValidatorAbi.js" @@ -27,14 +28,12 @@ import type { } from "./types.js" export async function createPermissionValidator< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined + entryPointVersion extends EntryPointVersion >( - client: Client, + client: Client, { signer, - entryPoint: entryPointAddress, + entryPoint, kernelVersion: _, policies, validUntil, @@ -44,16 +43,15 @@ export async function createPermissionValidator< signer: ModularSigner validUntil?: number validAfter?: number - policies: Policy[] - entryPoint: entryPoint - kernelVersion: GetKernelVersion + policies: Policy[] + entryPoint: EntryPointType + kernelVersion: GetKernelVersion validatorAddress?: Address } -): Promise> { +): Promise { const chainId = await getChainId(client) - const entryPointVersion = getEntryPointVersion(entryPointAddress) - if (entryPointVersion !== "v0.6") { + if (entryPoint.version !== "0.6") { throw new Error("Only EntryPoint 0.6 is supported") } @@ -152,8 +150,12 @@ export async function createPermissionValidator< signUserOperation: async (userOperation): Promise => { const userOpHash = getUserOperationHash({ - userOperation: { ...userOperation, signature: "0x" }, - entryPoint: entryPointAddress, + userOperation: { + ...userOperation, + signature: "0x" + } as UserOperation, + entryPointAddress: entryPoint.address, + entryPointVersion: entryPoint.version, chainId: chainId }) @@ -176,7 +178,7 @@ export async function createPermissionValidator< return 0n }, - async getDummySignature(userOperation) { + async getStubSignature(userOperation) { return concat([ getPermissionId(), ...policies.map((policy) => @@ -185,7 +187,7 @@ export async function createPermissionValidator< signer.getDummySignature() ]) }, - getPluginSerializationParams: (): ModularPermissionData => { + getPluginSerializationParams: (): ModularPermissionData => { return { validAfter, validUntil, diff --git a/plugins/modularPermission/types.ts b/plugins/modularPermission/types.ts index 63e7c51c..289cba1d 100644 --- a/plugins/modularPermission/types.ts +++ b/plugins/modularPermission/types.ts @@ -1,8 +1,6 @@ import type { KernelValidator } from "@zerodev/sdk" import type { Action, PluginValidityData } from "@zerodev/sdk/types" import type { ExtractAbiFunction, ExtractAbiFunctionNames } from "abitype" -import type { Pretty } from "abitype/src/types.js" -import type { EntryPoint } from "permissionless/types/entrypoint" import type { Abi, AbiFunction, @@ -17,10 +15,21 @@ import type { import type { Operation, ParamOperator } from "./policies/toMerklePolicy.js" import type { Policy } from "./policies/types.js" -export interface ModularPermissionData { +/** + * Taken from: https://github.com/wevm/abitype/blob/main/packages/abitype/src/types.ts + * Combines members of an intersection into a readable type. + * + * @link https://twitter.com/mattpocockuk/status/1622730173446557697?s=20&t=NdpAcmEFXY01xkqU3KO0Mg + * @example + * type Result = Pretty<{ a: string } | { b: string } | { c: number, d: bigint }> + * // ^? type Result = { a: string; b: string; c: number; d: bigint } + */ +export type Pretty = { [key in keyof type]: type[key] } & unknown + +export interface ModularPermissionData { validUntil?: number validAfter?: number - policies?: Policy[] + policies?: Policy[] } export type ExportModularPermissionAccountParams = { @@ -28,8 +37,8 @@ export type ExportModularPermissionAccountParams = { accountAddress: Address } -export type ModularPermissionAccountParams = { - modularPermissionParams: ModularPermissionData +export type ModularPermissionAccountParams = { + modularPermissionParams: ModularPermissionData action: Action validityData: PluginValidityData accountParams: ExportModularPermissionAccountParams @@ -37,9 +46,9 @@ export type ModularPermissionAccountParams = { privateKey?: Hex } -export type ModularPermissionPlugin = - KernelValidator & { - getPluginSerializationParams: () => ModularPermissionData +export type ModularPermissionPlugin = + KernelValidator<"ModularPermissionValidator"> & { + getPluginSerializationParams: () => ModularPermissionData } export type Nonces = { diff --git a/plugins/modularPermission/utils.ts b/plugins/modularPermission/utils.ts index cf790256..26700460 100644 --- a/plugins/modularPermission/utils.ts +++ b/plugins/modularPermission/utils.ts @@ -1,4 +1,3 @@ -import type { EntryPoint } from "permissionless/types" import type { ModularPermissionAccountParams, ModularPermissionPlugin @@ -14,19 +13,15 @@ export function bytesToBase64(bytes: Uint8Array) { return btoa(binString) } -export function isModularPermissionValidatorPlugin< - entryPoint extends EntryPoint ->( +export function isModularPermissionValidatorPlugin( // biome-ignore lint/suspicious/noExplicitAny: plugin: any -): plugin is ModularPermissionPlugin { +): plugin is ModularPermissionPlugin { return plugin?.getPluginSerializationParams !== undefined } -export const serializeModularPermissionAccountParams = < - entryPoint extends EntryPoint ->( - params: ModularPermissionAccountParams +export const serializeModularPermissionAccountParams = ( + params: ModularPermissionAccountParams ) => { // biome-ignore lint/suspicious/noExplicitAny: const replacer = (_: string, value: any) => { @@ -42,12 +37,8 @@ export const serializeModularPermissionAccountParams = < return base64String } -export const deserializeModularPermissionAccountParams = < - entryPoint extends EntryPoint ->( - params: string -) => { +export const deserializeModularPermissionAccountParams = (params: string) => { const uint8Array = base64ToBytes(params) const jsonString = new TextDecoder().decode(uint8Array) - return JSON.parse(jsonString) as ModularPermissionAccountParams + return JSON.parse(jsonString) as ModularPermissionAccountParams } diff --git a/plugins/multichain/CHANGELOG.md b/plugins/multi-chain-ecdsa/CHANGELOG.md similarity index 91% rename from plugins/multichain/CHANGELOG.md rename to plugins/multi-chain-ecdsa/CHANGELOG.md index 0a10acc9..ad423c5c 100644 --- a/plugins/multichain/CHANGELOG.md +++ b/plugins/multi-chain-ecdsa/CHANGELOG.md @@ -1,4 +1,10 @@ -# @zerodev/multi-chain-sdk +# @zerodev/multi-chain-ecdsa + +## 5.4.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` ## 5.3.12 diff --git a/plugins/multi-chain-ecdsa/actions/index.ts b/plugins/multi-chain-ecdsa/actions/index.ts new file mode 100644 index 00000000..4ad143d8 --- /dev/null +++ b/plugins/multi-chain-ecdsa/actions/index.ts @@ -0,0 +1 @@ +export * from "./signUserOperations.js" diff --git a/plugins/multi-chain-ecdsa/actions/signUserOperations.ts b/plugins/multi-chain-ecdsa/actions/signUserOperations.ts new file mode 100644 index 00000000..b3e436bb --- /dev/null +++ b/plugins/multi-chain-ecdsa/actions/signUserOperations.ts @@ -0,0 +1,117 @@ +import { + AccountNotFoundError, + type KernelSmartAccountImplementation +} from "@zerodev/sdk" +import MerkleTree from "merkletreejs" +import type { Assign, Chain, Client, Hex, Narrow, OneOf, Transport } from "viem" +import { + type DeriveEntryPointVersion, + type DeriveSmartAccount, + type EntryPointVersion, + type GetSmartAccountParameter, + type PrepareUserOperationReturnType, + type SmartAccount, + type UserOperation, + type UserOperationCalls, + type UserOperationRequest, + getUserOperationHash +} from "viem/account-abstraction" +import { + concatHex, + encodeAbiParameters, + keccak256, + parseAccount +} from "viem/utils" + +export type SignUserOperationsRequest< + account extends SmartAccount | undefined = SmartAccount | undefined, + accountOverride extends SmartAccount | undefined = SmartAccount | undefined, + calls extends readonly unknown[] = readonly unknown[], + // + _derivedAccount extends SmartAccount | undefined = DeriveSmartAccount< + account, + accountOverride + >, + _derivedVersion extends + EntryPointVersion = DeriveEntryPointVersion<_derivedAccount> +> = Assign< + UserOperationRequest<_derivedVersion>, + OneOf<{ calls: UserOperationCalls> } | { callData: Hex }> & { + chainId: number + } +> + +export type SignUserOperationsParameters< + account extends SmartAccount | undefined = SmartAccount | undefined, + accountOverride extends SmartAccount | undefined = SmartAccount | undefined, + calls extends readonly unknown[] = readonly unknown[], + request extends SignUserOperationsRequest< + account, + accountOverride, + calls + > = SignUserOperationsRequest +> = { userOperations: request[] } & GetSmartAccountParameter< + account, + accountOverride +> + +export type SignUserOperationsReturnType = PrepareUserOperationReturnType[] + +export async function signUserOperations< + account extends SmartAccount | undefined = SmartAccount | undefined, + chain extends Chain | undefined = Chain | undefined, + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] +>( + client: Client, + args_: SignUserOperationsParameters +): Promise { + const args = args_ as SignUserOperationsParameters + const { account: account_ = client.account, userOperations } = args + if (!account_) throw new AccountNotFoundError() + + const account = parseAccount( + account_ + ) as SmartAccount + + const userOpHashes = userOperations.map((userOp) => { + return getUserOperationHash({ + userOperation: { + ...userOp, + signature: "0x" + } as UserOperation, + entryPointAddress: account.entryPoint.address, + entryPointVersion: account.entryPoint.version, + chainId: userOp.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/constants.ts b/plugins/multi-chain-ecdsa/constants.ts new file mode 100644 index 00000000..94006ea7 --- /dev/null +++ b/plugins/multi-chain-ecdsa/constants.ts @@ -0,0 +1,2 @@ +export const MULTI_CHAIN_ECDSA_VALIDATOR_ADDRESS = + "0x5C97aA67Ba578E3c54ec5942A7563Ea9130E4f5F" diff --git a/plugins/multi-chain-ecdsa/index.ts b/plugins/multi-chain-ecdsa/index.ts new file mode 100644 index 00000000..f2eed9f4 --- /dev/null +++ b/plugins/multi-chain-ecdsa/index.ts @@ -0,0 +1,15 @@ +export { toMultiChainECDSAValidator } from "./toMultiChainECDSAValidator.js" +export { + type SignUserOperationsParameters, + type SignUserOperationsRequest, + type SignUserOperationsReturnType, + signUserOperations +} from "./actions/index.js" + +export { ecdsaGetMultiUserOpDummySignature } from "./utils/ecdsaGetMultiUserOpDummySignature.js" +export { + type MultiChainUserOpConfigForEnable, + ecdsaSignUserOpsWithEnable +} from "./utils/ecdsaSignUserOpsWithEnable.js" + +export * from "./constants.js" diff --git a/plugins/multichain/package.json b/plugins/multi-chain-ecdsa/package.json similarity index 87% rename from plugins/multichain/package.json rename to plugins/multi-chain-ecdsa/package.json index 72b7de32..c495df8e 100644 --- a/plugins/multichain/package.json +++ b/plugins/multi-chain-ecdsa/package.json @@ -1,6 +1,6 @@ { - "name": "@zerodev/multi-chain-validator", - "version": "5.3.12", + "name": "@zerodev/multi-chain-ecdsa-validator", + "version": "5.4.0", "author": "ZeroDev", "main": "./_cjs/index.js", "module": "./_esm/index.js", @@ -35,10 +35,9 @@ "lint:fix": "bun run lint --apply" }, "peerDependencies": { - "viem": ">=2.16.3 <2.18.0", - "@zerodev/sdk": "^5.2.15", - "@zerodev/webauthn-key": "^5.3.0", - "permissionless": ">=0.1.44 <=0.1.45" + "viem": "^2.21.40", + "@zerodev/sdk": "^5.4.0", + "@zerodev/webauthn-key": "^5.4.0" }, "dependencies": { "@simplewebauthn/browser": "^9.0.1", diff --git a/plugins/multichain/ecdsa/toMultiChainECDSAValidator.ts b/plugins/multi-chain-ecdsa/toMultiChainECDSAValidator.ts similarity index 50% rename from plugins/multichain/ecdsa/toMultiChainECDSAValidator.ts rename to plugins/multi-chain-ecdsa/toMultiChainECDSAValidator.ts index ded38f40..320f094d 100644 --- a/plugins/multichain/ecdsa/toMultiChainECDSAValidator.ts +++ b/plugins/multi-chain-ecdsa/toMultiChainECDSAValidator.ts @@ -1,56 +1,48 @@ -import type { GetKernelVersion, KernelValidator } from "@zerodev/sdk/types" -import type { TypedData } from "abitype" -import { type UserOperation, getUserOperationHash } from "permissionless" import { - SignTransactionNotSupportedBySmartAccount, - type SmartAccountSigner -} from "permissionless/accounts" -import type { - EntryPoint, - GetEntryPointVersion -} from "permissionless/types/entrypoint" + SignTransactionNotSupportedBySmartAccountError, + toSigner +} from "@zerodev/sdk" import type { - Address, - Chain, - Client, - Hex, - LocalAccount, - Transport, - TypedDataDefinition -} from "viem" + EntryPointType, + GetKernelVersion, + KernelValidator, + Signer +} from "@zerodev/sdk/types" +import type { TypedData } from "abitype" +import type { Address, Client, Hex, TypedDataDefinition } from "viem" +import { + type EntryPointVersion, + type UserOperation, + getUserOperationHash +} from "viem/account-abstraction" import { toAccount } from "viem/accounts" -import { signMessage, signTypedData } from "viem/actions" +import { signMessage } from "viem/actions" import { getChainId } from "viem/actions" -import { MULTI_CHAIN_ECDSA_VALIDATOR_ADDRESS } from "../constants.js" +import { MULTI_CHAIN_ECDSA_VALIDATOR_ADDRESS } from "./constants.js" +import { ecdsaGetMultiUserOpDummySignature } from "./utils/ecdsaGetMultiUserOpDummySignature.js" export async function toMultiChainECDSAValidator< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TSource extends string = "custom", - TAddress extends Address = Address + entryPointVersion extends EntryPointVersion >( - client: Client, + client: Client, { signer, - entryPoint: entryPointAddress, + entryPoint, kernelVersion: _, - validatorAddress + validatorAddress: validatorAddress_, + multiChainIds }: { - signer: SmartAccountSigner - entryPoint: entryPoint - kernelVersion: GetKernelVersion + signer: Signer + entryPoint: EntryPointType + kernelVersion: GetKernelVersion validatorAddress?: Address + multiChainIds?: number[] } -): Promise> { - validatorAddress = validatorAddress ?? MULTI_CHAIN_ECDSA_VALIDATOR_ADDRESS +): Promise> { + const validatorAddress = + validatorAddress_ ?? MULTI_CHAIN_ECDSA_VALIDATOR_ADDRESS // Get the private key related account - const viemSigner: LocalAccount = { - ...signer, - signTransaction: (_, __) => { - throw new SignTransactionNotSupportedBySmartAccount() - } - } as LocalAccount + const viemSigner = await toSigner({ signer }) // Fetch chain id const chainId = await getChainId(client) @@ -62,7 +54,7 @@ export async function toMultiChainECDSAValidator< return signMessage(client, { account: viemSigner, message }) }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new SignTransactionNotSupportedBySmartAccountError() }, async signTypedData< const TTypedData extends TypedData | Record, @@ -70,13 +62,7 @@ export async function toMultiChainECDSAValidator< | keyof TTypedData | "EIP712Domain" = keyof TTypedData >(typedData: TypedDataDefinition) { - return signTypedData( - client, - { - account: viemSigner, - ...typedData - } - ) + return viemSigner.signTypedData(typedData) } }) @@ -98,15 +84,14 @@ export async function toMultiChainECDSAValidator< } return 0n }, - async signUserOperation( - userOperation: UserOperation> - ) { - const hash = getUserOperationHash({ + async signUserOperation(userOperation) { + const hash = getUserOperationHash({ userOperation: { ...userOperation, signature: "0x" - }, - entryPoint: entryPointAddress, + } as UserOperation, + entryPointAddress: entryPoint.address, + entryPointVersion: entryPoint.version, chainId: chainId }) const signature = await signMessage(client, { @@ -115,8 +100,20 @@ export async function toMultiChainECDSAValidator< }) return signature }, - async getDummySignature(_userOperation) { - return "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c" + async getStubSignature(userOperation) { + if (!multiChainIds) + return "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c" + return ecdsaGetMultiUserOpDummySignature( + { + ...userOperation, + callGasLimit: 0n, + preVerificationGas: 0n, + verificationGasLimit: 0n + } as UserOperation, + multiChainIds.length, + entryPoint, + chainId + ) }, async isEnabled( _kernelAccountAddress: Address, diff --git a/plugins/multichain/tsconfig.build.json b/plugins/multi-chain-ecdsa/tsconfig.build.json similarity index 100% rename from plugins/multichain/tsconfig.build.json rename to plugins/multi-chain-ecdsa/tsconfig.build.json diff --git a/plugins/multichain/ecdsa/ecdsaGetMultiUserOpDummySignature.ts b/plugins/multi-chain-ecdsa/utils/ecdsaGetMultiUserOpDummySignature.ts similarity index 71% rename from plugins/multichain/ecdsa/ecdsaGetMultiUserOpDummySignature.ts rename to plugins/multi-chain-ecdsa/utils/ecdsaGetMultiUserOpDummySignature.ts index ed69938b..bb5e845f 100644 --- a/plugins/multichain/ecdsa/ecdsaGetMultiUserOpDummySignature.ts +++ b/plugins/multi-chain-ecdsa/utils/ecdsaGetMultiUserOpDummySignature.ts @@ -1,22 +1,24 @@ +import type { EntryPointType } from "@zerodev/sdk/types" import { MerkleTree } from "merkletreejs" -import { type UserOperation, getUserOperationHash } from "permissionless" -import type { - EntryPoint, - GetEntryPointVersion -} from "permissionless/types/entrypoint" import { type Hex, concatHex, encodeAbiParameters, keccak256 } from "viem" +import { + type EntryPointVersion, + type UserOperation, + getUserOperationHash +} from "viem/account-abstraction" export const ecdsaGetMultiUserOpDummySignature = < - entryPoint extends EntryPoint + entryPointVersion extends EntryPointVersion >( - userOperation: UserOperation>, + userOperation: UserOperation, numOfUserOps: number, - entryPoint: entryPoint, + entryPoint: EntryPointType, chainId: number ): Hex => { - const userOpHash = getUserOperationHash({ + const userOpHash = getUserOperationHash({ userOperation, - entryPoint, + entryPointAddress: entryPoint.address, + entryPointVersion: entryPoint.version, chainId }) diff --git a/plugins/multichain/ecdsa/ecdsaSignUserOpsWithEnable.ts b/plugins/multi-chain-ecdsa/utils/ecdsaSignUserOpsWithEnable.ts similarity index 86% rename from plugins/multichain/ecdsa/ecdsaSignUserOpsWithEnable.ts rename to plugins/multi-chain-ecdsa/utils/ecdsaSignUserOpsWithEnable.ts index 1c22996c..156e3572 100644 --- a/plugins/multichain/ecdsa/ecdsaSignUserOpsWithEnable.ts +++ b/plugins/multi-chain-ecdsa/utils/ecdsaSignUserOpsWithEnable.ts @@ -1,15 +1,10 @@ import { type Action, - type KernelSmartAccount, + type KernelSmartAccountImplementation, KernelV3AccountAbi, getEncodedPluginsData } from "@zerodev/sdk" import { MerkleTree } from "merkletreejs" -import type { UserOperation } from "permissionless" -import type { - EntryPoint, - GetEntryPointVersion -} from "permissionless/types/entrypoint" import { type Hex, concatHex, @@ -20,10 +15,17 @@ import { toFunctionSelector, zeroAddress } from "viem" +import type { + EntryPointVersion, + SmartAccount, + UserOperation +} from "viem/account-abstraction" -export type MultiChainUserOpConfigForEnable = { - account: KernelSmartAccount - userOp: UserOperation> +export type MultiChainUserOpConfigForEnable< + entryPointVersion extends EntryPointVersion +> = { + account: SmartAccount + userOp: UserOperation } /** @@ -32,12 +34,12 @@ export type MultiChainUserOpConfigForEnable = { * @returns Signed user operations */ export const ecdsaSignUserOpsWithEnable = async < - entryPoint extends EntryPoint + entryPointVersion extends EntryPointVersion >({ multiChainUserOpConfigsForEnable }: { - multiChainUserOpConfigsForEnable: MultiChainUserOpConfigForEnable[] -}): Promise>[]> => { + multiChainUserOpConfigsForEnable: MultiChainUserOpConfigForEnable[] +}): Promise[]> => { const pluginEnableTypedDatas = await Promise.all( multiChainUserOpConfigsForEnable.map(async (config) => { return config.account.kernelPluginManager.getPluginsEnableTypedData( diff --git a/plugins/multi-chain-web-authn/CHANGELOG.md b/plugins/multi-chain-web-authn/CHANGELOG.md new file mode 100644 index 00000000..c8ace23c --- /dev/null +++ b/plugins/multi-chain-web-authn/CHANGELOG.md @@ -0,0 +1,103 @@ +# @zerodev/multi-chain-web-auth + +## 5.4.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + +## 5.3.12 + +### Patch Changes + +- Fix bundlerTransport timeout issue + +## 5.3.11 + +### Patch Changes + +- Patch for latest permissionless 0.1.45 support + +## 5.3.10 + +### Patch Changes + +- Pinned permissionless version >=0.1.18 <=0.1.29 + +## 5.3.9 + +### Patch Changes + +- Added custom rpId support to the multi chain webAuthn validator + +## 5.3.8 + +### Patch Changes + +- Fixed webAuthnSignUserOps to correctly encode signature + +## 5.3.7 + +### Patch Changes + +- Encode signature differently depending on number of user ops + +## 5.3.6 + +### Patch Changes + +- Fixed signature encoding issue of single user op for multi chain webauthn validator + +## 5.3.5 + +### Patch Changes + +- Allow passing custom middleware to the multi chain client prepareMultiUserOpRequest + +## 5.3.4 + +### Minor Changes + +- Add serializaztion for the MultiChainWebAuthnValidator + +## 5.3.3 + +### Patch Changes + +- Separated webauthn validator and webauthn key module + +## 5.3.2 + +### Patch Changes + +- Removed redundant calls to passkey-server during message signing + +## 5.3.1 + +### Patch Changes + +- Update viem to 2.16.3 + +## 5.3.0 + +### Minor Changes + +- Kernel v3.1 released. And kernel versioning update. Added kernelVersion param in the interface. + +## 5.3.0-alpha-0 + +### Minor Changes + +- Integrated Kernel v3.1 and added kernelVersion param in account and plugins interface + +## 5.2.2 + +### Patch Changes + +- Added multi chain validator client + +## 5.2.0 + +### Patch Changes + +- add multi-chain validator sdk integration diff --git a/plugins/multi-chain-web-authn/actions/index.ts b/plugins/multi-chain-web-authn/actions/index.ts new file mode 100644 index 00000000..4ad143d8 --- /dev/null +++ b/plugins/multi-chain-web-authn/actions/index.ts @@ -0,0 +1 @@ +export * from "./signUserOperations.js" diff --git a/plugins/multi-chain-web-authn/actions/signUserOperations.ts b/plugins/multi-chain-web-authn/actions/signUserOperations.ts new file mode 100644 index 00000000..0935fe16 --- /dev/null +++ b/plugins/multi-chain-web-authn/actions/signUserOperations.ts @@ -0,0 +1,134 @@ +import { + AccountNotFoundError, + type KernelSmartAccountImplementation +} from "@zerodev/sdk" +import MerkleTree from "merkletreejs" +import type { Assign, Chain, Client, Hex, Narrow, OneOf, Transport } from "viem" +import { + type DeriveEntryPointVersion, + type DeriveSmartAccount, + type EntryPointVersion, + type GetSmartAccountParameter, + type PrepareUserOperationReturnType, + type SmartAccount, + type UserOperation, + type UserOperationCalls, + type UserOperationRequest, + getUserOperationHash +} from "viem/account-abstraction" +import { + concatHex, + encodeAbiParameters, + hashMessage, + keccak256, + parseAccount +} from "viem/utils" + +export type SignUserOperationsRequest< + account extends SmartAccount | undefined = SmartAccount | undefined, + accountOverride extends SmartAccount | undefined = SmartAccount | undefined, + calls extends readonly unknown[] = readonly unknown[], + // + _derivedAccount extends SmartAccount | undefined = DeriveSmartAccount< + account, + accountOverride + >, + _derivedVersion extends + EntryPointVersion = DeriveEntryPointVersion<_derivedAccount> +> = Assign< + UserOperationRequest<_derivedVersion>, + OneOf<{ calls: UserOperationCalls> } | { callData: Hex }> & { + chainId: number + } +> + +export type SignUserOperationsParameters< + account extends SmartAccount | undefined = SmartAccount | undefined, + accountOverride extends SmartAccount | undefined = SmartAccount | undefined, + calls extends readonly unknown[] = readonly unknown[], + request extends SignUserOperationsRequest< + account, + accountOverride, + calls + > = SignUserOperationsRequest +> = { userOperations: request[] } & GetSmartAccountParameter< + account, + accountOverride +> + +export type SignUserOperationsReturnType = PrepareUserOperationReturnType[] + +export async function signUserOperations< + account extends SmartAccount | undefined = SmartAccount | undefined, + chain extends Chain | undefined = Chain | undefined, + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] +>( + client: Client, + args_: SignUserOperationsParameters +): Promise { + const args = args_ as SignUserOperationsParameters + const { account: account_ = client.account, userOperations } = args + if (!account_) throw new AccountNotFoundError() + + const account = parseAccount( + account_ + ) as SmartAccount + + const userOpHashes = userOperations.map((userOp) => { + return getUserOperationHash({ + userOperation: { + ...userOp, + signature: "0x" + } as UserOperation, + entryPointAddress: account.entryPoint.address, + entryPointVersion: account.entryPoint.version, + chainId: userOp.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/multichain/constants.ts b/plugins/multi-chain-web-authn/constants.ts similarity index 50% rename from plugins/multichain/constants.ts rename to plugins/multi-chain-web-authn/constants.ts index cb64a094..b2c7c927 100644 --- a/plugins/multichain/constants.ts +++ b/plugins/multi-chain-web-authn/constants.ts @@ -1,4 +1,2 @@ -export const MULTI_CHAIN_ECDSA_VALIDATOR_ADDRESS = - "0x5C97aA67Ba578E3c54ec5942A7563Ea9130E4f5F" export const MULTI_CHAIN_WEBAUTHN_VALIDATOR_ADDRESS = "0x56Bf68A3Eb0b95A4EC0880E5F39Cf45CF58dAe70" diff --git a/plugins/multi-chain-web-authn/index.ts b/plugins/multi-chain-web-authn/index.ts new file mode 100644 index 00000000..10d1da6c --- /dev/null +++ b/plugins/multi-chain-web-authn/index.ts @@ -0,0 +1,10 @@ +export { + type SignUserOperationsParameters, + type SignUserOperationsRequest, + type SignUserOperationsReturnType, + signUserOperations +} from "./actions/index.js" + +export { webauthnGetMultiUserOpDummySignature } from "./utils/webauthnGetMultiUserOpDummySignature.js" +export { webauthnSignUserOpsWithEnable } from "./utils/webauthnSignUserOpsWithEnable.js" +export * from "./constants.js" diff --git a/plugins/multi-chain-web-authn/package.json b/plugins/multi-chain-web-authn/package.json new file mode 100644 index 00000000..ec59b650 --- /dev/null +++ b/plugins/multi-chain-web-authn/package.json @@ -0,0 +1,47 @@ +{ + "name": "@zerodev/multi-chain-web-authn-validator", + "version": "5.4.0", + "author": "ZeroDev", + "main": "./_cjs/index.js", + "module": "./_esm/index.js", + "types": "./_types/index.d.ts", + "typings": "./_types/index.d.ts", + "type": "module", + "sideEffects": false, + "license": "MIT", + "files": [ + "_esm", + "_cjs", + "_types", + "./**/*.ts", + "!_esm/**/*.tsbuildinfo", + "!_cjs/**/*.tsbuildinfo", + "!_types/**/*.tsbuildinfo", + "!.env", + "!./**/*.test.ts", + "!.changeset" + ], + "scripts": { + "build": "bun run clean && bun run build:cjs && bun run build:esm && bun run build:types", + "build:cjs": "tsc --project ./tsconfig.build.json --module commonjs --outDir ./_cjs --removeComments --verbatimModuleSyntax false && printf '{\"type\":\"commonjs\"}' > ./_cjs/package.json", + "build:esm": "tsc --project ./tsconfig.build.json --module es2020 --outDir ./_esm && printf '{\"type\": \"module\",\"sideEffects\":false}' > ./_esm/package.json", + "build:types": "tsc --project ./tsconfig.build.json --module esnext --declarationDir ./_types --emitDeclarationOnly --declaration --declarationMap", + "clean": "rimraf _esm _cjs _types", + "changeset": "changeset", + "changeset:release": "bun run build && changeset publish", + "changeset:version": "changeset version && bun install --lockfile-only", + "format": "biome format . --write", + "lint": "biome check .", + "lint:fix": "bun run lint --apply" + }, + "peerDependencies": { + "viem": "^2.21.40", + "@zerodev/sdk": "^5.4.0", + "@zerodev/webauthn-key": "^5.4.0" + }, + "dependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/typescript-types": "^8.3.4", + "merkletreejs": "^0.3.11" + } +} diff --git a/plugins/multichain/webauthn/toMultiChainWebAuthnValidator.ts b/plugins/multi-chain-web-authn/toMultiChainWebAuthnValidator.ts similarity index 70% rename from plugins/multichain/webauthn/toMultiChainWebAuthnValidator.ts rename to plugins/multi-chain-web-authn/toMultiChainWebAuthnValidator.ts index 9cbc25d8..9b8c0de2 100644 --- a/plugins/multichain/webauthn/toMultiChainWebAuthnValidator.ts +++ b/plugins/multi-chain-web-authn/toMultiChainWebAuthnValidator.ts @@ -1,5 +1,10 @@ import type { PublicKeyCredentialRequestOptionsJSON } from "@simplewebauthn/typescript-types" -import type { GetKernelVersion, KernelValidator } from "@zerodev/sdk/types" +import { SignTransactionNotSupportedBySmartAccountError } from "@zerodev/sdk" +import type { + EntryPointType, + GetKernelVersion, + KernelValidator +} from "@zerodev/sdk/types" import type { WebAuthnKey } from "@zerodev/webauthn-key" import { b64ToBytes, @@ -11,27 +16,28 @@ import { uint8ArrayToHexString } from "@zerodev/webauthn-key" import type { TypedData } from "abitype" -import { type UserOperation, getUserOperationHash } from "permissionless" -import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" -import type { EntryPoint, GetEntryPointVersion } from "permissionless/types" import { type Address, - type Chain, type Client, type Hex, type LocalAccount, type SignTypedDataParameters, type SignableMessage, - type Transport, type TypedDataDefinition, encodeAbiParameters, getTypesForEIP712Domain, hashTypedData, validateTypedData } from "viem" +import { + type EntryPointVersion, + type UserOperation, + getUserOperationHash +} from "viem/account-abstraction" import { toAccount } from "viem/accounts" import { getChainId, signMessage } from "viem/actions" -import { MULTI_CHAIN_WEBAUTHN_VALIDATOR_ADDRESS } from "../constants.js" +import { MULTI_CHAIN_WEBAUTHN_VALIDATOR_ADDRESS } from "./constants.js" +import { webauthnGetMultiUserOpDummySignature } from "./utils/webauthnGetMultiUserOpDummySignature.js" const signMessageUsingWebAuthn = async ( message: SignableMessage, @@ -117,26 +123,26 @@ const signMessageUsingWebAuthn = async ( } export async function toMultiChainWebAuthnValidator< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined + entryPointVersion extends EntryPointVersion >( - client: Client, + client: Client, { webAuthnKey, - entryPoint: entryPointAddress, + entryPoint, kernelVersion: _, rpId, - validatorAddress + validatorAddress, + multiChainIds }: { webAuthnKey: WebAuthnKey - entryPoint: entryPoint - kernelVersion: GetKernelVersion + entryPoint: EntryPointType + kernelVersion: GetKernelVersion rpId?: string validatorAddress?: Address + multiChainIds?: number[] } ): Promise< - KernelValidator & { + KernelValidator<"MultiChainWebAuthnValidator"> & { getSerializedData: () => string } > { @@ -155,7 +161,7 @@ export async function toMultiChainWebAuthnValidator< ]) }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new SignTransactionNotSupportedBySmartAccountError() }, async signTypedData< const TTypedData extends TypedData | Record, @@ -225,15 +231,14 @@ export async function toMultiChainWebAuthnValidator< } return 0n }, - async signUserOperation( - userOperation: UserOperation> - ) { - const hash = getUserOperationHash({ + async signUserOperation(userOperation) { + const hash = getUserOperationHash({ userOperation: { ...userOperation, signature: "0x" - }, - entryPoint: entryPointAddress, + } as UserOperation, + entryPointAddress: entryPoint.address, + entryPointVersion: entryPoint.version, chainId: chainId }) const signature = await signMessage(client, { @@ -257,32 +262,45 @@ export async function toMultiChainWebAuthnValidator< return encodedSignature }, - async getDummySignature(_userOperation) { - const signature = encodeAbiParameters( - [ - { name: "authenticatorData", type: "bytes" }, - { name: "clientDataJSON", type: "string" }, - { name: "responseTypeLocation", type: "uint256" }, - { name: "r", type: "uint256" }, - { name: "s", type: "uint256" }, - { name: "usePrecompiled", type: "bool" } - ], - [ - "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97631d00000000", - '{"type":"webauthn.get","challenge":"tbxXNFS9X_4Byr1cMwqKrIGB-_30a0QhZ6y7ucM0BOE","origin":"http://localhost:3000","crossOrigin":false, "other_keys_can_be_added_here":"do not compare clientDataJSON against a template. See https://goo.gl/yabPex"}', - 1n, - 44941127272049826721201904734628716258498742255959991581049806490182030242267n, - 9910254599581058084911561569808925251374718953855182016200087235935345969636n, - false - ] - ) - - return encodeAbiParameters( - [ - { name: "merkleData", type: "bytes" }, - { name: "signature", type: "bytes" } - ], - ["0x", signature] + async getStubSignature(userOperation) { + if (!multiChainIds) { + const signature = encodeAbiParameters( + [ + { name: "authenticatorData", type: "bytes" }, + { name: "clientDataJSON", type: "string" }, + { name: "responseTypeLocation", type: "uint256" }, + { name: "r", type: "uint256" }, + { name: "s", type: "uint256" }, + { name: "usePrecompiled", type: "bool" } + ], + [ + "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97631d00000000", + '{"type":"webauthn.get","challenge":"tbxXNFS9X_4Byr1cMwqKrIGB-_30a0QhZ6y7ucM0BOE","origin":"http://localhost:3000","crossOrigin":false, "other_keys_can_be_added_here":"do not compare clientDataJSON against a template. See https://goo.gl/yabPex"}', + 1n, + 44941127272049826721201904734628716258498742255959991581049806490182030242267n, + 9910254599581058084911561569808925251374718953855182016200087235935345969636n, + false + ] + ) + + return encodeAbiParameters( + [ + { name: "merkleData", type: "bytes" }, + { name: "signature", type: "bytes" } + ], + ["0x", signature] + ) + } + return webauthnGetMultiUserOpDummySignature( + { + ...userOperation, + callGasLimit: 0n, + preVerificationGas: 0n, + verificationGasLimit: 0n + } as UserOperation, + multiChainIds.length, + entryPoint, + chainId ) }, async isEnabled( @@ -293,35 +311,34 @@ export async function toMultiChainWebAuthnValidator< }, getSerializedData() { return serializeMultiChainWebAuthnValidatorData({ - entryPoint: entryPointAddress, + entryPoint, validatorAddress: currentValidatorAddress, pubKeyX: webAuthnKey.pubX, pubKeyY: webAuthnKey.pubY, authenticatorId: webAuthnKey.authenticatorId, authenticatorIdHash: webAuthnKey.authenticatorIdHash, - rpId + rpId, + multiChainIds }) } } } export async function deserializeMultiChainWebAuthnValidator< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined + entryPointVersion extends EntryPointVersion >( - client: Client, + client: Client, { serializedData, - entryPoint: entryPointAddress, + entryPoint: _, kernelVersion }: { serializedData: string - entryPoint: entryPoint - kernelVersion: GetKernelVersion + entryPoint: EntryPointType + kernelVersion: GetKernelVersion } ): Promise< - KernelValidator & { + KernelValidator<"MultiChainWebAuthnValidator"> & { getSerializedData: () => string } > { @@ -332,7 +349,8 @@ export async function deserializeMultiChainWebAuthnValidator< pubKeyY, authenticatorId, authenticatorIdHash, - rpId + rpId, + multiChainIds } = deserializeMultiChainWebAuthnValidatorData(serializedData) // Fetch chain id @@ -348,7 +366,7 @@ export async function deserializeMultiChainWebAuthnValidator< ]) }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new SignTransactionNotSupportedBySmartAccountError() }, async signTypedData< const TTypedData extends TypedData | Record, @@ -415,15 +433,14 @@ export async function deserializeMultiChainWebAuthnValidator< } return 0n }, - async signUserOperation( - userOperation: UserOperation> - ) { - const hash = getUserOperationHash({ + async signUserOperation(userOperation) { + const hash = getUserOperationHash({ userOperation: { ...userOperation, signature: "0x" }, - entryPoint: entryPointAddress, + entryPointAddress: entryPoint.address, + entryPointVersion: entryPoint.version, chainId: chainId }) const signature = await signMessage(client, { @@ -447,32 +464,46 @@ export async function deserializeMultiChainWebAuthnValidator< return encodedSignature }, - async getDummySignature(_userOperation) { - const signature = encodeAbiParameters( - [ - { name: "authenticatorData", type: "bytes" }, - { name: "clientDataJSON", type: "string" }, - { name: "responseTypeLocation", type: "uint256" }, - { name: "r", type: "uint256" }, - { name: "s", type: "uint256" }, - { name: "usePrecompiled", type: "bool" } - ], - [ - "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97631d00000000", - '{"type":"webauthn.get","challenge":"tbxXNFS9X_4Byr1cMwqKrIGB-_30a0QhZ6y7ucM0BOE","origin":"http://localhost:3000","crossOrigin":false, "other_keys_can_be_added_here":"do not compare clientDataJSON against a template. See https://goo.gl/yabPex"}', - 1n, - 44941127272049826721201904734628716258498742255959991581049806490182030242267n, - 9910254599581058084911561569808925251374718953855182016200087235935345969636n, - false - ] - ) + async getStubSignature(userOperation) { + if (!multiChainIds) { + const signature = encodeAbiParameters( + [ + { name: "authenticatorData", type: "bytes" }, + { name: "clientDataJSON", type: "string" }, + { name: "responseTypeLocation", type: "uint256" }, + { name: "r", type: "uint256" }, + { name: "s", type: "uint256" }, + { name: "usePrecompiled", type: "bool" } + ], + [ + "0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97631d00000000", + '{"type":"webauthn.get","challenge":"tbxXNFS9X_4Byr1cMwqKrIGB-_30a0QhZ6y7ucM0BOE","origin":"http://localhost:3000","crossOrigin":false, "other_keys_can_be_added_here":"do not compare clientDataJSON against a template. See https://goo.gl/yabPex"}', + 1n, + 44941127272049826721201904734628716258498742255959991581049806490182030242267n, + 9910254599581058084911561569808925251374718953855182016200087235935345969636n, + false + ] + ) + + return encodeAbiParameters( + [ + { name: "merkleData", type: "bytes" }, + { name: "signature", type: "bytes" } + ], + ["0x", signature] + ) + } - return encodeAbiParameters( - [ - { name: "merkleData", type: "bytes" }, - { name: "signature", type: "bytes" } - ], - ["0x", signature] + return webauthnGetMultiUserOpDummySignature( + { + ...userOperation, + callGasLimit: 0n, + preVerificationGas: 0n, + verificationGasLimit: 0n + } as UserOperation, + multiChainIds.length, + entryPoint, + chainId ) }, async isEnabled( @@ -496,13 +527,14 @@ export async function deserializeMultiChainWebAuthnValidator< } type MultiChainWebAuthnValidatorSerializedData = { - entryPoint: Address + entryPoint: EntryPointType validatorAddress: Address pubKeyX: bigint pubKeyY: bigint authenticatorId: string authenticatorIdHash: Hex rpId?: string + multiChainIds?: number[] } function serializeMultiChainWebAuthnValidatorData( diff --git a/plugins/multi-chain-web-authn/tsconfig.build.json b/plugins/multi-chain-web-authn/tsconfig.build.json new file mode 100644 index 00000000..cc7e1921 --- /dev/null +++ b/plugins/multi-chain-web-authn/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": "../../templates/typescript/tsconfig.build.json", + "include": ["./"], + "exclude": ["./**/*.test.ts", "./**/*.test-d.ts", "./**/*.bench.ts"], + "compilerOptions": { + "moduleResolution": "node", + "sourceMap": true, + "rootDir": "./", + "baseUrl": "./" + } +} diff --git a/plugins/multichain/webauthn/webauthnGetMultiUserOpDummySignature.ts b/plugins/multi-chain-web-authn/utils/webauthnGetMultiUserOpDummySignature.ts similarity index 81% rename from plugins/multichain/webauthn/webauthnGetMultiUserOpDummySignature.ts rename to plugins/multi-chain-web-authn/utils/webauthnGetMultiUserOpDummySignature.ts index 76230988..566cad20 100644 --- a/plugins/multichain/webauthn/webauthnGetMultiUserOpDummySignature.ts +++ b/plugins/multi-chain-web-authn/utils/webauthnGetMultiUserOpDummySignature.ts @@ -1,22 +1,24 @@ +import type { EntryPointType } from "@zerodev/sdk/types" import { MerkleTree } from "merkletreejs" -import { type UserOperation, getUserOperationHash } from "permissionless" -import type { - EntryPoint, - GetEntryPointVersion -} from "permissionless/types/entrypoint" import { type Hex, concatHex, encodeAbiParameters, keccak256 } from "viem" +import { + type EntryPointVersion, + type UserOperation, + getUserOperationHash +} from "viem/account-abstraction" export const webauthnGetMultiUserOpDummySignature = < - entryPoint extends EntryPoint + entryPointVersion extends EntryPointVersion >( - userOperation: UserOperation>, + userOperation: UserOperation, numOfUserOps: number, - entryPoint: entryPoint, + entryPoint: EntryPointType, chainId: number ): Hex => { - const userOpHash = getUserOperationHash({ + const userOpHash = getUserOperationHash({ userOperation, - entryPoint, + entryPointAddress: entryPoint.address, + entryPointVersion: entryPoint.version, chainId }) diff --git a/plugins/multichain/webauthn/webauthnSignUserOpsWithEnable.ts b/plugins/multi-chain-web-authn/utils/webauthnSignUserOpsWithEnable.ts similarity index 86% rename from plugins/multichain/webauthn/webauthnSignUserOpsWithEnable.ts rename to plugins/multi-chain-web-authn/utils/webauthnSignUserOpsWithEnable.ts index 71885c91..8ef2a750 100644 --- a/plugins/multichain/webauthn/webauthnSignUserOpsWithEnable.ts +++ b/plugins/multi-chain-web-authn/utils/webauthnSignUserOpsWithEnable.ts @@ -1,15 +1,10 @@ import { type Action, - type KernelSmartAccount, + type KernelSmartAccountImplementation, KernelV3AccountAbi, getEncodedPluginsData } from "@zerodev/sdk" import { MerkleTree } from "merkletreejs" -import type { UserOperation } from "permissionless" -import type { - EntryPoint, - GetEntryPointVersion -} from "permissionless/types/entrypoint" import { type Hex, concatHex, @@ -21,10 +16,17 @@ import { toFunctionSelector, zeroAddress } from "viem" +import type { + EntryPointVersion, + SmartAccount, + UserOperation +} from "viem/account-abstraction" -type MultiChainUserOpConfigForEnable = { - account: KernelSmartAccount - userOp: UserOperation> +type MultiChainUserOpConfigForEnable< + entryPointVersion extends EntryPointVersion +> = { + account: SmartAccount + userOp: UserOperation } /** @@ -32,11 +34,13 @@ type MultiChainUserOpConfigForEnable = { * @dev Sign user operations with enable signatures for multi-chain validator * @returns Signed user operations */ -export const webauthnSignUserOpsWithEnable = async ({ +export const webauthnSignUserOpsWithEnable = async < + entryPointVersion extends EntryPointVersion +>({ multiChainUserOpConfigsForEnable }: { - multiChainUserOpConfigsForEnable: MultiChainUserOpConfigForEnable[] -}): Promise>[]> => { + multiChainUserOpConfigsForEnable: MultiChainUserOpConfigForEnable[] +}): Promise[]> => { const pluginEnableTypedDatas = await Promise.all( multiChainUserOpConfigsForEnable.map(async (config) => { return config.account.kernelPluginManager.getPluginsEnableTypedData( diff --git a/plugins/multi-chain-weighted-validator/CHANGELOG.md b/plugins/multi-chain-weighted-validator/CHANGELOG.md index 6356097e..00829505 100644 --- a/plugins/multi-chain-weighted-validator/CHANGELOG.md +++ b/plugins/multi-chain-weighted-validator/CHANGELOG.md @@ -1,5 +1,17 @@ # @zerodev/multi-chain-weighted-validator +## 5.4.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + +## 5.4.0-beta.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + ## 5.3.1 ### Patch Changes diff --git a/plugins/multi-chain-weighted-validator/actions/approveUserOperation.ts b/plugins/multi-chain-weighted-validator/actions/approveUserOperation.ts index bbb75e72..afe6442a 100644 --- a/plugins/multi-chain-weighted-validator/actions/approveUserOperation.ts +++ b/plugins/multi-chain-weighted-validator/actions/approveUserOperation.ts @@ -1,77 +1,35 @@ -import type { KernelSmartAccount } from "@zerodev/sdk" +import type { KernelSmartAccountImplementation } from "@zerodev/sdk" +import { AccountNotFoundError } from "@zerodev/sdk" import { MerkleTree } from "merkletreejs" -import type { Middleware } from "permissionless/actions/smartAccount" -import type { - ENTRYPOINT_ADDRESS_V06_TYPE, - EntryPoint, - GetAccountParameter, - PartialBy, - Prettify, - UserOperation -} from "permissionless/types" -import { - AccountOrClientNotFoundError, - parseAccount -} from "permissionless/utils" import { type Chain, type Client, type Hex, + type RequiredBy, type Transport, publicActions } from "viem" +import type { + GetSmartAccountParameter, + SmartAccount, + UserOperation +} from "viem/account-abstraction" import { encodeAbiParameters, hashTypedData, keccak256, - parseAbiParameters + parseAbiParameters, + parseAccount } from "viem/utils" import { getValidatorAddress } from "../toMultiChainWeightedValidatorPlugin.js" export type ApproveUserOperationParameters< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount - | undefined + account extends SmartAccount | undefined = SmartAccount | undefined, + accountOverride extends SmartAccount | undefined = SmartAccount | undefined > = { - multiChainAccounts: KernelSmartAccount[] - userOperation: entryPoint extends ENTRYPOINT_ADDRESS_V06_TYPE - ? PartialBy< - UserOperation<"v0.6">, - | "sender" - | "nonce" - | "initCode" - | "callGasLimit" - | "verificationGasLimit" - | "preVerificationGas" - | "maxFeePerGas" - | "maxPriorityFeePerGas" - | "paymasterAndData" - | "signature" - > - : PartialBy< - UserOperation<"v0.7">, - | "sender" - | "nonce" - | "factory" - | "factoryData" - | "callGasLimit" - | "verificationGasLimit" - | "preVerificationGas" - | "maxFeePerGas" - | "maxPriorityFeePerGas" - | "paymaster" - | "paymasterVerificationGasLimit" - | "paymasterPostOpGasLimit" - | "paymasterData" - | "signature" - > -} & GetAccountParameter & - Middleware + multiChainAccounts: SmartAccount[] + userOperation: RequiredBy, "callData"> +} & GetSmartAccountParameter export type ApproveUserOperationReturnType = { signature: Hex @@ -82,32 +40,30 @@ export type ApproveUserOperationReturnType = { } export async function approveUserOperation< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount - | undefined + account extends SmartAccount | undefined = SmartAccount | undefined, + chain extends Chain | undefined = Chain | undefined, + accountOverride extends SmartAccount | undefined = undefined >( - client: Client, - args: Prettify< - ApproveUserOperationParameters - > + client: Client, + args: ApproveUserOperationParameters ): Promise { const { account: account_ = client.account, userOperation, multiChainAccounts } = args - if (!account_) throw new AccountOrClientNotFoundError() + if (!account_) throw new AccountNotFoundError() - const account = parseAccount(account_) as KernelSmartAccount - const validatorAddress = getValidatorAddress(account.entryPoint) + const account = parseAccount( + account_ + ) as SmartAccount + const validatorAddress = getValidatorAddress( + account.entryPoint, + account.kernelVersion + ) const fetchCallDataAndNonceHash = async ( - account: KernelSmartAccount + account: SmartAccount ): Promise => { return keccak256( encodeAbiParameters(parseAbiParameters("address, bytes, uint256"), [ diff --git a/plugins/multi-chain-weighted-validator/actions/getCurrentSigners.ts b/plugins/multi-chain-weighted-validator/actions/getCurrentSigners.ts new file mode 100644 index 00000000..80dd5726 --- /dev/null +++ b/plugins/multi-chain-weighted-validator/actions/getCurrentSigners.ts @@ -0,0 +1,61 @@ +import { + AccountNotFoundError, + type KernelSmartAccountImplementation +} from "@zerodev/sdk" +import type { Chain, Client, Hex, Transport } from "viem" +import type { SmartAccount } from "viem/account-abstraction" +import { readContract } from "viem/actions" +import { getAction, parseAccount } from "viem/utils" +import { MultiChainWeightedValidatorAbi } from "../abi" +import { getValidatorAddress } from "../toMultiChainWeightedValidatorPlugin.js" + +export async function getCurrentSigners< + account extends SmartAccount | undefined, + chain extends Chain | undefined +>( + client: Client +): Promise> { + const account_ = client.account + if (!account_) throw new AccountNotFoundError() + + const account = parseAccount( + account_ + ) as unknown as SmartAccount + + const validatorAddress = getValidatorAddress( + account.entryPoint, + account.kernelVersion + ) + + const multiChainWeightedStorage = await getAction( + client, + readContract, + "readContract" + )({ + abi: MultiChainWeightedValidatorAbi, + address: validatorAddress, + functionName: "multiChainWeightedStorage", + args: [account.address] + }) + + const guardiansLength = multiChainWeightedStorage[3] + + const signers: Array<{ encodedPublicKey: Hex; weight: number }> = [] + for (let i = 0; i < guardiansLength; i++) { + const guardian = await getAction( + client, + readContract, + "readContract" + )({ + abi: MultiChainWeightedValidatorAbi, + address: validatorAddress, + functionName: "guardian", + args: [BigInt(i), account.address] + }) + signers.push({ + encodedPublicKey: guardian[2], + weight: guardian[1] + }) + } + return signers +} diff --git a/plugins/multi-chain-weighted-validator/actions/sendUserOperationWithApprovals.ts b/plugins/multi-chain-weighted-validator/actions/sendUserOperationWithApprovals.ts index bec9ee9e..3a960581 100644 --- a/plugins/multi-chain-weighted-validator/actions/sendUserOperationWithApprovals.ts +++ b/plugins/multi-chain-weighted-validator/actions/sendUserOperationWithApprovals.ts @@ -1,13 +1,4 @@ -import type { KernelSmartAccount } from "@zerodev/sdk" -import { - AccountOrClientNotFoundError, - type UserOperation, - parseAccount -} from "permissionless" -import { sendUserOperation as sendUserOperationBundler } from "permissionless/actions" -import { prepareUserOperationRequest } from "permissionless/actions/smartAccount" -import type { SendUserOperationParameters } from "permissionless/actions/smartAccount" -import type { EntryPoint, GetEntryPointVersion } from "permissionless/types" +import { AccountNotFoundError } from "@zerodev/sdk" import { type Chain, type Client, @@ -17,53 +8,45 @@ import { encodeAbiParameters, publicActions } from "viem" -import type { Prettify } from "viem/chains" -import { getAction } from "viem/utils" +import { + type PrepareUserOperationParameters, + type SendUserOperationParameters, + type SmartAccount, + type UserOperation, + prepareUserOperation, + sendUserOperation +} from "viem/account-abstraction" +import { getAction, parseAccount } from "viem/utils" import { encodeSignatures } from "../utils.js" import type { ApproveUserOperationReturnType } from "./approveUserOperation.js" export type SendUserOperationWithApprovalsParameters< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount - | undefined -> = Prettify< - SendUserOperationParameters & { - approvals: ApproveUserOperationReturnType[] - } -> + account extends SmartAccount | undefined = SmartAccount | undefined, + accountOverride extends SmartAccount | undefined = SmartAccount | undefined, + calls extends readonly unknown[] = readonly unknown[] +> = SendUserOperationParameters & { + approvals: ApproveUserOperationReturnType[] +} export async function sendUserOperationWithApprovals< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount - | undefined + account extends SmartAccount | undefined, + chain extends Chain | undefined, + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] >( - client: Client, - args: Prettify< - SendUserOperationWithApprovalsParameters< - entryPoint, - TTransport, - TChain, - TAccount - > + client: Client, + args: SendUserOperationWithApprovalsParameters< + account, + accountOverride, + calls > ): Promise { - const { account: account_ = client.account } = args + const { account: account_ = client.account, approvals } = args client.chain - if (!account_) throw new AccountOrClientNotFoundError() + if (!account_) throw new AccountNotFoundError() - const account = parseAccount(account_) as KernelSmartAccount + const account = parseAccount(account_) as SmartAccount - const { userOperation: _userOperation, approvals } = args const chainId = client.chain ? client.chain.id : await client.extend(publicActions).getChainId() @@ -81,25 +64,24 @@ export async function sendUserOperationWithApprovals< const signatures = approvals.map((approval) => approval.signature) const encodedSignatures = encodeSignatures(approveMerkleData, signatures) - _userOperation.signature = encodedSignatures - _userOperation.signature = await account.getDummySignature( - _userOperation as UserOperation> - ) - const userOperation = await getAction( + args.signature = encodedSignatures + args.signature = await account.getStubSignature(args as UserOperation) + const userOperation_ = await getAction( client, - prepareUserOperationRequest, - "prepareUserOperationRequest" - )(args) - userOperation.signature = encodedSignatures + prepareUserOperation, + "prepareUserOperation" + )(args as PrepareUserOperationParameters) + userOperation_.signature = encodedSignatures - userOperation.signature = await account.signUserOperation( - userOperation as UserOperation> + userOperation_.signature = await account.signUserOperation( + userOperation_ as UserOperation ) - return sendUserOperationBundler(client, { - userOperation: userOperation as UserOperation< - GetEntryPointVersion - >, - entryPoint: account.entryPoint - }) + return await getAction( + client, + sendUserOperation, + "sendUserOperation" + )({ + ...args + } as SendUserOperationParameters) } diff --git a/plugins/multi-chain-weighted-validator/actions/updateSignersData.ts b/plugins/multi-chain-weighted-validator/actions/updateSignersData.ts new file mode 100644 index 00000000..de0acdf2 --- /dev/null +++ b/plugins/multi-chain-weighted-validator/actions/updateSignersData.ts @@ -0,0 +1,141 @@ +import { + AccountNotFoundError, + type KernelSmartAccountImplementation +} from "@zerodev/sdk" +import { encodeWebAuthnPubKey } from "@zerodev/webauthn-key" +import { + type Chain, + type Client, + type Hash, + type Hex, + type Transport, + concatHex, + encodeAbiParameters, + encodeFunctionData, + toHex +} from "viem" +import { + type SendUserOperationParameters, + type SmartAccount, + sendUserOperation +} from "viem/account-abstraction" +import { getAction, parseAccount } from "viem/utils" +import { MultiChainWeightedValidatorAbi } from "../abi.js" +import { + SIGNER_TYPE, + type WeightedValidatorConfig, + getValidatorAddress +} from "../index.js" +import { sortByPublicKey } from "../utils.js" +import type { ApproveUserOperationReturnType } from "./approveUserOperation.js" +import { + type SendUserOperationWithApprovalsParameters, + sendUserOperationWithApprovals +} from "./sendUserOperationWithApprovals.js" + +export type UpdateSignersDataParameters< + account extends SmartAccount | undefined = SmartAccount | undefined, + accountOverride extends SmartAccount | undefined = SmartAccount | undefined, + calls extends readonly unknown[] = readonly unknown[] +> = SendUserOperationParameters & { + approvals?: ApproveUserOperationReturnType[] + config: WeightedValidatorConfig +} + +export async function updateSignersData< + account extends SmartAccount | undefined, + chain extends Chain | undefined, + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] +>( + client: Client, + args: UpdateSignersDataParameters +): Promise { + const { account: account_ = client.account, config, approvals } = args + if (!account_) throw new AccountNotFoundError() + + const account = parseAccount( + account_ + ) as SmartAccount + + const validatorAddress = getValidatorAddress( + account.entryPoint, + account.kernelVersion + ) + + // Check if sum of weights is equal or greater than threshold + let totalWeight = 0 + for (const signer of config.signers) { + totalWeight += signer.weight + } + if (totalWeight < config.threshold) { + throw new Error( + `Sum of weights (${totalWeight}) is less than threshold (${config.threshold})` + ) + } + + // sort signers by address in descending order + const configSigners = config + ? [...config.signers] + .map((signer) => + typeof signer.publicKey === "object" + ? { + ...signer, + publicKey: encodeWebAuthnPubKey( + signer.publicKey + ) as Hex + } + : { ...signer, publicKey: signer.publicKey as Hex } + ) + .sort(sortByPublicKey) + : [] + + const call = { + to: validatorAddress, + value: 0n, + data: encodeFunctionData({ + abi: MultiChainWeightedValidatorAbi, + functionName: "renew", + args: [ + concatHex([ + toHex(config.threshold, { size: 3 }), + toHex(config.delay || 0, { size: 6 }), + encodeAbiParameters( + [{ name: "guardiansData", type: "bytes[]" }], + [ + configSigners.map((cfg) => + concatHex([ + cfg.publicKey.length === 42 + ? SIGNER_TYPE.ECDSA + : SIGNER_TYPE.PASSKEY, + toHex(cfg.weight, { size: 3 }), + cfg.publicKey + ]) + ) + ] + ) + ]) + ] + }) + } + + if (approvals) { + return getAction( + client, + sendUserOperationWithApprovals, + "sendUserOperationWithApprovals" + )({ + ...args, + calls: [call] + } as SendUserOperationWithApprovalsParameters) + } + + return getAction( + client, + sendUserOperation, + "sendUserOperation" + )({ + ...args, + calls: [call] + } as SendUserOperationParameters) +} diff --git a/plugins/multi-chain-weighted-validator/clients/decorators/multiChainWeightedKernelAccountClient.ts b/plugins/multi-chain-weighted-validator/clients/decorators/multiChainWeightedKernelAccountClient.ts index 8c029caf..dd9a8d53 100644 --- a/plugins/multi-chain-weighted-validator/clients/decorators/multiChainWeightedKernelAccountClient.ts +++ b/plugins/multi-chain-weighted-validator/clients/decorators/multiChainWeightedKernelAccountClient.ts @@ -1,11 +1,5 @@ -import { - type KernelAccountClientActions, - type KernelSmartAccount, - kernelAccountClientActions -} from "@zerodev/sdk" -import type { Middleware } from "permissionless/actions/smartAccount" -import type { EntryPoint, Prettify } from "permissionless/types" import type { Chain, Client, Hash, Transport } from "viem" +import type { SmartAccount } from "viem/account-abstraction" import { type ApproveUserOperationParameters, type ApproveUserOperationReturnType, @@ -16,39 +10,15 @@ import { sendUserOperationWithApprovals } from "../../actions/sendUserOperationWithApprovals.js" -export type MultiChainWeightedKernelAccountClientActions< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TSmartAccount extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount - | undefined -> = Omit< - KernelAccountClientActions, - | "sendUserOperation" - | "sendTransaction" - | "writeContract" - | "sendTransactions" -> & { +export type MultiChainWeightedKernelAccountClientActions = { /** * Approve a user operation with the given transport, chain, smart account and signer. * * @param args - Parameters for the approveUserOperation function * @returns A promise that resolves to the result of the approveUserOperation function */ - approveUserOperation: ( - args: Prettify< - Parameters< - typeof approveUserOperation< - entryPoint, - TTransport, - TChain, - TSmartAccount - > - >[1] - > + approveUserOperation: ( + args: ApproveUserOperationParameters ) => Promise /** * Sends a user operation with the given transport, chain, smart account and signer. @@ -56,78 +26,23 @@ export type MultiChainWeightedKernelAccountClientActions< * @param args - Parameters for the sendUserOperationWithSignatures function * @returns A promise that resolves to the result of the sendUserOperationWithSignatures function */ - sendUserOperationWithApprovals: ( - args: Prettify< - Parameters< - typeof sendUserOperationWithApprovals< - entryPoint, - TTransport, - TChain, - TSmartAccount - > - >[1] - > + sendUserOperationWithApprovals: ( + args: SendUserOperationWithApprovalsParameters ) => Promise } -export function multiChainWeightedKernelAccountClientActions< - entryPoint extends EntryPoint ->({ middleware }: Middleware) { +export function multiChainWeightedKernelAccountClientActions() { return < - TTransport extends Transport, TChain extends Chain | undefined = Chain | undefined, - TSmartAccount extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount + TSmartAccount extends SmartAccount | undefined = + | SmartAccount | undefined >( - client: Client - ): MultiChainWeightedKernelAccountClientActions< - entryPoint, - TTransport, - TChain, - TSmartAccount - > => { - const baseActions = kernelAccountClientActions({ middleware })(client) - const { - sendUserOperation, - sendTransaction, - writeContract, - sendTransactions, - ...rest - } = baseActions + client: Client + ): MultiChainWeightedKernelAccountClientActions => { return { - ...rest, - approveUserOperation: (args) => - approveUserOperation< - entryPoint, - TTransport, - TChain, - TSmartAccount - >(client, { - ...args, - middleware - } as ApproveUserOperationParameters< - entryPoint, - TTransport, - TChain, - TSmartAccount - >), + approveUserOperation: (args) => approveUserOperation(client, args), sendUserOperationWithApprovals: (args) => - sendUserOperationWithApprovals< - entryPoint, - TTransport, - TChain, - TSmartAccount - >(client, { - ...args, - middleware - } as SendUserOperationWithApprovalsParameters< - entryPoint, - TTransport, - TChain, - TSmartAccount - >) + sendUserOperationWithApprovals(client, args) } } } diff --git a/plugins/multi-chain-weighted-validator/clients/multiChainWeightedKernelAccountClient.ts b/plugins/multi-chain-weighted-validator/clients/multiChainWeightedKernelAccountClient.ts index 1f9e9b0e..3453d673 100644 --- a/plugins/multi-chain-weighted-validator/clients/multiChainWeightedKernelAccountClient.ts +++ b/plugins/multi-chain-weighted-validator/clients/multiChainWeightedKernelAccountClient.ts @@ -1,82 +1,109 @@ import { - type KernelSmartAccount, + type KernelAccountClientActions, createKernelAccountClient } from "@zerodev/sdk" import type { SmartAccountClientConfig } from "@zerodev/sdk/clients" -import type { EntryPoint } from "permissionless/types" -import type { BundlerRpcSchema } from "permissionless/types/bundler" -import type { Chain, Client, Transport } from "viem" +import type { + BundlerRpcSchema, + Chain, + Client, + Prettify, + RpcSchema, + Transport +} from "viem" +import type { + BundlerActions, + BundlerClientConfig, + SmartAccount +} from "viem/account-abstraction" import { type MultiChainWeightedKernelAccountClientActions, multiChainWeightedKernelAccountClientActions } from "./decorators/multiChainWeightedKernelAccountClient.js" export type MultiChainWeightedKernelAccountClient< - entryPoint extends EntryPoint, transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined, - account extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount - | undefined -> = Client< - transport, - chain, - account, - BundlerRpcSchema, - MultiChainWeightedKernelAccountClientActions< - entryPoint, + account extends SmartAccount | undefined = SmartAccount | undefined, + client extends Client | undefined = Client | undefined, + rpcSchema extends RpcSchema | undefined = undefined +> = Prettify< + Client< transport, - chain, - account + chain extends Chain + ? chain + : // biome-ignore lint/suspicious/noExplicitAny: + client extends Client + ? chain + : undefined, + account, + rpcSchema extends RpcSchema + ? [...BundlerRpcSchema, ...rpcSchema] + : BundlerRpcSchema, + BundlerActions & + KernelAccountClientActions & + MultiChainWeightedKernelAccountClientActions > -> +> & { + client: client + paymaster: BundlerClientConfig["paymaster"] | undefined + paymasterContext: BundlerClientConfig["paymasterContext"] | undefined + userOperation: BundlerClientConfig["userOperation"] | undefined +} -export const createMultiChainWeightedKernelAccountClient = < - TSmartAccount extends - | KernelSmartAccount - | undefined, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = undefined, - TEntryPoint extends EntryPoint = TSmartAccount extends KernelSmartAccount< - infer U - > - ? U - : never +export function createMultiChainWeightedKernelAccountClient< + transport extends Transport, + chain extends Chain | undefined = undefined, + account extends SmartAccount | undefined = undefined, + client extends Client | undefined = undefined, + rpcSchema extends RpcSchema | undefined = undefined >( parameters: SmartAccountClientConfig< - TEntryPoint, - TTransport, - TChain, - TSmartAccount + transport, + chain, + account, + client, + rpcSchema > ): MultiChainWeightedKernelAccountClient< - TEntryPoint, - TTransport, - TChain, - TSmartAccount -> => { + transport, + chain, + account, + client, + rpcSchema +> + +export function createMultiChainWeightedKernelAccountClient( + parameters: SmartAccountClientConfig +): MultiChainWeightedKernelAccountClient { const { + client: client_, key = "Account", name = "Multi Chain Weighted Kernel Account Client", - middleware + paymaster, + paymasterContext, + bundlerTransport, + userOperation } = parameters - const client = createKernelAccountClient({ - ...parameters, - name, - key - }) + const client = Object.assign( + createKernelAccountClient({ + ...parameters, + chain: parameters.chain ?? client_?.chain, + bundlerTransport, + key, + name + }), + { + client: client_, + paymaster, + paymasterContext, + userOperation, + type: "multiChainWeightedKernelAccountClient" + } + ) return client.extend( - multiChainWeightedKernelAccountClientActions({ - middleware - }) - ) as MultiChainWeightedKernelAccountClient< - TEntryPoint, - TTransport, - TChain, - TSmartAccount - > + multiChainWeightedKernelAccountClientActions() + ) as MultiChainWeightedKernelAccountClient } diff --git a/plugins/multi-chain-weighted-validator/constants.ts b/plugins/multi-chain-weighted-validator/constants.ts index 56ea3e4c..c611b45f 100644 --- a/plugins/multi-chain-weighted-validator/constants.ts +++ b/plugins/multi-chain-weighted-validator/constants.ts @@ -1,8 +1,7 @@ import { constants } from "@zerodev/sdk" import type { Action } from "@zerodev/sdk/types" -import { getEntryPointVersion } from "permissionless" -import type { EntryPoint } from "permissionless/types/entrypoint" import { toFunctionSelector } from "viem" +import type { EntryPointVersion } from "viem/account-abstraction" const RECOVERY_ACTION_ADDRESS_V06 = "0x2f65dB8039fe5CAEE0a8680D2879deB800F31Ae1" const RECOVERY_ACTION_ADDRESS_V07 = "0xe884C2868CC82c16177eC73a93f7D9E6F3A5DC6E" @@ -10,9 +9,10 @@ const RECOVERY_ACTION_SELECTOR = toFunctionSelector( "doRecovery(address, bytes)" ) -export const getRecoveryAction = (entryPoint: EntryPoint): Action => { - const entryPointVersion = getEntryPointVersion(entryPoint) - if (entryPointVersion === "v0.6") { +export const getRecoveryAction = ( + entryPointVersion: EntryPointVersion +): Action => { + if (entryPointVersion === "0.6") { return { address: RECOVERY_ACTION_ADDRESS_V06, selector: RECOVERY_ACTION_SELECTOR diff --git a/plugins/multi-chain-weighted-validator/index.ts b/plugins/multi-chain-weighted-validator/index.ts index 972b5655..4f8f901d 100644 --- a/plugins/multi-chain-weighted-validator/index.ts +++ b/plugins/multi-chain-weighted-validator/index.ts @@ -9,10 +9,15 @@ import { type ApproveUserOperationReturnType, approveUserOperation } from "./actions/approveUserOperation.js" +import { getCurrentSigners } from "./actions/getCurrentSigners.js" import { type SendUserOperationWithApprovalsParameters, sendUserOperationWithApprovals } from "./actions/sendUserOperationWithApprovals.js" +import { + type UpdateSignersDataParameters, + updateSignersData +} from "./actions/updateSignersData.js" import { type MultiChainWeightedKernelAccountClientActions, multiChainWeightedKernelAccountClientActions @@ -33,15 +38,11 @@ import { type WeightedSigner, type WeightedValidatorConfig, createMultiChainWeightedValidator, - getCurrentSigners, - getUpdateConfigCall, getValidatorAddress } from "./toMultiChainWeightedValidatorPlugin.js" export { createMultiChainWeightedValidator, - getUpdateConfigCall, - getCurrentSigners, type WeightedValidatorConfig, type WeightedSigner, getValidatorAddress, @@ -61,7 +62,10 @@ export { type MultiChainWeightedKernelAccountClient, createMultiChainWeightedKernelAccountClient, type MultiChainWeightedKernelAccountClientActions, - multiChainWeightedKernelAccountClientActions + multiChainWeightedKernelAccountClientActions, + type UpdateSignersDataParameters, + getCurrentSigners, + updateSignersData } export * from "./constants.js" export * from "./utils.js" diff --git a/plugins/multi-chain-weighted-validator/package.json b/plugins/multi-chain-weighted-validator/package.json index 328792c6..07cba3b3 100644 --- a/plugins/multi-chain-weighted-validator/package.json +++ b/plugins/multi-chain-weighted-validator/package.json @@ -1,6 +1,6 @@ { "name": "@zerodev/multi-chain-weighted-validator", - "version": "5.3.1", + "version": "5.4.0", "author": "ZeroDev", "main": "./_cjs/index.js", "module": "./_esm/index.js", @@ -40,9 +40,8 @@ "merkletreejs": "^0.3.11" }, "peerDependencies": { - "viem": ">=2.16.3 <2.18.0", - "@zerodev/sdk": "^5.2.1", - "@zerodev/webauthn-key": "^5.3.0", - "permissionless": ">=0.1.44 <=0.1.45" + "viem": "^2.21.40", + "@zerodev/sdk": "^5.4.0", + "@zerodev/webauthn-key": "^5.4.0" } } diff --git a/plugins/multi-chain-weighted-validator/signers/toECDSASigner.ts b/plugins/multi-chain-weighted-validator/signers/toECDSASigner.ts index 17fb291f..e9583e59 100644 --- a/plugins/multi-chain-weighted-validator/signers/toECDSASigner.ts +++ b/plugins/multi-chain-weighted-validator/signers/toECDSASigner.ts @@ -1,38 +1,28 @@ -import { constants, fixSignedData } from "@zerodev/sdk" +import { constants, fixSignedData, toSigner } from "@zerodev/sdk" +import type { Signer } from "@zerodev/sdk/types" import type { TypedData } from "abitype" -import { - SignTransactionNotSupportedBySmartAccount, - type SmartAccountSigner -} from "permissionless/accounts" -import type { Address, LocalAccount, TypedDataDefinition } from "viem" +import type { TypedDataDefinition } from "viem" import { toAccount } from "viem/accounts" import { SIGNER_TYPE } from "../constants.js" import type { WeightedSigner } from "../toMultiChainWeightedValidatorPlugin.js" -export type ECDSASignerParams< - TSource extends string = "custom", - TAddress extends Address = Address -> = { - signer: SmartAccountSigner +export type ECDSASignerParams = { + signer: Signer } -export function toECDSASigner< - TSource extends string = "custom", - TAddress extends Address = Address ->({ signer }: ECDSASignerParams): WeightedSigner { - const viemSigner: LocalAccount = { - ...signer, - signTransaction: (_, __) => { - throw new SignTransactionNotSupportedBySmartAccount() - } - } as LocalAccount +export async function toECDSASigner({ + signer +}: ECDSASignerParams): Promise { + const viemSigner = await toSigner({ signer }) const account = toAccount({ address: viemSigner.address, async signMessage({ message }) { return fixSignedData(await viemSigner.signMessage({ message })) }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new Error( + "Smart account signer doesn't need to sign transactions" + ) }, async signTypedData< const TTypedData extends TypedData | Record, diff --git a/plugins/multi-chain-weighted-validator/signers/toWebAuthnSigner.ts b/plugins/multi-chain-weighted-validator/signers/toWebAuthnSigner.ts index ebc78523..3157ad8f 100644 --- a/plugins/multi-chain-weighted-validator/signers/toWebAuthnSigner.ts +++ b/plugins/multi-chain-weighted-validator/signers/toWebAuthnSigner.ts @@ -10,7 +10,6 @@ import { uint8ArrayToHexString } from "@zerodev/webauthn-key" import type { TypedData } from "abitype" -import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" import { type Chain, type Client, @@ -140,7 +139,9 @@ export const toWebAuthnSigner = async < ]) }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new Error( + "Smart account signer doesn't need to sign transactions" + ) }, async signTypedData< const TTypedData extends TypedData | Record, diff --git a/plugins/multi-chain-weighted-validator/toMultiChainWeightedValidatorPlugin.ts b/plugins/multi-chain-weighted-validator/toMultiChainWeightedValidatorPlugin.ts index 0d2e3ef0..e797695d 100644 --- a/plugins/multi-chain-weighted-validator/toMultiChainWeightedValidatorPlugin.ts +++ b/plugins/multi-chain-weighted-validator/toMultiChainWeightedValidatorPlugin.ts @@ -1,25 +1,29 @@ -import type { GetKernelVersion, KernelValidator } from "@zerodev/sdk/types" +import { + SignTransactionNotSupportedBySmartAccountError, + toSigner, + validateKernelVersionWithEntryPoint +} from "@zerodev/sdk" +import type { + EntryPointType, + GetKernelVersion, + KernelValidator, + Signer +} from "@zerodev/sdk/types" import type { WebAuthnKey } from "@zerodev/webauthn-key" import type { TypedData } from "abitype" -import { - type UserOperation, - getEntryPointVersion, - getUserOperationHash -} from "permissionless" -import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" -import type { EntryPoint, GetEntryPointVersion } from "permissionless/types" import { type Address, - type Chain, type Client, type Hex, - type LocalAccount, - type Transport, type TypedDataDefinition, encodeAbiParameters, - encodeFunctionData, zeroAddress } from "viem" +import { + type EntryPointVersion, + type UserOperation, + getUserOperationHash +} from "viem/account-abstraction" import { toAccount } from "viem/accounts" import { getChainId, readContract } from "viem/actions" import { concatHex, getAction, toHex } from "viem/utils" @@ -28,12 +32,13 @@ import { MULTI_CHAIN_WEIGHTED_VALIDATOR_ADDRESS_V07, SIGNER_TYPE, decodeSignatures, - encodeSignatures + encodeSignatures, + sortByPublicKey } from "./index.js" import { encodeWebAuthnPubKey } from "./signers/toWebAuthnSigner.js" export type WeightedSigner = { - account: LocalAccount + account: Signer getDummySignature: () => Hex getPublicKey: () => Hex type: SIGNER_TYPE @@ -48,49 +53,42 @@ export interface WeightedValidatorConfig { delay?: number // in seconds } -// Sort addresses in descending order -const sortByPublicKey = ( - a: { publicKey: Hex } | { getPublicKey: () => Hex }, - b: { publicKey: Hex } | { getPublicKey: () => Hex } -) => { - if ("publicKey" in a && "publicKey" in b) - return a.publicKey.toLowerCase() < b.publicKey.toLowerCase() ? 1 : -1 - else if ("getPublicKey" in a && "getPublicKey" in b) - return a.getPublicKey().toLowerCase() < b.getPublicKey().toLowerCase() - ? 1 - : -1 - else return 0 -} - -export const getValidatorAddress = (entryPointAddress: EntryPoint): Address => { - const entryPointVersion = getEntryPointVersion(entryPointAddress) - if (entryPointVersion === "v0.6") +export const getValidatorAddress = < + entryPointVersion extends EntryPointVersion +>( + entryPoint: EntryPointType, + kernelVersion: GetKernelVersion, + validatorAddress?: Address +): Address => { + validateKernelVersionWithEntryPoint(entryPoint.version, kernelVersion) + if (entryPoint.version === "0.6") throw new Error("EntryPoint v0.6 not supported") - return MULTI_CHAIN_WEIGHTED_VALIDATOR_ADDRESS_V07 + return validatorAddress ?? MULTI_CHAIN_WEIGHTED_VALIDATOR_ADDRESS_V07 } export async function createMultiChainWeightedValidator< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined + entryPointVersion extends EntryPointVersion >( - client: Client, + client: Client, { config, - entryPoint: entryPointAddress, + entryPoint, signer, - validatorAddress, - kernelVersion: _ + validatorAddress: _validatorAddress, + kernelVersion }: { config?: WeightedValidatorConfig signer: WeightedSigner - entryPoint: entryPoint - kernelVersion: GetKernelVersion + entryPoint: EntryPointType + kernelVersion: GetKernelVersion validatorAddress?: Address } -): Promise> { - validatorAddress = - validatorAddress ?? getValidatorAddress(entryPointAddress) +): Promise> { + const validatorAddress = getValidatorAddress( + entryPoint, + kernelVersion, + _validatorAddress + ) if (!validatorAddress) { throw new Error("Validator address not provided") } @@ -130,11 +128,12 @@ export async function createMultiChainWeightedValidator< signer.getPublicKey().toLowerCase() ) } + const viemSigner = await toSigner({ signer: signer.account }) const account = toAccount({ address: zeroAddress, // note that this address is not used async signMessage({ message }) { - const signature = await signer.account.signMessage({ + const signature = await viemSigner.signMessage({ message }) @@ -144,7 +143,7 @@ export async function createMultiChainWeightedValidator< ]) }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new SignTransactionNotSupportedBySmartAccountError() }, async signTypedData< const TTypedData extends TypedData | Record, @@ -152,7 +151,7 @@ export async function createMultiChainWeightedValidator< | keyof TTypedData | "EIP712Domain" = keyof TTypedData >(typedData: TypedDataDefinition) { - const signature = await signer.account.signTypedData(typedData) + const signature = await viemSigner.signTypedData(typedData) return concatHex([ toHex(getIndexOfSigner(), { size: 1 }), @@ -167,8 +166,7 @@ export async function createMultiChainWeightedValidator< supportedKernelVersions: ">=0.3.0", address: validatorAddress, source: "MultiChainWeightedValidator", - getIdentifier: () => - validatorAddress ?? getValidatorAddress(entryPointAddress), + getIdentifier: () => validatorAddress, async getEnableData() { if (!config) return "0x" return concatHex([ @@ -197,9 +195,7 @@ export async function createMultiChainWeightedValidator< return 0n }, // Sign a user operation - async signUserOperation( - userOperation: UserOperation> - ) { + async signUserOperation(userOperation) { let signatures: readonly Hex[] = [] let merkleData: Hex = "0x" if (userOperation.signature !== "0x") { @@ -215,8 +211,9 @@ export async function createMultiChainWeightedValidator< userOperation: { ...userOperation, signature: "0x" - }, - entryPoint: entryPointAddress, + } as UserOperation, + entryPointAddress: entryPoint.address, + entryPointVersion: entryPoint.version, chainId: chainId }) @@ -227,9 +224,7 @@ export async function createMultiChainWeightedValidator< return encodeSignatures(merkleData, [...signatures, lastSignature]) }, - async getDummySignature( - userOperation: UserOperation> - ) { + async getStubSignature(userOperation) { let signatures: readonly Hex[] = [] let merkleData: Hex = "0x" if (userOperation.signature !== "0x") { @@ -258,9 +253,7 @@ export async function createMultiChainWeightedValidator< "readContract" )({ abi: MultiChainWeightedValidatorAbi, - address: - validatorAddress ?? - getValidatorAddress(entryPointAddress), + address: validatorAddress, functionName: "multiChainWeightedStorage", args: [kernelAccountAddress] }) @@ -272,9 +265,7 @@ export async function createMultiChainWeightedValidator< "readContract" )({ abi: MultiChainWeightedValidatorAbi, - address: - validatorAddress ?? - getValidatorAddress(entryPointAddress), + address: validatorAddress, functionName: "guardian", args: [BigInt(index), kernelAccountAddress] }) @@ -306,122 +297,3 @@ export async function createMultiChainWeightedValidator< } } } - -// TODO: move to client actions? -// -- approveUpdateConfigUserOp -// -- sendUpdateConfigUserOp -export function getUpdateConfigCall( - entryPointAddress: entryPoint, - config: WeightedValidatorConfig -): { - to: Address - value: bigint - data: Hex -} { - const validatorAddress = getValidatorAddress(entryPointAddress) - - // Check if sum of weights is equal or greater than threshold - let totalWeight = 0 - for (const signer of config.signers) { - totalWeight += signer.weight - } - if (totalWeight < config.threshold) { - throw new Error( - `Sum of weights (${totalWeight}) is less than threshold (${config.threshold})` - ) - } - - // sort signers by address in descending order - const configSigners = config - ? [...config.signers] - .map((signer) => - typeof signer.publicKey === "object" - ? { - ...signer, - publicKey: encodeWebAuthnPubKey( - signer.publicKey - ) as Hex - } - : { ...signer, publicKey: signer.publicKey as Hex } - ) - .sort(sortByPublicKey) - : [] - - return { - to: validatorAddress, - value: 0n, - data: encodeFunctionData({ - abi: MultiChainWeightedValidatorAbi, - functionName: "renew", - args: [ - concatHex([ - toHex(config.threshold, { size: 3 }), - toHex(config.delay || 0, { size: 6 }), - encodeAbiParameters( - [{ name: "guardiansData", type: "bytes[]" }], - [ - configSigners.map((cfg) => - concatHex([ - cfg.publicKey.length === 42 - ? SIGNER_TYPE.ECDSA - : SIGNER_TYPE.PASSKEY, - toHex(cfg.weight, { size: 3 }), - cfg.publicKey - ]) - ) - ] - ) - ]) - ] - }) - } -} - -export async function getCurrentSigners< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined ->( - client: Client, - { - entryPoint: entryPointAddress, - multiChainWeightedAccountAddress - }: { - entryPoint: entryPoint - multiChainWeightedAccountAddress: Address - } -): Promise> { - const validatorAddress = getValidatorAddress(entryPointAddress) - - const multiChainWeightedStorage = await getAction( - client, - readContract, - "readContract" - )({ - abi: MultiChainWeightedValidatorAbi, - address: validatorAddress, - functionName: "multiChainWeightedStorage", - args: [multiChainWeightedAccountAddress] - }) - - const guardiansLength = multiChainWeightedStorage[3] - - const signers: Array<{ encodedPublicKey: Hex; weight: number }> = [] - for (let i = 0; i < guardiansLength; i++) { - const guardian = await getAction( - client, - readContract, - "readContract" - )({ - abi: MultiChainWeightedValidatorAbi, - address: validatorAddress, - functionName: "guardian", - args: [BigInt(i), multiChainWeightedAccountAddress] - }) - signers.push({ - encodedPublicKey: guardian[2], - weight: guardian[1] - }) - } - return signers -} diff --git a/plugins/multi-chain-weighted-validator/utils.ts b/plugins/multi-chain-weighted-validator/utils.ts index 0b2197be..368be77b 100644 --- a/plugins/multi-chain-weighted-validator/utils.ts +++ b/plugins/multi-chain-weighted-validator/utils.ts @@ -25,3 +25,17 @@ export const decodeSignatures = ( signatures: [...signatures] } } + +// Sort addresses in descending order +export const sortByPublicKey = ( + a: { publicKey: Hex } | { getPublicKey: () => Hex }, + b: { publicKey: Hex } | { getPublicKey: () => Hex } +) => { + if ("publicKey" in a && "publicKey" in b) + return a.publicKey.toLowerCase() < b.publicKey.toLowerCase() ? 1 : -1 + else if ("getPublicKey" in a && "getPublicKey" in b) + return a.getPublicKey().toLowerCase() < b.getPublicKey().toLowerCase() + ? 1 + : -1 + else return 0 +} diff --git a/plugins/multi-tenant-session-account/CHANGELOG.md b/plugins/multi-tenant-session-account/CHANGELOG.md index 25eab25d..6ee06740 100644 --- a/plugins/multi-tenant-session-account/CHANGELOG.md +++ b/plugins/multi-tenant-session-account/CHANGELOG.md @@ -1,5 +1,23 @@ # @zerodev/session-account +## 5.4.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + +## 5.4.0-beta.1 + +### Patch Changes + +- Update `@zerodev/sdk` to `5.4.0-beta.x` and remove `permissionless` from peer deps + +## 5.4.0-beta.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + ## 5.3.2 ### Patch Changes diff --git a/plugins/multi-tenant-session-account/account/createSessionAccount.ts b/plugins/multi-tenant-session-account/account/createSessionAccount.ts index 3ec1a295..3747154b 100644 --- a/plugins/multi-tenant-session-account/account/createSessionAccount.ts +++ b/plugins/multi-tenant-session-account/account/createSessionAccount.ts @@ -1,27 +1,17 @@ -import { fixSignedData } from "@zerodev/sdk" +import { fixSignedData, toSigner } from "@zerodev/sdk" +import { getAccountNonce, isSmartAccountDeployed } from "@zerodev/sdk/actions" import { DUMMY_ECDSA_SIG } from "@zerodev/sdk/constants" -import { - type UserOperation, - getAccountNonce, - getUserOperationHash, - isSmartAccountDeployed -} from "permissionless" -import { - SignTransactionNotSupportedBySmartAccount, - type SmartAccount, - type SmartAccountSigner, - toSmartAccount -} from "permissionless/accounts" -import type { EntryPoint, GetEntryPointVersion } from "permissionless/types" +import type { + CallType, + EntryPointType, + GetEntryPointAbi, + Signer +} from "@zerodev/sdk/types" import { type Address, - type Chain, + type Assign, type Client, type Hex, - type LocalAccount, - type PublicActions, - type PublicRpcSchema, - type Transport, type TypedData, type TypedDataDefinition, concatHex, @@ -32,8 +22,19 @@ import { toFunctionSelector, toHex } from "viem" +import { + type EntryPointVersion, + type SmartAccount, + type SmartAccountImplementation, + type UserOperation, + entryPoint06Abi, + entryPoint07Abi, + entryPoint07Address, + getUserOperationHash, + toSmartAccount +} from "viem/account-abstraction" import { toAccount } from "viem/accounts" -import { getChainId, signMessage, signTypedData } from "viem/actions" +import { getChainId, signMessage } from "viem/actions" import { MultiTenantSessionAccountAbi } from "../abi/MultiTenantSessionAccountAbi.js" import { DMVersionToAddressMap, @@ -45,30 +46,38 @@ import { toDelegationHash } from "../utils/delegationManager.js" -export type SessionAccount< - entryPoint extends EntryPoint, - transport extends Transport = Transport, - chain extends Chain | undefined = Chain | undefined -> = SmartAccount & { - delegations: Delegation[] - encodeCallData: ( - args: SessionAccountEncodeCallDataArgs, - _delegations?: Delegation[] - ) => Promise -} +export type SessionAccountImplementation< + entryPointVersion extends EntryPointVersion = "0.7" +> = Assign< + SmartAccountImplementation< + GetEntryPointAbi, + entryPointVersion + >, + { + sign: NonNullable + delegations: Delegation[] + encodeCalls: ( + calls: Parameters[0], + callType?: CallType | undefined, + _delegations?: Delegation[] + ) => Promise + } +> export type CreateSessionAccountParameters< - entryPoint extends EntryPoint, - TSource extends string = "custom", - TAddress extends Address = Address + entryPointVersion extends EntryPointVersion = "0.7" > = { - entryPoint: entryPoint - sessionKeySigner: SmartAccountSigner + entryPoint: EntryPointType + sessionKeySigner: Signer delegations: Delegation[] multiTenantSessionAccountAddress?: Address delegatorInitCode?: Hex } +export type CreateSessionAccountReturnType< + entryPointVersion extends EntryPointVersion = "0.7" +> = SmartAccount> + export type SessionAccountEncodeCallDataArgs = | { to: Address @@ -82,34 +91,19 @@ export type SessionAccountEncodeCallDataArgs = }[] export async function createSessionAccount< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TSource extends string = "custom", - TAddress extends Address = Address + entryPointVersion extends EntryPointVersion >( - client: Client< - TTransport, - TChain, - undefined, - PublicRpcSchema, - PublicActions - >, + client: Client, { - entryPoint: entryPointAddress, + entryPoint, delegations, sessionKeySigner, multiTenantSessionAccountAddress: accountAddress = MULTI_TENANT_SESSION_ACCOUNT_ADDRESS, delegatorInitCode = "0x" - }: CreateSessionAccountParameters -): Promise> { - const viemSigner: LocalAccount = { - ...sessionKeySigner, - signTransaction: (_, __) => { - throw new SignTransactionNotSupportedBySmartAccount() - } - } as LocalAccount + }: CreateSessionAccountParameters +): Promise> { + const viemSigner = await toSigner({ signer: sessionKeySigner }) const chainId = await getChainId(client) @@ -120,7 +114,9 @@ export async function createSessionAccount< return signMessage(client, { account: viemSigner, message }) }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new Error( + "Smart account signer doesn't need to sign transactions" + ) }, async signTypedData< const TTypedData extends TypedData | Record, @@ -128,18 +124,9 @@ export async function createSessionAccount< | keyof TTypedData | "EIP712Domain" = keyof TTypedData >(typedData: TypedDataDefinition) { - return signTypedData( - client, - { - account: viemSigner, - ...typedData - } - ) + return viemSigner.signTypedData(typedData) } }) - const getInitCode = async () => { - return "0x" as Hex - } const getDelegations = async () => { delegations.unshift({ delegate: accountAddress, @@ -220,149 +207,158 @@ export async function createSessionAccount< return delegatorInitCode } - return { + const _entryPoint = { + address: entryPoint?.address ?? entryPoint07Address, + abi: ((entryPoint?.version ?? "0.7") === "0.6" + ? entryPoint06Abi + : entryPoint07Abi) as GetEntryPointAbi, + version: entryPoint?.version ?? "0.7" + } as const + + return toSmartAccount>({ + client, + entryPoint: _entryPoint, delegations, - ...toSmartAccount({ - client, - source: "multiTenantSessionAccount", - entryPoint: entryPointAddress, - address: accountAddress, - encodeCallData: async ( - tx: SessionAccountEncodeCallDataArgs, - _delegations?: Delegation[] - ) => { - const isBatch = Array.isArray(tx) - const executeUserOpSig = toFunctionSelector( - getAbiItem({ - abi: MultiTenantSessionAccountAbi, - name: "executeUserOp" - }) - ) + async getAddress() { + return accountAddress + }, + async encodeCalls(calls, _callType, _delegations) { + const isBatch = calls.length > 1 - const execMode = concatHex([ - isBatch ? "0x01" : "0x00", // 1 byte - "0x00", // 1 byte - "0x00000000", // 4 bytes - "0x00000000", // 4 bytes - pad("0x00000000", { size: 22 }) - ]) - const execData = isBatch - ? encodeAbiParameters( - [ - { - name: "executionBatch", - type: "tuple[]", - components: [ - { - name: "target", - type: "address" - }, - { - name: "value", - type: "uint256" - }, - { - name: "callData", - type: "bytes" - } - ] - } - ], - [ - tx.map((arg) => { - return { - target: arg.to, - value: arg.value, - callData: arg.data - } - }) - ] - ) - : concatHex([tx.to, toHex(tx.value, { size: 32 }), tx.data]) + const call = calls.length === 0 ? undefined : calls[0] - return concatHex([ - executeUserOpSig, - encodeAbiParameters( - [ - getDelegationTupleType(true), - { - type: "bytes32", - name: "execMode" - }, - { - type: "bytes", - name: "execData" - }, - { - type: "bytes", - name: "delegatorInitCode" - } - ], - [ - _delegations ?? delegations, - execMode, - execData, - await getDelegatorInitCode() - ] - ) - ]) - }, + if (!call) { + throw new Error("No calls to encode") + } - encodeDeployCallData: async (_tx) => { - throw new Error("encodeDeployCallData not supported yet") - }, - getDummySignature: async () => { - const signature = fixSignedData(DUMMY_ECDSA_SIG) - const { r, s, v, yParity } = parseSignature(signature) - return concatHex([r, s, toHex(v ?? yParity, { size: 1 })]) - }, - async getFactory() { - return undefined - }, - async getFactoryData() { - return undefined - }, - getInitCode, - getNonce: () => { - const key = pad(sessionAccount.address, { - dir: "right", - size: 24 - }) - return getAccountNonce(client, { - sender: accountAddress, - entryPoint: entryPointAddress, - key: BigInt(key) - }) - }, - signMessage: async () => { - throw new Error("signMessage not supported yet") - }, - signTransaction: () => { - throw new SignTransactionNotSupportedBySmartAccount() - }, - signTypedData: async () => { - throw new Error("signTypedData not supported yet") - }, - signUserOperation: async ( - userOperation: UserOperation> - ) => { - const hash = getUserOperationHash({ - userOperation: { - ...userOperation, - signature: "0x" - }, - entryPoint: entryPointAddress, - chainId: chainId + const executeUserOpSig = toFunctionSelector( + getAbiItem({ + abi: MultiTenantSessionAccountAbi, + name: "executeUserOp" }) - const signature = fixSignedData( - await signMessage(client, { - account: viemSigner, - message: { raw: hash } - }) + ) + + const execMode = concatHex([ + isBatch ? "0x01" : "0x00", // 1 byte + "0x00", // 1 byte + "0x00000000", // 4 bytes + "0x00000000", // 4 bytes + pad("0x00000000", { size: 22 }) + ]) + const execData = isBatch + ? encodeAbiParameters( + [ + { + name: "executionBatch", + type: "tuple[]", + components: [ + { + name: "target", + type: "address" + }, + { + name: "value", + type: "uint256" + }, + { + name: "callData", + type: "bytes" + } + ] + } + ], + [ + calls.map((arg) => { + return { + target: arg.to, + value: arg.value || 0n, + callData: arg.data || "0x" + } + }) + ] + ) + : concatHex([ + call.to, + toHex(call.value || 0n, { size: 32 }), + call.data || "0x" + ]) + + return concatHex([ + executeUserOpSig, + encodeAbiParameters( + [ + getDelegationTupleType(true), + { + type: "bytes32", + name: "execMode" + }, + { + type: "bytes", + name: "execData" + }, + { + type: "bytes", + name: "delegatorInitCode" + } + ], + [ + _delegations ?? delegations, + execMode, + execData, + await getDelegatorInitCode() + ] ) - const { r, s, v, yParity } = parseSignature(signature) - return concatHex([r, s, toHex(v ?? yParity, { size: 1 })]) + ]) + }, + getStubSignature: async () => { + const signature = fixSignedData(DUMMY_ECDSA_SIG) + const { r, s, v, yParity } = parseSignature(signature) + return concatHex([r, s, toHex(v ?? yParity, { size: 1 })]) + }, + async getFactoryArgs() { + return { + factory: undefined, + factoryData: undefined } - }) - } + }, + getNonce: () => { + const key = pad(sessionAccount.address, { + dir: "right", + size: 24 + }) + return getAccountNonce(client, { + address: accountAddress, + entryPointAddress: entryPoint.address, + key: BigInt(key) + }) + }, + async sign({ hash }) { + return this.signMessage({ message: hash }) + }, + signMessage: async () => { + throw new Error("signMessage not supported yet") + }, + signTypedData: async () => { + throw new Error("signTypedData not supported yet") + }, + signUserOperation: async (userOperation) => { + const hash = getUserOperationHash({ + userOperation: { + ...userOperation, + signature: "0x" + } as UserOperation, + entryPointAddress: entryPoint.address, + entryPointVersion: entryPoint.version, + chainId: chainId + }) + const signature = fixSignedData( + await signMessage(client, { + account: viemSigner, + message: { raw: hash } + }) + ) + const { r, s, v, yParity } = parseSignature(signature) + return concatHex([r, s, toHex(v ?? yParity, { size: 1 })]) + } + }) } diff --git a/plugins/multi-tenant-session-account/actions/delegate.ts b/plugins/multi-tenant-session-account/actions/delegate.ts index 4222f443..f615144e 100644 --- a/plugins/multi-tenant-session-account/actions/delegate.ts +++ b/plugins/multi-tenant-session-account/actions/delegate.ts @@ -1,31 +1,30 @@ -import { AccountOrClientNotFoundError, parseAccount } from "permissionless" -import type { SmartAccount } from "permissionless/accounts" -import type { SendTransactionWithPaymasterParameters } from "permissionless/actions/smartAccount" -import { sendTransaction } from "permissionless/actions/smartAccount" -import type { EntryPoint, Prettify } from "permissionless/types" -import type { Address, Chain, Client, Hash, Transport } from "viem" +import { AccountNotFoundError } from "@zerodev/sdk" +import { sendTransaction } from "@zerodev/sdk/actions" +import type { + Address, + Chain, + Client, + Hash, + Prettify, + SendTransactionParameters, + Transport +} from "viem" import { encodeFunctionData } from "viem" -import { getAction } from "viem/utils" +import type { + SendUserOperationParameters, + SmartAccount +} from "viem/account-abstraction" +import { getAction, parseAccount } from "viem/utils" import { DelegationManagerAbi } from "../abi/DelegationManagerAbi.js" import { DMVersionToAddressMap, ROOT_AUTHORITY } from "../constants.js" import type { Delegation } from "../types.js" export type SendDelegateUserOperationParameters< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | SmartAccount - | undefined = - | SmartAccount - | undefined + account extends SmartAccount | undefined = SmartAccount | undefined, + chain extends Chain | undefined = Chain | undefined, + chainOverride extends Chain | undefined = Chain | undefined > = Prettify< - SendTransactionWithPaymasterParameters< - entryPoint, - TTransport, - TChain, - TAccount - > & { + Partial> & { delegation?: Delegation sessionKeyAddress: Delegation["delegate"] caveats: Delegation["caveats"] @@ -34,22 +33,11 @@ export type SendDelegateUserOperationParameters< > export async function delegate< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | SmartAccount - | undefined = - | SmartAccount - | undefined + account extends SmartAccount | undefined, + chain extends Chain | undefined >( - client: Client, - args: SendDelegateUserOperationParameters< - entryPoint, - TTransport, - TChain, - TAccount - > + client: Client, + args: SendDelegateUserOperationParameters ): Promise { const { account: account_ = client.account, @@ -61,42 +49,36 @@ export async function delegate< } = args if (!account_) { - throw new AccountOrClientNotFoundError({ - docsPath: "/docs/actions/wallet/sendTransaction" - }) + throw new AccountNotFoundError() } - const account = parseAccount(account_) as SmartAccount< - entryPoint, - string, - TTransport, - TChain - > - - if (account.type !== "local") { - throw new Error("RPC account type not supported") - } + const account = parseAccount(account_) as SmartAccount return await getAction( client, - sendTransaction, + sendTransaction, "sendTransaction" )({ ...args, - to: delegationManagerAddress, - data: encodeFunctionData({ - abi: DelegationManagerAbi, - functionName: "delegate", - args: [ - delegation ?? { - delegate: sessionKeyAddress, - caveats, - authority: ROOT_AUTHORITY, - delegator: account.address, - salt: 0n, - signature: "0x" - } - ] - }) - }) + calls: [ + { + to: delegationManagerAddress, + data: encodeFunctionData({ + abi: DelegationManagerAbi, + functionName: "delegate", + args: [ + delegation ?? { + delegate: sessionKeyAddress, + caveats, + authority: ROOT_AUTHORITY, + delegator: account.address, + salt: 0n, + signature: "0x" + } + ] + }), + value: 0n + } + ] + } as SendTransactionParameters | SendUserOperationParameters) } diff --git a/plugins/multi-tenant-session-account/actions/encodeCallDataWithCAB.ts b/plugins/multi-tenant-session-account/actions/encodeCallDataWithCAB.ts index 189f5509..22e1859d 100644 --- a/plugins/multi-tenant-session-account/actions/encodeCallDataWithCAB.ts +++ b/plugins/multi-tenant-session-account/actions/encodeCallDataWithCAB.ts @@ -4,26 +4,12 @@ import { createInvoiceCall, withdrawCall } from "@zerodev/cab" -import { KernelV3ExecuteAbi } from "@zerodev/sdk" -import { - AccountOrClientNotFoundError, - deepHexlify, - parseAccount -} from "permissionless" -import type { SmartAccount } from "permissionless/accounts" -import type { - EntryPoint, - GetAccountParameter, - GetEntryPointVersion, - Prettify, - UserOperation -} from "permissionless/types" +import { AccountNotFoundError, KernelV3ExecuteAbi } from "@zerodev/sdk" import { http, type Address, type Chain, type Client, - type GetChainParameter, type Hash, type Hex, type Transport, @@ -36,8 +22,13 @@ import { pad, toHex } from "viem" +import type { + GetSmartAccountParameter, + SmartAccount +} from "viem/account-abstraction" +import { parseAccount } from "viem/accounts" import { getChainId } from "viem/actions" -import type { SessionAccount } from "../account/createSessionAccount.js" +import type { SessionAccountImplementation } from "../account/createSessionAccount.js" import { CAB_PAYMASTER_SERVER_URL, DMVersionToAddressMap @@ -55,43 +46,21 @@ import { import { toDelegationHash } from "../utils/index.js" export type EncodeCallDataWithCABParameters< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | SmartAccount - | undefined = - | SmartAccount - | undefined, - TChainOverride extends Chain | undefined = Chain | undefined -> = Prettify< - GetAccountParameter & - GetChainParameter & { - calls: { to: Address; value: bigint; data: Hex }[] - repayTokens: RepayTokens - enforcerVersion?: ENFORCER_VERSION - } -> + account extends SmartAccount | undefined = SmartAccount | undefined, + accountOverride extends SmartAccount | undefined = SmartAccount | undefined +> = GetSmartAccountParameter & { + calls: { to: Address; value: bigint; data: Hex }[] + repayTokens: RepayTokens + enforcerVersion?: ENFORCER_VERSION +} export async function encodeCallDataWithCAB< - TChain extends Chain | undefined, - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChainOverride extends Chain | undefined = Chain | undefined, - TAccount extends - | SmartAccount - | undefined = - | SmartAccount - | undefined + account extends SmartAccount | undefined, + chain extends Chain | undefined, + accountOverride extends SmartAccount | undefined = undefined >( - client: Client, - args: EncodeCallDataWithCABParameters< - entryPoint, - TTransport, - TChain, - TAccount, - TChainOverride - > + client: Client, + args: EncodeCallDataWithCABParameters ): Promise { const cabEnforcerAddress = getEnforcerAddress( args.enforcerVersion ?? "v0_2" @@ -99,18 +68,14 @@ export async function encodeCallDataWithCAB< const { account: account_ = client.account, calls, repayTokens } = args if (!account_) { - throw new AccountOrClientNotFoundError({ - docsPath: "/docs/actions/wallet/sendTransaction" - }) + throw new AccountNotFoundError() } - const account = parseAccount(account_) as SessionAccount + const account = parseAccount( + account_ + ) as SmartAccount const chainId = client.chain ? client.chain.id : await getChainId(client) - if (account.type !== "local") { - throw new Error("RPC account type not supported") - } - const cabClient = createPublicClient({ transport: http(CAB_PAYMASTER_SERVER_URL) }) @@ -183,8 +148,8 @@ export async function encodeCallDataWithCAB< preVerificationGas: 0n, paymasterPostOpGasLimit: 0n, paymasterVerificationGasLimit: 0n - }) as Partial>>, - account.entryPoint, + }), + account.entryPoint.address, // @ts-expect-error chainId, // @ts-expect-error @@ -284,8 +249,8 @@ export async function encodeCallDataWithCAB< preVerificationGas: 0n, paymasterPostOpGasLimit: 0n, paymasterVerificationGasLimit: 0n - }) as Partial>>, - account.entryPoint, + }), + account.entryPoint.address, // @ts-expect-error chainId, // @ts-expect-error @@ -397,7 +362,7 @@ export async function encodeCallDataWithCAB< args: cabEnforcerArgs } } - return await account.encodeCallData([ + return await account.encodeCalls([ ...withdrawCall({ accountAddress: delegatorAccountAddress, paymaster: cabPaymasterTokensResponse.paymaster, diff --git a/plugins/multi-tenant-session-account/actions/installDMAndDelegate.ts b/plugins/multi-tenant-session-account/actions/installDMAndDelegate.ts index 5d0cb77b..436c5d39 100644 --- a/plugins/multi-tenant-session-account/actions/installDMAndDelegate.ts +++ b/plugins/multi-tenant-session-account/actions/installDMAndDelegate.ts @@ -1,35 +1,32 @@ -import { AccountOrClientNotFoundError, parseAccount } from "permissionless" -import type { SmartAccount } from "permissionless/accounts" -import type { SendTransactionsWithPaymasterParameters } from "permissionless/actions/smartAccount" -import { sendTransactions } from "permissionless/actions/smartAccount" -import type { EntryPoint, Prettify } from "permissionless/types" -import type { Address, Chain, Client, Hash, Transport } from "viem" +import { AccountNotFoundError } from "@zerodev/sdk" +import { sendTransaction } from "@zerodev/sdk/actions" +import type { + Address, + Chain, + Client, + Hash, + Prettify, + SendTransactionParameters, + Transport +} from "viem" import { encodeFunctionData } from "viem" -import { getAction } from "viem/utils" +import type { + SendUserOperationParameters, + SmartAccount +} from "viem/account-abstraction" +import { getAction, parseAccount } from "viem/utils" import { DelegationManagerAbi } from "../abi/DelegationManagerAbi.js" +import type { SessionAccountImplementation } from "../account/createSessionAccount.js" import { DMVersionToAddressMap, ROOT_AUTHORITY } from "../constants.js" import type { Delegation } from "../types.js" import { getInstallDMAsExecutorCallData } from "../utils/delegationManager.js" export type SendInstallDMAndDelegateUserOperationParameters< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | SmartAccount - | undefined = - | SmartAccount - | undefined + account extends SmartAccount | undefined = SmartAccount | undefined, + chain extends Chain | undefined = Chain | undefined, + chainOverride extends Chain | undefined = Chain | undefined > = Prettify< - Omit< - SendTransactionsWithPaymasterParameters< - entryPoint, - TTransport, - TChain, - TAccount - >, - "transactions" - > & { + Partial> & { sessionKeyAddress: Delegation["delegate"] caveats: Delegation["caveats"] delegationManagerAddress?: Address @@ -37,22 +34,11 @@ export type SendInstallDMAndDelegateUserOperationParameters< > export async function installDMAndDelegate< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | SmartAccount - | undefined = - | SmartAccount - | undefined + account extends SmartAccount | undefined, + chain extends Chain | undefined >( - client: Client, - args: SendInstallDMAndDelegateUserOperationParameters< - entryPoint, - TTransport, - TChain, - TAccount - > + client: Client, + args: SendInstallDMAndDelegateUserOperationParameters ): Promise { const { account: account_ = client.account, @@ -63,29 +49,20 @@ export async function installDMAndDelegate< } = args if (!account_) { - throw new AccountOrClientNotFoundError({ - docsPath: "/docs/actions/wallet/sendTransaction" - }) + throw new AccountNotFoundError() } - const account = parseAccount(account_) as SmartAccount< - entryPoint, - string, - TTransport, - TChain - > - - if (account.type !== "local") { - throw new Error("RPC account type not supported") - } + const account = parseAccount( + account_ + ) as SmartAccount return await getAction( client, - sendTransactions, - "sendTransactions" + sendTransaction, + "sendTransaction" )({ ...args, - transactions: [ + calls: [ { to: account.address, data: getInstallDMAsExecutorCallData(), @@ -110,10 +87,5 @@ export async function installDMAndDelegate< value: 0n } ] - } as SendTransactionsWithPaymasterParameters< - entryPoint, - TTransport, - TChain, - TAccount - >) + } as SendTransactionParameters | SendUserOperationParameters) } diff --git a/plugins/multi-tenant-session-account/actions/installDMAsExecutor.ts b/plugins/multi-tenant-session-account/actions/installDMAsExecutor.ts index d843418d..58e460dc 100644 --- a/plugins/multi-tenant-session-account/actions/installDMAsExecutor.ts +++ b/plugins/multi-tenant-session-account/actions/installDMAsExecutor.ts @@ -1,74 +1,56 @@ -import { AccountOrClientNotFoundError, parseAccount } from "permissionless" -import type { SmartAccount } from "permissionless/accounts" -import type { SendTransactionWithPaymasterParameters } from "permissionless/actions/smartAccount" -import { sendTransaction } from "permissionless/actions/smartAccount" -import type { EntryPoint, Prettify } from "permissionless/types" -import type { Chain, Client, Hash, Transport } from "viem" -import { getAction } from "viem/utils" +import { AccountNotFoundError } from "@zerodev/sdk" +import { sendTransaction } from "@zerodev/sdk/actions" +import type { + Chain, + Client, + Hash, + Prettify, + SendTransactionParameters, + Transport +} from "viem" +import type { + SendUserOperationParameters, + SmartAccount +} from "viem/account-abstraction" +import { getAction, parseAccount } from "viem/utils" +import type { SessionAccountImplementation } from "../account/createSessionAccount.js" import { getInstallDMAsExecutorCallData } from "../utils/delegationManager.js" export type SendInstallDMAsExecutorUserOperationParameters< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | SmartAccount - | undefined = - | SmartAccount - | undefined -> = Prettify< - SendTransactionWithPaymasterParameters< - entryPoint, - TTransport, - TChain, - TAccount - > -> + account extends SmartAccount | undefined = SmartAccount | undefined, + chain extends Chain | undefined = Chain | undefined, + chainOverride extends Chain | undefined = Chain | undefined +> = Prettify>> export async function installDMAsExecutor< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | SmartAccount - | undefined = - | SmartAccount - | undefined + account extends SmartAccount | undefined, + chain extends Chain | undefined >( - client: Client, - args: SendInstallDMAsExecutorUserOperationParameters< - entryPoint, - TTransport, - TChain, - TAccount - > + client: Client, + args: SendInstallDMAsExecutorUserOperationParameters ): Promise { const { account: account_ = client.account } = args if (!account_) { - throw new AccountOrClientNotFoundError({ - docsPath: "/docs/actions/wallet/sendTransaction" - }) + throw new AccountNotFoundError() } - const account = parseAccount(account_) as SmartAccount< - entryPoint, - string, - TTransport, - TChain - > - - if (account.type !== "local") { - throw new Error("RPC account type not supported") - } + const account = parseAccount( + account_ + ) as SmartAccount return await getAction( client, - sendTransaction, + sendTransaction, "sendTransaction" )({ ...args, - to: account.address, - data: getInstallDMAsExecutorCallData() - }) + calls: [ + { + to: account.address, + data: getInstallDMAsExecutorCallData(), + value: 0n + } + ] + } as SendTransactionParameters | SendUserOperationParameters) } diff --git a/plugins/multi-tenant-session-account/actions/signDelegation.ts b/plugins/multi-tenant-session-account/actions/signDelegation.ts index 0907fc54..3b8d21fc 100644 --- a/plugins/multi-tenant-session-account/actions/signDelegation.ts +++ b/plugins/multi-tenant-session-account/actions/signDelegation.ts @@ -1,44 +1,31 @@ -import { AccountOrClientNotFoundError, parseAccount } from "permissionless" -import type { SmartAccount } from "permissionless/accounts" -import type { - EntryPoint, - GetAccountParameter, - Prettify -} from "permissionless/types" +import { AccountNotFoundError } from "@zerodev/sdk" import type { Address, Chain, Client, Hash, Transport } from "viem" import { decodeAbiParameters } from "viem" +import type { + GetSmartAccountParameter, + SmartAccount +} from "viem/account-abstraction" +import { parseAccount } from "viem/accounts" import { getChainId } from "viem/actions" +import type { SessionAccountImplementation } from "../account/createSessionAccount.js" import { DMVersionToAddressMap } from "../constants.js" import type { Delegation } from "../types.js" export type SignDelegationParameters< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | SmartAccount - | undefined = - | SmartAccount - | undefined -> = Prettify< - { - delegation: Delegation - delegationManagerAddress?: Address - } & GetAccountParameter -> + account extends SmartAccount | undefined = SmartAccount | undefined, + accountOverride extends SmartAccount | undefined = SmartAccount | undefined +> = { + delegation: Delegation + delegationManagerAddress?: Address +} & GetSmartAccountParameter export async function signDelegation< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | SmartAccount - | undefined = - | SmartAccount - | undefined + account extends SmartAccount | undefined, + chain extends Chain | undefined, + accountOverride extends SmartAccount | undefined = undefined >( - client: Client, - args: SignDelegationParameters + client: Client, + args: SignDelegationParameters ): Promise { const { account: account_ = client.account, @@ -48,24 +35,15 @@ export async function signDelegation< } = args if (!account_) { - throw new AccountOrClientNotFoundError({ - docsPath: "/docs/actions/wallet/sendTransaction" - }) + throw new AccountNotFoundError() } - const account = parseAccount(account_) as SmartAccount< - entryPoint, - string, - TTransport, - TChain - > + const account = parseAccount( + account_ + ) as SmartAccount const chainId = client.chain ? client.chain.id : await getChainId(client) - if (account.type !== "local") { - throw new Error("RPC account type not supported") - } - const signature = await account.signTypedData({ domain: { chainId, diff --git a/plugins/multi-tenant-session-account/clients/decorators/dmActionsEip7710.ts b/plugins/multi-tenant-session-account/clients/decorators/dmActionsEip7710.ts index 96c854bb..d8df75df 100644 --- a/plugins/multi-tenant-session-account/clients/decorators/dmActionsEip7710.ts +++ b/plugins/multi-tenant-session-account/clients/decorators/dmActionsEip7710.ts @@ -1,6 +1,5 @@ -import type { SmartAccount } from "permissionless/accounts" -import type { EntryPoint } from "permissionless/types" import type { Chain, Client, Hash, Transport } from "viem" +import type { SmartAccount } from "viem/account-abstraction" import { type EncodeCallDataWithCABParameters, type SendDelegateUserOperationParameters, @@ -16,104 +15,65 @@ import { import type { ENFORCER_VERSION } from "../../enforcers/cab-paymaster/toCABPaymasterEnforcer.js" export type DMActionsEip7710< - TEntryPoint extends EntryPoint, - TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | SmartAccount - | undefined = - | SmartAccount - | undefined + TSmartAccount extends SmartAccount | undefined = SmartAccount | undefined > = { - signDelegation: ( - args: SignDelegationParameters< - TEntryPoint, - TTransport, - TChain, - TAccount - > + signDelegation: < + accountOverride extends SmartAccount | undefined = + | SmartAccount + | undefined + >( + args: Parameters< + typeof signDelegation + >[1] ) => Promise - encodeCallDataWithCAB: ( - args: EncodeCallDataWithCABParameters< - TEntryPoint, - TTransport, - TChain, - TAccount, - TChainOverride - > + encodeCallDataWithCAB: ( + args: Parameters>[1] ) => Promise installDMAndDelegate: ( - args: SendInstallDMAndDelegateUserOperationParameters< - TEntryPoint, - TTransport, - TChain, - TAccount - > + args: SendInstallDMAndDelegateUserOperationParameters ) => Promise installDMAsExecutor: ( - args: SendInstallDMAsExecutorUserOperationParameters< - TEntryPoint, - TTransport, - TChain, - TAccount - > - ) => Promise - delegate: ( - args: SendDelegateUserOperationParameters< - TEntryPoint, - TTransport, - TChain, - TAccount - > + args: SendInstallDMAsExecutorUserOperationParameters ) => Promise + delegate: (args: SendDelegateUserOperationParameters) => Promise } -const dmActionsEip7710 = - < - TEntryPoint extends EntryPoint, - TTransport extends Transport, +function dmActionsEip7710({ + enforcerVersion +}: { + enforcerVersion: ENFORCER_VERSION +}) { + return < TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | SmartAccount - | undefined = - | SmartAccount + TSmartAccount extends SmartAccount | undefined = + | SmartAccount + | undefined, + accountOverride extends SmartAccount | undefined = + | SmartAccount | undefined - >({ - enforcerVersion - }: { - enforcerVersion: ENFORCER_VERSION - }) => - ( - client: Client - ): DMActionsEip7710 => ({ + >( + client: Client + ): DMActionsEip7710 => ({ signDelegation: (args) => signDelegation( - client as Client, + client as Client, { ...args - } as SignDelegationParameters< - TEntryPoint, - TTransport, - TChain, - TAccount - > + } as SignDelegationParameters ), encodeCallDataWithCAB: (args) => - encodeCallDataWithCAB( - client as Client, - { - ...args, - enforcerVersion: enforcerVersion - } as EncodeCallDataWithCABParameters< - TEntryPoint, - TTransport, - TChain, - TAccount - > - ), + encodeCallDataWithCAB(client, { + ...args, + enforcerVersion: enforcerVersion + } as EncodeCallDataWithCABParameters< + TSmartAccount, + accountOverride + >), installDMAndDelegate: (args) => installDMAndDelegate(client, args), - installDMAsExecutor: (args) => installDMAsExecutor(client, { ...args }), + installDMAsExecutor: (args) => installDMAsExecutor(client, args), delegate: (args) => delegate(client, args) }) +} export { dmActionsEip7710 } diff --git a/plugins/multi-tenant-session-account/index.ts b/plugins/multi-tenant-session-account/index.ts index 0aef08bf..c6108bc7 100644 --- a/plugins/multi-tenant-session-account/index.ts +++ b/plugins/multi-tenant-session-account/index.ts @@ -1,6 +1,8 @@ export { type CreateSessionAccountParameters, - type SessionAccount, + type CreateSessionAccountReturnType, + type SessionAccountEncodeCallDataArgs, + type SessionAccountImplementation, createSessionAccount } from "./account/createSessionAccount.js" diff --git a/plugins/multi-tenant-session-account/package.json b/plugins/multi-tenant-session-account/package.json index 839940e9..b49afe2b 100644 --- a/plugins/multi-tenant-session-account/package.json +++ b/plugins/multi-tenant-session-account/package.json @@ -1,6 +1,6 @@ { "name": "@zerodev/session-account", - "version": "5.3.2", + "version": "5.4.0", "author": "ZeroDev", "main": "./_cjs/index.js", "module": "./_esm/index.js", @@ -64,9 +64,8 @@ "lint:fix": "bun run lint --apply" }, "peerDependencies": { - "viem": ">=2.16.3 <2.18.0", - "permissionless": ">=0.1.44 <=0.1.45", - "@zerodev/sdk": "^5.3.14", + "viem": "^2.21.40", + "@zerodev/sdk": "^5.4.0", "@zerodev/cab": "^0.0.25" }, "dependencies": { diff --git a/plugins/multichain/actions/index.ts b/plugins/multichain/actions/index.ts deleted file mode 100644 index 3668e10d..00000000 --- a/plugins/multichain/actions/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./prepareMultiUserOpRequest.js" -export * from "./type.js" diff --git a/plugins/multichain/actions/prepareMultiUserOpRequest.ts b/plugins/multichain/actions/prepareMultiUserOpRequest.ts deleted file mode 100644 index 8061218e..00000000 --- a/plugins/multichain/actions/prepareMultiUserOpRequest.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { - AccountOrClientNotFoundError, - type UserOperation, - estimateUserOperationGas, - parseAccount -} from "permissionless" -import type { SmartAccount } from "permissionless/accounts" -import type { - PrepareUserOperationRequestParameters, - PrepareUserOperationRequestReturnType, - SponsorUserOperationReturnType -} from "permissionless/actions/smartAccount" -import type { - ENTRYPOINT_ADDRESS_V07_TYPE, - EntryPoint, - GetEntryPointVersion -} from "permissionless/types" -import type { StateOverrides } from "permissionless/types/bundler" -import type { Chain, Client, Transport } from "viem" -import { estimateFeesPerGas, getChainId } from "viem/actions" -import type { Prettify } from "viem/chains" -import { getAction } from "viem/utils" -import { ecdsaGetMultiUserOpDummySignature } from "../ecdsa/ecdsaGetMultiUserOpDummySignature.js" -import { webauthnGetMultiUserOpDummySignature } from "../webauthn/webauthnGetMultiUserOpDummySignature.js" -import { ValidatorType } from "./type.js" - -export async function prepareMultiUserOpRequest< - entryPoint extends EntryPoint = ENTRYPOINT_ADDRESS_V07_TYPE, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | SmartAccount - | undefined = - | SmartAccount - | undefined ->( - client: Client, - args: Prettify< - PrepareUserOperationRequestParameters< - entryPoint, - TTransport, - TChain, - TAccount - > - >, - validatorType: ValidatorType, - numOfUserOps: number, - stateOverrides?: StateOverrides -): Promise>> { - const { - account: account_ = client.account, - userOperation: partialUserOperation, - middleware - } = args - - if (!account_) throw new AccountOrClientNotFoundError() - - const account = parseAccount(account_) as SmartAccount< - ENTRYPOINT_ADDRESS_V07_TYPE, - string, - TTransport, - TChain - > - - const [sender, nonce, factory, factoryData, callData, gasEstimation] = - await Promise.all([ - partialUserOperation.sender || account.address, - partialUserOperation.nonce || account.getNonce(), - partialUserOperation.factory || account.getFactory(), - partialUserOperation.factoryData || account.getFactoryData(), - partialUserOperation.callData, - !partialUserOperation.maxFeePerGas || - !partialUserOperation.maxPriorityFeePerGas - ? estimateFeesPerGas(account.client) - : undefined - ]) - - const userOperation: UserOperation<"v0.7"> = { - sender, - nonce, - factory: factory, - factoryData: factoryData, - callData, - callGasLimit: partialUserOperation.callGasLimit || BigInt(0), - verificationGasLimit: - partialUserOperation.verificationGasLimit || BigInt(0), - preVerificationGas: - partialUserOperation.preVerificationGas || BigInt(0), - maxFeePerGas: - partialUserOperation.maxFeePerGas || - gasEstimation?.maxFeePerGas || - BigInt(0), - maxPriorityFeePerGas: - partialUserOperation.maxPriorityFeePerGas || - gasEstimation?.maxPriorityFeePerGas || - BigInt(0), - signature: partialUserOperation.signature || "0x" - } - - if (typeof middleware === "function") { - return middleware({ - userOperation, - entryPoint: account.entryPoint - } as { - userOperation: UserOperation> - entryPoint: entryPoint - }) as Promise> - } - - if (middleware && typeof middleware !== "function" && middleware.gasPrice) { - const gasPrice = await middleware.gasPrice() - userOperation.maxFeePerGas = gasPrice.maxFeePerGas - userOperation.maxPriorityFeePerGas = gasPrice.maxPriorityFeePerGas - } - - if (!userOperation.maxFeePerGas || !userOperation.maxPriorityFeePerGas) { - const estimateGas = await estimateFeesPerGas(account.client) - userOperation.maxFeePerGas = - userOperation.maxFeePerGas || estimateGas.maxFeePerGas - userOperation.maxPriorityFeePerGas = - userOperation.maxPriorityFeePerGas || - estimateGas.maxPriorityFeePerGas - } - - const chainId = await getChainId(client) - - if (userOperation.signature === "0x") { - if (validatorType === ValidatorType.ECDSA) { - userOperation.signature = await ecdsaGetMultiUserOpDummySignature( - userOperation, - numOfUserOps, - account.entryPoint, - chainId - ) - } - if (validatorType === ValidatorType.WEBAUTHN) { - userOperation.signature = - await webauthnGetMultiUserOpDummySignature( - userOperation, - numOfUserOps, - account.entryPoint, - chainId - ) - } - } - - if ( - middleware && - typeof middleware !== "function" && - middleware.sponsorUserOperation - ) { - const sponsorUserOperationData = (await middleware.sponsorUserOperation( - { - userOperation, - entryPoint: account.entryPoint - } as { - userOperation: UserOperation> - entryPoint: entryPoint - } - )) as SponsorUserOperationReturnType - - userOperation.callGasLimit = sponsorUserOperationData.callGasLimit - userOperation.verificationGasLimit = - sponsorUserOperationData.verificationGasLimit - userOperation.preVerificationGas = - sponsorUserOperationData.preVerificationGas - userOperation.paymaster = sponsorUserOperationData.paymaster - userOperation.paymasterVerificationGasLimit = - sponsorUserOperationData.paymasterVerificationGasLimit - userOperation.paymasterPostOpGasLimit = - sponsorUserOperationData.paymasterPostOpGasLimit - userOperation.paymasterData = sponsorUserOperationData.paymasterData - userOperation.maxFeePerGas = - sponsorUserOperationData.maxFeePerGas || userOperation.maxFeePerGas - userOperation.maxPriorityFeePerGas = - sponsorUserOperationData.maxPriorityFeePerGas || - userOperation.maxPriorityFeePerGas - - return userOperation as PrepareUserOperationRequestReturnType - } - - if ( - !userOperation.callGasLimit || - !userOperation.verificationGasLimit || - !userOperation.preVerificationGas - ) { - const gasParameters = await getAction( - client, - estimateUserOperationGas, - "estimateUserOperationGas" - )( - { - userOperation, - entryPoint: account.entryPoint - }, - // @ts-ignore getAction takes only two params but when compiled this will work - stateOverrides - ) - - userOperation.callGasLimit |= gasParameters.callGasLimit - userOperation.verificationGasLimit = - userOperation.verificationGasLimit || - gasParameters.verificationGasLimit - userOperation.preVerificationGas = - userOperation.preVerificationGas || gasParameters.preVerificationGas - - userOperation.paymasterPostOpGasLimit = - userOperation.paymasterPostOpGasLimit || - gasParameters.paymasterPostOpGasLimit - userOperation.paymasterPostOpGasLimit = - userOperation.paymasterPostOpGasLimit || - gasParameters.paymasterPostOpGasLimit - } - - return userOperation as PrepareUserOperationRequestReturnType -} diff --git a/plugins/multichain/actions/type.ts b/plugins/multichain/actions/type.ts deleted file mode 100644 index e3b59bc4..00000000 --- a/plugins/multichain/actions/type.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum ValidatorType { - ECDSA = "ECDSA", - WEBAUTHN = "WEBAUTHN" -} diff --git a/plugins/multichain/ecdsa/ecdsaSignUserOps.ts b/plugins/multichain/ecdsa/ecdsaSignUserOps.ts deleted file mode 100644 index 487f535b..00000000 --- a/plugins/multichain/ecdsa/ecdsaSignUserOps.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type { KernelSmartAccount } from "@zerodev/sdk" -import { MerkleTree } from "merkletreejs" -import { type UserOperation, getUserOperationHash } from "permissionless" -import type { - EntryPoint, - GetEntryPointVersion -} from "permissionless/types/entrypoint" -import { type Hex, concatHex, encodeAbiParameters, keccak256 } from "viem" - -export type MultiChainUserOperation = { - userOperation: UserOperation> - chainId: number -} - -export async function ecdsaSignUserOps({ - account, - multiUserOps, - entryPoint: entryPointAddress -}: { - account: KernelSmartAccount - multiUserOps: MultiChainUserOperation[] - entryPoint: entryPoint -}): Promise>[]> { - const userOpHashes = multiUserOps.map((multiUserOp, _index) => { - return getUserOperationHash({ - userOperation: { - ...multiUserOp.userOperation, - signature: "0x" - }, - entryPoint: entryPointAddress, - chainId: multiUserOp.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 merkleProofs = userOpHashes.map((hash) => { - return merkleTree.getHexProof(hash) as Hex[] - }) - - const multiChainSigs = multiUserOps.map((_, index) => { - const merkleProof = merkleProofs[index] - const encodedMerkleProof = encodeAbiParameters( - [{ name: "proof", type: "bytes32[]" }], - [merkleProof] - ) - return concatHex([ecdsaSig, merkleRoot, encodedMerkleProof]) - }) - - const signedMultiUserOps = multiUserOps.map((multiUserOp, index) => { - return { - ...multiUserOp.userOperation, - signature: multiChainSigs[index] - } - }) - - return signedMultiUserOps -} diff --git a/plugins/multichain/ecdsa/index.ts b/plugins/multichain/ecdsa/index.ts deleted file mode 100644 index 112c1bb4..00000000 --- a/plugins/multichain/ecdsa/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./ecdsaSignUserOps.js" -export * from "./ecdsaSignUserOpsWithEnable.js" -export * from "./toMultiChainECDSAValidator.js" diff --git a/plugins/multichain/index.ts b/plugins/multichain/index.ts deleted file mode 100644 index aa3983ef..00000000 --- a/plugins/multichain/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { KernelValidator } from "@zerodev/sdk/types" - -export type { KernelValidator } -export * from "./ecdsa/index.js" -export * from "./webauthn/index.js" -export * from "./actions/index.js" -export * from "./constants.js" -export * from "./multiChainClient.js" diff --git a/plugins/multichain/multiChainClient.ts b/plugins/multichain/multiChainClient.ts deleted file mode 100644 index 21d137b3..00000000 --- a/plugins/multichain/multiChainClient.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { - type KernelAccountClient, - type KernelSmartAccount, - type SmartAccountClientConfig, - getUserOperationGasPrice, - isProviderSet, - kernelAccountClientActions, - setPimlicoAsProvider -} from "@zerodev/sdk" -import { getEntryPointVersion } from "permissionless" -import type { SmartAccount } from "permissionless/accounts" -import type { - Middleware, - PrepareUserOperationRequestReturnType -} from "permissionless/actions/smartAccount" -import type { EntryPoint, Prettify } from "permissionless/types" -import type { StateOverrides } from "permissionless/types/bundler" -import { - http, - type Chain, - type Client, - type Transport, - createClient -} from "viem" -import { type ValidatorType, prepareMultiUserOpRequest } from "./actions" - -export type KernelAccountMultiChainClientActions< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TSmartAccount extends - | SmartAccount - | undefined = - | SmartAccount - | undefined -> = { - prepareMultiUserOpRequest: ( - args: Prettify< - Parameters< - typeof prepareMultiUserOpRequest< - entryPoint, - TTransport, - TChain, - TSmartAccount - > - >[1] - >, - validatorType: ValidatorType, - numOfUserOps: number, - stateOverrides?: StateOverrides - ) => Promise>> -} - -export function kernelAccountMultiChainClientActions< - entryPoint extends EntryPoint ->({ middleware }: Middleware) { - return < - TTransport extends Transport, - TChain extends Chain | undefined = Chain | undefined, - TSmartAccount extends - | SmartAccount - | undefined = - | SmartAccount - | undefined - >( - client: Client - ): KernelAccountMultiChainClientActions< - entryPoint, - TTransport, - TChain, - TSmartAccount - > => ({ - prepareMultiUserOpRequest: ( - args, - validatorType, - numOfUserOps, - stateOverrides - ) => - prepareMultiUserOpRequest( - client, - { - ...args, - middleware: { - ...middleware, - ...args.middleware - } - }, - validatorType, - numOfUserOps, - stateOverrides - ) - }) -} - -export type KernelMultiChainClient< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TSmartAccount extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount - | undefined -> = KernelAccountClient & { - prepareMultiUserOpRequest: ( - args: Prettify< - Parameters< - typeof prepareMultiUserOpRequest< - entryPoint, - TTransport, - TChain, - TSmartAccount - > - >[1] - >, - validatorType: ValidatorType, - numOfUserOps: number, - stateOverrides?: StateOverrides - ) => Promise>> -} - -export const createKernelMultiChainClient = < - TSmartAccount extends - | KernelSmartAccount - | undefined, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = undefined, - TEntryPoint extends EntryPoint = TSmartAccount extends KernelSmartAccount< - infer U - > - ? U - : never ->( - parameters: SmartAccountClientConfig< - TEntryPoint, - TTransport, - TChain, - TSmartAccount - > -): KernelMultiChainClient => { - const { - key = "Account", - name = "Kernel Account Client", - bundlerTransport, - entryPoint, - account - } = parameters - - if (!account) { - throw new Error("Kernel account is not provided") - } - - const entryPointVersion = getEntryPointVersion(entryPoint) - const shouldIncludePimlicoProvider = - bundlerTransport({}).config.key === "http" && - entryPointVersion === "v0.7" - - const client = createClient({ - ...parameters, - key, - name, - transport: (opts) => { - let _bundlerTransport = bundlerTransport({ - ...opts, - timeout: bundlerTransport({}).config.timeout, - retryCount: 0 - }) - if ( - !shouldIncludePimlicoProvider || - isProviderSet(_bundlerTransport.value?.url, "ALCHEMY") - ) - return _bundlerTransport - _bundlerTransport = http( - setPimlicoAsProvider(_bundlerTransport.value?.url) - )({ - ...opts, - timeout: bundlerTransport({}).config.timeout, - retryCount: 0 - }) - return _bundlerTransport - }, - type: "kernelAccountClient" - }) - - let middleware = parameters.middleware - if ( - (!middleware || - (typeof middleware !== "function" && !middleware.gasPrice)) && - client.transport?.url && - isProviderSet(client.transport.url, "PIMLICO") - ) { - const gasPrice = () => getUserOperationGasPrice(client) - middleware = { - ...middleware, - gasPrice - } - } - return client - .extend( - kernelAccountClientActions({ - middleware - }) - ) - .extend( - kernelAccountMultiChainClientActions({ - middleware - }) - ) as KernelMultiChainClient< - TEntryPoint, - TTransport, - TChain, - TSmartAccount - > -} diff --git a/plugins/multichain/webauthn/index.ts b/plugins/multichain/webauthn/index.ts deleted file mode 100644 index 3f2091d5..00000000 --- a/plugins/multichain/webauthn/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./webauthnSignUserOps.js" -export * from "./webauthnSignUserOpsWithEnable.js" -export * from "./toMultiChainWebAuthnValidator.js" -export { WebAuthnMode, toWebAuthnKey } from "@zerodev/webauthn-key" diff --git a/plugins/multichain/webauthn/webauthnSignUserOps.ts b/plugins/multichain/webauthn/webauthnSignUserOps.ts deleted file mode 100644 index 3358bb3e..00000000 --- a/plugins/multichain/webauthn/webauthnSignUserOps.ts +++ /dev/null @@ -1,90 +0,0 @@ -import type { KernelSmartAccount } from "@zerodev/sdk" -import { MerkleTree } from "merkletreejs" -import { type UserOperation, getUserOperationHash } from "permissionless" -import type { - EntryPoint, - GetEntryPointVersion -} from "permissionless/types/entrypoint" -import { - type Hex, - concatHex, - encodeAbiParameters, - hashMessage, - keccak256 -} from "viem" - -type MultiChainUserOperation = { - userOperation: UserOperation> - chainId: number -} - -export async function webauthnSignUserOps({ - account, - multiUserOps, - entryPoint: entryPointAddress -}: { - account: KernelSmartAccount - multiUserOps: MultiChainUserOperation[] - entryPoint: entryPoint -}): Promise>[]> { - const userOpHashes = multiUserOps.map((multiUserOp, _index) => { - return getUserOperationHash({ - userOperation: { - ...multiUserOp.userOperation, - signature: "0x" - }, - entryPoint: entryPointAddress, - chainId: multiUserOp.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 merkleProofs = userOpHashes.map((hash) => { - return merkleTree.getHexProof(hash) as Hex[] - }) - - const multiChainSigs = multiUserOps.map((_, index) => { - const merkleProof = merkleProofs[index] - const encodedMerkleProof = encodeAbiParameters( - [{ name: "merkleData", type: "bytes32[]" }], - [merkleProof] - ) - - const merkleData = concatHex([merkleRoot, encodedMerkleProof]) - return encodeAbiParameters( - [ - { - name: "merkleData", - type: "bytes" - }, - { - name: "signature", - type: "bytes" - } - ], - [merkleData, passkeySig] - ) - }) - - const signedMultiUserOps = multiUserOps.map((multiUserOp, index) => { - return { - ...multiUserOp.userOperation, - signature: multiChainSigs[index] - } - }) - - return signedMultiUserOps -} diff --git a/plugins/passkey/CHANGELOG.md b/plugins/passkey/CHANGELOG.md index e7f0b6f7..370a22d6 100644 --- a/plugins/passkey/CHANGELOG.md +++ b/plugins/passkey/CHANGELOG.md @@ -1,5 +1,17 @@ # @zerodev/passkey-validator +## 5.5.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + +## 5.5.0-beta.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + ## 5.4.2 ### Patch Changes @@ -14,7 +26,7 @@ ## 5.4.0 -### Patch Changes +### Minor Changes - Added new contract version for PasskeyValidator diff --git a/plugins/passkey/index.ts b/plugins/passkey/index.ts index 2abdf5ad..126c42ef 100644 --- a/plugins/passkey/index.ts +++ b/plugins/passkey/index.ts @@ -2,10 +2,14 @@ import { satisfiesRange, validateKernelVersionWithEntryPoint } from "@zerodev/sdk" -import type { GetKernelVersion, KernelValidator } from "@zerodev/sdk/types" +import type { + EntryPointType, + GetKernelVersion, + KernelValidator +} from "@zerodev/sdk/types" import { WebAuthnMode, toWebAuthnKey } from "@zerodev/webauthn-key" -import type { EntryPoint } from "permissionless/types/entrypoint.js" import { type Address, zeroAddress } from "viem" +import type { EntryPointVersion } from "viem/account-abstraction" import { PasskeyValidatorContractVersion, deserializePasskeyValidator, @@ -33,13 +37,15 @@ export const kernelVersionRangeToContractVersionToValidator: { } } -export const getValidatorAddress = ( - entryPointAddress: entryPoint, - kernelVersion: GetKernelVersion, +export const getValidatorAddress = < + entryPointVersion extends EntryPointVersion +>( + entryPoint: EntryPointType, + kernelVersion: GetKernelVersion, validatorContractVersion: PasskeyValidatorContractVersion, validatorAddress?: Address ): Address => { - validateKernelVersionWithEntryPoint(entryPointAddress, kernelVersion) + validateKernelVersionWithEntryPoint(entryPoint.version, kernelVersion) const passKeyValidatorAddress = Object.entries( kernelVersionRangeToContractVersionToValidator ).find(([range]) => satisfiesRange(kernelVersion, range))?.[1]?.[ diff --git a/plugins/passkey/package.json b/plugins/passkey/package.json index cdcaa2fb..7ee8e7ce 100644 --- a/plugins/passkey/package.json +++ b/plugins/passkey/package.json @@ -1,6 +1,6 @@ { "name": "@zerodev/passkey-validator", - "version": "5.4.2", + "version": "5.5.0", "author": "ZeroDev", "main": "./_cjs/index.js", "module": "./_esm/index.js", @@ -39,9 +39,8 @@ "@simplewebauthn/browser": "^8.3.4" }, "peerDependencies": { - "viem": ">=2.16.3 <2.18.0", - "@zerodev/sdk": "^5.2.1", - "@zerodev/webauthn-key": "^5.3.0", - "permissionless": ">=0.1.44 <=0.1.45" + "viem": "^2.21.40", + "@zerodev/sdk": "^5.4.0", + "@zerodev/webauthn-key": "^5.4.0" } } diff --git a/plugins/passkey/toPasskeyValidator.ts b/plugins/passkey/toPasskeyValidator.ts index e82299a1..f35bc2f1 100644 --- a/plugins/passkey/toPasskeyValidator.ts +++ b/plugins/passkey/toPasskeyValidator.ts @@ -1,5 +1,9 @@ import type { PublicKeyCredentialRequestOptionsJSON } from "@simplewebauthn/typescript-types" -import type { GetKernelVersion, KernelValidator } from "@zerodev/sdk/types" +import type { + EntryPointType, + GetKernelVersion, + KernelValidator +} from "@zerodev/sdk/types" import { type WebAuthnKey, b64ToBytes, @@ -13,27 +17,24 @@ import { uint8ArrayToHexString } from "@zerodev/webauthn-key" import type { TypedData } from "abitype" -import { type UserOperation, getUserOperationHash } from "permissionless" -import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" -import type { - EntryPoint, - GetEntryPointVersion -} from "permissionless/types/entrypoint" import { type Address, - type Chain, type Client, type Hex, type LocalAccount, type SignTypedDataParameters, type SignableMessage, - type Transport, type TypedDataDefinition, encodeAbiParameters, getTypesForEIP712Domain, hashTypedData, validateTypedData } from "viem" +import { + type EntryPointVersion, + type UserOperation, + getUserOperationHash +} from "viem/account-abstraction" import { toAccount } from "viem/accounts" import { signMessage } from "viem/actions" import { getChainId } from "viem/actions" @@ -125,31 +126,29 @@ export enum PasskeyValidatorContractVersion { } export async function toPasskeyValidator< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined + entryPointVersion extends EntryPointVersion >( - client: Client, + client: Client, { webAuthnKey, - entryPoint: entryPointAddress, + entryPoint, kernelVersion, validatorContractVersion, validatorAddress: _validatorAddress }: { webAuthnKey: WebAuthnKey - entryPoint: entryPoint - kernelVersion: GetKernelVersion + entryPoint: EntryPointType + kernelVersion: GetKernelVersion validatorContractVersion: PasskeyValidatorContractVersion validatorAddress?: Address } ): Promise< - KernelValidator & { + KernelValidator<"WebAuthnValidator"> & { getSerializedData: () => string } > { const validatorAddress = getValidatorAddress( - entryPointAddress, + entryPoint, kernelVersion, validatorContractVersion, _validatorAddress @@ -166,7 +165,9 @@ export async function toPasskeyValidator< ]) }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new Error( + "Smart account signer doesn't need to sign transactions" + ) }, async signTypedData< const TTypedData extends TypedData | Record, @@ -233,15 +234,14 @@ export async function toPasskeyValidator< } return 0n }, - async signUserOperation( - userOperation: UserOperation> - ) { + async signUserOperation(userOperation) { const hash = getUserOperationHash({ userOperation: { ...userOperation, signature: "0x" - }, - entryPoint: entryPointAddress, + } as UserOperation, + entryPointAddress: entryPoint.address, + entryPointVersion: entryPoint.version, chainId: chainId }) @@ -251,7 +251,7 @@ export async function toPasskeyValidator< }) return signature }, - async getDummySignature() { + async getStubSignature() { return encodeAbiParameters( [ { name: "authenticatorData", type: "bytes" }, @@ -280,7 +280,7 @@ export async function toPasskeyValidator< getSerializedData() { return serializePasskeyValidatorData({ - entryPoint: entryPointAddress, + entryPoint, validatorAddress, pubKeyX: webAuthnKey.pubX, pubKeyY: webAuthnKey.pubY, @@ -292,27 +292,25 @@ export async function toPasskeyValidator< } export async function deserializePasskeyValidator< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined + entryPointVersion extends EntryPointVersion >( - client: Client, + client: Client, { serializedData, - entryPoint: entryPointAddress, + entryPoint, kernelVersion }: { serializedData: string - entryPoint: entryPoint - kernelVersion: GetKernelVersion + entryPoint: EntryPointType + kernelVersion: GetKernelVersion } ): Promise< - KernelValidator & { + KernelValidator<"WebAuthnValidator"> & { getSerializedData: () => string } > { const { - entryPoint, + entryPoint: _entryPoint, validatorAddress, pubKeyX, pubKeyY, @@ -333,7 +331,9 @@ export async function deserializePasskeyValidator< ]) }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new Error( + "Smart account signer doesn't need to sign transactions" + ) }, async signTypedData< const TTypedData extends TypedData | Record, @@ -395,15 +395,14 @@ export async function deserializePasskeyValidator< async getNonceKey() { return 0n }, - async signUserOperation( - userOperation: UserOperation> - ) { + async signUserOperation(userOperation) { const hash = getUserOperationHash({ userOperation: { ...userOperation, signature: "0x" - }, - entryPoint: entryPointAddress, + } as UserOperation, + entryPointAddress: entryPoint.address, + entryPointVersion: entryPoint.version, chainId: chainId }) @@ -413,7 +412,7 @@ export async function deserializePasskeyValidator< }) return signature }, - async getDummySignature() { + async getStubSignature() { return encodeAbiParameters( [ { name: "authenticatorData", type: "bytes" }, @@ -442,7 +441,7 @@ export async function deserializePasskeyValidator< }, getSerializedData() { return serializePasskeyValidatorData({ - entryPoint, + entryPoint: _entryPoint, validatorAddress, pubKeyX, pubKeyY, diff --git a/plugins/permission/CHANGELOG.md b/plugins/permission/CHANGELOG.md index af4b4176..e4167606 100644 --- a/plugins/permission/CHANGELOG.md +++ b/plugins/permission/CHANGELOG.md @@ -1,5 +1,17 @@ # @zerodev/permissions +## 5.5.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + +## 5.5.0-beta.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + ## 5.4.10 ### Patch Changes diff --git a/plugins/permission/deserializePermissionAccount.ts b/plugins/permission/deserializePermissionAccount.ts index ac05d9a3..bf391436 100644 --- a/plugins/permission/deserializePermissionAccount.ts +++ b/plugins/permission/deserializePermissionAccount.ts @@ -7,22 +7,14 @@ import { } from "@zerodev/sdk" import { toKernelPluginManager } from "@zerodev/sdk/accounts" import type { + EntryPointType, GetKernelVersion, KERNEL_VERSION_TYPE, ValidatorInitData } from "@zerodev/sdk/types" -import { getEntryPointVersion } from "permissionless" -import type { SmartAccountSigner } from "permissionless/accounts" -import type { EntryPoint } from "permissionless/types" -import type { - Chain, - Client, - Hex, - PublicActions, - PublicRpcSchema, - Transport -} from "viem" +import type { Client, Hex } from "viem" import { decodeFunctionData } from "viem" +import type { EntryPointVersion } from "viem/account-abstraction" import { privateKeyToAccount } from "viem/accounts" import type { DecodeFunctionDataReturnType } from "viem/utils" import { @@ -40,25 +32,15 @@ import type { ModularSigner } from "./types.js" import { deserializePermissionAccountParams } from "./utils.js" export const deserializePermissionAccount = async < - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined + entryPointVersion extends EntryPointVersion >( - client: Client< - TTransport, - TChain, - undefined, - PublicRpcSchema, - PublicActions - >, - entryPointAddress: entryPoint, - kernelVersion: GetKernelVersion, + client: Client, + entryPoint: EntryPointType, + kernelVersion: GetKernelVersion, modularPermissionAccountParams: string, modularSigner?: ModularSigner ) => { - const entryPointVersion = getEntryPointVersion(entryPointAddress) - - if (entryPointVersion !== "v0.7") { + if (entryPoint.version !== "0.7") { throw new Error("Only EntryPoint 0.7 is supported") } const params = deserializePermissionAccountParams( @@ -66,10 +48,8 @@ export const deserializePermissionAccount = async < ) let signer: ModularSigner if (params.privateKey) - signer = toECDSASigner({ - signer: privateKeyToAccount( - params.privateKey - ) as SmartAccountSigner<"privateKey", `0x${string}`> + signer = await toECDSASigner({ + signer: privateKeyToAccount(params.privateKey) }) else if (modularSigner) signer = modularSigner else throw new Error("No signer or serialized sessionKey provided") @@ -81,7 +61,7 @@ export const deserializePermissionAccount = async < createPolicyFromParams(policy) ) || [] ), - entryPoint: entryPointAddress, + entryPoint, kernelVersion }) @@ -93,17 +73,17 @@ export const deserializePermissionAccount = async < pluginEnableSignature: params.enableSignature, validatorInitData, action: params.action, - entryPoint: entryPointAddress, + entryPoint, kernelVersion, ...params.validityData }) return createKernelAccount(client, { - entryPoint: entryPointAddress, + entryPoint, kernelVersion, plugins: kernelPluginManager, index, - deployedAccountAddress: params.accountParams.accountAddress, + address: params.accountParams.accountAddress, useMetaFactory }) } @@ -133,7 +113,6 @@ export const decodeParamsFromInitCode = ( ) => { let index: bigint | undefined let validatorInitData: ValidatorInitData | undefined - const initCodeWithoutFactoryAddress: Hex = `0x${initCode.slice(42)}` let deployWithFactoryFunctionData: | DecodeFunctionDataReturnType | DecodeFunctionDataReturnType @@ -141,12 +120,12 @@ export const decodeParamsFromInitCode = ( try { deployWithFactoryFunctionData = decodeFunctionData({ abi: KernelFactoryStakerAbi, - data: initCodeWithoutFactoryAddress + data: initCode }) } catch (error) { deployWithFactoryFunctionData = decodeFunctionData({ abi: KernelV3FactoryAbi, - data: initCodeWithoutFactoryAddress + data: initCode }) useMetaFactory = false } diff --git a/plugins/permission/package.json b/plugins/permission/package.json index b317ae43..80297d87 100644 --- a/plugins/permission/package.json +++ b/plugins/permission/package.json @@ -1,6 +1,6 @@ { "name": "@zerodev/permissions", - "version": "5.4.10", + "version": "5.5.0", "author": "ZeroDev", "main": "./_cjs/index.js", "module": "./_esm/index.js", @@ -56,9 +56,8 @@ "@simplewebauthn/browser": "^9.0.1" }, "peerDependencies": { - "viem": ">=2.16.3 <2.18.0", - "@zerodev/sdk": "^5.2.13", - "@zerodev/webauthn-key": "^5.3.0", - "permissionless": ">=0.1.44 <=0.1.45" + "viem": "^2.21.40", + "@zerodev/sdk": "^5.4.0", + "@zerodev/webauthn-key": "^5.4.0" } } diff --git a/plugins/permission/serializeMultiChainPermissionAccounts.ts b/plugins/permission/serializeMultiChainPermissionAccounts.ts index 66e5941c..2a1efbde 100644 --- a/plugins/permission/serializeMultiChainPermissionAccounts.ts +++ b/plugins/permission/serializeMultiChainPermissionAccounts.ts @@ -1,6 +1,5 @@ -import type { KernelSmartAccount } from "@zerodev/sdk" +import type { KernelSmartAccountImplementation } from "@zerodev/sdk" import { MerkleTree } from "merkletreejs" -import type { EntryPoint } from "permissionless/types" import { type Hex, concatHex, @@ -9,22 +8,20 @@ import { hashTypedData, keccak256 } from "viem" +import type { SmartAccount } from "viem/account-abstraction" import type { PermissionPlugin } from "./types.js" import { isPermissionValidatorPlugin, serializePermissionAccountParams } from "./utils.js" -export type MultiChainPermissionAccountsParams = - { - account: KernelSmartAccount - privateKey?: Hex - } +export type MultiChainPermissionAccountsParams = { + account: SmartAccount + privateKey?: Hex +} -export const serializeMultiChainPermissionAccounts = async < - entryPoint extends EntryPoint ->( - params: MultiChainPermissionAccountsParams[] +export const serializeMultiChainPermissionAccounts = async ( + params: MultiChainPermissionAccountsParams[] ): Promise => { if (params.length === 0) return [] @@ -33,7 +30,7 @@ export const serializeMultiChainPermissionAccounts = async < throw new Error("Account plugin is not a permission validator") } return ( - param.account.kernelPluginManager as PermissionPlugin + param.account.kernelPluginManager as PermissionPlugin ).getPluginSerializationParams() }) diff --git a/plugins/permission/serializePermissionAccount.ts b/plugins/permission/serializePermissionAccount.ts index bf40d3a1..a5c99b94 100644 --- a/plugins/permission/serializePermissionAccount.ts +++ b/plugins/permission/serializePermissionAccount.ts @@ -1,13 +1,13 @@ -import type { KernelSmartAccount } from "@zerodev/sdk" -import type { EntryPoint } from "permissionless/types" +import type { KernelSmartAccountImplementation } from "@zerodev/sdk" import type { Hex } from "viem" +import type { SmartAccount } from "viem/account-abstraction" import { isPermissionValidatorPlugin, serializePermissionAccountParams } from "./utils.js" -export const serializePermissionAccount = async ( - account: KernelSmartAccount, +export const serializePermissionAccount = async ( + account: SmartAccount, privateKey?: Hex, enableSignature?: Hex ): Promise => { diff --git a/plugins/permission/signers/toECDSASigner.ts b/plugins/permission/signers/toECDSASigner.ts index b1de49e6..2bfd9285 100644 --- a/plugins/permission/signers/toECDSASigner.ts +++ b/plugins/permission/signers/toECDSASigner.ts @@ -1,41 +1,29 @@ -import { constants, fixSignedData } from "@zerodev/sdk" +import { constants, fixSignedData, toSigner } from "@zerodev/sdk" +import type { Signer } from "@zerodev/sdk/types" import type { TypedData } from "abitype" -import { - SignTransactionNotSupportedBySmartAccount, - type SmartAccountSigner -} from "permissionless/accounts" -import type { Address, LocalAccount, TypedDataDefinition } from "viem" +import type { TypedDataDefinition } from "viem" import { toAccount } from "viem/accounts" import { ECDSA_SIGNER_CONTRACT } from "../constants.js" import type { ModularSigner, ModularSignerParams } from "../types.js" -export type ECDSAModularSignerParams< - TSource extends string = "custom", - TAddress extends Address = Address -> = ModularSignerParams & { - signer: SmartAccountSigner +export type ECDSAModularSignerParams = ModularSignerParams & { + signer: Signer } -export function toECDSASigner< - TSource extends string = "custom", - TAddress extends Address = Address ->({ +export async function toECDSASigner({ signer, signerContractAddress = ECDSA_SIGNER_CONTRACT -}: ECDSAModularSignerParams): ModularSigner { - const viemSigner: LocalAccount = { - ...signer, - signTransaction: (_, __) => { - throw new SignTransactionNotSupportedBySmartAccount() - } - } as LocalAccount +}: ECDSAModularSignerParams): Promise { + const viemSigner = await toSigner({ signer }) const account = toAccount({ address: viemSigner.address, async signMessage({ message }) { return fixSignedData(await viemSigner.signMessage({ message })) }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new Error( + "Smart account signer doesn't need to sign transactions" + ) }, async signTypedData< const TTypedData extends TypedData | Record, diff --git a/plugins/permission/signers/toWebAuthnSigner.ts b/plugins/permission/signers/toWebAuthnSigner.ts index 357eee1a..cc7821e5 100644 --- a/plugins/permission/signers/toWebAuthnSigner.ts +++ b/plugins/permission/signers/toWebAuthnSigner.ts @@ -10,7 +10,6 @@ import { uint8ArrayToHexString } from "@zerodev/webauthn-key" import type { TypedData } from "abitype" -import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" import { type Address, type Chain, @@ -161,7 +160,9 @@ export const toWebAuthnSigner = async < ]) }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new Error( + "Smart account signer doesn't need to sign transactions" + ) }, async signTypedData< const TTypedData extends TypedData | Record, diff --git a/plugins/permission/toPermissionValidator.ts b/plugins/permission/toPermissionValidator.ts index 6f9c4252..69053df1 100644 --- a/plugins/permission/toPermissionValidator.ts +++ b/plugins/permission/toPermissionValidator.ts @@ -1,18 +1,19 @@ import { KernelV3AccountAbi } from "@zerodev/sdk" -import { getEntryPointVersion, getUserOperationHash } from "permissionless" -import type { EntryPoint } from "permissionless/types/entrypoint" import { type Address, - type Chain, type Client, type Hex, - type Transport, concat, encodeAbiParameters, keccak256, slice, zeroAddress } from "viem" +import { + type EntryPointVersion, + type UserOperation, + getUserOperationHash +} from "viem/account-abstraction" import { getChainId, readContract } from "viem/actions" import { getAction } from "viem/utils" import { PolicyFlags } from "./constants.js" @@ -25,23 +26,20 @@ import type { } from "./types.js" export async function toPermissionValidator< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined + entryPointVersion extends EntryPointVersion >( - client: Client, + client: Client, { signer, policies, - entryPoint: entryPointAddress, + entryPoint, kernelVersion: _, flag = PolicyFlags.FOR_ALL_VALIDATION - }: PermissionPluginParams -): Promise> { + }: PermissionPluginParams +): Promise { const chainId = await getChainId(client) - const entryPointVersion = getEntryPointVersion(entryPointAddress) - if (entryPointVersion !== "v0.7") { + if (entryPoint.version !== "0.7") { throw new Error("Only EntryPoint 0.7 is supported") } @@ -101,8 +99,12 @@ export async function toPermissionValidator< signUserOperation: async (userOperation): Promise => { const userOpHash = getUserOperationHash({ - userOperation: { ...userOperation, signature: "0x" }, - entryPoint: entryPointAddress, + userOperation: { + ...userOperation, + signature: "0x" + } as UserOperation, + entryPointAddress: entryPoint.address, + entryPointVersion: entryPoint.version, chainId }) @@ -119,7 +121,7 @@ export async function toPermissionValidator< return 0n }, - async getDummySignature(_userOperation) { + async getStubSignature(_userOperation) { return concat(["0xff", signer.getDummySignature()]) }, getPluginSerializationParams: (): PermissionData => { diff --git a/plugins/permission/types.ts b/plugins/permission/types.ts index d15e00b8..19e1f4ff 100644 --- a/plugins/permission/types.ts +++ b/plugins/permission/types.ts @@ -1,11 +1,12 @@ import type { KernelValidator } from "@zerodev/sdk" import type { Action, + EntryPointType, GetKernelVersion, PluginValidityData } from "@zerodev/sdk/types" -import type { EntryPoint } from "permissionless/types/entrypoint" import type { Abi, Address, Hex, LocalAccount } from "viem" +import type { EntryPointVersion } from "viem/account-abstraction" import type { PolicyFlags } from "./constants.js" import type { CallPolicyParams, @@ -16,10 +17,7 @@ import type { TimestampPolicyParams } from "./policies/index.js" -export type PermissionPlugin = KernelValidator< - entryPoint, - "PermissionValidator" -> & { +export type PermissionPlugin = KernelValidator<"PermissionValidator"> & { getPluginSerializationParams: () => PermissionData } @@ -53,11 +51,13 @@ export type Policy = { | (TimestampPolicyParams & { type: "timestamp" }) } -export type PermissionPluginParams = { +export type PermissionPluginParams< + entryPointVersion extends EntryPointVersion +> = { signer: ModularSigner policies: Policy[] - entryPoint: entryPoint - kernelVersion: GetKernelVersion + entryPoint: EntryPointType + kernelVersion: GetKernelVersion flag?: PolicyFlags } diff --git a/plugins/permission/utils.ts b/plugins/permission/utils.ts index 0dc2601b..536f4d28 100644 --- a/plugins/permission/utils.ts +++ b/plugins/permission/utils.ts @@ -1,4 +1,3 @@ -import type { EntryPoint } from "permissionless/types" import type { PermissionAccountParams, PermissionPlugin } from "./types.js" export function base64ToBytes(base64: string) { @@ -11,10 +10,10 @@ export function bytesToBase64(bytes: Uint8Array) { return btoa(binString) } -export function isPermissionValidatorPlugin( +export function isPermissionValidatorPlugin( // biome-ignore lint/suspicious/noExplicitAny: plugin: any -): plugin is PermissionPlugin { +): plugin is PermissionPlugin { return plugin?.getPluginSerializationParams !== undefined } diff --git a/plugins/remoteSigner/CHANGELOG.md b/plugins/remoteSigner/CHANGELOG.md index 5811c082..ee6df6db 100644 --- a/plugins/remoteSigner/CHANGELOG.md +++ b/plugins/remoteSigner/CHANGELOG.md @@ -1,5 +1,11 @@ # @zerodev/remote-signer +## 5.3.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + ## 5.2.4 ### Patch Changes diff --git a/plugins/remoteSigner/package.json b/plugins/remoteSigner/package.json index 051fc9ab..a9559928 100644 --- a/plugins/remoteSigner/package.json +++ b/plugins/remoteSigner/package.json @@ -1,6 +1,6 @@ { "name": "@zerodev/remote-signer", - "version": "5.2.4", + "version": "5.3.0", "author": "ZeroDev", "main": "./_cjs/index.js", "module": "./_esm/index.js", @@ -35,8 +35,7 @@ "lint:fix": "bun run lint --apply" }, "peerDependencies": { - "viem": ">=2.16.3 <2.18.0", - "@zerodev/sdk": "^5.2.1", - "permissionless": ">=0.1.44 <=0.1.45" + "viem": "^2.21.40", + "@zerodev/sdk": "^5.4.0" } } diff --git a/plugins/remoteSigner/toRemoteSigner.ts b/plugins/remoteSigner/toRemoteSigner.ts index 7f97cda4..b2c0c94b 100644 --- a/plugins/remoteSigner/toRemoteSigner.ts +++ b/plugins/remoteSigner/toRemoteSigner.ts @@ -1,6 +1,8 @@ -import { fixSignedData } from "@zerodev/sdk" +import { + SignTransactionNotSupportedBySmartAccountError, + fixSignedData +} from "@zerodev/sdk" import type { TypedData } from "abitype" -import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" import { type Hex, type LocalAccount, @@ -106,7 +108,7 @@ export async function toRemoteSigner({ return fixSignedData(await signMessageWithRemoteSigner(message)) }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new SignTransactionNotSupportedBySmartAccountError() }, async signTypedData< const TTypedData extends TypedData | Record, diff --git a/plugins/session-key/CHANGELOG.md b/plugins/session-key/CHANGELOG.md index 0ff1c957..2ad2bed4 100644 --- a/plugins/session-key/CHANGELOG.md +++ b/plugins/session-key/CHANGELOG.md @@ -1,5 +1,17 @@ # @zerodev/session-key +## 5.5.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + +## 5.5.0-beta.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + ## 5.4.4 ### Patch Changes diff --git a/plugins/session-key/deserializeSessionKeyAccount.ts b/plugins/session-key/deserializeSessionKeyAccount.ts index c8878e3b..12aac870 100644 --- a/plugins/session-key/deserializeSessionKeyAccount.ts +++ b/plugins/session-key/deserializeSessionKeyAccount.ts @@ -1,48 +1,49 @@ -import { - KernelAccountAbi, - type KernelSmartAccount, - createKernelAccount -} from "@zerodev/sdk" +import { KernelAccountAbi, createKernelAccount, toSigner } from "@zerodev/sdk" import { KernelFactoryAbi } from "@zerodev/sdk" -import { toKernelPluginManager } from "@zerodev/sdk/accounts" -import type { GetKernelVersion, ValidatorInitData } from "@zerodev/sdk/types" -import { getEntryPointVersion } from "permissionless" -import type { SmartAccountSigner } from "permissionless/accounts" -import type { EntryPoint } from "permissionless/types/entrypoint" -import type { Address, Chain, Hex, Transport } from "viem" +import { + type KernelSmartAccountImplementation, + toKernelPluginManager +} from "@zerodev/sdk/accounts" +import type { + EntryPointType, + GetKernelVersion, + Signer, + ValidatorInitData +} from "@zerodev/sdk/types" +import type { Address, Client, Hex, LocalAccount } from "viem" import { decodeFunctionData } from "viem" +import type { EntryPointVersion, SmartAccount } from "viem/account-abstraction" import { privateKeyToAccount } from "viem/accounts" import { SESSION_KEY_VALIDATOR_ADDRESS } from "./index.js" import { signerToSessionKeyValidator } from "./toSessionKeyValidatorPlugin.js" import { deserializeSessionKeyAccountParams } from "./utils.js" export const deserializeSessionKeyAccount = async < - entryPoint extends EntryPoint, - TSource extends string = "custom", - TAddress extends Address = Address + entryPointVersion extends EntryPointVersion >( - client: Parameters[0], - entryPointAddress: entryPoint, - kernelVersion: GetKernelVersion, + client: Client, + entryPoint: EntryPointType, + kernelVersion: GetKernelVersion, sessionKeyAccountParams: string, - sessionKeySigner?: SmartAccountSigner, + sessionKeySigner?: Signer, validatorAddress: Address = SESSION_KEY_VALIDATOR_ADDRESS -): Promise> => { - const entryPointVersion = getEntryPointVersion(entryPointAddress) - - if (entryPointVersion !== "v0.6") { +): Promise< + SmartAccount> +> => { + if (entryPoint.version !== "0.6") { throw new Error("Only EntryPoint 0.6 is supported") } const params = deserializeSessionKeyAccountParams(sessionKeyAccountParams) - let signer: SmartAccountSigner + let signer: LocalAccount if (params.privateKey) signer = privateKeyToAccount(params.privateKey) - else if (sessionKeySigner) signer = sessionKeySigner + else if (sessionKeySigner) + signer = await toSigner({ signer: sessionKeySigner }) else throw new Error("No signer or serialized sessionKey provided") const sessionKeyPlugin = await signerToSessionKeyValidator(client, { signer, validatorData: params.sessionKeyParams, - entryPoint: entryPointAddress, + entryPoint, kernelVersion, validatorAddress }) @@ -56,7 +57,7 @@ export const deserializeSessionKeyAccount = async < pluginEnableSignature: params.enableSignature, validatorInitData, action: params.action, - entryPoint: entryPointAddress, + entryPoint, kernelVersion, ...params.validityData }) @@ -64,8 +65,8 @@ export const deserializeSessionKeyAccount = async < return createKernelAccount(client, { plugins: kernelPluginManager, index, - deployedAccountAddress: params.accountParams.accountAddress, - entryPoint: entryPointAddress, + address: params.accountParams.accountAddress, + entryPoint, kernelVersion }) } @@ -76,7 +77,7 @@ export const decodeParamsFromInitCode = (initCode: Hex) => { if (initCode === "0x") return { index, validatorInitData } const createAccountFunctionData = decodeFunctionData({ abi: KernelFactoryAbi, - data: `0x${initCode.slice(42)}` + data: initCode }) if (createAccountFunctionData.functionName === "createAccount") { index = createAccountFunctionData.args[2] diff --git a/plugins/session-key/deserializeSessionKeyAccountV0_2.ts b/plugins/session-key/deserializeSessionKeyAccountV0_2.ts index 49718b86..81c91eef 100644 --- a/plugins/session-key/deserializeSessionKeyAccountV0_2.ts +++ b/plugins/session-key/deserializeSessionKeyAccountV0_2.ts @@ -1,41 +1,44 @@ +import { toSigner } from "@zerodev/sdk" import { toKernelPluginManager } from "@zerodev/sdk/accounts" import { KernelFactoryV2Abi, createKernelAccountV0_2 } from "@zerodev/sdk/accounts" -import type { ValidatorInitData } from "@zerodev/sdk/types" -import type { GetKernelVersion } from "@zerodev/sdk/types/kernel.js" -import type { SmartAccountSigner } from "permissionless/accounts" -import type { EntryPoint } from "permissionless/types/entrypoint" -import type { Address, Hex } from "viem" +import type { + EntryPointType, + Signer, + ValidatorInitData +} from "@zerodev/sdk/types" +import type { GetKernelVersion } from "@zerodev/sdk/types" +import type { Address, Client, Hex, LocalAccount } from "viem" import { decodeFunctionData } from "viem" +import type { EntryPointVersion } from "viem/account-abstraction" import { privateKeyToAccount } from "viem/accounts" import { SESSION_KEY_VALIDATOR_ADDRESS } from "./index.js" import { signerToSessionKeyValidator } from "./toSessionKeyValidatorPlugin.js" import { deserializeSessionKeyAccountParams } from "./utils.js" export const deserializeSessionKeyAccountV0_2 = async < - entryPoint extends EntryPoint, - TSource extends string = "custom", - TAddress extends Address = Address + entryPointVersion extends EntryPointVersion >( - client: Parameters[0], - entryPointAddress: entryPoint, + client: Client, + entryPoint: EntryPointType, sessionKeyAccountParams: string, - sessionKeySigner?: SmartAccountSigner, + sessionKeySigner?: Signer, validatorAddress: Address = SESSION_KEY_VALIDATOR_ADDRESS ) => { const params = deserializeSessionKeyAccountParams(sessionKeyAccountParams) - let signer: SmartAccountSigner + let signer: LocalAccount if (params.privateKey) signer = privateKeyToAccount(params.privateKey) - else if (sessionKeySigner) signer = sessionKeySigner + else if (sessionKeySigner) + signer = await toSigner({ signer: sessionKeySigner }) else throw new Error("No signer or serialized sessionKey provided") const sessionKeyPlugin = await signerToSessionKeyValidator(client, { signer, validatorData: params.sessionKeyParams, - entryPoint: entryPointAddress, - kernelVersion: "0.0.2" as GetKernelVersion, + entryPoint, + kernelVersion: "0.0.2" as GetKernelVersion, validatorAddress }) @@ -49,15 +52,15 @@ export const deserializeSessionKeyAccountV0_2 = async < validatorInitData, action: params.action, kernelVersion: "0.0.2", - entryPoint: entryPointAddress, + entryPoint, ...params.validityData }) return createKernelAccountV0_2(client, { plugins: kernelPluginManager, index, - deployedAccountAddress: params.accountParams.accountAddress, - entryPoint: entryPointAddress + address: params.accountParams.accountAddress, + entryPoint: { address: entryPoint.address, version: "0.6" } }) } diff --git a/plugins/session-key/package.json b/plugins/session-key/package.json index 8f440d70..9071f26f 100644 --- a/plugins/session-key/package.json +++ b/plugins/session-key/package.json @@ -1,6 +1,6 @@ { "name": "@zerodev/session-key", - "version": "5.4.4", + "version": "5.5.0", "author": "ZeroDev", "main": "./_cjs/index.js", "module": "./_esm/index.js", @@ -38,8 +38,7 @@ "merkletreejs": "^0.3.11" }, "peerDependencies": { - "viem": ">=2.16.3 <2.18.0", - "@zerodev/sdk": "^5.2.1", - "permissionless": ">=0.1.44 <=0.1.45" + "viem": "^2.21.40", + "@zerodev/sdk": "^5.4.0" } } diff --git a/plugins/session-key/revokeSessionKey.ts b/plugins/session-key/revokeSessionKey.ts index b6c8cabf..00e87e99 100644 --- a/plugins/session-key/revokeSessionKey.ts +++ b/plugins/session-key/revokeSessionKey.ts @@ -1,5 +1,7 @@ -import type { KernelAccountClient, KernelSmartAccount } from "@zerodev/sdk" -import type { ENTRYPOINT_ADDRESS_V06_TYPE } from "permissionless/types/entrypoint" +import type { + KernelAccountClient, + KernelSmartAccountImplementation +} from "@zerodev/sdk" import { type Address, type Chain, @@ -7,6 +9,7 @@ import { type Transport, encodeFunctionData } from "viem" +import type { SmartAccount } from "viem/account-abstraction" import { SESSION_KEY_VALIDATOR_ADDRESS } from "./index.js" const SessionKeyValidatorAbi = [ @@ -287,22 +290,17 @@ const SessionKeyValidatorAbi = [ } ] -export const revokeSessionKey = async < - entryPoint extends ENTRYPOINT_ADDRESS_V06_TYPE, - TChain extends Chain | undefined = Chain | undefined, - TTransport extends Transport = Transport ->( +export const revokeSessionKey = async ( accountClient: KernelAccountClient< - entryPoint, - TTransport, - TChain, - KernelSmartAccount + Transport, + Chain, + SmartAccount >, sessionKeyAddress: Address = "0x" ): Promise => { return await accountClient.sendUserOperation({ - userOperation: { - callData: await accountClient.account.encodeCallData({ + callData: await accountClient.account.encodeCalls([ + { to: SESSION_KEY_VALIDATOR_ADDRESS, value: 0n, data: encodeFunctionData({ @@ -310,7 +308,7 @@ export const revokeSessionKey = async < functionName: "disable", args: [sessionKeyAddress] }) - }) - } + } + ]) }) } diff --git a/plugins/session-key/serializeSessionKeyAccount.ts b/plugins/session-key/serializeSessionKeyAccount.ts index 854ef76c..f2a6627f 100644 --- a/plugins/session-key/serializeSessionKeyAccount.ts +++ b/plugins/session-key/serializeSessionKeyAccount.ts @@ -1,13 +1,15 @@ -import type { KernelSmartAccount } from "@zerodev/sdk" -import type { EntryPoint } from "permissionless/types/entrypoint" +import type { KernelSmartAccountImplementation } from "@zerodev/sdk" import type { Hex } from "viem" +import type { EntryPointVersion, SmartAccount } from "viem/account-abstraction" import { isSessionKeyValidatorPlugin, serializeSessionKeyAccountParams } from "./utils.js" -export const serializeSessionKeyAccount = async ( - account: KernelSmartAccount, +export const serializeSessionKeyAccount = async < + entryPointVersion extends EntryPointVersion +>( + account: SmartAccount>, privateKey?: Hex ): Promise => { if (!isSessionKeyValidatorPlugin(account.kernelPluginManager)) @@ -21,7 +23,7 @@ export const serializeSessionKeyAccount = async ( account.address ) const accountParams = { - initCode: await account.getInitCode(), + initCode: await account.generateInitCode(), accountAddress: account.address } diff --git a/plugins/session-key/toSessionKeyValidatorPlugin.ts b/plugins/session-key/toSessionKeyValidatorPlugin.ts index a105e498..f156f4bd 100644 --- a/plugins/session-key/toSessionKeyValidatorPlugin.ts +++ b/plugins/session-key/toSessionKeyValidatorPlugin.ts @@ -2,11 +2,8 @@ import type { TypedData } from "abitype" import { type Abi, type Address, - type Chain, type Client, type Hex, - type LocalAccount, - type Transport, type TypedDataDefinition, keccak256, pad, @@ -14,25 +11,23 @@ import { zeroAddress } from "viem" import { toAccount } from "viem/accounts" -import { - getChainId, - readContract, - signMessage, - signTypedData -} from "viem/actions" +import { getChainId, readContract, signMessage } from "viem/actions" import { concat, concatHex, getAction } from "viem/utils" import { SessionKeyValidatorAbi } from "./abi/SessionKeyValidatorAbi.js" -import { KernelAccountAbi } from "@zerodev/sdk" +import { KernelAccountAbi, toSigner } from "@zerodev/sdk" import { constants } from "@zerodev/sdk" -import type { GetKernelVersion } from "@zerodev/sdk/types" +import type { + EntryPointType, + GetKernelVersion, + Signer +} from "@zerodev/sdk/types" import { MerkleTree } from "merkletreejs" -import { getEntryPointVersion, getUserOperationHash } from "permissionless" import { - SignTransactionNotSupportedBySmartAccount, - type SmartAccountSigner -} from "permissionless/accounts" -import type { EntryPoint } from "permissionless/types/entrypoint" + type EntryPointVersion, + type UserOperation, + getUserOperationHash +} from "viem/account-abstraction" import { SESSION_KEY_VALIDATOR_ADDRESS } from "./index.js" import type { SessionKeyData, @@ -63,32 +58,26 @@ export enum ParamOperator { export const anyPaymaster = "0x0000000000000000000000000000000000000001" export async function signerToSessionKeyValidator< - entryPoint extends EntryPoint, + entryPointVersion extends EntryPointVersion, TAbi extends Abi | readonly unknown[], - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TSource extends string = "custom", - TAddress extends Address = Address, TFunctionName extends string | undefined = string >( - client: Client, + client: Client, { signer, - entryPoint: entryPointAddress, + entryPoint, kernelVersion: _, validatorData, validatorAddress = SESSION_KEY_VALIDATOR_ADDRESS }: { - signer: SmartAccountSigner + signer: Signer validatorData?: SessionKeyData - entryPoint: entryPoint - kernelVersion: GetKernelVersion + entryPoint: EntryPointType + kernelVersion: GetKernelVersion validatorAddress?: Address } -): Promise> { - const entryPointVersion = getEntryPointVersion(entryPointAddress) - - if (entryPointVersion !== "v0.6") { +): Promise { + if (entryPoint.version !== "0.6") { throw new Error("Only EntryPoint 0.6 is supported") } const sessionKeyData: SessionKeyData = { @@ -122,12 +111,7 @@ export async function signerToSessionKeyValidator< }, operation: perm.operation ?? Operation.Call })) ?? [] - const viemSigner: LocalAccount = { - ...signer, - signTransaction: (_, __) => { - throw new SignTransactionNotSupportedBySmartAccount() - } - } as LocalAccount + const viemSigner = await toSigner({ signer }) // // Fetch chain id const [chainId] = await Promise.all([getChainId(client)]) @@ -139,7 +123,9 @@ export async function signerToSessionKeyValidator< return signMessage(client, { account: viemSigner, message }) }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new Error( + "Smart account signer doesn't need to sign transactions" + ) }, async signTypedData< const TTypedData extends TypedData | Record, @@ -147,13 +133,7 @@ export async function signerToSessionKeyValidator< | keyof TTypedData | "EIP712Domain" = keyof TTypedData >(typedData: TypedDataDefinition) { - return signTypedData( - client, - { - account: viemSigner, - ...typedData - } - ) + return viemSigner.signTypedData(typedData) } }) @@ -185,7 +165,7 @@ export async function signerToSessionKeyValidator< enabledLastNonce ?? (await getSessionNonces(kernelAccountAddress)).lastNonce + 1n return concat([ - signer.address, + viemSigner.address, pad(merkleTree.getHexRoot() as Hex, { size: 32 }), pad(toHex(sessionKeyData?.validAfter ?? 0), { size: 6 @@ -265,8 +245,12 @@ export async function signerToSessionKeyValidator< signUserOperation: async (userOperation): Promise => { const userOpHash = getUserOperationHash({ - userOperation: { ...userOperation, signature: "0x" }, - entryPoint: entryPointAddress, + userOperation: { + ...userOperation, + signature: "0x" + } as UserOperation, + entryPointAddress: entryPoint.address, + entryPointVersion: entryPoint.version, chainId: chainId }) @@ -276,7 +260,7 @@ export async function signerToSessionKeyValidator< }) const fixedSignature = fixSignedData(signature) return concat([ - signer.address, + viemSigner.address, fixedSignature, getEncodedPermissionProofData(userOperation.callData) ]) @@ -289,9 +273,9 @@ export async function signerToSessionKeyValidator< return 0n }, - async getDummySignature(userOperation) { + async getStubSignature(userOperation) { return concat([ - signer.address, + viemSigner.address, constants.DUMMY_ECDSA_SIG, getEncodedPermissionProofData(userOperation.callData) ]) @@ -324,7 +308,7 @@ export async function signerToSessionKeyValidator< args: [signer.address as Address, kernelAccountAddress] }) const enableDataHex = concatHex([ - signer.address, + viemSigner.address, pad(enableData[0], { size: 32 }), pad(toHex(enableData[1]), { size: 6 }), pad(toHex(enableData[2]), { size: 6 }), diff --git a/plugins/session-key/types.ts b/plugins/session-key/types.ts index 61afbf75..e34cbb5e 100644 --- a/plugins/session-key/types.ts +++ b/plugins/session-key/types.ts @@ -8,11 +8,20 @@ import type { ExtractAbiFunction } from "abitype" import type { ExtractAbiFunctionNames } from "abitype" -import type { Pretty } from "abitype/src/types.js" -import type { EntryPoint } from "permissionless/types/entrypoint" import type { Abi, AbiStateMutability, Address, Hex, Narrow } from "viem" import type { Operation, ParamOperator } from "./toSessionKeyValidatorPlugin.js" +/** + * Taken from: https://github.com/wevm/abitype/blob/main/packages/abitype/src/types.ts + * Combines members of an intersection into a readable type. + * + * @link https://twitter.com/mattpocockuk/status/1622730173446557697?s=20&t=NdpAcmEFXY01xkqU3KO0Mg + * @example + * type Result = Pretty<{ a: string } | { b: string } | { c: number, d: bigint }> + * // ^? type Result = { a: string; b: string; c: number; d: bigint } + */ +export type Pretty = { [key in keyof type]: type[key] } & unknown + export type SessionNonces = { lastNonce: bigint invalidNonce: bigint @@ -79,10 +88,7 @@ export type SessionKeyAccountParams = { privateKey?: Hex } -export type SessionKeyPlugin = KernelValidator< - entryPoint, - "SessionKeyValidator" -> & { +export type SessionKeyPlugin = KernelValidator<"SessionKeyValidator"> & { getPluginSerializationParams(): SessionKeyData } diff --git a/plugins/session-key/utils.ts b/plugins/session-key/utils.ts index a188a5c7..b932d7c0 100644 --- a/plugins/session-key/utils.ts +++ b/plugins/session-key/utils.ts @@ -1,6 +1,5 @@ import { KernelAccountAbi } from "@zerodev/sdk" import type { AbiFunction } from "abitype" -import type { ENTRYPOINT_ADDRESS_V06_TYPE } from "permissionless/types/entrypoint" import { type Abi, type Address, @@ -198,12 +197,10 @@ export function bytesToBase64(bytes: Uint8Array) { return btoa(binString) } -export function isSessionKeyValidatorPlugin< - entryPoint extends ENTRYPOINT_ADDRESS_V06_TYPE ->( +export function isSessionKeyValidatorPlugin( // biome-ignore lint/suspicious/noExplicitAny: plugin: any -): plugin is SessionKeyPlugin { +): plugin is SessionKeyPlugin { return plugin?.getPluginSerializationParams !== undefined } // We need to be able to serialize bigint to transmit session key over diff --git a/plugins/social/CHANGELOG.md b/plugins/social/CHANGELOG.md index 66101c23..73fb6cf4 100644 --- a/plugins/social/CHANGELOG.md +++ b/plugins/social/CHANGELOG.md @@ -1,5 +1,11 @@ # @zerodev/social-validator +## 5.2.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + ## 5.1.3 ### Patch Changes diff --git a/plugins/social/package.json b/plugins/social/package.json index ed997497..0c2cc147 100644 --- a/plugins/social/package.json +++ b/plugins/social/package.json @@ -1,6 +1,6 @@ { "name": "@zerodev/social-validator", - "version": "5.1.3", + "version": "5.2.0", "author": "ZeroDev", "main": "./_cjs/index.js", "module": "./_esm/index.js", @@ -35,10 +35,9 @@ "lint:fix": "bun run lint --apply" }, "peerDependencies": { - "@zerodev/ecdsa-validator": "^5.2.3", - "@zerodev/sdk": "^5.2.1", - "permissionless": ">=0.1.44 <=0.1.45", - "viem": ">=2.16.3 <2.18.0" + "@zerodev/ecdsa-validator": "^5.4.0", + "@zerodev/sdk": "^5.4.0", + "viem": "^2.21.40" }, "dependencies": { "@magic-ext/oauth": "^22.0.3", diff --git a/plugins/social/toSocialValidatorPlugin.ts b/plugins/social/toSocialValidatorPlugin.ts index da6b8b0e..9a769a67 100644 --- a/plugins/social/toSocialValidatorPlugin.ts +++ b/plugins/social/toSocialValidatorPlugin.ts @@ -1,10 +1,13 @@ import { OAuthExtension } from "@magic-ext/oauth" import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator" -import type { GetKernelVersion, KernelValidator } from "@zerodev/sdk/types" +import type { + EntryPointType, + GetKernelVersion, + KernelValidator +} from "@zerodev/sdk/types" import { Magic } from "magic-sdk" -import { providerToSmartAccountSigner } from "permissionless" -import type { EntryPoint } from "permissionless/types/entrypoint" -import type { Chain, Client, Transport } from "viem" +import type { Client } from "viem" +import type { EntryPointVersion } from "viem/account-abstraction" export async function isAuthorized({ projectId @@ -41,21 +44,19 @@ export async function initiateLogin({ } export async function getSocialValidator< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined + entryPointVersion extends EntryPointVersion >( - client: Client, + client: Client, { - entryPoint: entryPointAddress, + entryPoint, kernelVersion, projectId }: { - entryPoint: entryPoint - kernelVersion: GetKernelVersion + entryPoint: EntryPointType + kernelVersion: GetKernelVersion projectId: string } -): Promise> { +): Promise> { const magic = await getMagic({ projectId }) const authorized = await isAuthorized({ projectId }) @@ -64,11 +65,10 @@ export async function getSocialValidator< } const magicProvider = await magic.wallet.getProvider() - const smartAccountSigner = await providerToSmartAccountSigner(magicProvider) const ecdsaValidator = await signerToEcdsaValidator(client, { - signer: smartAccountSigner, - entryPoint: entryPointAddress, + signer: magicProvider, + entryPoint, kernelVersion }) diff --git a/plugins/webauthn-key/CHANGELOG.md b/plugins/webauthn-key/CHANGELOG.md index 66f1b9a0..ab1b8286 100644 --- a/plugins/webauthn-key/CHANGELOG.md +++ b/plugins/webauthn-key/CHANGELOG.md @@ -1,5 +1,17 @@ # @zerodev/passkey-validator +## 5.4.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + +## 5.4.0-beta.0 + +### Minor Changes + +- Update for `viem@2.18.x` + ## 5.3.3 ### Patch Changes diff --git a/plugins/webauthn-key/package.json b/plugins/webauthn-key/package.json index 50a7cf7f..9a358aba 100644 --- a/plugins/webauthn-key/package.json +++ b/plugins/webauthn-key/package.json @@ -1,6 +1,6 @@ { "name": "@zerodev/webauthn-key", - "version": "5.3.3", + "version": "5.4.0", "author": "ZeroDev", "main": "./_cjs/index.js", "module": "./_esm/index.js", @@ -39,6 +39,6 @@ "@simplewebauthn/browser": "^8.3.4" }, "peerDependencies": { - "viem": ">=2.16.3 <2.18.0" + "viem": "^2.21.40" } } diff --git a/plugins/webauthn-key/utils.ts b/plugins/webauthn-key/utils.ts index d488d1c7..2383f40c 100644 --- a/plugins/webauthn-key/utils.ts +++ b/plugins/webauthn-key/utils.ts @@ -68,7 +68,7 @@ export function parseAndNormalizeSig(derSig: Hex): { r: bigint; s: bigint } { } type PasskeyValidatorSerializedData = { - entryPoint: Hex + entryPoint: { address: Hex; version: string } validatorAddress: Hex pubKeyX: bigint pubKeyY: bigint diff --git a/plugins/weighted-ecdsa/CHANGELOG.md b/plugins/weighted-ecdsa/CHANGELOG.md index aa1dcf33..652c8162 100644 --- a/plugins/weighted-ecdsa/CHANGELOG.md +++ b/plugins/weighted-ecdsa/CHANGELOG.md @@ -1,5 +1,11 @@ # @zerodev/weighted-ecdsa-validator +## 5.4.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + ## 5.3.3 ### Patch Changes diff --git a/plugins/weighted-ecdsa/constants.ts b/plugins/weighted-ecdsa/constants.ts index 4bc12483..0dc33700 100644 --- a/plugins/weighted-ecdsa/constants.ts +++ b/plugins/weighted-ecdsa/constants.ts @@ -2,10 +2,13 @@ import { satisfiesRange, validateKernelVersionWithEntryPoint } from "@zerodev/sdk" -import type { Action, GetKernelVersion } from "@zerodev/sdk/types" -import { getEntryPointVersion } from "permissionless" -import type { EntryPoint } from "permissionless/types/entrypoint" +import type { + Action, + EntryPointType, + GetKernelVersion +} from "@zerodev/sdk/types" import { type Address, toFunctionSelector, zeroAddress } from "viem" +import type { EntryPointVersion } from "viem/account-abstraction" const RECOVERY_ACTION_ADDRESS_V06 = "0x2f65dB8039fe5CAEE0a8680D2879deB800F31Ae1" const RECOVERY_ACTION_ADDRESS_V07 = "0xe884C2868CC82c16177eC73a93f7D9E6F3A5DC6E" @@ -13,9 +16,10 @@ const RECOVERY_ACTION_SELECTOR = toFunctionSelector( "doRecovery(address, bytes)" ) -export const getRecoveryAction = (entryPoint: EntryPoint): Action => { - const entryPointVersion = getEntryPointVersion(entryPoint) - if (entryPointVersion === "v0.6") { +export const getRecoveryAction = ( + entryPointVersion: EntryPointVersion +): Action => { + if (entryPointVersion === "0.6") { return { address: RECOVERY_ACTION_ADDRESS_V06, selector: RECOVERY_ACTION_SELECTOR @@ -34,12 +38,14 @@ export const kernelVersionRangeToValidator: { "0.3.0 || 0.3.1": "0xeD89244160CfE273800B58b1B534031699dFeEEE" } -export const getValidatorAddress = ( - entryPointAddress: entryPoint, - kernelVersion: GetKernelVersion, +export const getValidatorAddress = < + entryPointVersion extends EntryPointVersion +>( + entryPoint: EntryPointType, + kernelVersion: GetKernelVersion, validatorAddress?: Address ): Address => { - validateKernelVersionWithEntryPoint(entryPointAddress, kernelVersion) + validateKernelVersionWithEntryPoint(entryPoint.version, kernelVersion) const weightedEcdsaValidatorAddress = Object.entries( kernelVersionRangeToValidator ).find(([range]) => satisfiesRange(kernelVersion, range))?.[1] diff --git a/plugins/weighted-ecdsa/package.json b/plugins/weighted-ecdsa/package.json index b3af313e..f2cd74bd 100644 --- a/plugins/weighted-ecdsa/package.json +++ b/plugins/weighted-ecdsa/package.json @@ -1,6 +1,6 @@ { "name": "@zerodev/weighted-ecdsa-validator", - "version": "5.3.3", + "version": "5.4.0", "author": "ZeroDev", "main": "./_cjs/index.js", "module": "./_esm/index.js", @@ -35,8 +35,7 @@ "lint:fix": "bun run lint --apply" }, "peerDependencies": { - "viem": ">=2.16.3 <2.18.0", - "@zerodev/sdk": "^5.2.1", - "permissionless": ">=0.1.44 <=0.1.45" + "viem": "^2.21.40", + "@zerodev/sdk": "^5.4.0" } } diff --git a/plugins/weighted-ecdsa/toWeightedECDSAValidatorPlugin.ts b/plugins/weighted-ecdsa/toWeightedECDSAValidatorPlugin.ts index 30c3d7fc..7607a5b6 100644 --- a/plugins/weighted-ecdsa/toWeightedECDSAValidatorPlugin.ts +++ b/plugins/weighted-ecdsa/toWeightedECDSAValidatorPlugin.ts @@ -1,20 +1,17 @@ -import { KernelAccountAbi } from "@zerodev/sdk" -import type { GetKernelVersion, KernelValidator } from "@zerodev/sdk/types" -import type { TypedData } from "abitype" -import { type UserOperation, getUserOperationHash } from "permissionless" -import { - SignTransactionNotSupportedBySmartAccount, - type SmartAccountSigner -} from "permissionless/accounts" +import { KernelAccountAbi, toSigner } from "@zerodev/sdk" import type { - EntryPoint, - GetEntryPointVersion -} from "permissionless/types/entrypoint" + EntryPointType, + GetKernelVersion, + KernelValidator, + Signer +} from "@zerodev/sdk/types" +import type { TypedData } from "abitype" import { type Address, type Chain, type Client, type Hex, + type LocalAccount, type Transport, type TypedDataDefinition, encodeAbiParameters, @@ -22,6 +19,11 @@ import { keccak256, parseAbiParameters } from "viem" +import { + type EntryPointVersion, + type UserOperation, + getUserOperationHash +} from "viem/account-abstraction" import { toAccount } from "viem/accounts" import { getChainId, readContract } from "viem/actions" import { getAction } from "viem/utils" @@ -43,29 +45,25 @@ const sortByAddress = (a: { address: Address }, b: { address: Address }) => { } export async function createWeightedECDSAValidator< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TSource extends string = "custom", - TAddress extends Address = Address + entryPointVersion extends EntryPointVersion >( - client: Client, + client: Client, { config, signers: _signers, - entryPoint: entryPointAddress, + entryPoint, kernelVersion, validatorAddress: _validatorAddress }: { config?: WeightedECDSAValidatorConfig - signers: Array> - entryPoint: entryPoint - kernelVersion: GetKernelVersion + signers: Array + entryPoint: EntryPointType + kernelVersion: GetKernelVersion validatorAddress?: Address } -): Promise> { +): Promise> { const validatorAddress = getValidatorAddress( - entryPointAddress, + entryPoint, kernelVersion, _validatorAddress ) @@ -86,8 +84,13 @@ export async function createWeightedECDSAValidator< // sort signers by address in descending order const configSigners = config ? [...config.signers].sort(sortByAddress) : [] + const signers_: LocalAccount[] = [] + for (const signer of _signers) { + signers_.push(await toSigner({ signer })) + } + // sort signers by address in descending order - const signers = _signers.sort(sortByAddress) + const signers = signers_.sort(sortByAddress) // Fetch chain id const chainId = await getChainId(client) @@ -111,7 +114,9 @@ export async function createWeightedECDSAValidator< return `0x${signatures}` }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new Error( + "Smart account signer doesn't need to sign transactions" + ) }, async signTypedData< const TTypedData extends TypedData | Record, @@ -165,9 +170,7 @@ export async function createWeightedECDSAValidator< return 0n }, // Sign a user operation - async signUserOperation( - userOperation: UserOperation> - ) { + async signUserOperation(userOperation) { const callDataAndNonceHash = keccak256( encodeAbiParameters( parseAbiParameters("address, bytes, uint256"), @@ -211,8 +214,9 @@ export async function createWeightedECDSAValidator< userOperation: { ...userOperation, signature: "0x" - }, - entryPoint: entryPointAddress, + } as UserOperation, + entryPointAddress: entryPoint.address, + entryPointVersion: entryPoint.version, chainId: chainId }) @@ -232,9 +236,7 @@ export async function createWeightedECDSAValidator< // Get simple dummy signature // Equivalent to signUserOperation for now - async getDummySignature( - userOperation: UserOperation> - ) { + async getStubSignature(userOperation) { const callDataAndNonceHash = keccak256( encodeAbiParameters( parseAbiParameters("address, bytes, uint256"), @@ -311,9 +313,11 @@ export async function createWeightedECDSAValidator< } // [TODO] - Create an action which can work with weightedEcdsaKernelAccountClient -export function getUpdateConfigCall( - entryPointAddress: entryPoint, - kernelVersion: GetKernelVersion, +export function getUpdateConfigCall< + entryPointVersion extends EntryPointVersion +>( + entryPoint: EntryPointType, + kernelVersion: GetKernelVersion, newConfig: WeightedECDSAValidatorConfig ): { to: Address @@ -321,10 +325,7 @@ export function getUpdateConfigCall( data: Hex } { const signers = [...newConfig.signers].sort(sortByAddress) - const validatorAddress = getValidatorAddress( - entryPointAddress, - kernelVersion - ) + const validatorAddress = getValidatorAddress(entryPoint, kernelVersion) return { to: validatorAddress, @@ -344,25 +345,25 @@ export function getUpdateConfigCall( // [TODO] - Create an action which can work with weightedEcdsaKernelAccountClient export async function getCurrentSigners< - entryPoint extends EntryPoint, + entryPointVersion extends EntryPointVersion, TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined >( client: Client, { - entryPoint: entryPointAddress, + entryPoint, kernelVersion, multiSigAccountAddress, validatorAddress: _validatorAddress }: { - entryPoint: entryPoint - kernelVersion: GetKernelVersion + entryPoint: EntryPointType + kernelVersion: GetKernelVersion multiSigAccountAddress: Address validatorAddress?: Address } ): Promise> { const validatorAddress = getValidatorAddress( - entryPointAddress, + entryPoint, kernelVersion, _validatorAddress ) diff --git a/plugins/weighted-r1-k1/CHANGELOG.md b/plugins/weighted-r1-k1/CHANGELOG.md index 8fd5c5c3..67a9b5ca 100644 --- a/plugins/weighted-r1-k1/CHANGELOG.md +++ b/plugins/weighted-r1-k1/CHANGELOG.md @@ -1,5 +1,17 @@ # @zerodev/weighted-validator +## 5.4.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + +## 5.4.0-beta.0 + +### Minor Changes + +- Migrate to using `viem@2.18.x` with native AA modules instead of `permissionless` + ## 5.3.1 ### Patch Changes diff --git a/plugins/weighted-r1-k1/actions/approveUserOperation.ts b/plugins/weighted-r1-k1/actions/approveUserOperation.ts index 53681248..8d5f46d1 100644 --- a/plugins/weighted-r1-k1/actions/approveUserOperation.ts +++ b/plugins/weighted-r1-k1/actions/approveUserOperation.ts @@ -1,96 +1,48 @@ import { - type KernelSmartAccount, + AccountNotFoundError, + type KernelSmartAccountImplementation, KernelV3AccountAbi, isPluginInitialized } from "@zerodev/sdk" -import type { SmartAccount } from "permissionless/accounts" -import type { Middleware } from "permissionless/actions/smartAccount" -import type { - ENTRYPOINT_ADDRESS_V06_TYPE, - EntryPoint, - GetAccountParameter, - PartialBy, - Prettify, - UserOperation -} from "permissionless/types" -import { - AccountOrClientNotFoundError, - parseAccount -} from "permissionless/utils" import type { Chain, Client, Hex, Transport } from "viem" +import type { + PrepareUserOperationParameters, + SmartAccount, + UserOperationCall +} from "viem/account-abstraction" import { encodeAbiParameters, getAbiItem, keccak256, parseAbiParameters, + parseAccount, toFunctionSelector } from "viem/utils" import { getValidatorAddress } from "../toWeightedValidatorPlugin.js" export type ApproveUserOperationParameters< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | SmartAccount - | undefined = - | SmartAccount - | undefined -> = { - userOperation: entryPoint extends ENTRYPOINT_ADDRESS_V06_TYPE - ? PartialBy< - UserOperation<"v0.6">, - | "sender" - | "nonce" - | "initCode" - | "callGasLimit" - | "verificationGasLimit" - | "preVerificationGas" - | "maxFeePerGas" - | "maxPriorityFeePerGas" - | "paymasterAndData" - | "signature" - > - : PartialBy< - UserOperation<"v0.7">, - | "sender" - | "nonce" - | "factory" - | "factoryData" - | "callGasLimit" - | "verificationGasLimit" - | "preVerificationGas" - | "maxFeePerGas" - | "maxPriorityFeePerGas" - | "paymaster" - | "paymasterVerificationGasLimit" - | "paymasterPostOpGasLimit" - | "paymasterData" - | "signature" - > -} & GetAccountParameter & - Middleware + account extends SmartAccount | undefined = SmartAccount | undefined, + accountOverride extends SmartAccount | undefined = SmartAccount | undefined, + calls extends readonly unknown[] = readonly unknown[] +> = PrepareUserOperationParameters export async function approveUserOperation< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | SmartAccount - | undefined = - | SmartAccount - | undefined + account extends SmartAccount | undefined = SmartAccount | undefined, + chain extends Chain | undefined = Chain | undefined, + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] >( - client: Client, - args: Prettify< - ApproveUserOperationParameters - > + client: Client, + args_: ApproveUserOperationParameters ): Promise { - const { account: account_ = client.account, userOperation } = args - if (!account_) throw new AccountOrClientNotFoundError() + const args = args_ as ApproveUserOperationParameters + const { account: account_ = client.account, ...userOperation } = args + if (!account_) throw new AccountNotFoundError() - const account = parseAccount(account_) as KernelSmartAccount - const validatorAddress = getValidatorAddress(account.entryPoint) + const account = parseAccount( + account_ + ) as SmartAccount + const validatorAddress = getValidatorAddress(account.entryPoint.version) const actionSelector = toFunctionSelector( getAbiItem({ abi: KernelV3AccountAbi, name: "execute" }) @@ -132,7 +84,11 @@ export async function approveUserOperation< const callDataAndNonceHash = keccak256( encodeAbiParameters(parseAbiParameters("address, bytes, uint256"), [ userOperation.sender || account.address, - userOperation.callData, + userOperation.calls + ? await account.encodeCalls( + userOperation.calls as UserOperationCall[] + ) + : userOperation.callData, userOperation.nonce || (await account.getNonce()) ]) ) diff --git a/plugins/weighted-r1-k1/actions/getCurrentSigners.ts b/plugins/weighted-r1-k1/actions/getCurrentSigners.ts new file mode 100644 index 00000000..7072ef54 --- /dev/null +++ b/plugins/weighted-r1-k1/actions/getCurrentSigners.ts @@ -0,0 +1,63 @@ +import { + AccountNotFoundError, + type KernelSmartAccountImplementation +} from "@zerodev/sdk" +import type { Chain, Client, Hex, Transport } from "viem" +import type { SmartAccount } from "viem/account-abstraction" +import { readContract } from "viem/actions" +import { getAction, parseAccount } from "viem/utils" +import { WeightedValidatorAbi } from "../abi" +import { getValidatorAddress } from "../index.js" + +export type GetCurrentSignersReturnType = Array<{ + encodedPublicKey: Hex + weight: number +}> + +export async function getCurrentSigners< + account extends SmartAccount | undefined, + chain extends Chain | undefined +>( + client: Client +): Promise { + const account_ = client.account + if (!account_) throw new AccountNotFoundError() + + const account = parseAccount( + account_ + ) as unknown as SmartAccount + + const validatorAddress = getValidatorAddress(account.entryPoint.version) + + const weightedStorage = await getAction( + client, + readContract, + "readContract" + )({ + abi: WeightedValidatorAbi, + address: validatorAddress, + functionName: "weightedStorage", + args: [account.address] + }) + + const guardiansLength = weightedStorage[3] + + const signers: Array<{ encodedPublicKey: Hex; weight: number }> = [] + for (let i = 0; i < guardiansLength; i++) { + const guardian = await getAction( + client, + readContract, + "readContract" + )({ + abi: WeightedValidatorAbi, + address: validatorAddress, + functionName: "guardian", + args: [BigInt(i), account.address] + }) + signers.push({ + encodedPublicKey: guardian[2], + weight: guardian[1] + }) + } + return signers +} diff --git a/plugins/weighted-r1-k1/actions/sendUserOperationWithSignatures.ts b/plugins/weighted-r1-k1/actions/sendUserOperationWithSignatures.ts index 0702c5a4..acc87d29 100644 --- a/plugins/weighted-r1-k1/actions/sendUserOperationWithSignatures.ts +++ b/plugins/weighted-r1-k1/actions/sendUserOperationWithSignatures.ts @@ -1,20 +1,11 @@ import { + AccountNotFoundError, type Action, - type KernelSmartAccount, + type KernelSmartAccountImplementation, KernelV3AccountAbi, getEncodedPluginsData, isPluginInitialized } from "@zerodev/sdk" -import { - AccountOrClientNotFoundError, - type UserOperation, - parseAccount -} from "permissionless" -import type { SmartAccount } from "permissionless/accounts" -import { sendUserOperation as sendUserOperationBundler } from "permissionless/actions" -import { prepareUserOperationRequest } from "permissionless/actions/smartAccount" -import type { SendUserOperationParameters } from "permissionless/actions/smartAccount" -import type { EntryPoint, GetEntryPointVersion } from "permissionless/types" import { type Chain, type Client, @@ -23,51 +14,52 @@ import { type Transport, zeroAddress } from "viem" -import type { Prettify } from "viem/chains" -import { getAbiItem, getAction, toFunctionSelector } from "viem/utils" +import { + type PrepareUserOperationParameters, + type SendUserOperationParameters, + type SmartAccount, + type UserOperation, + prepareUserOperation, + sendUserOperation +} from "viem/account-abstraction" +import { + getAbiItem, + getAction, + parseAccount, + toFunctionSelector +} from "viem/utils" import { encodeSignatures } from "../utils.js" export type SendUserOperationWithSignaturesParameters< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | SmartAccount - | undefined = - | SmartAccount - | undefined -> = Prettify< - SendUserOperationParameters & { - signatures: Hex[] - } -> + account extends SmartAccount | undefined = SmartAccount | undefined, + accountOverride extends SmartAccount | undefined = SmartAccount | undefined, + calls extends readonly unknown[] = readonly unknown[] +> = SendUserOperationParameters & { + signatures: Hex[] +} export async function sendUserOperationWithSignatures< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends - | SmartAccount - | undefined = - | SmartAccount - | undefined + account extends SmartAccount | undefined, + chain extends Chain | undefined, + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] >( - client: Client, - args: Prettify< - SendUserOperationWithSignaturesParameters< - entryPoint, - TTransport, - TChain, - TAccount - > + client: Client, + args_: SendUserOperationWithSignaturesParameters< + account, + accountOverride, + calls > ): Promise { + const args = args_ as SendUserOperationWithSignaturesParameters const { account: account_ = client.account } = args - if (!account_) throw new AccountOrClientNotFoundError() + if (!account_) throw new AccountNotFoundError() - const account = parseAccount(account_) as KernelSmartAccount + const account = parseAccount( + account_ + ) as SmartAccount - const { userOperation: _userOperation, signatures } = args + const { signatures, ..._userOperation } = args const action: Action = { selector: toFunctionSelector( @@ -92,10 +84,8 @@ export async function sendUserOperationWithSignatures< // if the regular validator is not enabled, encode with enable signatures if (!isPluginEnabled) { const dummySignature = - await account.kernelPluginManager.regularValidator.getDummySignature( - _userOperation as UserOperation< - GetEntryPointVersion - > + await account.kernelPluginManager.regularValidator.getStubSignature( + _userOperation as UserOperation ) const encodedDummySignatures = await getEncodedPluginsData({ @@ -111,22 +101,15 @@ export async function sendUserOperationWithSignatures< const userOperation = await getAction( client, - prepareUserOperationRequest< - entryPoint, - TTransport, - TChain, - TAccount - >, - "prepareUserOperationRequest" - )(args) + prepareUserOperation, + "prepareUserOperation" + )(_userOperation as PrepareUserOperationParameters) const encodedSignatures = await getEncodedPluginsData({ enableSignature: encodeSignatures(signatures), userOpSignature: await account.kernelPluginManager.signUserOperationWithActiveValidator( - userOperation as UserOperation< - GetEntryPointVersion - > + userOperation as UserOperation ), action, enableData: await account.kernelPluginManager.getEnableData( @@ -136,62 +119,61 @@ export async function sendUserOperationWithSignatures< userOperation.signature = encodedSignatures - return sendUserOperationBundler(client, { - userOperation: userOperation as UserOperation< - GetEntryPointVersion - >, - entryPoint: account.entryPoint - }) + return await getAction( + client, + sendUserOperation, + "sendUserOperation" + )({ ...userOperation } as SendUserOperationParameters< + account, + accountOverride + >) // if the regular validator is enabled, use signUserOperationWithActiveValidator directly } else { const userOperation = await getAction( client, - prepareUserOperationRequest< - entryPoint, - TTransport, - TChain, - TAccount - >, - "prepareUserOperationRequest" - )(args) + prepareUserOperation, + "prepareUserOperation" + )(args as PrepareUserOperationParameters) userOperation.signature = await account.kernelPluginManager.signUserOperationWithActiveValidator( - userOperation as UserOperation< - GetEntryPointVersion - > + userOperation as UserOperation ) - return sendUserOperationBundler(client, { - userOperation: userOperation as UserOperation< - GetEntryPointVersion - >, - entryPoint: account.entryPoint - }) + return await getAction( + client, + sendUserOperation, + "sendUserOperation" + )({ ...userOperation } as SendUserOperationParameters< + account, + accountOverride + >) } } const encodedSignatures = encodeSignatures(signatures) _userOperation.signature = encodedSignatures - _userOperation.signature = await account.getDummySignature( - _userOperation as UserOperation> + _userOperation.signature = await account.getStubSignature( + _userOperation as UserOperation ) const userOperation = await getAction( client, - prepareUserOperationRequest, - "prepareUserOperationRequest" - )(args) + prepareUserOperation, + "prepareUserOperation" + )(_userOperation as PrepareUserOperationParameters) userOperation.signature = encodedSignatures userOperation.signature = await account.signUserOperation( - userOperation as UserOperation> + userOperation as UserOperation ) - return sendUserOperationBundler(client, { - userOperation: userOperation as UserOperation< - GetEntryPointVersion - >, - entryPoint: account.entryPoint - }) + return await getAction( + client, + sendUserOperation, + "sendUserOperation" + )({ ...userOperation } as SendUserOperationParameters< + account, + accountOverride + >) } diff --git a/plugins/weighted-r1-k1/actions/updateSignersData.ts b/plugins/weighted-r1-k1/actions/updateSignersData.ts new file mode 100644 index 00000000..0cddf43d --- /dev/null +++ b/plugins/weighted-r1-k1/actions/updateSignersData.ts @@ -0,0 +1,133 @@ +import { + AccountNotFoundError, + type KernelSmartAccountImplementation +} from "@zerodev/sdk" +import { encodeWebAuthnPubKey } from "@zerodev/webauthn-key" +import { + type Chain, + type Client, + type Hash, + type Hex, + type Transport, + concatHex, + encodeAbiParameters, + encodeFunctionData, + toHex +} from "viem" +import { + type SendUserOperationParameters, + type SmartAccount, + sendUserOperation +} from "viem/account-abstraction" +import { getAction, parseAccount } from "viem/utils" +import { WeightedValidatorAbi } from "../abi.js" +import { + SIGNER_TYPE, + type SendUserOperationWithSignaturesParameters, + type WeightedValidatorConfig, + getValidatorAddress, + sendUserOperationWithSignatures +} from "../index.js" +import { sortByPublicKey } from "../utils.js" + +export type UpdateSignersDataParameters< + account extends SmartAccount | undefined = SmartAccount | undefined, + accountOverride extends SmartAccount | undefined = SmartAccount | undefined, + calls extends readonly unknown[] = readonly unknown[] +> = SendUserOperationParameters & { + signatures?: Hex[] + config: WeightedValidatorConfig +} + +export async function updateSignersData< + account extends SmartAccount | undefined, + chain extends Chain | undefined, + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] +>( + client: Client, + args: UpdateSignersDataParameters +): Promise { + const { account: account_ = client.account, config, signatures } = args + if (!account_) throw new AccountNotFoundError() + + const account = parseAccount( + account_ + ) as SmartAccount + + const validatorAddress = getValidatorAddress(account.entryPoint.version) + + // Check if sum of weights is equal or greater than threshold + let totalWeight = 0 + for (const signer of config.signers) { + totalWeight += signer.weight + } + if (totalWeight < config.threshold) { + throw new Error( + `Sum of weights (${totalWeight}) is less than threshold (${config.threshold})` + ) + } // sort signers by address in descending order + const configSigners = config + ? [...config.signers] + .map((signer) => + typeof signer.publicKey === "object" + ? { + ...signer, + publicKey: encodeWebAuthnPubKey( + signer.publicKey + ) as Hex + } + : { ...signer, publicKey: signer.publicKey as Hex } + ) + .sort(sortByPublicKey) + : [] + + const call = { + to: validatorAddress, + value: 0n, + data: encodeFunctionData({ + abi: WeightedValidatorAbi, + functionName: "renew", + args: [ + concatHex([ + toHex(config.threshold, { size: 3 }), + toHex(config.delay || 0, { size: 6 }), + encodeAbiParameters( + [{ name: "guardiansData", type: "bytes[]" }], + [ + configSigners.map((cfg) => + concatHex([ + cfg.publicKey.length === 42 + ? SIGNER_TYPE.ECDSA + : SIGNER_TYPE.PASSKEY, + toHex(cfg.weight, { size: 3 }), + cfg.publicKey + ]) + ) + ] + ) + ]) + ] + }) + } + + if (signatures) { + return getAction( + client, + sendUserOperationWithSignatures, + "sendUserOperationWithSignatures" + )({ + ...args, + calls: [call] + } as SendUserOperationWithSignaturesParameters) + } + + return getAction( + client, + sendUserOperation, + "sendUserOperation" + )({ + ...args, + calls: [call] + } as SendUserOperationParameters) +} diff --git a/plugins/weighted-r1-k1/clients/decorators/weightedKernelAccountClient.ts b/plugins/weighted-r1-k1/clients/decorators/weightedKernelAccountClient.ts index 67ce6a79..44a25645 100644 --- a/plugins/weighted-r1-k1/clients/decorators/weightedKernelAccountClient.ts +++ b/plugins/weighted-r1-k1/clients/decorators/weightedKernelAccountClient.ts @@ -1,52 +1,40 @@ -import { - type KernelAccountClientActions, - type KernelSmartAccount, - kernelAccountClientActions -} from "@zerodev/sdk" -import type { Middleware } from "permissionless/actions/smartAccount" -import type { EntryPoint, Prettify } from "permissionless/types" import type { Chain, Client, Hash, Hex, Transport } from "viem" +import type { SmartAccount } from "viem/account-abstraction" import { type ApproveUserOperationParameters, approveUserOperation } from "../../actions/approveUserOperation.js" +import { + type GetCurrentSignersReturnType, + getCurrentSigners +} from "../../actions/getCurrentSigners.js" import { type SendUserOperationWithSignaturesParameters, sendUserOperationWithSignatures } from "../../actions/sendUserOperationWithSignatures.js" +import { + type UpdateSignersDataParameters, + updateSignersData +} from "../../actions/updateSignersData.js" export type WeightedKernelAccountClientActions< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, - TSmartAccount extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount - | undefined -> = KernelAccountClientActions< - entryPoint, - TTransport, - TChain, - TSmartAccount -> & { + TSmartAccount extends SmartAccount | undefined = SmartAccount | undefined +> = { /** * Approve a user operation with the given transport, chain, smart account and signer. * * @param args - Parameters for the approveUserOperation function * @returns A promise that resolves to the result of the approveUserOperation function */ - approveUserOperation: ( - args: Prettify< - Parameters< - typeof approveUserOperation< - entryPoint, - TTransport, - TChain, - TSmartAccount - > - >[1] - > + approveUserOperation: < + accountOverride extends SmartAccount | undefined = + | SmartAccount + | undefined + >( + args: Parameters< + typeof approveUserOperation + >[1] ) => Promise /** * Sends a user operation with the given transport, chain, smart account and signer. @@ -54,66 +42,41 @@ export type WeightedKernelAccountClientActions< * @param args - Parameters for the sendUserOperationWithSignatures function * @returns A promise that resolves to the result of the sendUserOperationWithSignatures function */ - sendUserOperationWithSignatures: ( - args: Prettify< - Parameters< - typeof sendUserOperationWithSignatures< - entryPoint, - TTransport, - TChain, - TSmartAccount - > - >[1] - > + sendUserOperationWithSignatures: ( + args: SendUserOperationWithSignaturesParameters ) => Promise + /** + * Retrieves the current signers for the smart account. + * + * @returns A promise that resolves to an array of objects, each containing an encoded public key and its associated weight. + */ + getCurrentSigner: () => Promise + + updateSignersData: (args: UpdateSignersDataParameters) => Promise } -export function weightedKernelAccountClientActions< - entryPoint extends EntryPoint ->({ middleware }: Middleware) { +export function weightedKernelAccountClientActions() { return < - TTransport extends Transport, TChain extends Chain | undefined = Chain | undefined, - TSmartAccount extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount + TSmartAccount extends SmartAccount | undefined = + | SmartAccount + | undefined, + accountOverride extends SmartAccount | undefined = + | SmartAccount | undefined >( - client: Client - ): WeightedKernelAccountClientActions< - entryPoint, - TTransport, - TChain, - TSmartAccount - > => ({ - ...kernelAccountClientActions({ middleware })(client), + client: Client + ): WeightedKernelAccountClientActions => ({ approveUserOperation: (args) => - approveUserOperation( + approveUserOperation( client, - { - ...args, - middleware - } as ApproveUserOperationParameters< - entryPoint, - TTransport, - TChain, - TSmartAccount + args as ApproveUserOperationParameters< + TSmartAccount, + accountOverride > ), sendUserOperationWithSignatures: (args) => - sendUserOperationWithSignatures< - entryPoint, - TTransport, - TChain, - TSmartAccount - >(client, { - ...args, - middleware - } as SendUserOperationWithSignaturesParameters< - entryPoint, - TTransport, - TChain, - TSmartAccount - >) + sendUserOperationWithSignatures(client, args), + getCurrentSigner: () => getCurrentSigners(client), + updateSignersData: (args) => updateSignersData(client, args) }) } diff --git a/plugins/weighted-r1-k1/clients/weightedKernelAccountClient.ts b/plugins/weighted-r1-k1/clients/weightedKernelAccountClient.ts index 166bb987..96bcc774 100644 --- a/plugins/weighted-r1-k1/clients/weightedKernelAccountClient.ts +++ b/plugins/weighted-r1-k1/clients/weightedKernelAccountClient.ts @@ -1,77 +1,94 @@ import { - type KernelSmartAccount, + type KernelAccountClientActions, createKernelAccountClient } from "@zerodev/sdk" import type { SmartAccountClientConfig } from "@zerodev/sdk/clients" -import type { EntryPoint } from "permissionless/types" -import type { BundlerRpcSchema } from "permissionless/types/bundler" -import type { Chain, Client, Transport } from "viem" +import type { + BundlerRpcSchema, + Chain, + Client, + Prettify, + RpcSchema, + Transport +} from "viem" +import type { BundlerActions, SmartAccount } from "viem/account-abstraction" import { type WeightedKernelAccountClientActions, weightedKernelAccountClientActions } from "./decorators/weightedKernelAccountClient.js" export type WeightedKernelAccountClient< - entryPoint extends EntryPoint, transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined, - account extends - | KernelSmartAccount - | undefined = - | KernelSmartAccount - | undefined -> = Client< - transport, - chain, - account, - BundlerRpcSchema, - WeightedKernelAccountClientActions + account extends SmartAccount | undefined = SmartAccount | undefined, + client extends Client | undefined = Client | undefined, + rpcSchema extends RpcSchema | undefined = undefined +> = Prettify< + Client< + transport, + chain extends Chain + ? chain + : // biome-ignore lint/suspicious/noExplicitAny: + client extends Client + ? chain + : undefined, + account, + rpcSchema extends RpcSchema + ? [...BundlerRpcSchema, ...rpcSchema] + : BundlerRpcSchema, + BundlerActions & + KernelAccountClientActions & + WeightedKernelAccountClientActions + > > -export const createWeightedKernelAccountClient = < - TSmartAccount extends - | KernelSmartAccount - | undefined, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = undefined, - TEntryPoint extends EntryPoint = TSmartAccount extends KernelSmartAccount< - infer U - > - ? U - : never +export function createWeightedKernelAccountClient< + transport extends Transport, + chain extends Chain | undefined = undefined, + account extends SmartAccount | undefined = undefined, + client extends Client | undefined = undefined, + rpcSchema extends RpcSchema | undefined = undefined >( parameters: SmartAccountClientConfig< - TEntryPoint, - TTransport, - TChain, - TSmartAccount + transport, + chain, + account, + client, + rpcSchema > -): WeightedKernelAccountClient< - TEntryPoint, - TTransport, - TChain, - TSmartAccount -> => { +): WeightedKernelAccountClient + +export function createWeightedKernelAccountClient( + parameters: SmartAccountClientConfig +): WeightedKernelAccountClient { const { + client: client_, key = "Account", name = "Weighted Kernel Account Client", - middleware + paymaster, + paymasterContext, + bundlerTransport, + userOperation } = parameters - const client = createKernelAccountClient({ - ...parameters, - name, - key - }) + const client = Object.assign( + createKernelAccountClient({ + ...parameters, + chain: parameters.chain ?? client_?.chain, + bundlerTransport, + key, + name + }), + { + client: client_, + paymaster, + paymasterContext, + userOperation, + type: "weightedKernelAccountClient" + } + ) return client.extend( - weightedKernelAccountClientActions({ - middleware - }) - ) as WeightedKernelAccountClient< - TEntryPoint, - TTransport, - TChain, - TSmartAccount - > + weightedKernelAccountClientActions() + ) as WeightedKernelAccountClient } diff --git a/plugins/weighted-r1-k1/constants.ts b/plugins/weighted-r1-k1/constants.ts index ef175eef..4a7572e7 100644 --- a/plugins/weighted-r1-k1/constants.ts +++ b/plugins/weighted-r1-k1/constants.ts @@ -1,8 +1,7 @@ import { constants } from "@zerodev/sdk" import type { Action } from "@zerodev/sdk/types" -import { getEntryPointVersion } from "permissionless" -import type { EntryPoint } from "permissionless/types/entrypoint" import { toFunctionSelector } from "viem" +import type { EntryPointVersion } from "viem/account-abstraction" const RECOVERY_ACTION_ADDRESS_V06 = "0x2f65dB8039fe5CAEE0a8680D2879deB800F31Ae1" const RECOVERY_ACTION_ADDRESS_V07 = "0xe884C2868CC82c16177eC73a93f7D9E6F3A5DC6E" @@ -10,9 +9,10 @@ const RECOVERY_ACTION_SELECTOR = toFunctionSelector( "doRecovery(address, bytes)" ) -export const getRecoveryAction = (entryPoint: EntryPoint): Action => { - const entryPointVersion = getEntryPointVersion(entryPoint) - if (entryPointVersion === "v0.6") { +export const getRecoveryAction = ( + entryPointVersion: EntryPointVersion +): Action => { + if (entryPointVersion === "0.6") { return { address: RECOVERY_ACTION_ADDRESS_V06, selector: RECOVERY_ACTION_SELECTOR diff --git a/plugins/weighted-r1-k1/index.ts b/plugins/weighted-r1-k1/index.ts index 62cdacce..89f4953d 100644 --- a/plugins/weighted-r1-k1/index.ts +++ b/plugins/weighted-r1-k1/index.ts @@ -27,8 +27,6 @@ import { type WeightedSigner, type WeightedValidatorConfig, createWeightedValidator, - getCurrentSigners, - getUpdateConfigCall, getValidatorAddress } from "./toWeightedValidatorPlugin.js" @@ -37,8 +35,6 @@ export { type WeightedValidatorConfig, type WeightedSigner, getValidatorAddress, - getUpdateConfigCall, - getCurrentSigners, type KernelValidator, toECDSASigner, type ECDSASignerParams, diff --git a/plugins/weighted-r1-k1/package.json b/plugins/weighted-r1-k1/package.json index 653fe559..fbecce4c 100644 --- a/plugins/weighted-r1-k1/package.json +++ b/plugins/weighted-r1-k1/package.json @@ -1,6 +1,6 @@ { "name": "@zerodev/weighted-validator", - "version": "5.3.1", + "version": "5.4.0", "author": "ZeroDev", "main": "./_cjs/index.js", "module": "./_esm/index.js", @@ -39,9 +39,8 @@ "@simplewebauthn/browser": "^8.3.4" }, "peerDependencies": { - "viem": ">=2.16.3 <2.18.0", - "@zerodev/sdk": "^5.3.11", - "@zerodev/webauthn-key": "^5.3.0", - "permissionless": ">=0.1.44 <=0.1.45" + "viem": "^2.21.40", + "@zerodev/sdk": "^5.4.0", + "@zerodev/webauthn-key": "^5.4.0" } } diff --git a/plugins/weighted-r1-k1/signers/toECDSASigner.ts b/plugins/weighted-r1-k1/signers/toECDSASigner.ts index 5d2a73a9..0a2be0e3 100644 --- a/plugins/weighted-r1-k1/signers/toECDSASigner.ts +++ b/plugins/weighted-r1-k1/signers/toECDSASigner.ts @@ -1,38 +1,28 @@ -import { constants, fixSignedData } from "@zerodev/sdk" +import { constants, fixSignedData, toSigner } from "@zerodev/sdk" +import type { Signer } from "@zerodev/sdk/types" import type { TypedData } from "abitype" -import { - SignTransactionNotSupportedBySmartAccount, - type SmartAccountSigner -} from "permissionless/accounts" -import type { Address, LocalAccount, TypedDataDefinition } from "viem" +import type { TypedDataDefinition } from "viem" import { toAccount } from "viem/accounts" import { SIGNER_TYPE } from "../constants.js" import type { WeightedSigner } from "../toWeightedValidatorPlugin.js" -export type ECDSASignerParams< - TSource extends string = "custom", - TAddress extends Address = Address -> = { - signer: SmartAccountSigner +export type ECDSASignerParams = { + signer: Signer } -export function toECDSASigner< - TSource extends string = "custom", - TAddress extends Address = Address ->({ signer }: ECDSASignerParams): WeightedSigner { - const viemSigner: LocalAccount = { - ...signer, - signTransaction: (_, __) => { - throw new SignTransactionNotSupportedBySmartAccount() - } - } as LocalAccount +export async function toECDSASigner({ + signer +}: ECDSASignerParams): Promise { + const viemSigner = await toSigner({ signer }) const account = toAccount({ address: viemSigner.address, async signMessage({ message }) { return fixSignedData(await viemSigner.signMessage({ message })) }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new Error( + "Smart account signer doesn't need to sign transactions" + ) }, async signTypedData< const TTypedData extends TypedData | Record, diff --git a/plugins/weighted-r1-k1/signers/toWebAuthnSigner.ts b/plugins/weighted-r1-k1/signers/toWebAuthnSigner.ts index e708353d..89edfbde 100644 --- a/plugins/weighted-r1-k1/signers/toWebAuthnSigner.ts +++ b/plugins/weighted-r1-k1/signers/toWebAuthnSigner.ts @@ -10,7 +10,6 @@ import { uint8ArrayToHexString } from "@zerodev/webauthn-key" import type { TypedData } from "abitype" -import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" import { type Chain, type Client, @@ -140,7 +139,9 @@ export const toWebAuthnSigner = async < ]) }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new Error( + "Smart account signer doesn't need to sign transactions" + ) }, async signTypedData< const TTypedData extends TypedData | Record, diff --git a/plugins/weighted-r1-k1/toWeightedValidatorPlugin.ts b/plugins/weighted-r1-k1/toWeightedValidatorPlugin.ts index 14abb228..c5c0429b 100644 --- a/plugins/weighted-r1-k1/toWeightedValidatorPlugin.ts +++ b/plugins/weighted-r1-k1/toWeightedValidatorPlugin.ts @@ -1,28 +1,24 @@ -import type { GetKernelVersion, KernelValidator } from "@zerodev/sdk/types" +import type { + EntryPointType, + GetKernelVersion, + KernelValidator +} from "@zerodev/sdk/types" import type { WebAuthnKey } from "@zerodev/webauthn-key" import type { TypedData } from "abitype" -import { - type UserOperation, - getEntryPointVersion, - getUserOperationHash -} from "permissionless" -import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" -import type { - EntryPoint, - GetEntryPointVersion -} from "permissionless/types/entrypoint" import { type Address, - type Chain, type Client, type Hex, type LocalAccount, - type Transport, type TypedDataDefinition, encodeAbiParameters, - encodeFunctionData, zeroAddress } from "viem" +import { + type EntryPointVersion, + type UserOperation, + getUserOperationHash +} from "viem/account-abstraction" import { toAccount } from "viem/accounts" import { getChainId, readContract } from "viem/actions" import { concatHex, getAction, toHex } from "viem/utils" @@ -31,7 +27,8 @@ import { SIGNER_TYPE, WEIGHTED_VALIDATOR_ADDRESS_V07, decodeSignatures, - encodeSignatures + encodeSignatures, + sortByPublicKey } from "./index.js" import { encodeWebAuthnPubKey } from "./signers/toWebAuthnSigner.js" @@ -51,49 +48,34 @@ export interface WeightedValidatorConfig { delay?: number // in seconds } -// Sort addresses in descending order -const sortByPublicKey = ( - a: { publicKey: Hex } | { getPublicKey: () => Hex }, - b: { publicKey: Hex } | { getPublicKey: () => Hex } -) => { - if ("publicKey" in a && "publicKey" in b) - return a.publicKey.toLowerCase() < b.publicKey.toLowerCase() ? 1 : -1 - else if ("getPublicKey" in a && "getPublicKey" in b) - return a.getPublicKey().toLowerCase() < b.getPublicKey().toLowerCase() - ? 1 - : -1 - else return 0 -} - -export const getValidatorAddress = (entryPointAddress: EntryPoint): Address => { - const entryPointVersion = getEntryPointVersion(entryPointAddress) - if (entryPointVersion === "v0.6") +export const getValidatorAddress = ( + entryPointVersion: EntryPointVersion +): Address => { + if (entryPointVersion === "0.6") throw new Error("EntryPoint v0.6 not supported") return WEIGHTED_VALIDATOR_ADDRESS_V07 } export async function createWeightedValidator< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined + entryPointVersion extends EntryPointVersion >( - client: Client, + client: Client, { config, - entryPoint: entryPointAddress, + entryPoint, kernelVersion: _, signer, - validatorAddress + validatorAddress: validatorAddress_ }: { config?: WeightedValidatorConfig signer: WeightedSigner - entryPoint: entryPoint - kernelVersion: GetKernelVersion + entryPoint: EntryPointType + kernelVersion: GetKernelVersion validatorAddress?: Address } -): Promise> { - validatorAddress = - validatorAddress ?? getValidatorAddress(entryPointAddress) +): Promise> { + const validatorAddress = + validatorAddress_ ?? getValidatorAddress(entryPoint.version) if (!validatorAddress) { throw new Error("Validator address not provided") } @@ -150,7 +132,9 @@ export async function createWeightedValidator< ]) }, async signTransaction(_, __) { - throw new SignTransactionNotSupportedBySmartAccount() + throw new Error( + "Smart account signer doesn't need to sign transactions" + ) }, async signTypedData< const TTypedData extends TypedData | Record, @@ -173,8 +157,7 @@ export async function createWeightedValidator< validatorType: "SECONDARY", address: validatorAddress, source: "WeightedValidator", - getIdentifier: () => - validatorAddress ?? getValidatorAddress(entryPointAddress), + getIdentifier: () => validatorAddress, async getEnableData() { if (!config) return "0x" return concatHex([ @@ -203,9 +186,7 @@ export async function createWeightedValidator< return 0n }, // Sign a user operation - async signUserOperation( - userOperation: UserOperation> - ) { + async signUserOperation(userOperation) { let signatures: readonly Hex[] = [] if (userOperation.signature !== "0x") { signatures = decodeSignatures(userOperation.signature) @@ -215,8 +196,9 @@ export async function createWeightedValidator< userOperation: { ...userOperation, signature: "0x" - }, - entryPoint: entryPointAddress, + } as UserOperation, + entryPointAddress: entryPoint.address, + entryPointVersion: entryPoint.version, chainId: chainId }) @@ -227,11 +209,10 @@ export async function createWeightedValidator< return encodeSignatures([...signatures, lastSignature]) }, - async getDummySignature( - userOperation: UserOperation> - ) { + async getStubSignature(userOperation) { let signatures: readonly Hex[] = [] if (userOperation.signature !== "0x") { + console.log(userOperation.signature) signatures = decodeSignatures(userOperation.signature) } @@ -255,9 +236,7 @@ export async function createWeightedValidator< "readContract" )({ abi: WeightedValidatorAbi, - address: - validatorAddress ?? - getValidatorAddress(entryPointAddress), + address: validatorAddress, functionName: "weightedStorage", args: [kernelAccountAddress] }) @@ -269,9 +248,7 @@ export async function createWeightedValidator< "readContract" )({ abi: WeightedValidatorAbi, - address: - validatorAddress ?? - getValidatorAddress(entryPointAddress), + address: validatorAddress, functionName: "guardian", args: [BigInt(index), kernelAccountAddress] }) @@ -303,117 +280,3 @@ export async function createWeightedValidator< } } } - -export function getUpdateConfigCall( - entryPointAddress: entryPoint, - config: WeightedValidatorConfig -): { - to: Address - value: bigint - data: Hex -} { - const validatorAddress = getValidatorAddress(entryPointAddress) - - // Check if sum of weights is equal or greater than threshold - let totalWeight = 0 - for (const signer of config.signers) { - totalWeight += signer.weight - } - if (totalWeight < config.threshold) { - throw new Error( - `Sum of weights (${totalWeight}) is less than threshold (${config.threshold})` - ) - } // sort signers by address in descending order - const configSigners = config - ? [...config.signers] - .map((signer) => - typeof signer.publicKey === "object" - ? { - ...signer, - publicKey: encodeWebAuthnPubKey( - signer.publicKey - ) as Hex - } - : { ...signer, publicKey: signer.publicKey as Hex } - ) - .sort(sortByPublicKey) - : [] - - return { - to: validatorAddress, - value: 0n, - data: encodeFunctionData({ - abi: WeightedValidatorAbi, - functionName: "renew", - args: [ - concatHex([ - toHex(config.threshold, { size: 3 }), - toHex(config.delay || 0, { size: 6 }), - encodeAbiParameters( - [{ name: "guardiansData", type: "bytes[]" }], - [ - configSigners.map((cfg) => - concatHex([ - cfg.publicKey.length === 42 - ? SIGNER_TYPE.ECDSA - : SIGNER_TYPE.PASSKEY, - toHex(cfg.weight, { size: 3 }), - cfg.publicKey - ]) - ) - ] - ) - ]) - ] - }) - } -} - -export async function getCurrentSigners< - entryPoint extends EntryPoint, - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined ->( - client: Client, - { - entryPoint: entryPointAddress, - weightedAccountAddress - }: { - entryPoint: entryPoint - weightedAccountAddress: Address - } -): Promise> { - const validatorAddress = getValidatorAddress(entryPointAddress) - - const weightedStorage = await getAction( - client, - readContract, - "readContract" - )({ - abi: WeightedValidatorAbi, - address: validatorAddress, - functionName: "weightedStorage", - args: [weightedAccountAddress] - }) - - const guardiansLength = weightedStorage[3] - - const signers: Array<{ encodedPublicKey: Hex; weight: number }> = [] - for (let i = 0; i < guardiansLength; i++) { - const guardian = await getAction( - client, - readContract, - "readContract" - )({ - abi: WeightedValidatorAbi, - address: validatorAddress, - functionName: "guardian", - args: [BigInt(i), weightedAccountAddress] - }) - signers.push({ - encodedPublicKey: guardian[2], - weight: guardian[1] - }) - } - return signers -} diff --git a/plugins/weighted-r1-k1/utils.ts b/plugins/weighted-r1-k1/utils.ts index 6eb36386..1768b5f9 100644 --- a/plugins/weighted-r1-k1/utils.ts +++ b/plugins/weighted-r1-k1/utils.ts @@ -14,3 +14,17 @@ export const decodeSignatures = (signaturesData: Hex): Hex[] => { ) return [...signatures] } + +// Sort addresses in descending order +export const sortByPublicKey = ( + a: { publicKey: Hex } | { getPublicKey: () => Hex }, + b: { publicKey: Hex } | { getPublicKey: () => Hex } +) => { + if ("publicKey" in a && "publicKey" in b) + return a.publicKey.toLowerCase() < b.publicKey.toLowerCase() ? 1 : -1 + else if ("getPublicKey" in a && "getPublicKey" in b) + return a.getPublicKey().toLowerCase() < b.getPublicKey().toLowerCase() + ? 1 + : -1 + else return 0 +}