UNPKG

@zerodev/sdk

Version:

A utility library for working with ERC-4337

265 lines 10.6 kB
import { concatHex, createNonceManager, encodeDeployData, encodeFunctionData, getTypesForEIP712Domain, hashTypedData, parseAbi, validateTypedData } from "viem"; import { 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 { KernelVersionToAddressesMap } from "../../../constants.js"; import { MULTISEND_ADDRESS, encodeMultiSend, multiSendAbi } from "../../utils/multisend.js"; import { isKernelPluginManager, toKernelPluginManager } from "../../utils/toKernelPluginManager.js"; import { KernelAccountV2Abi } from "./abi/KernelAccountV2Abi.js"; import { KernelFactoryV2Abi } from "./abi/KernelFactoryV2Abi.js"; // Safe's library for create and create2: https://github.com/safe-global/safe-contracts/blob/0acdd35a203299585438f53885df630f9d486a86/contracts/libraries/CreateCall.sol // Address was found here: https://github.com/safe-global/safe-deployments/blob/926ec6bbe2ebcac3aa2c2c6c0aff74aa590cbc6a/src/assets/v1.4.1/create_call.json const createCallAddress = "0x9b35Af71d77eaf8d7e40252370304687390A1A52"; const createCallAbi = parseAbi([ "function performCreate(uint256 value, bytes memory deploymentData) public returns (address newContract)", "function performCreate2(uint256 value, bytes memory deploymentData, bytes32 salt) public returns (address newContract)" ]); /** * Default addresses for kernel smart account */ export const KERNEL_ADDRESSES = { FACTORY_ADDRESS: "0xaee9762ce625e0a8f7b184670fb57c37bfe1d0f1", ENTRYPOINT_V0_6: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" }; /** * Get the account initialization code for a kernel smart account * @param index * @param factoryAddress * @param ecdsaValidatorAddress */ const getAccountInitCode = async ({ index, factoryAddress, validatorAddress, enableData }) => { // Build the account init code return concatHex([ factoryAddress, encodeFunctionData({ abi: KernelFactoryV2Abi, functionName: "createAccount", args: [validatorAddress, enableData, index] }) ]); }; /** * Build a kernel smart account from a private key, that use the ECDSA signer behind the scene * @param client * @param privateKey * @param entryPoint * @param index * @param factoryAddress * @param ecdsaValidatorAddress * @param deployedAccountAddress */ export async function createKernelAccountV0_2(client, { plugins, entryPoint, index = 0n, factoryAddress = KERNEL_ADDRESSES.FACTORY_ADDRESS, address }) { const kernelPluginManager = isKernelPluginManager(plugins) ? plugins : await toKernelPluginManager(client, { sudo: plugins.sudo, regular: plugins.regular, action: plugins.action, pluginEnableSignature: plugins.pluginEnableSignature, kernelVersion: "0.0.2", entryPoint }); // Helper to generate the init code for the smart account const generateInitCode = async () => { const validatorInitData = await kernelPluginManager.getValidatorInitData(); return getAccountInitCode({ index, factoryAddress, validatorAddress: validatorInitData.validatorAddress, enableData: validatorInitData.enableData }); }; const getFactoryArgs = async () => { return { factory: factoryAddress, factoryData: await generateInitCode() }; }; // 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 }) { return kernelPluginManager.signMessage({ message }); }, async signTransaction(_, __) { throw new Error("Smart account signer doesn't need to sign transactions"); }, async signTypedData(typedData) { const _typedData = 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 }); const typedHash = hashTypedData(typedData); return kernelPluginManager.signMessage({ message: { raw: typedHash } }); } }); const _entryPoint = { address: entryPoint?.address ?? entryPoint06Address, abi: entryPoint06Abi, version: entryPoint?.version ?? "0.6" }; let chainId; const getMemoizedChainId = async () => { if (chainId) return chainId; chainId = client.chain ? client.chain.id : await getAction(client, getChainId, "getChainId")({}); return chainId; }; return toSmartAccount({ kernelVersion: "0.0.2", client, entryPoint: _entryPoint, kernelPluginManager, factoryAddress, accountImplementationAddress: KernelVersionToAddressesMap["0.0.2"].accountImplementationAddress, generateInitCode, encodeModuleInstallCallData: async () => { return await kernelPluginManager.encodeModuleInstallCallData(accountAddress); }, 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; const { factory, factoryData } = await getFactoryArgs(); // Get the sender address based on the init code accountAddress = await getSenderAddress(client, { factory, factoryData, entryPointAddress: entryPoint.address }); return accountAddress; }, // 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 }); }, // Sign a user operation 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({ abi: KernelAccountV2Abi, functionName: "execute", args: [ createCallAddress, 0n, encodeFunctionData({ abi: createCallAbi, functionName: "performCreate", args: [ 0n, encodeDeployData({ abi: _tx.abi, bytecode: _tx.bytecode, args: _tx.args }) ] }), 1 // Delegate call ] }); }, // Encode a call async encodeCalls(calls, callType) { if (calls.length > 1) { const multiSendCallData = encodeFunctionData({ abi: multiSendAbi, functionName: "multiSend", args: [encodeMultiSend(calls)] }); return encodeFunctionData({ abi: KernelAccountV2Abi, functionName: "execute", args: [MULTISEND_ADDRESS, 0n, multiSendCallData, 1] }); } const call = calls.length === 0 ? undefined : calls[0]; if (!call) { throw new Error("No calls to encode"); } // Default to `call` if (!callType || callType === "call") { if (call.to.toLowerCase() === accountAddress.toLowerCase()) { return call.data || "0x"; } return encodeFunctionData({ abi: KernelAccountV2Abi, functionName: "execute", args: [call.to, call.value || 0n, call.data || "0x", 0] }); } if (callType === "delegatecall") { return encodeFunctionData({ abi: KernelAccountV2Abi, functionName: "execute", args: [call.to, 0n, call.data || "0x", 1] }); } throw new Error("Invalid call type"); }, // Get simple dummy signature async getStubSignature(userOperation) { if (!userOperation) { throw new Error("No user operation provided"); } return kernelPluginManager.getStubSignature(userOperation); } }); } //# sourceMappingURL=createKernelAccountV0_2.js.map