UNPKG

@abstraxn/paymaster

Version:

Abstraxn Paymaster to interact with Paymaster Services that interacts with ( veriying and token ) paymasters

320 lines 16.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AbstraxnPaymaster = void 0; const properties_1 = require("@ethersproject/properties"); const Types_1 = require("./utils/Types"); const ethers_1 = require("ethers"); const constants_1 = require("./constants"); const httpRequests_1 = require("./utils/httpRequests"); const HelperFunction_1 = require("./utils/HelperFunction"); const defaultPaymasterConfig = { paymasterUrl: "", strictMode: false, // Set your desired default value for strictMode here }; /** * @dev Hybrid - Generic Gas Abstraction paymaster */ class PaymasterError extends Error { constructor(message, statusCode = 400, result = null) { super(message); this.name = "CustomError"; this.error = true; this.statusCode = statusCode; this.result = result; } toJSON() { return { error: this.error, statusCode: this.statusCode, message: this.message, result: this.result, }; } } class AbstraxnPaymaster { constructor(config) { const mergedConfig = { ...defaultPaymasterConfig, ...config, }; this.paymasterConfig = mergedConfig; } /** * @dev Prepares the user operation by resolving properties and converting certain values to hexadecimal format. * @param userOp The partial user operation. * @returns A Promise that resolves to the prepared partial user operation. */ async prepareUserOperation(userOp) { userOp = await (0, properties_1.resolveProperties)(userOp); if (userOp.nonce !== null && userOp.nonce !== undefined) { userOp.nonce = ethers_1.BigNumber.from(userOp.nonce).toHexString(); } if (userOp.callGasLimit !== null && userOp.callGasLimit !== undefined) { userOp.callGasLimit = ethers_1.BigNumber.from(userOp.callGasLimit).toString(); } if (userOp.verificationGasLimit !== null && userOp.verificationGasLimit !== undefined) { userOp.verificationGasLimit = ethers_1.BigNumber.from(userOp.verificationGasLimit).toString(); } if (userOp.preVerificationGas !== null && userOp.preVerificationGas !== undefined) { userOp.preVerificationGas = ethers_1.BigNumber.from(userOp.preVerificationGas).toString(); } if (userOp.maxFeePerGas !== null && userOp.maxFeePerGas !== undefined) { userOp.maxFeePerGas = ethers_1.BigNumber.from(userOp.maxFeePerGas).toString(); } if (userOp.maxPriorityFeePerGas !== null && userOp.maxPriorityFeePerGas !== undefined) { userOp.maxPriorityFeePerGas = ethers_1.BigNumber.from(userOp.maxPriorityFeePerGas).toString(); } userOp.signature = userOp.signature || "0x"; userOp.paymasterAndData = userOp.paymasterAndData || "0x"; return userOp; } /** * @dev Builds a token approval transaction for the Abstraxn token paymaster. * @param tokenPaymasterRequest The token paymaster request data. This will include information about chosen feeQuote, spender address and optional flag to provide maxApproval * @param provider Optional provider object. * @returns A Promise that resolves to the built transaction object. */ async buildTokenApprovalTransaction(tokenPaymasterRequest) { const feeTokenAddress = tokenPaymasterRequest.feeQuote.tokenAddress; const spender = tokenPaymasterRequest.spender; // TODO move below notes to separate method // Note: should also check in caller if the approval is already given, if yes return object with address or data 0 // Note: we would need userOp here to get the account/owner info to check allowance let requiredApproval = ethers_1.BigNumber.from(0).toString(); if (tokenPaymasterRequest.maxApproval && tokenPaymasterRequest.maxApproval == true) { requiredApproval = ethers_1.ethers.constants.MaxUint256; } else { requiredApproval = Math.ceil(tokenPaymasterRequest.feeQuote.maxGasFee * Math.pow(10, tokenPaymasterRequest.feeQuote.decimal)).toString(); } const erc20Interface = new ethers_1.ethers.utils.Interface(JSON.stringify(constants_1.ERC20_ABI)); try { const data = erc20Interface.encodeFunctionData("approve", [spender, requiredApproval]); // TODO? // Note: For some tokens we may need to set allowance to 0 first so that would return batch of transactions and changes the return type to Transaction[] // In that case we would return two objects in an array, first of them being.. /* { to: erc20.address, value: ethers.BigNumber.from(0), data: erc20.interface.encodeFunctionData('approve', [spender, BigNumber.from("0")]) } */ // const zeroValue: ethers.BigNumber = ethers.BigNumber.from(0); // const value: BigNumberish | undefined = zeroValue as any; return { to: feeTokenAddress, value: ethers_1.ethers.BigNumber.from(0), data: data, }; } catch (error) { console.error("Error encoding function data:", error); throw new Error("Failed to encode function data"); } } /** * @dev Retrieves paymaster fee quotes or data based on the provided user operation and paymaster service data. * @param userOp The partial user operation. * @param paymasterServiceData The paymaster service data containing token information and sponsorship details. Devs can send just the preferred token or array of token addresses in case of mode "ERC20" and sartAccountInfo in case of "sponsored" mode. * @returns A Promise that resolves to the fee quotes or data response. */ async getPaymasterFeeQuotesOrData(userOp, paymasterServiceData) { var _a, _b, _c; try { userOp = await this.prepareUserOperation(userOp); } catch (err) { throw err; } let mode = null; let expiryDuration = null; const calculateGasLimits = (_a = paymasterServiceData.calculateGasLimits) !== null && _a !== void 0 ? _a : true; let preferredToken = null; let feeTokensArray = []; let webhookData = null; if (paymasterServiceData.mode) { mode = paymasterServiceData.mode; // Validation on the mode passed / define allowed enums } if (paymasterServiceData.expiryDuration) { expiryDuration = paymasterServiceData.expiryDuration; } preferredToken = (paymasterServiceData === null || paymasterServiceData === void 0 ? void 0 : paymasterServiceData.preferredToken) ? paymasterServiceData === null || paymasterServiceData === void 0 ? void 0 : paymasterServiceData.preferredToken : preferredToken; feeTokensArray = (((_b = paymasterServiceData === null || paymasterServiceData === void 0 ? void 0 : paymasterServiceData.tokenList) === null || _b === void 0 ? void 0 : _b.length) !== 0 ? paymasterServiceData === null || paymasterServiceData === void 0 ? void 0 : paymasterServiceData.tokenList : feeTokensArray); webhookData = (_c = paymasterServiceData === null || paymasterServiceData === void 0 ? void 0 : paymasterServiceData.webhookData) !== null && _c !== void 0 ? _c : webhookData; try { const response = await (0, httpRequests_1.sendRequest)({ url: `${this.paymasterConfig.paymasterUrl}`, method: httpRequests_1.HttpMethod.Post, body: { method: "pm_getFeeQuoteOrData", params: [ userOp, { ...(mode !== null && { mode }), calculateGasLimits: calculateGasLimits, ...(expiryDuration !== null && { expiryDuration }), tokenInfo: { tokenList: feeTokensArray, ...(preferredToken !== null && { preferredToken }), }, sponsorshipInfo: { ...(webhookData !== null && { webhookData }), }, }, ], // As per current API id: (0, HelperFunction_1.getTimestampInSeconds)(), jsonrpc: "2.0", }, }); if (response && response.result) { if (response.result.mode == Types_1.PaymasterMode.ERC20) { const feeQuotesResponse = response.result.feeQuotes; const paymasterAddress = response.result.paymasterAddress; // check all objects iterate and populate below calculation for all tokens return { feeQuotes: feeQuotesResponse, tokenPaymasterAddress: paymasterAddress }; } else if (response.result.mode == Types_1.PaymasterMode.SPONSORED) { const paymasterAndData = response.result.paymasterAndData; const preVerificationGas = response.result.preVerificationGas; const verificationGasLimit = response.result.verificationGasLimit; const callGasLimit = response.result.callGasLimit; return { paymasterAndData: paymasterAndData, preVerificationGas: preVerificationGas, verificationGasLimit: verificationGasLimit, callGasLimit: callGasLimit, }; } else { const errorObject = { code: 417, message: "Expectation Failed: Invalid mode in Paymaster service response", }; throw errorObject; } } } catch (error) { console.error("Failed to fetch Fee Quotes or Paymaster data - reason: ", JSON.stringify(error)); // Note: we may not throw if we include strictMode off and return paymasterData '0x'. if (!this.paymasterConfig.strictMode && paymasterServiceData.mode == Types_1.PaymasterMode.SPONSORED && ((error === null || error === void 0 ? void 0 : error.message.includes("Smart contract data not found")) || (error === null || error === void 0 ? void 0 : error.message.includes("No policies were set"))) // can also check based on error.code being -32xxx ) { return { paymasterAndData: "0x", // send below values same as userOp gasLimits preVerificationGas: userOp.preVerificationGas, verificationGasLimit: userOp.verificationGasLimit, callGasLimit: userOp.callGasLimit, }; } throw error; } throw new Error("Failed to fetch feeQuote or paymaster data"); } /** * @dev Retrieves the paymaster and data based on the provided user operation and paymaster service data. * @param userOp The partial user operation. * @param paymasterServiceData Optional paymaster service data. * @returns A Promise that resolves to the paymaster and data string. */ async getPaymasterAndData(userOp, paymasterServiceData) { var _a, _b, _c; try { userOp = await this.prepareUserOperation(userOp); } catch (err) { throw err; } if ((paymasterServiceData === null || paymasterServiceData === void 0 ? void 0 : paymasterServiceData.mode) === undefined) { throw new Error("mode is required in paymasterServiceData"); } const mode = paymasterServiceData.mode; const calculateGasLimits = (_a = paymasterServiceData.calculateGasLimits) !== null && _a !== void 0 ? _a : true; let tokenInfo = null; let expiryDuration = null; let webhookData = null; if (mode === Types_1.PaymasterMode.ERC20) { if (!(paymasterServiceData === null || paymasterServiceData === void 0 ? void 0 : paymasterServiceData.feeTokenAddress) && (paymasterServiceData === null || paymasterServiceData === void 0 ? void 0 : paymasterServiceData.feeTokenAddress) === ethers_1.ethers.constants.AddressZero) { throw new Error("feeTokenAddress is required and should be non-zero"); } tokenInfo = { feeTokenAddress: paymasterServiceData.feeTokenAddress, }; } webhookData = (_b = paymasterServiceData === null || paymasterServiceData === void 0 ? void 0 : paymasterServiceData.webhookData) !== null && _b !== void 0 ? _b : webhookData; expiryDuration = (_c = paymasterServiceData === null || paymasterServiceData === void 0 ? void 0 : paymasterServiceData.expiryDuration) !== null && _c !== void 0 ? _c : expiryDuration; // Note: The idea is before calling this below rpc, userOp values presense and types should be in accordance with how we call eth_estimateUseropGas on the bundler let response; try { response = await (0, httpRequests_1.sendRequest)({ url: `${this.paymasterConfig.paymasterUrl}`, method: httpRequests_1.HttpMethod.Post, body: { method: "pm_sponsorUserOperation", params: [ userOp, { mode: mode, calculateGasLimits: calculateGasLimits, ...(expiryDuration !== null && { expiryDuration }), ...(tokenInfo !== null && { tokenInfo }), sponsorshipInfo: { ...(webhookData !== null && { webhookData }), }, }, ], id: (0, HelperFunction_1.getTimestampInSeconds)(), jsonrpc: "2.0", }, }); if (response && response.result) { const paymasterAndData = response.result.paymasterAndData; const preVerificationGas = response.result.preVerificationGas; const verificationGasLimit = response.result.verificationGasLimit; const callGasLimit = response.result.callGasLimit; return { paymasterAndData: paymasterAndData, preVerificationGas: preVerificationGas, verificationGasLimit: verificationGasLimit, callGasLimit: callGasLimit, }; } } catch (error) { console.error("Error in generating paymasterAndData - reason: ", JSON.stringify(error)); if (!this.paymasterConfig.strictMode && ((error === null || error === void 0 ? void 0 : error.message.includes("Smart contract data not found")) || (error === null || error === void 0 ? void 0 : error.message.includes("No policies were set"))) // can also check based on error.code being -32xxx ) { return { paymasterAndData: "0x", // send below values same as userOp gasLimits preVerificationGas: userOp.preVerificationGas, verificationGasLimit: userOp.verificationGasLimit, callGasLimit: userOp.callGasLimit, maxPriorityFeePerGas: userOp.maxPriorityFeePerGas, maxFeePerGas: userOp.maxFeePerGas, }; } // console.error("Failed to fetch paymasterAndData - reason: ", JSON.stringify(error)); throw error; } throw new PaymasterError(response.message, response.statusCode, response.result); } /** * * @param userOp user operation * @param paymasterServiceData optional extra information to be passed to paymaster service * @returns paymasterAndData with valid length but mock signature */ async getDummyPaymasterAndData() { return "0x"; } } exports.AbstraxnPaymaster = AbstraxnPaymaster; //# sourceMappingURL=AbstraxnPaymaster.js.map