UNPKG

permissionless

Version:

A utility library for working with ERC-4337

215 lines • 8.93 kB
import { concat, concatHex, domainSeparator, encodeAbiParameters, encodeFunctionData, encodePacked, getTypesForEIP712Domain, hashMessage, hashTypedData, keccak256, stringToHex, toHex, validateTypedData } from "viem"; import { entryPoint07Abi, entryPoint07Address, getUserOperationHash, toSmartAccount } from "viem/account-abstraction"; import { getChainId, readContract } from "viem/actions"; import { getAction } from "viem/utils"; import { getAccountNonce } from "../../actions/public/getAccountNonce.js"; import { decode7579Calls } from "../../utils/decode7579Calls.js"; import { encode7579Calls } from "../../utils/encode7579Calls.js"; import { toOwner } from "../../utils/toOwner.js"; const wrapMessageHash = (messageHash, { accountAddress, version, chainId }) => { const _domainSeparator = domainSeparator({ domain: { name: "Nexus", version: version, chainId, verifyingContract: accountAddress } }); const parentStructHash = keccak256(encodeAbiParameters([{ type: "bytes32" }, { type: "bytes32" }], [ keccak256(stringToHex("PersonalSign(bytes prefixed)")), messageHash ])); return keccak256(concatHex(["0x1901", _domainSeparator, parentStructHash])); }; /** * The account creation ABI for Biconomy Smart Account (from the biconomy SmartAccountFactory) */ /** * Default addresses for Biconomy Smart Account */ const BICONOMY_ADDRESSES = { K1_VALIDATOR_FACTORY_ADDRESS: "0x00000bb19a3579F4D779215dEf97AFbd0e30DB55", K1_VALIDATOR_ADDRESS: "0x00000004171351c442B202678c48D8AB5B321E8f" }; export async function toNexusSmartAccount(parameters) { const { owners, client, index = 0n, address, version, factoryAddress = BICONOMY_ADDRESSES.K1_VALIDATOR_FACTORY_ADDRESS, validatorAddress = BICONOMY_ADDRESSES.K1_VALIDATOR_ADDRESS, attesters = [], threshold = 0 } = parameters; const localOwner = await toOwner({ owner: owners[0] }); const entryPoint = { address: parameters.entryPoint?.address ?? entryPoint07Address, abi: entryPoint07Abi, version: parameters.entryPoint?.version ?? "0.7" }; let accountAddress = address; const getFactoryArgs = async () => { return { factory: factoryAddress, factoryData: encodeFunctionData({ abi: [ { name: "createAccount", type: "function", stateMutability: "nonpayable", inputs: [ { type: "address", name: "eoaOwner" }, { type: "uint256", name: "index" }, { type: "address[]", name: "attesters" }, { type: "uint8", name: "threshold" } ], outputs: [{ type: "address" }] } ], functionName: "createAccount", args: [ localOwner.address, index, attesters.sort((left, right) => left.toLowerCase().localeCompare(right.toLowerCase())), threshold ] }) }; }; let chainId; const getMemoizedChainId = async () => { if (chainId) return chainId; chainId = client.chain ? client.chain.id : await getAction(client, getChainId, "getChainId")({}); return chainId; }; return toSmartAccount({ client, entryPoint, getFactoryArgs, async getAddress() { if (accountAddress) return accountAddress; accountAddress = await readContract(client, { address: factoryAddress, abi: [ { name: "computeAccountAddress", type: "function", stateMutability: "view", inputs: [ { type: "address", name: "eoaOwner" }, { type: "uint256", name: "index" }, { type: "address[]", name: "attesters" }, { type: "uint8", name: "threshold" } ], outputs: [{ type: "address" }] } ], functionName: "computeAccountAddress", args: [ localOwner.address, index, attesters.sort((left, right) => left.toLowerCase().localeCompare(right.toLowerCase())), threshold ] }); return accountAddress; }, 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`; }, async sign({ hash }) { return this.signMessage({ message: hash }); }, async signMessage({ message }) { const wrappedMessageHash = wrapMessageHash(hashMessage(message), { version, accountAddress: await this.getAddress(), chainId: await getMemoizedChainId() }); const signature = await localOwner.signMessage({ message: { raw: wrappedMessageHash } }); return encodePacked(["address", "bytes"], [validatorAddress, signature]); }, async signTypedData(typedData) { const { message, primaryType, types: _types, domain } = typedData; const types = { EIP712Domain: getTypesForEIP712Domain({ domain: domain }), ..._types }; validateTypedData({ domain, message, primaryType, types }); const typedHash = hashTypedData({ message, primaryType, types, domain }); const wrappedMessageHash = wrapMessageHash(typedHash, { version, accountAddress: await this.getAddress(), chainId: await getMemoizedChainId() }); const signature = await localOwner.signMessage({ message: { raw: wrappedMessageHash } }); return encodePacked(["address", "bytes"], [validatorAddress, signature]); }, async signUserOperation(parameters) { const { chainId = await getMemoizedChainId(), ...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 }); return await localOwner.signMessage({ message: { raw: hash } }); } }); } //# sourceMappingURL=toNexusSmartAccount.js.map