@etherspot/prime-sdk
Version:
Etherspot Prime (Account Abstraction) SDK
277 lines (276 loc) • 11.9 kB
JavaScript
"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;