UNPKG

@etherspot/modular-sdk

Version:

Etherspot Modular SDK - build with ERC-7579 smart accounts modules

253 lines 11.4 kB
import { Factory } from './interfaces.js'; import { Exception, getGasFee, getViemAddress, getPublicClient } from "./common/index.js"; import { isWalletConnectProvider, isWalletProvider, WalletConnect2WalletProvider } from './wallet/index.js'; import { DEFAULT_QUERY_PAGE_SIZE, Networks } from './network/index.js'; import { EtherspotWalletAPI, HttpRpcClient, VerifyingPaymasterAPI } from './base/index.js'; import { SignMessageDto, validateDto } from './dto/index.js'; import { ErrorHandler } from './errorHandler/errorHandler.service.js'; import { EtherspotBundler } from './bundler/index.js'; import { formatEther, http } from 'viem'; import { BigNumber } from './types/bignumber.js'; /** * Modular-Sdk * * @category Modular-Sdk */ export class ModularSdk { constructor(walletProvider, optionsLike) { this.userOpsBatch = { to: [], data: [], value: [] }; let walletConnectProvider; if (isWalletConnectProvider(walletProvider)) { walletConnectProvider = new WalletConnect2WalletProvider(walletProvider); } else if (!isWalletProvider(walletProvider)) { throw new Exception('Invalid wallet provider'); } const { index, chainId, rpcProviderUrl, accountAddress, } = optionsLike; this.chainId = chainId; this.index = index ?? 0; if (!optionsLike.bundlerProvider) { optionsLike.bundlerProvider = new EtherspotBundler(chainId); } this.factoryUsed = optionsLike.factoryWallet ?? Factory.ETHERSPOT; let viemClientUrl = ''; if (rpcProviderUrl) { viemClientUrl = rpcProviderUrl; } else { viemClientUrl = optionsLike.bundlerProvider.url; } this.providerUrl = viemClientUrl; this.publicClient = getPublicClient({ chainId: chainId, transport: http(viemClientUrl) }); let entryPointAddress = '', walletFactoryAddress = ''; if (Networks[chainId]) { entryPointAddress = Networks[chainId].contracts.entryPoint; if (Networks[chainId].contracts.walletFactory == '') throw new Exception('The selected factory is not deployed in the selected chain_id'); walletFactoryAddress = Networks[chainId].contracts.walletFactory; } if (optionsLike.entryPointAddress) entryPointAddress = optionsLike.entryPointAddress; if (optionsLike.walletFactoryAddress) walletFactoryAddress = optionsLike.walletFactoryAddress; if (entryPointAddress == '') throw new Exception('entryPointAddress not set on the given chain_id'); if (walletFactoryAddress == '') throw new Exception('walletFactoryAddress not set on the given chain_id'); this.account = this.account; this.etherspotWallet = new EtherspotWalletAPI({ optionsLike, entryPointAddress, factoryAddress: walletFactoryAddress, predefinedAccountAddress: accountAddress, index: this.index, wallet: walletConnectProvider ?? walletProvider, publicClient: this.publicClient, }); this.bundler = new HttpRpcClient(optionsLike.bundlerProvider.url, entryPointAddress, chainId, this.publicClient); } get supportedNetworks() { return this.etherspotWallet.services.networkService.supportedNetworks; } /** * destroys */ destroy() { this.etherspotWallet.context.destroy(); } getPublicClient() { return this.publicClient; } getProviderUrl() { return this.providerUrl; } // wallet /** * signs message * @param dto * @return Promise<string> */ async signMessage(dto) { await validateDto(dto, SignMessageDto); await this.etherspotWallet.require({ network: false, }); return await this.etherspotWallet.signMessage(dto); } getEOAAddress() { return this.etherspotWallet.getEOAAddress(); } async getCounterFactualAddress() { return this.etherspotWallet.getCounterFactualAddress(); } async estimate(params = {}) { const { paymasterDetails, gasDetails, callGasLimit, key } = params; const dummySignature = "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c"; if (this.userOpsBatch.to.length < 1) { throw new ErrorHandler('cannot sign empty transaction batch', 1); } if (paymasterDetails?.url) { const paymasterAPI = new VerifyingPaymasterAPI(paymasterDetails.url, this.etherspotWallet.entryPointAddress, paymasterDetails.context ?? {}); this.etherspotWallet.setPaymasterApi(paymasterAPI); } else this.etherspotWallet.setPaymasterApi(null); const tx = { target: this.userOpsBatch.to, values: this.userOpsBatch.value, data: this.userOpsBatch.data, dummySignature: dummySignature, ...gasDetails, }; const gasInfo = await this.getGasFee(); const partialtx = await this.etherspotWallet.createUnsignedUserOp({ ...tx, maxFeePerGas: gasInfo.maxFeePerGas, maxPriorityFeePerGas: gasInfo.maxPriorityFeePerGas, }, key); if (callGasLimit) { partialtx.callGasLimit = BigNumber.from(callGasLimit).toHexString(); } if (await this.etherspotWallet.checkAccountPhantom()) { partialtx.factory = this.etherspotWallet.factoryAddress; } if (!paymasterDetails?.url) { const bundlerGasEstimate = await this.bundler.getVerificationGasInfo(partialtx); // if user has specified the gas prices then use them if (gasDetails?.maxFeePerGas && gasDetails?.maxPriorityFeePerGas) { partialtx.maxFeePerGas = gasDetails.maxFeePerGas; partialtx.maxPriorityFeePerGas = gasDetails.maxPriorityFeePerGas; } // if estimation has gas prices use them, otherwise fetch them in a separate call else if (bundlerGasEstimate.maxFeePerGas && bundlerGasEstimate.maxPriorityFeePerGas) { partialtx.maxFeePerGas = bundlerGasEstimate.maxFeePerGas; partialtx.maxPriorityFeePerGas = bundlerGasEstimate.maxPriorityFeePerGas; } if (bundlerGasEstimate.preVerificationGas) { partialtx.preVerificationGas = BigNumber.from(bundlerGasEstimate.preVerificationGas); partialtx.verificationGasLimit = BigNumber.from(bundlerGasEstimate.verificationGasLimit ?? bundlerGasEstimate.verificationGas); const expectedCallGasLimit = BigNumber.from(bundlerGasEstimate.callGasLimit); if (!callGasLimit) partialtx.callGasLimit = expectedCallGasLimit; else if (BigNumber.from(callGasLimit).lt(expectedCallGasLimit)) throw new ErrorHandler(`CallGasLimit is too low. Expected atleast ${expectedCallGasLimit.toString()}`); } } return partialtx; } async getGasFee() { const version = await this.bundler.getBundlerVersion(); if (version && version.includes('skandha')) return this.bundler.getSkandhaGasPrice(); return getGasFee(this.publicClient); } async send(userOp, isUserOpAlreadySigned = false) { const signedUserOp = isUserOpAlreadySigned ? userOp : await this.etherspotWallet.signUserOp(userOp); return this.bundler.sendUserOpToBundler(signedUserOp); } async signTypedData(msg) { return this.etherspotWallet.signTypedData(msg); } async getNativeBalance() { if (!this.etherspotWallet.accountAddress) { await this.getCounterFactualAddress(); } if (!this.etherspotWallet.accountAddress) { throw new ErrorHandler('No account address found', 1); } const balance = await this.publicClient.getBalance({ address: getViemAddress(this.etherspotWallet.accountAddress) }); return formatEther(balance); } async getUserOpReceipt(userOpHash) { //return this.bundler.getUserOpsReceipt(userOpHash); return await this.etherspotWallet.getUserOpReceipt(userOpHash); } async getUserOpHash(userOp) { return this.etherspotWallet.getUserOpHash(userOp); } async addUserOpsToBatch(tx) { if (!tx.data && !tx.value) throw new ErrorHandler('Data and Value both cannot be empty', 1); this.userOpsBatch.to.push(tx.to); this.userOpsBatch.value.push(tx.value ?? BigNumber.from(0)); this.userOpsBatch.data.push(tx.data ?? '0x'); return this.userOpsBatch; } async clearUserOpsFromBatch() { this.userOpsBatch.to = []; this.userOpsBatch.data = []; this.userOpsBatch.value = []; } async isModuleInstalled(moduleTypeId, module) { return this.etherspotWallet.isModuleInstalled(moduleTypeId, module); } async installModule(moduleTypeId, module, initData) { const installData = await this.etherspotWallet.installModule(moduleTypeId, module, initData); this.clearUserOpsFromBatch(); await this.addUserOpsToBatch({ to: this.etherspotWallet.accountAddress ?? await this.getCounterFactualAddress(), data: installData }); const op = await this.estimate(); const uoHash = await this.send(op); return uoHash; } async getPreviousModuleAddress(moduleTypeId, module) { return this.etherspotWallet.getPreviousAddress(module, moduleTypeId); } async generateModuleDeInitData(moduleTypeId, module, moduleDeInitData) { return await this.etherspotWallet.generateModuleDeInitData(moduleTypeId, module, moduleDeInitData); } async getPreviousAddress(moduleTypeId, targetAddress) { return await this.etherspotWallet.getPreviousAddress(targetAddress, moduleTypeId); } async uninstallModule(moduleTypeId, module, deinitData) { const uninstallData = await this.etherspotWallet.uninstallModule(moduleTypeId, module, deinitData); this.clearUserOpsFromBatch(); await this.addUserOpsToBatch({ to: this.etherspotWallet.accountAddress ?? await this.getCounterFactualAddress(), data: uninstallData }); const op = await this.estimate(); const uoHash = await this.send(op); return uoHash; } async getAllModules(pageSize = DEFAULT_QUERY_PAGE_SIZE) { const modules = await this.etherspotWallet.getAllModules(pageSize); return modules; } async totalGasEstimated(userOp) { const callGasLimit = BigNumber.from(await userOp.callGasLimit); const verificationGasLimit = BigNumber.from(await userOp.verificationGasLimit); const preVerificationGas = BigNumber.from(await userOp.preVerificationGas); return callGasLimit.add(verificationGasLimit).add(preVerificationGas); } async getNonce(key = BigNumber.from(0)) { const nonce = await this.etherspotWallet.getNonce(key); return nonce; } } //# sourceMappingURL=sdk.js.map