UNPKG

@zerodev/sdk

Version:

A utility library for working with ERC-4337

414 lines (393 loc) 14.4 kB
import { satisfies } from "semver" import { type Address, type Client, type Hex, concat, concatHex, maxUint16, maxUint192, pad, 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 { ONLY_ENTRYPOINT_HOOK_ADDRESS, VALIDATOR_MODE, VALIDATOR_TYPE } from "../../constants.js" import { type KernelPluginManager, type KernelPluginManagerParams, ValidatorMode } from "../../types/kernel.js" import { getKernelV3Nonce } from "../kernel/utils/account/ep0_7/getKernelV3Nonce.js" import { accountMetadata } from "../kernel/utils/common/accountMetadata.js" import { getActionSelector } from "../kernel/utils/common/getActionSelector.js" import { getEncodedPluginsData as getEncodedPluginsDataV1 } from "../kernel/utils/plugins/ep0_6/getEncodedPluginsData.js" import { getPluginsEnableTypedData as getPluginsEnableTypedDataV1 } from "../kernel/utils/plugins/ep0_6/getPluginsEnableTypedData.js" import { getEncodedPluginsData as getEncodedPluginsDataV2 } from "../kernel/utils/plugins/ep0_7/getEncodedPluginsData.js" 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< entryPointVersion extends EntryPointVersion >( // biome-ignore lint/suspicious/noExplicitAny: <explanation> plugin: any ): plugin is KernelPluginManager<entryPointVersion> { return plugin?.getPluginEnableSignature !== undefined } export async function toKernelPluginManager< entryPointVersion extends EntryPointVersion >( client: Client, { sudo, regular, hook, pluginEnableSignature, validatorInitData, action, validAfter = 0, validUntil = 0, entryPoint, kernelVersion, chainId, isPreInstalled = false }: KernelPluginManagerParams<entryPointVersion> ): Promise<KernelPluginManager<entryPointVersion>> { if ( (sudo && !satisfies(kernelVersion, sudo?.supportedKernelVersions)) || (regular && !satisfies(kernelVersion, regular?.supportedKernelVersions)) ) { throw new Error( "Either sudo or/and regular validator version mismatch. Update to latest plugin package and use the proper plugin version" ) } let pluginEnabled = isPreInstalled const activeValidator = regular || sudo if (!activeValidator) { throw new Error("One of `sudo` or `regular` validator must be set") } action = { selector: action?.selector ?? getActionSelector(entryPoint.version), address: action?.address ?? zeroAddress } if ( entryPoint.version === "0.7" && (action.address.toLowerCase() !== zeroAddress.toLowerCase() || action.selector.toLowerCase() !== getActionSelector(entryPoint.version).toLowerCase()) && kernelVersion === "0.3.0" ) { action.hook = { address: action.hook?.address ?? ONLY_ENTRYPOINT_HOOK_ADDRESS } } if (!action) { throw new Error("Action data must be set") } const getSignatureData = async ( accountAddress: Address, selector: Hex, userOpSignature: Hex = "0x" ): Promise<Hex> => { if (!action) { throw new Error("Action data must be set") } if (entryPoint.version === "0.6") { if (regular) { if (pluginEnabled) { return ValidatorMode.plugin } if (await isPluginEnabled(accountAddress, selector)) { return ValidatorMode.plugin } const enableSignature = await getPluginEnableSignature(accountAddress) if (!enableSignature) { throw new Error("Enable signature not set") } return getEncodedPluginsDataV1({ accountAddress, enableSignature, action, validator: regular, validUntil, validAfter }) } else if (sudo) { return ValidatorMode.sudo } else { throw new Error( "One of `sudo` or `regular` validator must be set" ) } } if (regular) { if (pluginEnabled) { return userOpSignature } if (await isPluginEnabled(accountAddress, action.selector)) { return userOpSignature } const enableSignature = await getPluginEnableSignature(accountAddress) return getEncodedPluginsDataV2({ enableSignature, userOpSignature, action, enableData: await regular.getEnableData(accountAddress), hook }) } else if (sudo) { return userOpSignature } else { throw new Error("One of `sudo` or `regular` validator must be set") } } const isPluginEnabled = async (accountAddress: Address, selector: Hex) => { if (isPreInstalled) return true if (!action) { throw new Error("Action data must be set") } if (!regular) throw new Error("regular validator not set") if (entryPoint.version === "0.6") { return regular.isEnabled(accountAddress, selector) } const isEnabled = (await regular.isEnabled(accountAddress, action.selector)) || (await isPluginInitialized(client, accountAddress, regular.address)) if (isEnabled) { pluginEnabled = true } return isEnabled } const getPluginEnableSignature = async (accountAddress: Address) => { if (!action) { throw new Error("Action data must be set") } if (pluginEnableSignature) return pluginEnableSignature if (!sudo) throw new Error( "sudo validator not set -- need it to enable the validator" ) if (!regular) throw new Error("regular validator not set") const { version } = await accountMetadata( client, accountAddress, kernelVersion ) if (!chainId) { chainId = client.chain?.id ?? (await getChainId(client)) } let ownerSig: Hex if (entryPoint.version === "0.6") { const typeData = await getPluginsEnableTypedDataV1({ accountAddress, chainId, kernelVersion: version ?? kernelVersion, action, validator: regular, validUntil, validAfter }) ownerSig = await sudo.signTypedData(typeData) pluginEnableSignature = ownerSig return ownerSig } const validatorNonce = await getKernelV3Nonce(client, accountAddress) const typedData = await getPluginsEnableTypedDataV2({ accountAddress, chainId, kernelVersion: version, action, hook, validator: regular, validatorNonce }) ownerSig = await sudo.signTypedData(typedData) pluginEnableSignature = ownerSig return ownerSig } const getIdentifier = (isSudo = false) => { const validator = (isSudo ? sudo : regular) ?? activeValidator return concat([ VALIDATOR_TYPE[validator.validatorType], validator.getIdentifier() ]) } const getPluginsEnableTypedData = async (accountAddress: Address) => { if (!action) { throw new Error("Action data must be set") } if (!sudo) throw new Error( "sudo validator not set -- need it to enable the validator" ) if (!regular) throw new Error("regular validator not set") const { version } = await accountMetadata( client, accountAddress, kernelVersion ) const validatorNonce = await getKernelV3Nonce(client, accountAddress) if (!chainId) { chainId = client.chain?.id ?? (await getChainId(client)) } const typedData = await getPluginsEnableTypedDataV2({ accountAddress, chainId, kernelVersion: version, action, validator: regular, validatorNonce }) return typedData } return { sudoValidator: sudo, regularValidator: regular, activeValidatorMode: sudo && !regular ? "sudo" : "regular", ...activeValidator, hook, getIdentifier, encodeModuleInstallCallData: async (accountAddress: Address) => { if (!action) { throw new Error("Action data must be set") } if (!regular) throw new Error("regular validator not set") if (entryPoint.version === "0.6") { return await encodeModuleInstallCallDataEpV06({ accountAddress, selector: action.selector, executor: action.address, validator: regular?.address, validUntil, validAfter, enableData: await regular.getEnableData(accountAddress) }) } throw new Error("EntryPoint v0.7 not supported yet") }, signUserOperation: async (userOperation) => { const userOpSig = await activeValidator.signUserOperation(userOperation) if (entryPoint.version === "0.6") { return concatHex([ await getSignatureData( userOperation.sender, userOperation.callData.toString().slice(0, 10) as Hex ), userOpSig ]) } return await getSignatureData( userOperation.sender, userOperation.callData.toString().slice(0, 10) as Hex, userOpSig ) }, getAction: () => { if (!action) { throw new Error("Action data must be set") } return action }, getValidityData: () => ({ validAfter, validUntil }), getStubSignature: async (userOperation) => { const userOpSig = await activeValidator.getStubSignature(userOperation) if (entryPoint.version === "0.6") { return concatHex([ await getSignatureData( userOperation.sender, userOperation.callData.toString().slice(0, 10) as Hex ), userOpSig ]) } return await getSignatureData( userOperation.sender, userOperation.callData.toString().slice(0, 10) as Hex, userOpSig ) }, getNonceKey: async ( accountAddress = zeroAddress, customNonceKey = 0n ) => { if (!action) { throw new Error("Action data must be set") } if (entryPoint.version === "0.6") { if (customNonceKey > maxUint192) { throw new Error( "Custom nonce key must be equal or less than maxUint192 for 0.6" ) } return await activeValidator.getNonceKey( accountAddress, customNonceKey ) } if (customNonceKey > maxUint16) throw new Error( "Custom nonce key must be equal or less than 2 bytes(maxUint16) for v0.7" ) const validatorMode = !regular || (await isPluginEnabled(accountAddress, action.selector)) ? VALIDATOR_MODE.DEFAULT : VALIDATOR_MODE.ENABLE const validatorType = regular ? VALIDATOR_TYPE[regular.validatorType] : VALIDATOR_TYPE.SUDO const encoding = pad( concatHex([ validatorMode, // 1 byte validatorType, // 1 byte pad(activeValidator.getIdentifier(), { size: 20, dir: "right" }), // 20 bytes pad( toHex( await activeValidator.getNonceKey( accountAddress, customNonceKey ) ), { size: 2 } ) // 2 byte ]), { size: 24 } ) const encodedNonceKey = BigInt(encoding) return encodedNonceKey }, getPluginEnableSignature, getPluginsEnableTypedData, getValidatorInitData: async () => { if (validatorInitData) return validatorInitData return { validatorAddress: sudo?.address ?? activeValidator.address, enableData: (await sudo?.getEnableData()) ?? (await activeValidator.getEnableData()), identifier: pad(getIdentifier(true), { size: 21, dir: "right" }) } }, signUserOperationWithActiveValidator: async (userOperation) => { return activeValidator.signUserOperation(userOperation) } } }