UNPKG

@zerodev/sdk

Version:

A utility library for working with ERC-4337

313 lines (285 loc) 9.84 kB
import { type Account, type Address, type Assign, type Chain, type EIP1193Provider, type Hex, type LocalAccount, type OneOf, type Transport, type TypedData, type TypedDataDefinition, 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 } 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" export type KernelSmartAccountV1Implementation = Assign< SmartAccountImplementation<GetEntryPointAbi<"0.6">, "0.6">, { sign: NonNullable<SmartAccountImplementation["sign"]> encodeCalls: ( calls: Parameters<SmartAccountImplementation["encodeCalls"]>[0], callType?: CallType | undefined ) => Promise<Hex> generateInitCode: () => Promise<Hex> encodeModuleInstallCallData: () => Promise<Hex> } > export type CreateKernelAccountV1ReturnType = SmartAccount<KernelSmartAccountV1Implementation> const createAccountAbi = [ { inputs: [ { internalType: "address", name: "_owner", type: "address" }, { internalType: "uint256", name: "_index", type: "uint256" } ], name: "createAccount", outputs: [ { internalType: "contract EIP1967Proxy", name: "proxy", type: "address" } ], stateMutability: "nonpayable", type: "function" } ] const executeAndRevertAbi = [ { inputs: [ { internalType: "address", name: "to", type: "address" }, { internalType: "uint256", name: "value", type: "uint256" }, { internalType: "bytes", name: "data", type: "bytes" }, { internalType: "enum Operation", name: "operation", type: "uint8" } ], name: "executeAndRevert", outputs: [], stateMutability: "nonpayable", type: "function" } ] const KERNEL_V1_ADDRESSES: { FACTORY_ADDRESS: Address ENTRYPOINT_V0_6: Address } = { FACTORY_ADDRESS: "0x4E4946298614FC299B50c947289F4aD0572CB9ce", ENTRYPOINT_V0_6: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" } export async function createKernelAccountV1( client: KernelSmartAccountV1Implementation["client"], { signer, address, entryPoint, index = 0n }: { signer: OneOf< | EIP1193Provider | WalletClient<Transport, Chain | undefined, Account> | LocalAccount > entryPoint: { address: Address version: "0.6" } address?: Address index?: bigint } ): Promise<CreateKernelAccountV1ReturnType> { const viemSigner: LocalAccount = { ...signer, signTransaction: (_, __) => { throw new Error( "Smart account signer doesn't need to sign transactions" ) } } as LocalAccount // Fetch chain id const chainId = await getChainId(client) const generateInitCode = async (): Promise<Hex> => { return encodeFunctionData({ abi: createAccountAbi, functionName: "createAccount", args: [signer.address, index] }) } const getFactoryArgs = async () => { return { factory: KERNEL_V1_ADDRESSES.FACTORY_ADDRESS, factoryData: await generateInitCode() } } // 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 }) })()) if (!accountAddress) throw new Error("Account address not found") const account = toAccount({ address: accountAddress, async signMessage({ message }) { return viemSigner.signMessage({ message }) }, async signTransaction(_, __) { throw new Error( "Smart account signer doesn't need to sign transactions" ) }, async signTypedData< const TTypedData extends TypedData | Record<string, unknown>, TPrimaryType extends | keyof TTypedData | "EIP712Domain" = keyof TTypedData >(typedData: TypedDataDefinition<TTypedData, TPrimaryType>) { return viemSigner.signTypedData(typedData) } }) const _entryPoint = { address: entryPoint?.address ?? entryPoint06Address, abi: entryPoint06Abi, version: entryPoint?.version ?? "0.6" } return toSmartAccount<KernelSmartAccountV1Implementation>({ ...account, generateInitCode, encodeModuleInstallCallData: async () => { throw new Error("Not implemented") }, 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<string, unknown>, TPrimaryType extends | keyof TTypedData | "EIP712Domain" = keyof TTypedData >(typedData: TypedDataDefinition<TTypedData, TPrimaryType>) { return viemSigner.signTypedData(typedData) }, 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 }, getFactoryArgs, client, entryPoint: _entryPoint, async getNonce() { return getAccountNonce(client, { address: accountAddress, entryPointAddress: entryPoint.address }) }, async signUserOperation(userOperation) { const hash = getUserOperationHash({ userOperation: { ...userOperation, sender: userOperation.sender ?? (await this.getAddress()), signature: "0x" }, entryPointAddress: entryPoint.address, entryPointVersion: entryPoint.version, chainId: chainId }) const signature = await signMessage(client, { account: viemSigner, message: { raw: hash } }) return signature }, // async getInitCode() { // if (smartAccountDeployed) return "0x" // smartAccountDeployed = await isSmartAccountDeployed( // client, // accountAddress // ) // if (smartAccountDeployed) return "0x" // return generateInitCode() // }, async encodeCalls(calls, callType): Promise<Hex> { 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(calls)] }) return encodeFunctionData({ abi: executeAndRevertAbi, functionName: "executeAndRevert", args: [MULTISEND_ADDRESS, 0n, multiSendCallData, 1n] }) } 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: executeAndRevertAbi, functionName: "executeAndRevert", args: [call.to, call.value || 0n, call.data, 0n] }) } if (callType === "delegatecall") { return encodeFunctionData({ abi: executeAndRevertAbi, functionName: "executeAndRevert", args: [call.to, call.value || 0n, call.data, 1n] }) } throw new Error("Invalid call type") }, async getStubSignature() { return "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c" } }) }