UNPKG

@charterlabs/permissionless

Version:

A utility library for working with ERC-4337

383 lines (361 loc) 15 kB
import type { Assign, Prettify, } from "viem" import { type Address, type Client, type Hex, concat, encodeFunctionData, toHex, } from "viem" import { type SmartAccount, type SmartAccountImplementation, entryPoint07Abi, entryPoint07Address, toSmartAccount } from "viem/account-abstraction" import { readContract } from "viem/actions" import { getAccountNonce } from "../../actions/public/getAccountNonce" import { decode7579Calls } from "../../utils/decode7579Calls" import { encode7579Calls } from "../../utils/encode7579Calls" /** * The account creation ABI for Biconomy Smart Account (from the biconomy SmartAccountFactory) */ /** * Default addresses for Biconomy Smart Account */ const BICONOMY_ADDRESSES: { CHARTER_FACTORY_ADDRESS: Address K1_VALIDATOR_ADDRESS: Address } = { CHARTER_FACTORY_ADDRESS: "0xF6044Df1977DebF1069dE44a3Bf002d8738E25D6", K1_VALIDATOR_ADDRESS: "0x9Bf4eF98187990262579B0C98764683A824dDc5b" } export type ToNexusSmartAccountParameters = Prettify<{ client: Client version: "1.0.0" address?: Address | undefined entryPoint?: { address: Address version: "0.7" } index?: bigint factoryAddress?: Address validatorAddress?: Address attesters?: Address[] threshold?: number /** * Validator data containing the public key X and Y coordinates */ validatorData?: { pubKeyX: bigint pubKeyY: bigint } /** * Hash of the authenticator ID */ authenticatorIdHash?: Hex /** * Array of executor modules with their configuration data */ executors?: { module: Address; data: Hex }[] /** * Hook module configuration */ hook?: { module: Address data: Hex } /** * Array of fallback modules with their configuration data */ fallbacks?: { module: Address; data: Hex }[] /** * Array of pre-validation hooks */ preValidationHooks?: { hookType: bigint; module: Address; data: Hex }[] /** * Address of the registry contract */ registryAddress?: Address }> export type NexusSmartAccountImplementation = Assign< SmartAccountImplementation<typeof entryPoint07Abi, "0.7">, { sign: NonNullable<SmartAccountImplementation["sign"]> } > export type ToNexusSmartAccountReturnType = Prettify< SmartAccount<NexusSmartAccountImplementation> > export async function toNexusSmartAccount( parameters: ToNexusSmartAccountParameters ): Promise<ToNexusSmartAccountReturnType> { const { client, index = 0n, address, factoryAddress = BICONOMY_ADDRESSES.CHARTER_FACTORY_ADDRESS, validatorAddress = BICONOMY_ADDRESSES.K1_VALIDATOR_ADDRESS, attesters = [], threshold = 0 } = parameters const entryPoint = { address: parameters.entryPoint?.address ?? entryPoint07Address, abi: entryPoint07Abi, version: parameters.entryPoint?.version ?? "0.7" } let _accountAddress: Address | undefined = address const getFactoryArgs = async () => { // Carefully handle defaults only for undefined values const validatorData = parameters.validatorData === undefined ? { pubKeyX: 0n, pubKeyY: 0n } : parameters.validatorData const authenticatorIdHash = parameters.authenticatorIdHash === undefined ? "0x0000000000000000000000000000000000000000000000000000000000000000" as Hex : parameters.authenticatorIdHash const executors = parameters.executors === undefined ? [] : parameters.executors const hook = parameters.hook === undefined ? { module: "0x0000000000000000000000000000000000000000" as Address, data: "0x" as Hex } : parameters.hook const fallbacks = parameters.fallbacks === undefined ? [] : parameters.fallbacks const preValidationHooks = parameters.preValidationHooks === undefined ? [] : parameters.preValidationHooks const registryConfig = { registry: parameters.registryAddress === undefined ? "0x0000000000000000000000000000000000000000" as Address : parameters.registryAddress, attesters: attesters.sort((left, right) => left.toLowerCase().localeCompare(right.toLowerCase()) ) as readonly Address[], threshold } return { factory: factoryAddress, factoryData: encodeFunctionData({ abi: [ { name: "createAccount", type: "function", stateMutability: "payable", inputs: [ { type: "uint256", name: "index" }, { type: "tuple", name: "validatorData", components: [ { type: "uint256", name: "pubKeyX" }, { type: "uint256", name: "pubKeyY" } ] }, { type: "bytes32", name: "authenticatorIdHash" }, { type: "tuple[]", name: "executors", components: [ { type: "address", name: "module" }, { type: "bytes", name: "data" } ] }, { type: "tuple", name: "hook", components: [ { type: "address", name: "module" }, { type: "bytes", name: "data" } ] }, { type: "tuple[]", name: "fallbacks", components: [ { type: "address", name: "module" }, { type: "bytes", name: "data" } ] }, { type: "tuple[]", name: "preValidationHooks", components: [ { type: "uint256", name: "hookType" }, { type: "address", name: "module" }, { type: "bytes", name: "data" } ] }, { type: "tuple", name: "registryConfig", components: [ { type: "address", name: "registry" }, { type: "address[]", name: "attesters" }, { type: "uint8", name: "threshold" } ] } ], outputs: [{ type: "address", name: "" }] } ], functionName: "createAccount", args: [ index, validatorData, authenticatorIdHash, executors, hook, fallbacks, preValidationHooks, registryConfig ] }) } } return toSmartAccount({ client, entryPoint, getFactoryArgs, async getAddress() { if (_accountAddress) return _accountAddress // If we need to compute the address const computedAddress = await readContract(client, { address: factoryAddress, abi: [ { name: "computeAccountAddress", type: "function", stateMutability: "view", inputs: [ { type: "uint256", name: "index" }, { type: "tuple", name: "validatorData", components: [ { type: "uint256", name: "pubKeyX" }, { type: "uint256", name: "pubKeyY" } ] }, { type: "bytes32", name: "authenticatorIdHash" }, { type: "tuple[]", name: "executors", components: [ { type: "address", name: "module" }, { type: "bytes", name: "data" } ] }, { type: "tuple", name: "hook", components: [ { type: "address", name: "module" }, { type: "bytes", name: "data" } ] }, { type: "tuple[]", name: "fallbacks", components: [ { type: "address", name: "module" }, { type: "bytes", name: "data" } ] }, { type: "tuple[]", name: "preValidationHooks", components: [ { type: "uint256", name: "hookType" }, { type: "address", name: "module" }, { type: "bytes", name: "data" } ] }, { type: "tuple", name: "registryConfig", components: [ { type: "address", name: "registry" }, { type: "address[]", name: "attesters" }, { type: "uint8", name: "threshold" } ] } ], outputs: [{ type: "address", name: "expectedAddress" }] } ], functionName: "computeAccountAddress", args: [ index, parameters.validatorData === undefined ? { pubKeyX: 0n, pubKeyY: 0n } : parameters.validatorData, parameters.authenticatorIdHash === undefined ? "0x0000000000000000000000000000000000000000000000000000000000000000" as Hex : parameters.authenticatorIdHash, parameters.executors === undefined ? [] : parameters.executors, parameters.hook === undefined ? { module: "0x0000000000000000000000000000000000000000" as Address, data: "0x" as Hex } : parameters.hook, parameters.fallbacks === undefined ? [] : parameters.fallbacks, parameters.preValidationHooks === undefined ? [] : parameters.preValidationHooks, { registry: parameters.registryAddress === undefined ? "0x0000000000000000000000000000000000000000" as Address : parameters.registryAddress, attesters: attesters.sort((left, right) => left.toLowerCase().localeCompare(right.toLowerCase()) ) as readonly Address[], threshold } ] }) _accountAddress = computedAddress return computedAddress }, async getNonce(args) { const TIMESTAMP_ADJUSTMENT = 16777215n // max value for size 3 const defaultedKey = (args?.key ?? 0n) % TIMESTAMP_ADJUSTMENT const defaultedValidationMode = "0x00" const key = concat([ toHex(defaultedKey, { size: 3 }), defaultedValidationMode, validatorAddress ]) const address = await this.getAddress() return getAccountNonce(client, { address, entryPointAddress: entryPoint.address, key: BigInt(key) }) }, encodeCalls: async (calls) => { return encode7579Calls({ mode: { type: calls.length > 1 ? "batchcall" : "call", revertOnError: false, selector: "0x", context: "0x" }, callData: calls }) }, async decodeCalls(callData) { return decode7579Calls(callData).callData }, async getStubSignature() { const dynamicPart = validatorAddress.substring(2).padEnd(40, "0") return `0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000${dynamicPart}000000000000000000000000000000000000000000000000000000000000004181d4b4981670cb18f99f0b4a66446df1bf5b204d24cfcb659bf38ba27a4359b5711649ec2423c5e1247245eba2964679b6a1dbb85c992ae40b9b00c6935b02ff1b00000000000000000000000000000000000000000000000000000000000000` as Hex }, async sign({ hash }) { return this.signMessage({ message: hash }) }, // README: Logic Moved to client side async signMessage() { throw new Error('Not implemented') }, // README: Logic Moved to client side async signTypedData() { throw new Error('Not implemented') }, async signUserOperation() { throw new Error('Not implemented') } }) }