UNPKG

@etherspot/prime-sdk

Version:

Etherspot Prime (Account Abstraction) SDK

277 lines (276 loc) 11.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseAccountAPI = void 0; const ethers_1 = require("ethers"); const contracts_1 = require("../contracts"); const utils_1 = require("ethers/lib/utils"); const common_1 = require("../common"); const calcPreVerificationGas_1 = require("./calcPreVerificationGas"); const __1 = require(".."); const context_1 = require("../context"); class BaseAccountAPI { constructor(params) { this.isPhantom = true; const optionsLike = params.optionsLike; if (!(0, __1.isWalletProvider)(params.walletProvider)) { throw new common_1.Exception('Invalid wallet provider'); } const { chainId, stateStorage, rpcProviderUrl, factoryWallet, bundlerProvider, } = optionsLike; this.services = { networkService: new __1.NetworkService(chainId), walletService: new __1.WalletService(params.walletProvider, { provider: rpcProviderUrl, }, bundlerProvider.url, chainId), stateService: new __1.StateService({ storage: stateStorage, }), }; this.context = new context_1.Context(this.services); this.factoryUsed = factoryWallet; this.provider = params.provider; this.overheads = params.overheads; this.entryPointAddress = params.entryPointAddress; this.accountAddress = params.accountAddress; this.factoryAddress = params.factoryAddress; this.entryPointView = contracts_1.EntryPoint__factory.connect(params.entryPointAddress, params.provider).connect(ethers_1.ethers.constants.AddressZero); this.nonceManager = contracts_1.INonceManager__factory.connect(params.entryPointAddress, params.provider).connect(ethers_1.ethers.constants.AddressZero); } get state() { return this.services.stateService; } get state$() { return this.services.stateService.state$; } get error$() { return this.context.error$; } get supportedNetworks() { return this.services.networkService.supportedNetworks; } destroy() { this.context.destroy(); } async signMessage(dto) { const { message } = await (0, __1.validateDto)(dto, __1.SignMessageDto); await this.require({ network: false, }); return this.services.walletService.signMessage(message); } async setPaymasterApi(paymaster) { this.paymasterAPI = paymaster; } async require(options = {}) { options = Object.assign({ network: true, wallet: true }, options); const { walletService } = this.services; if (options.network && !walletService.chainId) { throw new common_1.Exception('Unknown network'); } if (options.wallet && !walletService.EOAAddress) { throw new common_1.Exception('Require wallet'); } } getNetworkChainId(networkName = null) { let result; if (!networkName) { ({ chainId: result } = this.services.networkService); } else { const network = this.supportedNetworks.find(({ name }) => name === networkName); if (!network) { throw new common_1.Exception('Unsupported network'); } ({ chainId: result } = network); } return result; } async validateResolveName(options = {}) { options = Object.assign({}, options); const { networkService } = this.services; if (options.network && !networkService.chainId) { throw new common_1.Exception('Unknown network'); } if (!options.name) { throw new common_1.Exception('Require name'); } } async init() { if ((await this.provider.getCode(this.entryPointAddress)) === '0x') { throw new Error(`entryPoint not deployed at ${this.entryPointAddress}`); } await this.getAccountAddress(); return this; } async checkAccountPhantom() { if (!this.isPhantom) { return this.isPhantom; } const senderAddressCode = await this.provider.getCode(this.getAccountAddress()); if (senderAddressCode.length > 2) { this.isPhantom = false; } else { } return this.isPhantom; } async getCounterFactualAddress() { const initCode = await this.getAccountInitCode(); try { await this.entryPointView.callStatic.getSenderAddress(initCode); } catch (e) { return e.errorArgs.sender; } throw new Error('must handle revert'); } async getInitCode() { if (await this.checkAccountPhantom()) { return await this.getAccountInitCode(); } return '0x'; } async getVerificationGasLimit() { return 100000; } async getPreVerificationGas(userOp) { const p = await (0, utils_1.resolveProperties)(userOp); return (0, calcPreVerificationGas_1.calcPreVerificationGas)(p, this.overheads); } packUserOp(userOp) { return (0, common_1.packUserOp)(userOp, false); } async encodeUserOpCallDataAndGasLimit(detailsForUserOp) { var _a, _b; function parseNumber(a) { if (a == null || a === '') return null; return ethers_1.BigNumber.from(a.toString()); } const value = (_a = parseNumber(detailsForUserOp.value)) !== null && _a !== void 0 ? _a : ethers_1.BigNumber.from(0); let callData; const data = detailsForUserOp.data; let target = detailsForUserOp.target; if (typeof data === 'string') { if (typeof target !== 'string') { throw new Error('must have target address if data is single value'); } callData = await this.encodeExecute(target, value, data); } else if (this.factoryUsed === __1.Factory.SIMPLE_ACCOUNT && target.length === 1) { callData = await this.encodeExecute(target[0], detailsForUserOp.values[0], data[0]); } else { if (typeof target === 'string') { target = Array(data.length).fill(target); } callData = await this.encodeBatch(target, detailsForUserOp.values, data); } const callGasLimit = (_b = parseNumber(detailsForUserOp.gasLimit)) !== null && _b !== void 0 ? _b : ethers_1.BigNumber.from(35000); return { callData, callGasLimit, }; } async getUserOpHash(userOp) { const op = await (0, utils_1.resolveProperties)(userOp); const provider = this.services.walletService.getWalletProvider(); const chainId = await provider.getNetwork().then((net) => net.chainId); return (0, common_1.getUserOpHash)(op, this.entryPointAddress, chainId); } async getAccountAddress() { if (this.senderAddress == null) { if (this.accountAddress != null) { this.senderAddress = this.accountAddress; } else { this.senderAddress = await this.getCounterFactualAddress(); } } return this.senderAddress; } async estimateCreationGas(initCode) { if (initCode == null || initCode === '0x') return 0; const deployerAddress = initCode.substring(0, 42); const deployerCallData = '0x' + initCode.substring(42); const provider = this.services.walletService.getWalletProvider(); return await provider.estimateGas({ to: deployerAddress, data: deployerCallData }); } async createUnsignedUserOp(info, key = 0) { var _a, _b, _c; const { callData, callGasLimit } = await this.encodeUserOpCallDataAndGasLimit(info); const initCode = await this.getInitCode(); const initGas = await this.estimateCreationGas(initCode); const verificationGasLimit = ethers_1.BigNumber.from(await this.getVerificationGasLimit()).add(initGas); let { maxFeePerGas, maxPriorityFeePerGas } = info; if (maxFeePerGas == null || maxPriorityFeePerGas == null) { const provider = this.services.walletService.getWalletProvider(); let feeData = {}; try { feeData = await provider.getFeeData(); } catch (err) { console.warn("getGas: eth_maxPriorityFeePerGas failed, falling back to legacy gas price."); const gas = await provider.getGasPrice(); feeData = { maxFeePerGas: gas, maxPriorityFeePerGas: gas }; } if (maxFeePerGas == null) { maxFeePerGas = (_a = feeData.maxFeePerGas) !== null && _a !== void 0 ? _a : undefined; } if (maxPriorityFeePerGas == null) { maxPriorityFeePerGas = (_b = feeData.maxPriorityFeePerGas) !== null && _b !== void 0 ? _b : undefined; } } const partialUserOp = { sender: await this.getAccountAddress(), nonce: await this.getNonce(key), initCode, callData, callGasLimit, verificationGasLimit, maxFeePerGas, maxPriorityFeePerGas, }; let paymasterAndData = null; if (this.paymasterAPI != null) { const userOpForPm = Object.assign(Object.assign({}, partialUserOp), { preVerificationGas: this.getPreVerificationGas(partialUserOp) }); paymasterAndData = (await this.paymasterAPI.getPaymasterAndData(userOpForPm)); partialUserOp.verificationGasLimit = paymasterAndData.result.verificationGasLimit; partialUserOp.preVerificationGas = paymasterAndData.result.preVerificationGas; partialUserOp.callGasLimit = paymasterAndData.result.callGasLimit; } partialUserOp.paymasterAndData = paymasterAndData ? paymasterAndData.result.paymasterAndData : '0x'; return Object.assign(Object.assign({}, partialUserOp), { preVerificationGas: this.getPreVerificationGas(partialUserOp), signature: (_c = info.dummySignature) !== null && _c !== void 0 ? _c : '0x' }); } async signUserOp(userOp) { if (this.paymasterAPI != null) { const paymasterAndData = await this.paymasterAPI.getPaymasterAndData(userOp); userOp.paymasterAndData = paymasterAndData.result.paymasterAndData; userOp.verificationGasLimit = paymasterAndData.result.verificationGasLimit; userOp.preVerificationGas = paymasterAndData.result.preVerificationGas; userOp.callGasLimit = paymasterAndData.result.callGasLimit; } const userOpHash = await this.getUserOpHash(userOp); const signature = await this.signUserOpHash(userOpHash); return Object.assign(Object.assign({}, userOp), { signature }); } async createSignedUserOp(info, key = 0) { return await this.signUserOp(await this.createUnsignedUserOp(info, key)); } async getUserOpReceipt(userOpHash, timeout = 30000, interval = 5000) { const endtime = Date.now() + timeout; while (Date.now() < endtime) { const events = await this.entryPointView.queryFilter(this.entryPointView.filters.UserOperationEvent(userOpHash)); if (events.length > 0) { return events[0].transactionHash; } await new Promise((resolve) => setTimeout(resolve, interval)); } return null; } async signTypedData(types, message) { const initCode = await this.getInitCode(); return this.services.walletService.signTypedData(types, message, this.factoryAddress, initCode); } } exports.BaseAccountAPI = BaseAccountAPI;