UNPKG

@verified-network/verified-sdk

Version:

An SDK to develop applications on the Verified Network

999 lines (998 loc) 39.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createMultiChainNexusAccount = exports.waitForTransactionReceiptsDecorator = exports.unDelegateDecorator = exports.buildDefaultInstructions = exports.createNexusAccount = exports.computeNexusAccount = exports.PARENT_TYPEHASH = exports.EXECUTE_BATCH = exports.MODE_PAYLOAD = exports.UNUSED = exports.MODE_DEFAULT = exports.EXECTYPE_DELEGATE = exports.EXECTYPE_TRY = exports.EXECTYPE_DEFAULT = exports.CALLTYPE_BATCH = exports.CALLTYPE_SINGLE = void 0; const viem_1 = require("viem"); const boostrap_json_1 = __importDefault(require("./abi/boostrap.json")); const factory_json_1 = __importDefault(require("./abi/factory.json")); const entrypoint_1 = require("./abi/entrypoint"); const account_abstraction_1 = require("viem/account-abstraction"); const accounts_1 = require("viem/accounts"); const utils_1 = require("viem/utils"); const composability_1 = require("./abi/composability"); const NEXUS_ACCOUNT_FACTORY_ADDRESS = "0x000000001D1D5004a02bAfAb9de2D6CE5b7B13de"; const NEXUS_BOOTSTRAP_ADDRESS = "0x00000000D3254452a909E4eeD47455Af7E27C289"; const DUMMY_SIGNATURE = "0x81d4b4981670cb18f99f0b4a66446df1bf5b204d24cfcb659bf38ba27a4359b5711649ec2423c5e1247245eba2964679b6a1dbb85c992ae40b9b00c6935b02ff1b"; const ENTRY_POINT_ADDRESS = "0x0000000071727De22E5E9d8BAf0edAc6f37da032"; const ENTRYPOINT_SIMULATIONS_any = "0x74Cb5e4eE81b86e70f9045036a1C5477de69eE87"; exports.CALLTYPE_SINGLE = "0x00"; // 1 byte exports.CALLTYPE_BATCH = "0x01"; // 1 byte exports.EXECTYPE_DEFAULT = "0x00"; // 1 byte exports.EXECTYPE_TRY = "0x01"; // 1 byte exports.EXECTYPE_DELEGATE = "0xFF"; // 1 byte exports.MODE_DEFAULT = "0x00000000"; // 4 bytes exports.UNUSED = "0x00000000"; // 4 bytes exports.MODE_PAYLOAD = "0x00000000000000000000000000000000000000000000"; // 22 bytes const EXECUTE_SINGLE = (0, viem_1.concat)([ exports.CALLTYPE_SINGLE, exports.EXECTYPE_DEFAULT, exports.MODE_DEFAULT, exports.UNUSED, exports.MODE_PAYLOAD, ]); exports.EXECUTE_BATCH = (0, viem_1.concat)([ exports.CALLTYPE_BATCH, exports.EXECTYPE_DEFAULT, exports.MODE_DEFAULT, exports.UNUSED, exports.MODE_PAYLOAD, ]); exports.PARENT_TYPEHASH = "TypedDataSign(Contents contents,string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)Contents(bytes32 stuff)"; const index = BigInt(0); const toInitData = (mod) => { const module = mod.module || mod.address; const data = mod.initData || mod.data; if (!module || !data) { throw new Error("Module or data is missing"); } return { module, data }; }; const getInitData = (params) => (0, viem_1.encodeAbiParameters)([ { name: "bootstrap", type: "address" }, { name: "initData", type: "bytes" }, ], [ params.bootStrapAddress, (0, viem_1.encodeFunctionData)({ abi: boostrap_json_1.default.abi, functionName: "initNexusWithDefaultValidatorAndOtherModulesNoRegistry", args: [ params.defaultValidator.data, params.validators, params.executors, params.hook, params.fallbacks, params.prevalidationHooks, ], }), ]); const toValidator = (parameters) => { const { deInitData = "0x", type = "validator", signer, data = "0x", module, ...rest } = parameters; return { deInitData, data, module, address: module, signer, type, getStubSignature: async () => DUMMY_SIGNATURE, signUserOpHash: async (userOpHash) => await signer.signMessage({ message: { raw: userOpHash } }), signMessage: async (message) => await signer.signMessage({ message }), ...rest, }; }; const toDefaultModule = (parameters) => toValidator({ initData: parameters.signer.address, data: parameters.signer.address, deInitData: "0x", ...parameters, address: viem_1.zeroAddress, module: viem_1.zeroAddress, type: "validator", getStubSignature: async () => DUMMY_SIGNATURE, }); const toEmptyHook = () => ({ module: viem_1.zeroAddress, data: viem_1.zeroHash, }); const computeNexusAccount = async (chain, transport, signer) => { const publicClient = (0, viem_1.createPublicClient)({ chain, transport }); const defaultValidator = toDefaultModule({ signer }); const validators = []; const executors = []; const hook = toEmptyHook(); const fallbacks = []; const registryAddress = viem_1.zeroAddress; const factoryAddress = NEXUS_ACCOUNT_FACTORY_ADDRESS; const bootStrapAddress = NEXUS_BOOTSTRAP_ADDRESS; const prevalidationHooks = []; const initData = getInitData({ defaultValidator: toInitData(defaultValidator), validators: validators.map(toInitData), executors: executors.map(toInitData), hook: toInitData(hook), fallbacks: fallbacks.map(toInitData), registryAddress, bootStrapAddress, prevalidationHooks, }); const salt = (0, viem_1.pad)((0, viem_1.toHex)(index), { size: 32 }); const nexusAccount = await publicClient.readContract({ address: factoryAddress, abi: factory_json_1.default.abi, functionName: "computeAccountAddress", args: [initData, salt], }); if (nexusAccount?.toLowerCase() !== viem_1.zeroAddress?.toLowerCase()) { return nexusAccount; } return; }; exports.computeNexusAccount = computeNexusAccount; const computeNexusAddress = async ({ chain, transport, index, initData, factoryAddress = NEXUS_ACCOUNT_FACTORY_ADDRESS, }) => { const publicClient = (0, viem_1.createPublicClient)({ chain, transport }); const salt = (0, viem_1.pad)((0, viem_1.toHex)(index), { size: 32 }); const address = await publicClient.readContract({ address: factoryAddress, abi: factory_json_1.default.abi, functionName: "computeAccountAddress", args: [initData, salt], }); if (!address || address === viem_1.zeroAddress) { throw new Error("Failed to compute Nexus account address"); } return address; }; const buildInitCode = ({ factoryAddress = NEXUS_ACCOUNT_FACTORY_ADDRESS, factoryData, }) => { return (0, viem_1.concatHex)([factoryAddress, factoryData]); }; const sanitizeUrl = (value) => { return value.replace(/https?:\/\/[^\s]+/g, ""); }; class NonceManager { constructor() { this.isNonceKeyBeingCalculated = new Map(); } static getInstance() { if (!NonceManager.instance) { NonceManager.instance = new NonceManager(); } return NonceManager.instance; } buildNonceStoreKey(accountAddress, chainId) { return `${accountAddress.toLowerCase()}::${chainId}`; } async getDefaultNonceKey(accountAddress, chainId) { const storeKey = this.buildNonceStoreKey(accountAddress, chainId); while (this.isNonceKeyBeingCalculated.get(storeKey)) { await new Promise((resolve) => setTimeout(resolve, 1)); } this.isNonceKeyBeingCalculated.set(storeKey, true); const key = BigInt(Date.now()); await new Promise((resolve) => setTimeout(resolve, 1)); this.isNonceKeyBeingCalculated.set(storeKey, false); return key; } async getNonceWithKey(client, accountAddress, parameters) { const TIMESTAMP_ADJUSTMENT = BigInt(16777215); const { key: key_, validationMode, moduleAddress } = parameters; try { const adjustedKey = BigInt(key_) % TIMESTAMP_ADJUSTMENT; const key = (0, viem_1.concat)([ (0, viem_1.toHex)(adjustedKey, { size: 3 }), validationMode, moduleAddress, ]); const entryPointContract = (0, viem_1.getContract)({ address: ENTRY_POINT_ADDRESS, abi: entrypoint_1.EntrypointAbi, client, }); const nonce = await entryPointContract.read.getNonce([ accountAddress, BigInt(key), ]); return { nonceKey: BigInt(key), nonce }; } catch (error) { const errorMessage = error.message ?? "RPC issue"; throw new Error(`Failed to fetch nonce due to the error: ${sanitizeUrl(errorMessage)}`); } } } const getDefaultNonceKey = async (accountAddress, chainId) => { const manager = NonceManager.getInstance(); return manager.getDefaultNonceKey(accountAddress, chainId); }; const getNonceWithKeyUtil = async (client, accountAddress, parameters) => { const manager = NonceManager.getInstance(); return manager.getNonceWithKey(client, accountAddress, parameters); }; const buildNexusRuntime = ({ chain, transport, signer, index, factoryAddress = NEXUS_ACCOUNT_FACTORY_ADDRESS, factoryData, initData, defaultValidator, meeConfig, walletClient, }) => { const publicClient = (0, viem_1.createPublicClient)({ chain, transport }); let cachedAddress; let module = defaultValidator; // Lazy counterfactual address const getAddress = async () => { if (cachedAddress) return cachedAddress; cachedAddress = await computeNexusAddress({ chain, transport, index, initData, factoryAddress, }); return cachedAddress; }; // Init code for deployment / bundlers const getInitCode = () => (0, viem_1.concatHex)([factoryAddress, factoryData]); // Module management const setModule = (newModule) => { module = newModule; }; const getModule = () => module; // Nonce with key const getNonceWithKey = async (accountAddress, params) => { const key = params?.key ?? (await getDefaultNonceKey(accountAddress, chain.id)); const moduleAddress = params?.moduleAddress ?? module.module; const validationMode = params?.validationMode ?? "0x00"; return getNonceWithKeyUtil(publicClient, accountAddress, { key, validationMode, moduleAddress, }); }; // User operation hash const getUserOpHash = (userOp) => { return (0, account_abstraction_1.getUserOperationHash)({ chainId: chain.id, entryPointAddress: account_abstraction_1.entryPoint07Address, entryPointVersion: "0.7", userOperation: userOp, }); }; const toDelegation = async (params) => { const { authorization: authorization_, multiChain, delegatedContract, chainId, } = params || {}; const contractAddress = delegatedContract || meeConfig.implementationAddress; const authorization = authorization_ || (await walletClient.signAuthorization({ contractAddress, chainId: multiChain ? 0 : chainId, })); const eip7702Auth = { chainId: `0x${authorization.chainId.toString(16)}`, address: authorization.address, nonce: `0x${authorization.nonce.toString(16)}`, r: authorization.r, s: authorization.s, yParity: `0x${authorization.yParity.toString(16)}`, }; return eip7702Auth; }; const isDelegated = async () => { const code = await publicClient.getCode({ address: signer.address }); return (!!code && code ?.toLowerCase() .includes(meeConfig.implementationAddress.substring(2).toLowerCase())); }; const unDelegate = async (params) => { const { authorization } = params || {}; const deAuthorization = authorization || (await walletClient.signAuthorization({ address: viem_1.zeroAddress, executor: "self", })); return await walletClient.sendTransaction({ to: signer.address, data: "0xdeadbeef", type: "eip7702", authorizationList: [deAuthorization], }); }; return { getAddress, getInitCode, publicClient, signer, module, setModule, getModule, getNonceWithKey, getUserOpHash, isDelegated, toDelegation, unDelegate, }; }; const encodeExecuteBatch = async (calls, mode = exports.EXECUTE_BATCH) => { const executionAbiParams = { type: "tuple[]", components: [ { name: "target", type: "address" }, { name: "value", type: "uint256" }, { name: "callData", type: "bytes" }, ], }; const executions = calls.map((tx) => ({ target: tx.to, callData: tx.data ?? "0x", value: BigInt(tx.value ?? BigInt(0)), })); const executionCalldataPrep = (0, viem_1.encodeAbiParameters)([executionAbiParams], [executions]); return (0, viem_1.encodeFunctionData)({ abi: (0, viem_1.parseAbi)([ "function execute(bytes32 mode, bytes calldata executionCalldata) external", ]), functionName: "execute", args: [mode, executionCalldataPrep], }); }; const encodeExecute = async (call, mode = EXECUTE_SINGLE) => { const executionCalldata = (0, viem_1.encodePacked)(["address", "uint256", "bytes"], [ call.to, BigInt(call.value ?? BigInt(0)), (call.data ?? "0x"), ]); return (0, viem_1.encodeFunctionData)({ abi: (0, viem_1.parseAbi)([ "function execute(bytes32 mode, bytes calldata executionCalldata) external", ]), functionName: "execute", args: [mode, executionCalldata], }); }; const encodeExecuteComposable = async (calls) => { const composableCalls = calls.map((call) => { return { to: call.to, value: call.value ?? BigInt(0), functionSig: call.functionSig, inputParams: call.inputParams, outputParams: call.outputParams, }; }); return (0, viem_1.encodeFunctionData)({ abi: composability_1.COMPOSABILITY_MODULE_ABI, functionName: "executeComposable", args: [composableCalls], }); }; const signMessage = async (message, module) => { const tempSignature = await module.signMessage(message); return (0, viem_1.encodePacked)(["address", "bytes"], [module.module, tempSignature]); }; const signUserOperation = async (userOpHash, module) => { return module.signUserOpHash(userOpHash); }; const toSigner = async ({ signer, address, }) => { if (!signer) { throw new Error("Signer is required"); } if ("provider" in signer) { const wallet = signer; const address = await wallet.getAddress(); return (0, accounts_1.toAccount)({ address: (0, viem_1.getAddress)(address), async signMessage({ message }) { if (typeof message === "string") { return await wallet.signMessage(message); } if (typeof message?.raw === "string") { return await wallet.signMessage((0, viem_1.hexToBytes)(message.raw)); } return await wallet.signMessage(message.raw); }, async signTransaction(_) { throw new Error("Not supported"); }, async signTypedData(typedData) { return wallet.signTypedData(typedData.domain, typedData.types, typedData.message); }, }); } if ("type" in signer && signer.type === "local") { return signer; } let walletClient = undefined; if ("request" in signer) { if (!address) { try { [address] = await signer.request({ method: "eth_requestAccounts", }); } catch { [address] = await signer.request({ method: "eth_accounts", }); } } if (!address) { // For TS to be happy throw new Error("address is required"); } walletClient = (0, viem_1.createWalletClient)({ account: address, transport: (0, viem_1.custom)(signer), }); } if (!walletClient) { walletClient = signer; } const addressFromWalletClient = walletClient?.account?.address ?? (await walletClient?.getAddresses())?.[0]; if (!addressFromWalletClient) { throw new Error("address not found in wallet client"); } return (0, accounts_1.toAccount)({ address: addressFromWalletClient, async signMessage({ message }) { return walletClient.signMessage({ message }); }, async signTypedData(typedData) { return (0, utils_1.getAction)(walletClient, accounts_1.signTypedData, "signTypedData")(typedData); }, async signTransaction(_) { throw new Error("Not supported"); }, }); }; const toWalletClient = ({ unresolvedSigner, resolvedSigner, chain, transport, }) => { const browserSigner = unresolvedSigner?.transport?.key === "custom"; return (0, viem_1.createWalletClient)(browserSigner ? { account: resolvedSigner.address, chain, // @ts-ignore transport: (0, viem_1.custom)(window?.ethereum), } : { account: resolvedSigner, chain, transport, }).extend(viem_1.publicActions); }; const prepareValidators = async (signer, meeConfig, customValidators) => { return []; }; const prepareExecutors = (meeConfig, customExecutors) => { let executors = []; executors = customExecutors || []; return executors; }; const prepareFallbacks = (meeConfig, customFallbacks) => { let fallbacks = []; fallbacks = customFallbacks || []; return fallbacks; }; const getInitDataNoRegistry = (params) => { try { return (0, viem_1.encodeAbiParameters)([ { name: "bootstrap", type: "address" }, { name: "bootstrapData", type: "bytes" }, ], [ params.bootStrapAddress, (0, viem_1.encodeFunctionData)({ abi: boostrap_json_1.default?.abi, functionName: "initNexusWithDefaultValidatorAndOtherModulesNoRegistry", args: [ params.defaultValidator.data, params.validators, params.executors, params.hook, params.fallbacks, params.prevalidationHooks, ], }), ]); } catch (err) { console.error("getInitDataNoRegistry failed with error: ", err); } }; const getFactoryData = (parameters) => { const { index, initData } = parameters; const salt = (0, viem_1.pad)((0, viem_1.toHex)(index), { size: 32 }); return (0, viem_1.encodeFunctionData)({ abi: (0, viem_1.parseAbi)([ "function createAccount(bytes initData, bytes32 salt) external returns (address)", ]), functionName: "createAccount", args: [initData, salt], }); }; const prepareFactoryData = (meeConfig, initDataParams) => { let factoryData = "0x"; let initData = "0x"; initData = initDataParams.customInitData || getInitDataNoRegistry({ defaultValidator: initDataParams.defaultValidator, prevalidationHooks: initDataParams.prevalidationHooks, validators: initDataParams.validators, executors: initDataParams.executors, hook: initDataParams.hook, fallbacks: initDataParams.fallbacks, bootStrapAddress: meeConfig.bootStrapAddress, }); factoryData = getFactoryData({ initData, index: initDataParams.accountIndex, }); return { initData, factoryData }; }; const getAccountDomainStructFields = async (publicClient, accountAddress) => { const accountDomainStructFields = (await publicClient.readContract({ address: accountAddress, abi: (0, viem_1.parseAbi)([ "function eip712Domain() public view returns (bytes1 fields, string memory name, string memory version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] memory extensions)", ]), functionName: "eip712Domain", })); const [, name, version, chainId, verifyingContract, salt] = accountDomainStructFields; const params = (0, viem_1.parseAbiParameters)([ "bytes32", "bytes32", "uint256", "address", "bytes32", ]); return (0, viem_1.encodeAbiParameters)(params, [ (0, viem_1.keccak256)((0, viem_1.toBytes)(name)), (0, viem_1.keccak256)((0, viem_1.toBytes)(version)), chainId, verifyingContract, salt, ]); }; const eip712WrapHash = (typedHash, appDomainSeparator) => (0, viem_1.keccak256)((0, viem_1.concat)(["0x1901", appDomainSeparator, typedHash])); const typeToString = (typeDef) => { return Object.entries(typeDef).map(([key, fields]) => { const fieldArray = Array.isArray(fields) ? fields : []; const fieldStrings = fieldArray .map((field) => `${field.type} ${field.name}`) .join(","); return `${key}(${fieldStrings})`; }); }; const createNexusAccount = async ({ chain, transport, _signer, index = BigInt(0), customValidators, customExecutors, customHook, customFallbacks, customPrevalidationHooks, customInitData, meeConfig, }) => { const publicClient = (0, viem_1.createPublicClient)({ chain, transport }); const addressesToDeploymentSet = new Set([ meeConfig.bootStrapAddress, meeConfig.defaultValidatorAddress, meeConfig.validatorAddress, meeConfig.factoryAddress, meeConfig.implementationAddress, ]); if (meeConfig.moduleRegistry) { addressesToDeploymentSet.add(meeConfig.moduleRegistry.registryAddress); } if (meeConfig.composableModuleAddress) { addressesToDeploymentSet.add(meeConfig.composableModuleAddress); } // Filtering zero address because sometimes the default validator is zeroAddress which needs to be excluded const addressesToDeploymentCheck = [...addressesToDeploymentSet].filter((address) => address !== viem_1.zeroAddress); await Promise.all(addressesToDeploymentCheck.map(async (address) => { // Checks if the MEE contracts are deployed or not // This ensures the MEE version suite is supported or not for the chain const bytecode = await publicClient.getCode({ address, }); if (!bytecode || bytecode === "0x") { console.debug(`MEE version (${meeConfig.version}) is not supported for the ${chain.name} chain. Contract address (${address}) is not deployed`); throw new Error(`MEE version (${meeConfig.version}) is not supported for the ${chain.name} chain.`); } })); const signer = await toSigner({ signer: _signer }); const walletClient = toWalletClient({ unresolvedSigner: _signer, resolvedSigner: signer, chain, transport, }); // Prepare validator modules const validators = await prepareValidators(signer, meeConfig, customValidators); const defaultValidator = toDefaultModule({ signer }); // For 1.2.x accounts, no explicit validators will be added. So default validator will be used let module = validators[0] || defaultValidator; // Prepare executor modules const executors = prepareExecutors(meeConfig, customExecutors); // Prepare hook module const hook = customHook || toEmptyHook(); // Prepare fallback modules const fallbacks = prepareFallbacks(meeConfig, customFallbacks); // Generate the initialization data for the account using the initNexus function const prevalidationHooks = customPrevalidationHooks || []; // prepare factory data const { initData, factoryData } = prepareFactoryData(meeConfig, { accountIndex: index, defaultValidator: toInitData(defaultValidator), prevalidationHooks, validators: validators.map(toInitData), executors: executors.map(toInitData), hook: toInitData(hook), fallbacks: fallbacks.map(toInitData), customInitData, }); let accountId; if (meeConfig?.implementationAddress) { accountId = (await publicClient.readContract({ address: meeConfig.implementationAddress, abi: (0, viem_1.parseAbi)(["function accountId() public view returns (string)"]), functionName: "accountId", args: [], })); } const runtime = buildNexusRuntime({ chain, transport, signer, index, factoryAddress: NEXUS_ACCOUNT_FACTORY_ADDRESS, factoryData, initData, defaultValidator, meeConfig, walletClient, }); const getNonce = async (parameters) => { const accountAddress = await runtime.getAddress(); const { nonce } = await runtime.getNonceWithKey(accountAddress, parameters); return nonce; }; const signTypedData = async (parameters) => { const { message, primaryType, types: _types, domain } = parameters; if (!domain) throw new Error("Missing domain"); if (!message) throw new Error("Missing message"); const types = { EIP712Domain: (0, viem_1.getTypesForEIP712Domain)({ domain }), ..._types, }; // @ts-ignore: Comes from nexus parent typehash const messageStuff = message.stuff; // @ts-ignore validateTypedData({ domain, message, primaryType, types, }); const appDomainSeparator = (0, viem_1.domainSeparator)({ domain }); const accountDomainStructFields = await getAccountDomainStructFields(publicClient, await runtime.getAddress()); const parentStructHash = (0, viem_1.keccak256)((0, viem_1.encodePacked)(["bytes", "bytes"], [ (0, viem_1.encodeAbiParameters)((0, viem_1.parseAbiParameters)(["bytes32, bytes32"]), [ (0, viem_1.keccak256)((0, viem_1.toBytes)(exports.PARENT_TYPEHASH)), messageStuff, ]), accountDomainStructFields, ])); const wrappedTypedHash = eip712WrapHash(parentStructHash, appDomainSeparator); let signature = await module.signMessage({ raw: (0, viem_1.toBytes)(wrappedTypedHash), }); const contentsType = (0, viem_1.toBytes)(typeToString(types)[1]); const signatureData = (0, viem_1.concatHex)([ signature, appDomainSeparator, messageStuff, (0, viem_1.toHex)(contentsType), (0, viem_1.toHex)(contentsType.length, { size: 2 }), ]); signature = (0, viem_1.encodePacked)(["address", "bytes"], [module.module, signatureData]); return signature; }; return (0, account_abstraction_1.toSmartAccount)({ client: publicClient, entryPoint: { abi: entrypoint_1.EntrypointAbi, address: ENTRY_POINT_ADDRESS, version: "0.7", }, getAddress: runtime.getAddress, encodeCalls: (calls) => { return calls.length === 1 ? encodeExecute(calls[0], runtime.module) : encodeExecuteBatch(calls, runtime.module); }, getFactoryArgs: async () => ({ factory: meeConfig.factoryAddress, factoryData, }), getStubSignature: async () => module.getStubSignature(), /** * @description Signs a message * @param params - The parameters for signing * @param params.message - The message to sign * @returns The signature */ async signMessage({ message }) { const tempSignature = await module.signMessage(message); return (0, viem_1.encodePacked)(["address", "bytes"], [module.module, tempSignature]); }, signTypedData, signUserOperation: async (parameters) => { const { chainId = publicClient.chain.id, ...userOpWithoutSender } = parameters; const address = await runtime.getAddress(); const userOperation = { ...userOpWithoutSender, sender: address, }; const hash = (0, account_abstraction_1.getUserOperationHash)({ chainId, entryPointAddress: account_abstraction_1.entryPoint07Address, entryPointVersion: "0.7", userOperation, }); return await module.signUserOpHash(hash); }, getNonce, extend: { entryPointAddress: account_abstraction_1.entryPoint07Address, accountId, encodeExecute, encodeExecuteBatch, encodeExecuteComposable, factoryData, registryAddress: meeConfig.moduleRegistry?.registryAddress || viem_1.zeroAddress, walletClient, version: meeConfig, isDelegated: runtime.isDelegated, toDelegation: runtime.toDelegation, unDelegate: runtime.unDelegate, getAddress: runtime.getAddress, getInitCode: runtime.getInitCode, getNonceWithKey: runtime.getNonceWithKey, getUserOpHash: runtime.getUserOpHash, setModule: runtime.setModule, getModule: runtime.getModule, factoryAddress: NEXUS_ACCOUNT_FACTORY_ADDRESS, signer: runtime.signer, publicClient: runtime.publicClient, chain, module: runtime.module, }, }); }; exports.createNexusAccount = createNexusAccount; const buildChainConfigurations = (chains, transports) => { if (chains.length !== transports.length) { throw new Error("chains and transports length mismatch"); } return chains.map((chain, i) => ({ chain, transport: transports[i], })); }; const buildDefaultInstructions = async (baseParams, instructions) => { const { currentInstructions = [] } = baseParams; return [ ...currentInstructions, ...(Array.isArray(instructions) ? instructions : [instructions]), ]; }; exports.buildDefaultInstructions = buildDefaultInstructions; const buildDecorator = async (baseParams, parameters) => { const { type, data } = parameters; switch (type) { // case "intent": { // return buildIntent(baseParams, data) // } case "default": { return (0, exports.buildDefaultInstructions)(baseParams, data); } // case "transferFrom": { // return buildTransferFrom(baseParams, data) // } // case "transfer": { // return buildTransfer(baseParams, data) // } // case "approve": { // return buildApprove(baseParams, data) // } // case "withdrawal": { // return buildWithdrawal(baseParams, data) // } // case "batch": { // return buildBatch(baseParams, data) // } // case "multichain": { // return buildMultichainInstructions(baseParams, data) // } default: { throw new Error(`Unknown build action type: ${type}`); } } }; const getUnifiedERC20BalanceDecorator = async (parameters) => { const { mcToken, account: account_ } = parameters; const relevantTokensByChain = Array.from(mcToken.deployments).filter(([chainId]) => account_.deployments.some((account) => account.client.chain?.id === chainId)); const balances = await Promise.all(relevantTokensByChain.map(async ([chainId, address]) => { const { publicClient, address: accountAddress } = account_.deploymentOn(chainId, true); const tokenContract = (0, viem_1.getContract)({ abi: viem_1.erc20Abi, address, client: publicClient, }); const [balance, decimals] = await Promise.all([ tokenContract.read.balanceOf([accountAddress]), tokenContract.read.decimals(), ]); return { balance, decimals, chainId, }; })); return { ...balances .map((balance) => { return { balance: balance.balance, decimals: balance.decimals, }; }) .reduce((curr, acc) => { if (curr.decimals !== acc.decimals) { throw Error(` Error while trying to fetch a unified ERC20 balance. The addresses provided in the mapping don't have the same number of decimals across all chains. The function can't fetch a unified balance for token mappings with differing decimals. `); } return { balance: curr.balance + acc.balance, decimals: curr.decimals, }; }), breakdown: balances, mcToken, }; }; const waitForTransactionReceipts = async (parameters) => { const receipts = await Promise.all(parameters.account.deployments.map(({ publicClient }, i) => publicClient.waitForTransactionReceipt({ hash: parameters.hashes[i], confirmations: 5, }))); const failure = receipts.find((receipt) => receipt.status !== "success"); return { receipts, status: failure ? failure.status : "success" }; }; const isDelegatedDecorator = async (parameters) => (await Promise.all(parameters.account.deployments.map(({ isDelegated }) => isDelegated()))).every(Boolean); const unDelegateDecorator = async (parameters) => { const hashes = await Promise.all(parameters.account.deployments.map(({ unDelegate }) => unDelegate())); return await waitForTransactionReceipts({ account: parameters.account, hashes, }); }; exports.unDelegateDecorator = unDelegateDecorator; const waitForTransactionReceiptsDecorator = async (parameters) => { const receipts = await Promise.all(parameters.account.deployments.map(({ publicClient }, i) => publicClient.waitForTransactionReceipt({ hash: parameters.hashes[i], confirmations: 5, }))); const failure = receipts.find((receipt) => receipt.status !== "success"); return { receipts, status: failure ? failure.status : "success" }; }; exports.waitForTransactionReceiptsDecorator = waitForTransactionReceiptsDecorator; const createMultiChainNexusAccount = async (params) => { const { signer: unresolvedSigner, chains, transports, ...accountParameters } = params; if (!chains?.length) { throw new Error("No chains provided"); } const chainConfigurations = buildChainConfigurations(chains, transports); if (chainConfigurations.length === 0) { throw new Error("No chain configuration provided"); } const meeConfig = { version: "2.0.0", accountId: "biconomy.nexus.1.2.0", factoryAddress: "0x000000001D1D5004a02bAfAb9de2D6CE5b7B13de", // Nexus Account Factory Address bootStrapAddress: "0x00000000D3254452a909E4eeD47455Af7E27C289", implementationAddress: "0x000000004F43C49e93C970E84001853a70923B03", validatorAddress: "0x00000000d12897DDAdC2044614A9677B191A2d95", // K1 MEE Validator Address defaultValidatorAddress: viem_1.zeroAddress, ethForwarderAddress: "0x000000Afe527A978Ecb761008Af475cfF04132a1", }; // 🔁 Create one Nexus account per chain const deployments = await Promise.all(chainConfigurations.map(async ({ chain, transport }) => (0, exports.createNexusAccount)({ chain, transport, _signer: unresolvedSigner, meeConfig, ...accountParameters, }))); /* -------------------------------------------------- */ /* Deployment helpers */ /* -------------------------------------------------- */ function deploymentOn(chainId, strictMode) { const deployment = deployments.find((dep) => dep.publicClient.chain?.id === chainId); if (!deployment && strictMode) { throw new Error(`Deployment not found for chainId: ${chainId}`); } return deployment; } function addressOn(chainId, strictMode) { return deploymentOn(chainId, strictMode)?.address; } /* -------------------------------------------------- */ /* Base multichain account */ /* -------------------------------------------------- */ const baseAccount = { signer: deployments[0].signer, // resolved signer deployments, deploymentOn, addressOn, }; // /* -------------------------------------------------- */ // /* Decorators (SDK parity) */ // /* -------------------------------------------------- */ const getUnifiedERC20Balance = (mcToken) => getUnifiedERC20BalanceDecorator({ mcToken, account: baseAccount, }); const build = (params, currentInstructions) => buildDecorator({ currentInstructions, accountAddress: baseAccount.signer.address, }, params); // const buildComposable = ( // params: any, // currentInstructions?: any[] // ) => // buildComposableDecorator( // { // currentInstructions, // accountAddress: baseAccount.signer.address // }, // params // ) // const buildBridgeInstructions = ( // params: any // ) => // buildBridgeInstructionsDecorator({ // ...params // }) // const queryBridge = (params: any) => // queryBridgeDecorator({ ...params }) const isDelegated = (parameters) => isDelegatedDecorator({ ...parameters, account: baseAccount, }); const unDelegate = (parameters) => (0, exports.unDelegateDecorator)({ ...parameters, account: baseAccount, }); const waitForTransactionReceipts = (parameters) => (0, exports.waitForTransactionReceiptsDecorator)({ ...parameters, account: baseAccount, }); // const read = <T>(params: any) => // multichainRead(baseAccount, params) as Promise< // any // > /* -------------------------------------------------- */ /* Delegation (chainId = 0 semantics) */ /* -------------------------------------------------- */ const toDelegation = async () => await deployments[0].extend.toDelegation({ multiChain: true }); /* -------------------------------------------------- */ /* Final multichain account */ /* -------------------------------------------------- */ return { ...baseAccount, getUnifiedERC20Balance, build, // buildComposable, // buildBridgeInstructions, // queryBridge, isDelegated, unDelegate, waitForTransactionReceipts, // read, toDelegation, }; }; exports.createMultiChainNexusAccount = createMultiChainNexusAccount;