UNPKG

aa-schnorr-multisig-sdk

Version:

Account Abstraction Schnorr Multi-Signatures SDK

163 lines (162 loc) 10.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getEvent = exports.createSmartAccount = exports.saltToHex = exports.getAccountImplementationAddress = exports.isDeployed = exports.predictAccountAddrOnchain = exports.predictAccountImplementationAddrOffchain = exports.predictFactoryAddrOffchain = exports.predictAccountAddrOffchain = exports.ENTRY_POINT_ALCHEMY_ADDRESS = exports.PROXY_FACTORY_ADDRESS = void 0; const ethers_1 = require("ethers"); const typechain_1 = require("../generated/typechain"); const abi_1 = require("../generated/abi"); const deploymentManager_1 = require("../generated/deployments/deploymentManager"); // Proxy address contract used to deploy (using create2) new MultiSigSmartAccount Factory // see: https://github.com/Arachnid/deterministic-deployment-proxy exports.PROXY_FACTORY_ADDRESS = "0x4e59b44847b379578588920ca78fbf26c0b4956c"; // Alchemy Supported Entry Point // see: https://docs.alchemy.com/reference/eth-supportedentrypoints exports.ENTRY_POINT_ALCHEMY_ADDRESS = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"; /** * Calculates offchain MultiSigSmartAccount address with create2. * @param factoryAddress MultiSigSmartAccountFactory address * @param accountImplementationAddress MultiSigSmartAccount implementation address * @param combinedAddresses combined schnorr signers' public addresses used as contract owners * @param salt salt text: string or number * @returns predicted MultiSigSmartAccount address */ function predictAccountAddrOffchain(factoryAddress, accountImplementationAddress, combinedAddresses, salt) { // ERC1967Proxy is the contract which gets deployed when creating new MultiSigSmartAccount const erc1967ProxyBytecode = typechain_1.ERC1967Proxy__factory.bytecode; const smartAccountInterface = typechain_1.MultiSigSmartAccount__factory.createInterface(); const encodedInitializer = smartAccountInterface.encodeFunctionData("initialize", [combinedAddresses]); // ERC1967Proxy takes two parameters while deploying: implementation and encoded init data const encodedConstructorInitCode = ethers_1.ethers.utils.defaultAbiCoder.encode(["address", "bytes"], [accountImplementationAddress, encodedInitializer]); // Calculating initCodeHash keccak256(contractByteCode+ConstructorCode) const initByteCode = ethers_1.ethers.utils.solidityPack(["bytes", "bytes"], [erc1967ProxyBytecode, encodedConstructorInitCode]); const initCodeHash = ethers_1.ethers.utils.keccak256(initByteCode); return ethers_1.ethers.utils.getCreate2Address(factoryAddress, (0, exports.saltToHex)(salt), initCodeHash); } exports.predictAccountAddrOffchain = predictAccountAddrOffchain; /** * Calculates offchain MultiSigSmartAccount address with create2 * @param salt salt text: string or number * @param entryPointAddress Account Abstraction's Entry Point address (default: Alchemy Entry Point) * @returns predicted MultiSigSmartAccount Factory address */ function predictFactoryAddrOffchain(salt, entryPointAddress) { const factoryBytecode = typechain_1.MultiSigSmartAccountFactory__factory.bytecode; const saltHex = (0, exports.saltToHex)(salt); const entryPointAddr = entryPointAddress !== null && entryPointAddress !== void 0 ? entryPointAddress : exports.ENTRY_POINT_ALCHEMY_ADDRESS; // Factory takes only one parameter while deploying: Entry Point address const encodedConstructorInitCode = ethers_1.ethers.utils.defaultAbiCoder.encode(["address", "bytes32"], [entryPointAddr, saltHex]); // Calculating initCodeHash keccak256(contractByteCode+ConstructorCode) const initCode = ethers_1.ethers.utils.solidityPack(["bytes", "bytes"], [factoryBytecode, encodedConstructorInitCode]); const initCodeHash = ethers_1.ethers.utils.keccak256(initCode); return ethers_1.ethers.utils.getCreate2Address(exports.PROXY_FACTORY_ADDRESS, saltHex, initCodeHash); } exports.predictFactoryAddrOffchain = predictFactoryAddrOffchain; /** * Calculates offchain MultiSigSmartAccount implementation address with create2 * @param factoryAddress MultiSigSmartAccount Factory address. * If not known, can be predicted with given `factorySalt` and `entryPointAddress` params * @param factorySalt salt text: string or number - the same used for Factory deployment * @param entryPointAddress Account Abstraction's Entry Point address (default: Alchemy Entry Point) * @returns */ function predictAccountImplementationAddrOffchain(factorySalt, factoryAddress, entryPointAddress) { const entryPointAddr = entryPointAddress !== null && entryPointAddress !== void 0 ? entryPointAddress : exports.ENTRY_POINT_ALCHEMY_ADDRESS; const multiSigFactoryAddress = factoryAddress !== null && factoryAddress !== void 0 ? factoryAddress : predictFactoryAddrOffchain(factorySalt, entryPointAddr); const smartAccountByteCode = typechain_1.MultiSigSmartAccount__factory.bytecode; // Factory takes only one parameter while deploying: Entry Point address const encodedConstructorInitCode = ethers_1.ethers.utils.defaultAbiCoder.encode(["address"], [entryPointAddr]); // Calculating initCodeHash keccak256(contractByteCode+ConstructorCode) const initCode = ethers_1.ethers.utils.solidityPack(["bytes", "bytes"], [smartAccountByteCode, encodedConstructorInitCode]); const initCodeHash = ethers_1.ethers.utils.keccak256(initCode); return ethers_1.ethers.utils.getCreate2Address(multiSigFactoryAddress, factorySalt, initCodeHash); } exports.predictAccountImplementationAddrOffchain = predictAccountImplementationAddrOffchain; /** * Calculates MultiSigSmartAccount address with create2 and onchain data. * @param factoryAddress MultiSigSmartAccountFactory address * @param combinedAddresses combined schnorr signers' public addresses used as contract owners * @param salt salt text: string or number * @param ethersSignerOrProvider Signer or Provider type to call the Factory contract * @returns predicted MultiSigSmartAccount address */ async function predictAccountAddrOnchain(factoryAddress, combinedAddresses, salt, ethersSignerOrProvider) { const smartAccountFactory = new ethers_1.ethers.Contract(factoryAddress, abi_1.MultiSigSmartAccountFactory_abi, ethersSignerOrProvider); const saltHash = (0, exports.saltToHex)(salt); const predictedAccount = await smartAccountFactory.getAccountAddress(combinedAddresses, saltHash); return predictedAccount; } exports.predictAccountAddrOnchain = predictAccountAddrOnchain; /** * Determines if a given contract is deployed at the given address. * @param address contract address * @param provider provider to call contract * @returns true if contract deployed or false otherwise */ async function isDeployed(address, provider) { const code = await provider.getCode(address); return code.slice(2).length > 0; } exports.isDeployed = isDeployed; /** * Helper for getting account implementation address from Account Factory * @param factoryAddress deployed MultiSigSmartAccountFactory address * @param ethersSignerOrProvider signer or provider to call contract * @returns account implementation address */ async function getAccountImplementationAddress(factoryAddress, ethersSignerOrProvider) { const smartAccountFactory = new ethers_1.ethers.Contract(factoryAddress, abi_1.MultiSigSmartAccountFactory_abi, ethersSignerOrProvider); const accountImplementation = await smartAccountFactory.accountImplementation(); return accountImplementation; } exports.getAccountImplementationAddress = getAccountImplementationAddress; /** * Checks if salt is Hex and if not - converts from string or number to hashed string with keccak256. * @param salt salt text: string or number * @returns hashed salt */ const saltToHex = (salt) => { const saltString = salt.toString(); if (ethers_1.ethers.utils.isHexString(saltString)) return saltString; return ethers_1.ethers.utils.id(saltString); }; exports.saltToHex = saltToHex; /** * Creates MultiSigSmartAccount with create2 and onchain data. * @param combinedAddresses combined schnorr signers' public addresses used as contract owners * @param ethersSignerOrProvider Signer or Provider type to call the Factory contract * @param salt optional salt text (string or number) * @param factoryAddress optional MultiSigSmartAccountFactory address - if not given, the deployed factory address will be taken * @param chainId optional chainId number needed to get deployed factory address only if factoryAddress not given * @returns predicted MultiSigSmartAccount address */ async function createSmartAccount(combinedAddresses, ethersSignerOrProvider, salt, factoryAddress, chainId) { // create factory const _factoryAddr = factoryAddress || (await (async () => { const a = await deploymentManager_1.deploymentManager.read(); return a[chainId].MultiSigSmartAccountFactory; })()); if (!_factoryAddr) throw new Error("Smart Account Factory address not found"); const smartAccountFactory = new ethers_1.ethers.Contract(_factoryAddr, abi_1.MultiSigSmartAccountFactory_abi, ethersSignerOrProvider); // generate salt hash - empty string by default const _salt = salt !== null && salt !== void 0 ? salt : ""; const saltHash = (0, exports.saltToHex)(_salt); const createTx = await smartAccountFactory.createAccount(combinedAddresses, saltHash); const event = await getEvent(createTx, smartAccountFactory, "MultiSigSmartAccountCreated"); const accountAddress = event.args[0]; return accountAddress; } exports.createSmartAccount = createSmartAccount; // eslint-disable-next-line @typescript-eslint/no-explicit-any async function getEvent(tx, contract, eventName) { var _a, _b; const receipt = await contract.provider.getTransactionReceipt(tx.hash); const eventFragment = contract.interface.getEvent(eventName); const topic = contract.interface.getEventTopic(eventFragment); const logs = (_b = (_a = receipt.logs) === null || _a === void 0 ? void 0 : _a.filter((log) => log.topics.includes(topic))) !== null && _b !== void 0 ? _b : ""; if (logs.length === 0) throw new Error(`Event ${eventName} was not emmited`); return contract.interface.parseLog(logs[0]); } exports.getEvent = getEvent;