@abstraxn/account
Version:
@abstraxn/account: Empower ERC-4337 smart accounts with seamless APIs for enhanced decentralized finance experiences.
734 lines • 39.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AbstraxnSmartAccount = void 0;
// Import necessary libraries and modules
const ethers_1 = require("ethers");
const Constants_1 = require("../utils/Constants");
const Instances_1 = require("../utils/Instances");
const core_types_1 = require("@abstraxn/core-types");
const providers_1 = require("@ethersproject/providers");
const common_1 = require("@abstraxn/common");
const modules_1 = require("@abstraxn/modules");
const SmartAccount_v2_ABI_1 = require("../abi/SmartAccount_v2_ABI");
const utils_1 = require("ethers/lib/utils");
const lru_cache_1 = require("lru-cache");
const paymaster_1 = require("@abstraxn/paymaster");
// Class representing AbstraxnSmartAccount
class AbstraxnSmartAccount {
// Constructor for AbstraxnSmartAccount
constructor(_config) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
// LRUCache for checking if a contract is deployed
this.isContractDeployedCache = new lru_cache_1.LRUCache({
max: 500,
});
// Initialize properties with provided configuration or default values
this.chainId = _config.chainId;
this.provider = (_a = _config.provider) !== null && _a !== void 0 ? _a : new providers_1.JsonRpcProvider(common_1.RPC_PROVIDER_URLS[this.chainId]);
this.activeValidationModule = (_b = _config.defaultValidationModule) !== null && _b !== void 0 ? _b : modules_1.BaseValidationModule;
this.defaultValidationModule = (_c = _config.defaultValidationModule) !== null && _c !== void 0 ? _c : modules_1.BaseValidationModule;
this.bundler = _config.bundler;
this.signer = _config.signer;
this.index = (_d = _config.index) !== null && _d !== void 0 ? _d : 0;
this.entryPointAddress =
(_e = _config.entryPointAddress) !== null && _e !== void 0 ? _e : Constants_1.DEFAULT_ENTRYPOINT_ADDRESS;
this.implementationAddress =
(_f = _config.implementationAddress) !== null && _f !== void 0 ? _f : Constants_1.DEFAULT_IMPLEMENTATION_ADDRESS;
this.smartFactoryAddress =
(_g = _config.smartFactoryAddress) !== null && _g !== void 0 ? _g : Constants_1.FACTORY_ADDRESS;
this.proxyCreationCode =
(_h = _config.proxyCreationCode) !== null && _h !== void 0 ? _h : Constants_1.PROXY_CREATION_CODE;
this.fallbackHandlerAddress =
(_j = _config.fallbackHandlerAddress) !== null && _j !== void 0 ? _j : Constants_1.DEFAULT_FALLBACK_HANDLER_ADDRESS;
if (_config.paymaster) {
this.paymaster = _config.paymaster;
}
}
// Static method to create an instance of AbstraxnSmartAccount
static async create(abstraxnSmartAccountConfig) {
const instance = new AbstraxnSmartAccount(abstraxnSmartAccountConfig);
if (abstraxnSmartAccountConfig.defaultValidationModule) {
instance.defaultValidationModule = abstraxnSmartAccountConfig.defaultValidationModule;
}
else {
instance.defaultValidationModule = await modules_1.ECDSAOwnershipValidationModule.create({
signer: abstraxnSmartAccountConfig.signer,
});
}
return instance;
}
// Method to get the account address for the smart account
async getAccountAddress() {
const accountAddress = await this.getCounterFactualAddress();
return accountAddress;
}
// Check if the provider is defined
isProviderDefined() {
if (!this.provider)
throw new Error("Provider is undefined");
return true;
}
// Check if an account is deployed at a given address
async isAccountDeployed(address) {
if (this.isContractDeployedCache.get(address)) {
return true;
}
this.isProviderDefined();
let isDeployed = false;
const contractCode = await this.provider.getCode(address);
if (contractCode.length > 2) {
this.isContractDeployedCache.set(address, true);
isDeployed = true;
}
else {
isDeployed = false;
}
return isDeployed;
}
// Get the initialization code for the smart account
async getInitCode() {
if (await this.isAccountDeployed(await this.getAccountAddress())) {
return "0x";
}
return this.getAccountInitCode();
}
// Get the initialization code for creating a smart account
async getAccountInitCode() {
const factoryContract = this.smartFactoryAddress;
const contract = (0, Instances_1.smartAccountFactoryContract)(this.provider, factoryContract);
return (0, utils_1.hexConcat)([
factoryContract,
contract.interface.encodeFunctionData("deployCounterFactualAccount", [
this.defaultValidationModule.getAddress(),
await this.defaultValidationModule.getInitData(),
this.index,
]),
]);
}
// Get the counterfactual address for the smart account
async getCounterFactualAddress() {
const validationModule = this.defaultValidationModule;
const index = this.index;
try {
const contract = (0, Instances_1.smartAccount)(this.implementationAddress, this.provider);
const initCalldata = contract.interface.encodeFunctionData("init", [
this.fallbackHandlerAddress,
validationModule.getAddress(),
await validationModule.getInitData(),
]);
const proxyCreationCodeHash = (0, utils_1.solidityKeccak256)(["bytes", "uint256"], [this.chainId == core_types_1.ChainId.BERACHAIN_BEPOLIA ? Constants_1.PROXY_CREATION_CODE_BERA : this.proxyCreationCode, this.implementationAddress]);
const salt = (0, utils_1.solidityKeccak256)(["bytes32", "uint256"], [(0, utils_1.keccak256)(initCalldata), index]);
const counterFactualAddress = (0, utils_1.getCreate2Address)(this.smartFactoryAddress, salt, proxyCreationCodeHash);
return counterFactualAddress;
}
catch (e) {
throw new Error(`Failed to get counterfactual address, ${e}`);
}
}
// Async method to get the smart account contract instance
async _getAccountContract() {
let accountContract = new ethers_1.ethers.Contract(await this.getAccountAddress(), SmartAccount_v2_ABI_1.SMART_ACCOUNTV2_ABI, this.provider);
return accountContract;
}
// Async method to get the nonce for a specific nonce space (or default to 0)
async getNonce(nonceKey) {
const nonceSpace = nonceKey !== null && nonceKey !== void 0 ? nonceKey : 0;
try {
const accountContract = await this._getAccountContract();
const nonce = await accountContract.nonce(nonceSpace);
return nonce;
}
catch (e) {
console.debug("Failed to get nonce from deployed account. Returning 0 as nonce");
return ethers_1.BigNumber.from(0);
}
}
// Private method to get the nonce for building user operations
async getBuildUserOpNonce(nonceOptions) {
var _a;
let nonce = ethers_1.BigNumber.from(0);
try {
if (nonceOptions === null || nonceOptions === void 0 ? void 0 : nonceOptions.nonceOverride) {
nonce = ethers_1.BigNumber.from(nonceOptions === null || nonceOptions === void 0 ? void 0 : nonceOptions.nonceOverride);
}
else {
const _nonceSpace = (_a = nonceOptions === null || nonceOptions === void 0 ? void 0 : nonceOptions.nonceKey) !== null && _a !== void 0 ? _a : 0;
nonce = await this.getNonce(_nonceSpace);
}
}
catch (error) {
// Not throwing this error as nonce would be 0 if this.getNonce() throws an exception, which is the expected flow for undeployed accounts
console.log("Error while getting nonce for the account. This is expected for undeployed accounts; set nonce to 0");
}
return nonce;
}
// Check if an active validation module is defined
isActiveValidationModuleDefined() {
if (!this.activeValidationModule)
throw new Error("Must provide an instance of the active validation module.");
return true;
}
// Async method to get a dummy signature based on the provided validation module
async getDummySignature(params) {
this.isActiveValidationModuleDefined();
return this.activeValidationModule.getDummySignature(params);
}
// Private method to get gas fee values, considering bundler and overrides
async getGasFeeValues(overrides, skipBundlerGasEstimation) {
const gasFeeValues = {
maxFeePerGas: overrides === null || overrides === void 0 ? void 0 : overrides.maxFeePerGas,
maxPriorityFeePerGas: overrides === null || overrides === void 0 ? void 0 : overrides.maxPriorityFeePerGas,
};
try {
if (this.bundler && !gasFeeValues.maxFeePerGas && !gasFeeValues.maxPriorityFeePerGas && (skipBundlerGasEstimation !== null && skipBundlerGasEstimation !== void 0 ? skipBundlerGasEstimation : true)) {
const gasFeeEstimation = await this.bundler.getGasFeeValues();
gasFeeValues.maxFeePerGas = gasFeeEstimation.maxFeePerGas;
gasFeeValues.maxPriorityFeePerGas = gasFeeEstimation.maxPriorityFeePerGas;
}
return gasFeeValues;
}
catch (error) {
console.error("Error while getting gasFeeValues from the bundler. Provided bundler might not have the getGasFeeValues endpoint", error);
return gasFeeValues;
}
}
// Async method to encode an "execute" transaction for a single recipient
async encodeExecute(to, value, data) {
const accountContract = await this._getAccountContract();
return accountContract.interface.encodeFunctionData("execute", [to, value, data]);
}
// Async method to encode an "executeBatch" transaction for multiple recipients
async encodeExecuteBatch(to, value, data) {
const accountContract = await this._getAccountContract();
return accountContract.interface.encodeFunctionData("executeBatch", [to, value, data]);
}
// Validate if required fields are present in the UserOperation
validateUserOp(userOp, requiredFields) {
for (const field of requiredFields) {
if (!userOp[field]) {
throw new Error(`${String(field)} is missing in the UserOp`);
}
}
return true;
}
// Async method to get the gas limit for the verification process
async getVerificationGasLimit(initCode) {
// Verification gas should be max(initGas(wallet deployment) + validateUserOp + validatePaymasterUserOp, postOp)
const initGas = await this.estimateCreationGas(initCode);
const validateUserOpGas = ethers_1.BigNumber.from(Constants_1.DefaultGasLimits.validatePaymasterUserOpGas + Constants_1.DefaultGasLimits.validateUserOpGas);
const postOpGas = ethers_1.BigNumber.from(Constants_1.DefaultGasLimits.postOpGas);
let verificationGasLimit = ethers_1.BigNumber.from(validateUserOpGas).add(initGas);
if (ethers_1.BigNumber.from(postOpGas).gt(verificationGasLimit)) {
verificationGasLimit = postOpGas;
}
if (this.defaultValidationModule.getAddress() === modules_1.DEFAULT_PASSKEY_MODULE && initCode != '0x') {
verificationGasLimit = verificationGasLimit.add(600000);
}
return verificationGasLimit;
}
// Async method to get the gas limit for the verification process
async getCallGasLimit(userOp) {
let callGasLimit = await this.provider.estimateGas({
from: this.entryPointAddress,
to: userOp.sender,
data: userOp.callData,
});
if (userOp.initCode && userOp.initCode != '0x') {
callGasLimit = ethers_1.BigNumber.from(callGasLimit).add(100000);
}
return callGasLimit;
}
// Async method to estimate gas for the deployment of a smart contract
async estimateCreationGas(initCode) {
if (initCode == null || initCode === "0x")
return 0;
const deployerAddress = initCode.substring(0, 42);
const deployerCallData = "0x" + initCode.substring(42);
return this.provider.estimateGas({ to: deployerAddress, data: deployerCallData });
}
// Async method to get the gas limit for the pre-verification step in the user operation
async getPreVerificationGas(userOp) {
return (0, Constants_1.calcPreVerificationGas)(userOp);
}
// Async method to calculate gas values for various steps in the user operation
async calculateUserOpGasValues(userOp) {
var _a, _b, _c, _d;
if (!this.provider)
throw new Error("Provider is not present for making rpc calls");
let feeData = null;
if (userOp.maxFeePerGas === undefined ||
userOp.maxFeePerGas === null ||
userOp.maxPriorityFeePerGas === undefined ||
userOp.maxPriorityFeePerGas === null) {
feeData = await this.provider.getFeeData();
}
if (userOp.maxFeePerGas === undefined || userOp.maxFeePerGas === null) {
userOp.maxFeePerGas = (_b = (_a = feeData === null || feeData === void 0 ? void 0 : feeData.maxFeePerGas) !== null && _a !== void 0 ? _a : feeData === null || feeData === void 0 ? void 0 : feeData.gasPrice) !== null && _b !== void 0 ? _b : (await this.provider.getGasPrice());
}
if (userOp.maxPriorityFeePerGas === undefined || userOp.maxPriorityFeePerGas === null) {
userOp.maxPriorityFeePerGas = (_d = (_c = feeData === null || feeData === void 0 ? void 0 : feeData.maxPriorityFeePerGas) !== null && _c !== void 0 ? _c : feeData === null || feeData === void 0 ? void 0 : feeData.gasPrice) !== null && _d !== void 0 ? _d : (await this.provider.getGasPrice());
}
if (userOp.initCode)
userOp.verificationGasLimit = await this.getVerificationGasLimit(userOp.initCode);
userOp.callGasLimit = await this.getCallGasLimit(userOp);
userOp.preVerificationGas = await this.getPreVerificationGas(userOp);
return userOp;
}
// Async method to estimate gas values for a user operation
async estimateUserOpGas(params) {
var _a, _b, _c, _d;
// Extract parameters
let userOp = params.userOp;
const { overrides, skipBundlerGasEstimation, paymasterServiceData } = params;
const requiredFields = ["sender", "nonce", "initCode", "callData"];
// Validate required fields in userOp
this.validateUserOp(userOp, requiredFields);
let finalUserOp = userOp;
const skipBundlerCall = skipBundlerGasEstimation !== null && skipBundlerGasEstimation !== void 0 ? skipBundlerGasEstimation : true;
// Override gas values in userOp if provided in overrides params
if (overrides) {
userOp = { ...userOp, ...overrides };
}
if (skipBundlerCall) {
if (this.paymaster) {
if ((0, Constants_1.isNullOrUndefined)(userOp.maxFeePerGas) || (0, Constants_1.isNullOrUndefined)(userOp.maxPriorityFeePerGas)) {
throw new Error("maxFeePerGas and maxPriorityFeePerGas are required for skipBundlerCall mode");
}
if ((paymasterServiceData === null || paymasterServiceData === void 0 ? void 0 : paymasterServiceData.mode) === paymaster_1.PaymasterMode.SPONSORED) {
// Making call to paymaster to get gas estimations for userOp
const { callGasLimit, verificationGasLimit, preVerificationGas, paymasterAndData } = await this.paymaster.getPaymasterAndData(userOp, paymasterServiceData);
if (paymasterAndData === "0x" && (callGasLimit === undefined || verificationGasLimit === undefined || preVerificationGas === undefined)) {
throw new Error("Since you intend to use sponsorship paymaster, please check and make sure policies are set on the dashboard");
}
finalUserOp.verificationGasLimit = verificationGasLimit !== null && verificationGasLimit !== void 0 ? verificationGasLimit : userOp.verificationGasLimit;
finalUserOp.callGasLimit = callGasLimit !== null && callGasLimit !== void 0 ? callGasLimit : userOp.callGasLimit;
finalUserOp.preVerificationGas = preVerificationGas !== null && preVerificationGas !== void 0 ? preVerificationGas : userOp.preVerificationGas;
finalUserOp.paymasterAndData = paymasterAndData !== null && paymasterAndData !== void 0 ? paymasterAndData : userOp.paymasterAndData;
}
else {
// use dummy values for gas limits as fee quote call will ignore this later.
finalUserOp.callGasLimit = Constants_1.DefaultGasLimit.callGasLimit;
finalUserOp.verificationGasLimit = Constants_1.DefaultGasLimit.verificationGasLimit;
finalUserOp.preVerificationGas = Constants_1.DefaultGasLimit.preVerificationGas;
}
}
else {
// Skip paymaster call and set paymasterAndData to "0x"
console.warn("Skipped paymaster call. If you are using paymasterAndData, generate data externally");
finalUserOp = await this.calculateUserOpGasValues(userOp);
finalUserOp.paymasterAndData = "0x";
}
}
else {
if (!this.bundler)
throw new Error("Bundler is not provided");
delete userOp.maxFeePerGas;
delete userOp.maxPriorityFeePerGas;
// Making a call to bundler to get gas estimations for userOp
const { callGasLimit, verificationGasLimit, preVerificationGas, maxFeePerGas, maxPriorityFeePerGas } = await this.bundler.estimateUserOpGas(userOp);
// If neither user sent gas fee nor the bundler, estimate gas from the provider
if ((0, Constants_1.isNullOrUndefined)(userOp.maxFeePerGas) &&
(0, Constants_1.isNullOrUndefined)(userOp.maxPriorityFeePerGas) &&
((0, Constants_1.isNullOrUndefined)(maxFeePerGas) || (0, Constants_1.isNullOrUndefined)(maxPriorityFeePerGas))) {
const feeData = await this.provider.getFeeData();
finalUserOp.maxFeePerGas = (_b = (_a = feeData.maxFeePerGas) !== null && _a !== void 0 ? _a : feeData.gasPrice) !== null && _b !== void 0 ? _b : (await this.provider.getGasPrice());
finalUserOp.maxPriorityFeePerGas = (_d = (_c = feeData.maxPriorityFeePerGas) !== null && _c !== void 0 ? _c : feeData.gasPrice) !== null && _d !== void 0 ? _d : (await this.provider.getGasPrice());
}
else {
finalUserOp.maxFeePerGas = maxFeePerGas !== null && maxFeePerGas !== void 0 ? maxFeePerGas : userOp.maxFeePerGas;
finalUserOp.maxPriorityFeePerGas = maxPriorityFeePerGas !== null && maxPriorityFeePerGas !== void 0 ? maxPriorityFeePerGas : userOp.maxPriorityFeePerGas;
}
finalUserOp.verificationGasLimit = verificationGasLimit !== null && verificationGasLimit !== void 0 ? verificationGasLimit : userOp.verificationGasLimit;
finalUserOp.callGasLimit = callGasLimit !== null && callGasLimit !== void 0 ? callGasLimit : userOp.callGasLimit;
finalUserOp.preVerificationGas = preVerificationGas !== null && preVerificationGas !== void 0 ? preVerificationGas : userOp.preVerificationGas;
finalUserOp.paymasterAndData = "0x";
}
finalUserOp = await (0, Constants_1.convertToFactor)(finalUserOp);
return finalUserOp;
}
// Async method to build a UserOperation based on a list of transactions
async buildUserOp(transactions, buildUseropDto) {
var _a;
// Extract transaction details
const to = transactions.map((element) => element.to);
const data = transactions.map((element) => { var _a; return (_a = element.data) !== null && _a !== void 0 ? _a : "0x"; });
const value = transactions.map((element) => { var _a; return (_a = element.value) !== null && _a !== void 0 ? _a : ethers_1.BigNumber.from("0"); });
// Fetch necessary data asynchronously
const initCodeFetchPromise = this.getInitCode();
const dummySignatureFetchPromise = this.getDummySignature(buildUseropDto === null || buildUseropDto === void 0 ? void 0 : buildUseropDto.params);
const [nonceFromFetch, initCode, signature, finalGasFeeValue] = await Promise.all([
this.getBuildUserOpNonce(buildUseropDto === null || buildUseropDto === void 0 ? void 0 : buildUseropDto.nonceOptions),
initCodeFetchPromise,
dummySignatureFetchPromise,
this.getGasFeeValues(buildUseropDto === null || buildUseropDto === void 0 ? void 0 : buildUseropDto.overrides, buildUseropDto === null || buildUseropDto === void 0 ? void 0 : buildUseropDto.skipBundlerGasEstimation),
]);
// Validate transactions array
if (transactions.length === 0) {
throw new Error("Transactions array cannot be empty");
}
// Determine the callData based on the number of transactions and user preferences
let callData = "";
if (transactions.length > 1 || (buildUseropDto === null || buildUseropDto === void 0 ? void 0 : buildUseropDto.forceEncodeForBatch)) {
callData = await this.encodeExecuteBatch(to, value, data);
}
else {
// transactions.length must be 1
callData = await this.encodeExecute(to[0], value[0], data[0]);
}
// Build the UserOperation object
let userOp = {
sender: await this.getAccountAddress(),
nonce: nonceFromFetch,
initCode,
callData: callData,
maxFeePerGas: finalGasFeeValue.maxFeePerGas || undefined,
maxPriorityFeePerGas: finalGasFeeValue.maxPriorityFeePerGas || undefined,
};
// For this Smart Account, the current validation module's dummy signature will be used to estimate gas
userOp.signature = signature;
// Note: Can change the default behavior of calling estimations using bundler/local
userOp = await this.estimateUserOpGas({
userOp,
overrides: buildUseropDto === null || buildUseropDto === void 0 ? void 0 : buildUseropDto.overrides,
skipBundlerGasEstimation: buildUseropDto === null || buildUseropDto === void 0 ? void 0 : buildUseropDto.skipBundlerGasEstimation,
paymasterServiceData: buildUseropDto === null || buildUseropDto === void 0 ? void 0 : buildUseropDto.paymasterServiceData,
});
userOp.paymasterAndData = (_a = userOp.paymasterAndData) !== null && _a !== void 0 ? _a : "0x";
return userOp;
}
// sendUserOp to bundler
async sendUserOp(userOp, params) {
delete userOp.signature;
const userOperation = await this.signUserOp(userOp, params);
const bundlerResponse = await this.sendSignedUserOp(userOperation, params);
return bundlerResponse;
}
// Method to sign a UserOperation
async signUserOp(userOp, params) {
// Ensure that an active validation module is defined
this.isActiveValidationModuleDefined();
const requiredFields = [
"sender",
"nonce",
"initCode",
"callData",
"callGasLimit",
"verificationGasLimit",
"preVerificationGas",
"maxFeePerGas",
"maxPriorityFeePerGas",
"paymasterAndData",
];
// Validate required fields in userOp
this.validateUserOp(userOp, requiredFields);
// Calculate the hash of the UserOperation
const userOpHash = await this.getUserOpHash(userOp);
// Sign the hash using the active validation module
// Note: If the account is undeployed, use ERC-6492
// Review: Should only be needed for signMessage
/*if (!(await this.isAccountDeployed(await this.getAccountAddress()))) {
const coder = new ethers.utils.AbiCoder();
const populatedTransaction = await this.factory?.populateTransaction.deployCounterFactualAccount(
await this.defaultValidationModule.getAddress(),
await this.defaultValidationModule.getInitData(),
this.index,
);
moduleSig =
coder.encode(["address", "bytes", "bytes"], [this.factoryAddress, populatedTransaction?.data, moduleSig]) +
"6492649264926492649264926492649264926492649264926492649264926492"; // magic suffix
userOp.signature = moduleSig;
return userOp as UserOperation;
}*/
// Encode the signature with the module address
if (this.defaultValidationModule.getAddress().toLocaleLowerCase() === modules_1.DEFAULT_MULTISIG_MODULE.toLocaleLowerCase()) {
const { validUntil, validAfter } = await this.validThru();
if (!params || !(params === null || params === void 0 ? void 0 : params.signatures)) {
throw new Error("Signatures are not provided");
}
const sigConcat = await this.buildSignatureBytes(params === null || params === void 0 ? void 0 : params.signatures);
const coder = new ethers_1.ethers.utils.AbiCoder();
let signPacked = coder.encode(["uint48", "uint48", "bytes"], [validAfter, validUntil, sigConcat]);
// let signPacked = ethers.utils.solidityPack(["uint48", "uint48", "bytes"], [validAfter, validUntil, sigConcat])
const packSig = coder.encode(["bytes", "address"], [signPacked, this.activeValidationModule.getAddress()]);
// const packSig = ethers.utils.solidityPack(["bytes", "address"], [signPacked, this.activeValidationModule.getAddress()]);
userOp.signature = packSig;
}
else if (this.defaultValidationModule.getAddress().toLocaleLowerCase() === modules_1.DEFAULT_PASSKEY_MODULE.toLocaleLowerCase()) {
const { validUntil, validAfter } = await this.validThru();
const coder = new ethers_1.ethers.utils.AbiCoder();
const encodedSig = coder.encode(["bytes", "uint256", "uint256", "uint48", "uint48", "bytes", "string"], [params === null || params === void 0 ? void 0 : params.challenge, params === null || params === void 0 ? void 0 : params.r, params === null || params === void 0 ? void 0 : params.s, validUntil, validAfter, params === null || params === void 0 ? void 0 : params.authenticatorData, params === null || params === void 0 ? void 0 : params.clientDataJsonPost]);
const signatureWithModuleAddress = ethers_1.ethers.utils.solidityPack(["bytes", "address"], [encodedSig, this.activeValidationModule.getAddress()]);
userOp.signature = signatureWithModuleAddress;
}
else {
const coder = new ethers_1.ethers.utils.AbiCoder();
const moduleSig = await this.activeValidationModule.signUserOpHash(userOpHash, params);
const signatureWithModuleAddress = coder.encode(["bytes", "address"], [moduleSig, this.activeValidationModule.getAddress()]);
// const signatureWithModuleAddress = ethers.utils.solidityPack(
// ["bytes", "address"],
// [moduleSig, this.activeValidationModule.getAddress()],
// );
userOp.signature = signatureWithModuleAddress;
}
return userOp;
}
// Method to send a signed UserOperation to the bundler
async sendSignedUserOp(userOp, params) {
const requiredFields = [
"sender",
"nonce",
"initCode",
"callData",
"callGasLimit",
"verificationGasLimit",
"preVerificationGas",
"maxFeePerGas",
"maxPriorityFeePerGas",
"paymasterAndData",
"signature",
];
// Validate required fields in userOp
this.validateUserOp(userOp, requiredFields);
// Ensure that a bundler is provided
if (!this.bundler)
throw new Error("Bundler is not provided");
// Send the signed UserOperation to the bundler
const bundlerResponse = await this.bundler.sendUserOp(userOp, params === null || params === void 0 ? void 0 : params.simulationType);
return bundlerResponse;
}
// Method to calculate the hash of a UserOperation
async getUserOpHash(userOp) {
// Calculate the hash of the UserOperation using keccak256
const userOpHash = (0, utils_1.keccak256)((0, Constants_1.packUserOp)(userOp, true));
// Encode additional information for hashing
const enc = utils_1.defaultAbiCoder.encode(["bytes32", "address", "uint256"], [userOpHash, this.entryPointAddress, this.chainId]);
// Final hash calculation
return (0, utils_1.keccak256)(enc);
}
validateUserOpAndPaymasterRequest(userOp, tokenPaymasterRequest) {
var _a;
if ((0, Constants_1.isNullOrUndefined)(userOp.callData)) {
throw new Error("UserOp callData cannot be undefined");
}
const feeTokenAddress = (_a = tokenPaymasterRequest === null || tokenPaymasterRequest === void 0 ? void 0 : tokenPaymasterRequest.feeQuote) === null || _a === void 0 ? void 0 : _a.tokenAddress;
if (!feeTokenAddress || feeTokenAddress == ethers_1.ethers.constants.AddressZero) {
throw new Error("Invalid or missing token address. Token address must be part of the feeQuote in tokenPaymasterRequest");
}
const spender = tokenPaymasterRequest === null || tokenPaymasterRequest === void 0 ? void 0 : tokenPaymasterRequest.spender;
if (!spender || spender == ethers_1.ethers.constants.AddressZero) {
throw new Error("Invalid or missing spender address. Sepnder address must be part of tokenPaymasterRequest");
}
}
async buildTokenPaymasterUserOp(userOp, tokenPaymasterRequest) {
this.validateUserOpAndPaymasterRequest(userOp, tokenPaymasterRequest);
try {
let batchTo = [];
let batchValue = [];
let batchData = [];
let newCallData = userOp.callData;
if (this.paymaster) {
// Make a call to paymaster.buildTokenApprovalTransaction() with necessary details
// Review: might request this form of an array of Transaction
const approvalRequest = await this.paymaster.buildTokenApprovalTransaction(tokenPaymasterRequest, this.provider);
if (approvalRequest.data == "0x" || approvalRequest.to == ethers_1.ethers.constants.AddressZero) {
return userOp;
}
if ((0, Constants_1.isNullOrUndefined)(userOp.callData)) {
throw new Error("UserOp callData cannot be undefined");
}
const account = await this._getAccountContract();
const decodedSmartAccountData = account.interface.parseTransaction({
data: userOp.callData.toString(),
});
if (!decodedSmartAccountData) {
throw new Error("Could not parse userOp call data for this smart account");
}
const smartAccountExecFunctionName = decodedSmartAccountData.name;
if (smartAccountExecFunctionName === "execute" || smartAccountExecFunctionName === "execute_ncC") {
const methodArgsSmartWalletExecuteCall = decodedSmartAccountData.args;
const toOriginal = methodArgsSmartWalletExecuteCall[0];
const valueOriginal = methodArgsSmartWalletExecuteCall[1];
const dataOriginal = methodArgsSmartWalletExecuteCall[2];
batchTo.push(toOriginal);
batchValue.push(valueOriginal);
batchData.push(dataOriginal);
}
else if (smartAccountExecFunctionName === "executeBatch" || smartAccountExecFunctionName === "executeBatch_y6U") {
const methodArgsSmartWalletExecuteCall = decodedSmartAccountData.args;
batchTo = methodArgsSmartWalletExecuteCall[0];
batchValue = methodArgsSmartWalletExecuteCall[1];
batchData = methodArgsSmartWalletExecuteCall[2];
}
if (approvalRequest.to && approvalRequest.data && approvalRequest.value) {
batchTo = [approvalRequest.to, ...batchTo];
batchValue = [approvalRequest.value, ...batchValue];
batchData = [approvalRequest.data, ...batchData];
newCallData = await this.encodeExecuteBatch(batchTo, batchValue, batchData);
const decodedSmartAccountDataNew = account.interface.parseTransaction({
data: newCallData.toString(),
});
}
const finalUserOp = {
...userOp,
callData: newCallData,
};
return finalUserOp;
}
}
catch (error) {
console.error("Failed to update callData with error", error);
return userOp;
}
return userOp;
}
async enableModule(moduleAddress) {
const tx = await this.getEnableModuleData(moduleAddress);
const partialUserOp = await this.buildUserOp([tx]);
return this.sendUserOp(partialUserOp);
}
async getEnableModuleData(moduleAddress) {
const accountContract = await this._getAccountContract();
const data = accountContract.interface.encodeFunctionData("enableModule", [moduleAddress]);
const tx = {
to: await this.getAccountAddress(),
value: "0",
data: data,
};
return tx;
}
async getSetupAndEnableModuleData(moduleAddress, moduleSetupData) {
const accountContract = await this._getAccountContract();
const data = accountContract.interface.encodeFunctionData("setupAndEnableModule", [moduleAddress, moduleSetupData]);
const tx = {
to: await this.getAccountAddress(),
value: "0",
data: data,
};
return tx;
}
async disableModule(prevModule, moduleAddress) {
const tx = await this.getDisableModuleData(prevModule, moduleAddress);
const partialUserOp = await this.buildUserOp([tx]);
return this.sendUserOp(partialUserOp);
}
async getDisableModuleData(prevModule, moduleAddress) {
const accountContract = await this._getAccountContract();
const data = accountContract.interface.encodeFunctionData("disableModule", [prevModule, moduleAddress]);
const tx = {
to: await this.getAccountAddress(),
value: "0",
data: data,
};
return tx;
}
async isModuleEnabled(moduleName) {
const accountContract = await this._getAccountContract();
return accountContract.isModuleEnabled(moduleName);
}
async validThru() {
var today = new Date();
var firstMinute = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0);
var lastMinute = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 23, 59, 59);
var firstMinuteTimestamp = firstMinute.getTime();
var lastMinuteTimestamp = lastMinute.getTime();
const hex = (Number((lastMinuteTimestamp.valueOf() / 1000).toFixed(0))).toString(16);
const hex1 = (Number((firstMinuteTimestamp.valueOf() / 1000).toFixed(0))).toString(16);
let str = '0x';
let str1 = '0x';
for (let i = 0; i < 14 - hex.length; i++) {
str += '0';
}
for (let i = 0; i < 14 - hex1.length; i++) {
str1 += '0';
}
str += hex;
str1 += hex1;
return { validUntil: str, validAfter: str1 };
}
async getSignForMultiSigWallet(signer, userOp) {
const { validUntil, validAfter } = await this.validThru();
let signature = await signer._signTypedData({
chainId: this.chainId,
verifyingContract: modules_1.DEFAULT_MULTISIG_MODULE,
}, {
SafeOp: [
{
name: "safe",
type: "address",
},
{
name: "nonce",
type: "uint256",
},
{
name: "initCode",
type: "bytes",
},
{
name: "callData",
type: "bytes",
},
{
name: "callGasLimit",
type: "uint256",
},
{
name: "verificationGasLimit",
type: "uint256",
},
{
name: "preVerificationGas",
type: "uint256",
},
{
name: "maxFeePerGas",
type: "uint256",
},
{
name: "maxPriorityFeePerGas",
type: "uint256",
},
{
name: "paymasterAndData",
type: "bytes",
},
{
name: "validAfter",
type: "uint48",
},
{
name: "validUntil",
type: "uint48",
},
{
name: "entryPoint",
type: "address",
},
],
}, {
safe: userOp.sender,
nonce: userOp.nonce,
initCode: userOp.initCode,
callData: userOp.callData,
callGasLimit: userOp.callGasLimit,
verificationGasLimit: userOp.verificationGasLimit,
preVerificationGas: userOp.preVerificationGas,
maxFeePerGas: userOp.maxFeePerGas,
maxPriorityFeePerGas: userOp.maxPriorityFeePerGas,
paymasterAndData: userOp.paymasterAndData,
validAfter: validAfter,
validUntil: validUntil,
entryPoint: this.entryPointAddress,
});
return signature;
}
async buildSignatureBytes(signs) {
let signatureBytes = "0x";
for (const sig of signs) {
signatureBytes += sig.slice(2);
}
return signatureBytes;
}
}
exports.AbstraxnSmartAccount = AbstraxnSmartAccount;
//# sourceMappingURL=AbstraxnSmartAccount.service.js.map