import { type Account, type Address, type Assign, type Chain, type Client, type Hex, type JsonRpcAccount, type LocalAccount, type OneOf, type Prettify, type Transport, type WalletClient, decodeFunctionData, encodeAbiParameters, encodeFunctionData, encodePacked, getContractAddress, hexToBigInt, keccak256, parseAbiParameters } from "viem" import { type SmartAccount, type SmartAccountImplementation, entryPoint06Abi, entryPoint06Address, getUserOperationHash, toSmartAccount } from "viem/account-abstraction" import { signMessage } from "viem/actions" import { getAccountNonce } from "../../actions/public/getAccountNonce.js" import { type EthereumProvider, toOwner } from "../../utils/toOwner.js" import { BiconomyAbi, FactoryAbi } from "./abi/BiconomySmartAccountAbi.js" const BICONOMY_PROXY_CREATION_CODE = "0x6080346100aa57601f61012038819003918201601f19168301916001600160401b038311848410176100af578084926020946040528339810103126100aa57516001600160a01b0381168082036100aa5715610065573055604051605a90816100c68239f35b60405162461bcd60e51b815260206004820152601e60248201527f496e76616c696420696d706c656d656e746174696f6e206164647265737300006044820152606490fd5b600080fd5b634e487b7160e01b600052604160045260246000fdfe608060405230546000808092368280378136915af43d82803e156020573d90f35b3d90fdfea2646970667358221220a03b18dce0be0b4c9afe58a9eb85c35205e2cf087da098bbf1d23945bf89496064736f6c63430008110033" /** * The account creation ABI for Biconomy Smart Account (from the biconomy SmartAccountFactory) */ /** * Default addresses for Biconomy Smart Account */ const BICONOMY_ADDRESSES: { ECDSA_OWNERSHIP_REGISTRY_MODULE: Address FACTORY_ADDRESS: Address ACCOUNT_V2_0_LOGIC: Address DEFAULT_FALLBACK_HANDLER_ADDRESS: Address } = { ECDSA_OWNERSHIP_REGISTRY_MODULE: "0x0000001c5b32F37F5beA87BDD5374eB2aC54eA8e", FACTORY_ADDRESS: "0x000000a56Aaca3e9a4C479ea6b6CD0DbcB6634F5", ACCOUNT_V2_0_LOGIC: "0x0000002512019Dafb59528B82CB92D3c5D2423aC", DEFAULT_FALLBACK_HANDLER_ADDRESS: "0x0bBa6d96BD616BedC6BFaa341742FD43c60b83C1" } /** * Get the account initialization code for Biconomy smart account with ECDSA as default authorization module * @param owner * @param index * @param factoryAddress * @param ecdsaValidatorAddress */ const getAccountInitCode = async ({ owner, index, ecdsaModuleAddress }: { owner: Address index: bigint ecdsaModuleAddress: Address }): Promise => { if (!owner) throw new Error("Owner account not found") // Build the module setup data const ecdsaOwnershipInitData = encodeFunctionData({ abi: BiconomyAbi, functionName: "initForSmartAccount", args: [owner] }) // Build the account init code return encodeFunctionData({ abi: FactoryAbi, functionName: "deployCounterFactualAccount", args: [ecdsaModuleAddress, ecdsaOwnershipInitData, index] }) } export type ToBiconomySmartAccountParameters = Prettify<{ client: Client< Transport, Chain | undefined, JsonRpcAccount | LocalAccount | undefined > owners: [ OneOf< | EthereumProvider | WalletClient | LocalAccount > ] address?: Address | undefined entryPoint: { address: Address version: "0.6" } nonceKey?: bigint index?: bigint factoryAddress?: Address ecdsaModuleAddress?: Address accountLogicAddress?: Address fallbackHandlerAddress?: Address }> export type BiconomySmartAccountImplementation = Assign< SmartAccountImplementation, { sign: NonNullable } > export type ToBiconomySmartAccountReturnType = Prettify< SmartAccount > const getAccountAddress = async ({ factoryAddress, accountLogicAddress, fallbackHandlerAddress, ecdsaModuleAddress, owner, index = BigInt(0) }: { factoryAddress: Address accountLogicAddress: Address fallbackHandlerAddress: Address ecdsaModuleAddress: Address owner: Address index?: bigint }): Promise
=> { // Build the module setup data const ecdsaOwnershipInitData = encodeFunctionData({ abi: BiconomyAbi, functionName: "initForSmartAccount", args: [owner] }) // Build account init code const initialisationData = encodeFunctionData({ abi: BiconomyAbi, functionName: "init", args: [ fallbackHandlerAddress, ecdsaModuleAddress, ecdsaOwnershipInitData ] }) const deploymentCode = encodePacked( ["bytes", "uint256"], [BICONOMY_PROXY_CREATION_CODE, hexToBigInt(accountLogicAddress)] ) const salt = keccak256( encodePacked( ["bytes32", "uint256"], [keccak256(encodePacked(["bytes"], [initialisationData])), index] ) ) return getContractAddress({ from: factoryAddress, salt, bytecode: deploymentCode, opcode: "CREATE2" }) } /** * @deprecated Biconomy Smart Account is deprecated. Please use toNexusSmartAccount instead. * @see toNexusSmartAccount */ export async function toBiconomySmartAccount( parameters: ToBiconomySmartAccountParameters ): Promise { const { owners, client, index = 0n, address, accountLogicAddress = BICONOMY_ADDRESSES.ACCOUNT_V2_0_LOGIC, fallbackHandlerAddress = BICONOMY_ADDRESSES.DEFAULT_FALLBACK_HANDLER_ADDRESS, ecdsaModuleAddress = BICONOMY_ADDRESSES.ECDSA_OWNERSHIP_REGISTRY_MODULE, factoryAddress = BICONOMY_ADDRESSES.FACTORY_ADDRESS } = parameters const localOwner = await toOwner({ owner: owners[0] }) const entryPoint = { address: parameters.entryPoint?.address ?? entryPoint06Address, abi: entryPoint06Abi, version: parameters.entryPoint?.version ?? "0.6" } let accountAddress: Address | undefined = address const getFactoryArgs = async () => { return { factory: factoryAddress, factoryData: await getAccountInitCode({ owner: localOwner.address, index, ecdsaModuleAddress }) } } return toSmartAccount({ client, entryPoint, getFactoryArgs, async getAddress() { if (accountAddress) return accountAddress // Get the sender address based on the init code accountAddress = await getAccountAddress({ owner: localOwner.address, ecdsaModuleAddress, factoryAddress, accountLogicAddress, fallbackHandlerAddress, index }) return accountAddress }, async getNonce(args) { const address = await this.getAddress() return getAccountNonce(client, { address, entryPointAddress: entryPoint.address, key: parameters?.nonceKey ?? args?.key }) }, encodeCalls: async (calls) => { if (calls.length > 1) { // Encode a batched call return encodeFunctionData({ abi: BiconomyAbi, functionName: "executeBatch_y6U", args: [ calls.map((a) => a.to), calls.map((a) => a.value ?? 0n), calls.map((a) => a.data ?? "0x") ] }) } const call = calls.length === 0 ? undefined : calls[0] if (!call) { throw new Error("No calls to encode") } // Encode a simple call return encodeFunctionData({ abi: BiconomyAbi, functionName: "execute_ncC", args: [call.to, call.value ?? 0n, call.data ?? "0x"] }) }, decodeCalls: async (data) => { const decoded = decodeFunctionData({ abi: BiconomyAbi, data }) if (decoded.functionName === "execute_ncC") { return [ { to: decoded.args[0], value: decoded.args[1], data: decoded.args[2] } ] } if (decoded.functionName === "executeBatch_y6U") { const calls: { to: Address; value: bigint; data: Hex }[] = [] for (let i = 0; i < decoded.args[0].length; i++) { calls.push({ to: decoded.args[0][i], value: decoded.args[1][i], data: decoded.args[2][i] }) } return calls } throw new Error("Invalid function name") }, // Get simple dummy signature for ECDSA module authorization async getStubSignature() { const dynamicPart = ecdsaModuleAddress.substring(2).padEnd(40, "0") return `0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000${dynamicPart}000000000000000000000000000000000000000000000000000000000000004181d4b4981670cb18f99f0b4a66446df1bf5b204d24cfcb659bf38ba27a4359b5711649ec2423c5e1247245eba2964679b6a1dbb85c992ae40b9b00c6935b02ff1b00000000000000000000000000000000000000000000000000000000000000` }, async sign({ hash }) { return this.signMessage({ message: hash }) }, async signMessage({ message }) { let signature = await localOwner.signMessage({ message }) const potentiallyIncorrectV = Number.parseInt( signature.slice(-2), 16 ) if (![27, 28].includes(potentiallyIncorrectV)) { const correctV = potentiallyIncorrectV + 27 signature = (signature.slice(0, -2) + correctV.toString(16)) as Hex } return encodeAbiParameters( [{ type: "bytes" }, { type: "address" }], [signature, ecdsaModuleAddress] ) }, async signTypedData(typedData) { let signature = await localOwner.signTypedData(typedData) const potentiallyIncorrectV = Number.parseInt( signature.slice(-2), 16 ) if (![27, 28].includes(potentiallyIncorrectV)) { const correctV = potentiallyIncorrectV + 27 signature = (signature.slice(0, -2) + correctV.toString(16)) as Hex } return encodeAbiParameters( [{ type: "bytes" }, { type: "address" }], [signature, ecdsaModuleAddress] ) }, // Sign a user operation async signUserOperation(parameters) { const { chainId = client.chain?.id, ...userOperation } = parameters if (!chainId) throw new Error("Chain id not found") 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: localOwner, message: { raw: hash } }) // userOp signature is encoded module signature + module address const signatureWithModuleAddress = encodeAbiParameters( parseAbiParameters("bytes, address"), [signature, ecdsaModuleAddress] ) return signatureWithModuleAddress } }) }