aa-schnorr-multisig-sdk
Version:
Account Abstraction Schnorr Multi-Signatures SDK
163 lines (162 loc) • 10.3 kB
JavaScript
"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;