diff --git a/packages/bundler/src/Bundler.ts b/packages/bundler/src/Bundler.ts index 3782d7066..2b6e25dda 100644 --- a/packages/bundler/src/Bundler.ts +++ b/packages/bundler/src/Bundler.ts @@ -4,7 +4,7 @@ import { IBundler } from "./interfaces/IBundler.js"; import { GetUserOperationReceiptResponse, GetUserOpByHashResponse, - Bundlerconfig, + BundlerConfig, UserOpResponse, EstimateUserOpGasResponse, UserOpReceipt, @@ -37,18 +37,29 @@ import { sendRequest, HttpMethod, StateOverrideSet } from "@biconomy/common"; export class Bundler implements IBundler { private bundlerConfig: BundlerConfigWithChainId; - // eslint-disable-next-line no-unused-vars + /** + * @description The polling interval per chain for the tx receipt in milliseconds. Default value is 5 seconds + */ UserOpReceiptIntervals!: { [key in number]?: number }; + /** + * @description The polling interval per chain for the tx result in milliseconds. Default value is 0.5 seconds + * */ UserOpWaitForTxHashIntervals!: { [key in number]?: number }; + /** + * @description The maximum duration in milliseconds per chain to wait for the tx receipt. Default value is 30 seconds + */ UserOpReceiptMaxDurationIntervals!: { [key in number]?: number }; + /** + * @description The maximum duration in milliseconds per chain to wait for the tx hash. Default value is 20 seconds + */ UserOpWaitForTxHashMaxDurationIntervals!: { [key in number]?: number }; private provider: PublicClient; - constructor(bundlerConfig: Bundlerconfig) { + constructor(bundlerConfig: BundlerConfig) { const parsedChainId: number = bundlerConfig?.chainId || extractChainIdFromBundlerUrl(bundlerConfig.bundlerUrl); this.bundlerConfig = { ...bundlerConfig, chainId: parsedChainId }; @@ -85,13 +96,44 @@ export class Bundler implements IBundler { } /** - * @param userOpHash - * @description This function will fetch gasPrices from bundler - * @returns Promise + * + * @description This function will estimate gasPrices from bundler + * It is expected that the userOp is already signed and the paymasterAndData is already provided by the caller + * + * @param userOpHash {@link UserOperationStruct} + * @param stateOverrideSet {@link StateOverrideSet} + * @returns Promise + * + * @example + * + * import { createBundler } from "@biconomy/bundler" + * import { createSmartAccountClient } from "@biconomy/smartaccount" + * + * const bundler = await createBundler({ + * bundlerUrl: "", // <-- Read about this at https://docs.biconomy.io/dashboard#bundler-url + * }); + * + * const smartWallet = await createSmartAccountClient({ + * signer, + * bundler + * }); + * + * const tx = { + * to: "0x000000D50C68705bd6897B2d17c7de32FB519fDA", + * data: "0x" + * }; + * + * const userOp = await smartWallet.buildUserOp([tx]); + * const { + * maxFeePerGas, + * maxPriorityFeePerGas, + * verificationGasLimit, + * callGasLimit, + * preVerificationGas, + * } = await bundler.estimateUserOpGas(userOp); + * */ async estimateUserOpGas(userOp: UserOperationStruct, stateOverrideSet?: StateOverrideSet): Promise { - // expected dummySig and possibly dummmy paymasterAndData should be provided by the caller - // bundler doesn't know account and paymaster implementation userOp = transformUserOP(userOp); const bundlerUrl = this.getBundlerUrl(); @@ -123,9 +165,36 @@ export class Bundler implements IBundler { /** * - * @param userOp - * @description This function will send signed userOp to bundler to get mined on chain - * @returns Promise + * Sends a user operation via the bundler + * + * @param userOp {@link UserOperationStruct} + * @param simulationParam {@link SimulationType} + * @description This function will take a user op as an input and send it to the bundler. + * @returns Promise<{@link UserOpResponse}> that you can use to track the user operation. + * + * @example + * import { createBundler } from "@biconomy/bundler" + * import { createSmartAccountClient } from "@biconomy/smartaccount" + * + * const bundler = await createBundler({ + * bundlerUrl: "", // <-- Read about this at https://docs.biconomy.io/dashboard#bundler-url + * }); + * + * const smartWallet = await createSmartAccountClient({ + * signer, + * bundler + * }); + * + * const tx = { + * to: "0x000000D50C68705bd6897B2d17c7de32FB519fDA", + * data: "0x" + * }; + * + * const userOp = await smartWallet.buildUserOp([tx]); + * + * const { wait } = await bundler.sendUserOp(userOp); + * const { success, receipt } = await wait(); + * */ async sendUserOp(userOp: UserOperationStruct, simulationParam?: SimulationType): Promise { const chainId = this.bundlerConfig.chainId; @@ -238,7 +307,7 @@ export class Bundler implements IBundler { * * @param userOpHash * @description This function will return userOpReceipt for a given userOpHash - * @returns Promise + * @returns Promise<{@link UserOpReceipt}> */ async getUserOpReceipt(userOpHash: string): Promise { const bundlerUrl = this.getBundlerUrl(); @@ -260,10 +329,10 @@ export class Bundler implements IBundler { } /** + * @description This function will return userOp status for a given hash * * @param userOpHash - * @description This function will return userOpReceipt for a given userOpHash - * @returns Promise + * @returns Promise<{@link UserOpStatus}> */ async getUserOpStatus(userOpHash: string): Promise { const bundlerUrl = this.getBundlerUrl(); @@ -286,9 +355,20 @@ export class Bundler implements IBundler { /** * - * @param userOpHash * @description this function will return UserOpByHashResponse for given UserOpHash - * @returns Promise + * + * @param userOpHash hash of the user operation + * @returns Promise<{@link UserOpByHashResponse}> that you can use to track the user operation. + * + * @example + * import { createBundler } from "@biconomy/bundler" + * + * const bundler = await createBundler({ + * bundlerUrl: "", // <-- Read about this at https://docs.biconomy.io/dashboard#bundler-url + * }); + * + * const { transactionHash } = await bundler.getUserOpByHash("0x..."); + * */ async getUserOpByHash(userOpHash: string): Promise { const bundlerUrl = this.getBundlerUrl(); @@ -311,6 +391,7 @@ export class Bundler implements IBundler { /** * @description This function will return the gas fee values + * @returns Promise<{@link GasFeeValues}> */ async getGasFeeValues(): Promise { const bundlerUrl = this.getBundlerUrl(); @@ -330,7 +411,7 @@ export class Bundler implements IBundler { return response.result; } - public static async create(config: Bundlerconfig): Promise { + public static async create(config: BundlerConfig): Promise { return new Bundler(config); } } diff --git a/packages/bundler/src/utils/Constants.ts b/packages/bundler/src/utils/Constants.ts index 8ffd436a5..38957555b 100644 --- a/packages/bundler/src/utils/Constants.ts +++ b/packages/bundler/src/utils/Constants.ts @@ -1,13 +1,20 @@ +/** + * @description The polling interval per chain for the tx receipt in milliseconds. Default value is 5 seconds +*/ export const UserOpReceiptIntervals: { [key in number]?: number } = { [1]: 10000, }; -// Note: Default value is 500(0.5sec) +/** + * @description The polling interval per chain for the tx hash in milliseconds. Default value is 0.5 seconds + */ export const UserOpWaitForTxHashIntervals: { [key in number]?: number } = { [1]: 1000, }; -// Note: Default value is 30000 (30sec) +/** + * @description The maximum duration in milliseconds per chain to wait for the tx receipt. Default value is 30 seconds + */ export const UserOpReceiptMaxDurationIntervals: { [key in number]?: number } = { [1]: 300000, [80001]: 50000, @@ -19,7 +26,9 @@ export const UserOpReceiptMaxDurationIntervals: { [key in number]?: number } = { [59140]: 50000, // linea testnet }; -// Note: Default value is 20000 (20sec) +/** + * @description The maximum duration in milliseconds per chain to wait for the tx hash. Default value is 20 seconds + */ export const UserOpWaitForTxHashMaxDurationIntervals: { [key in number]?: number } = { [1]: 20000, }; diff --git a/packages/bundler/src/utils/Types.ts b/packages/bundler/src/utils/Types.ts index 1da57281f..e69fd6b94 100644 --- a/packages/bundler/src/utils/Types.ts +++ b/packages/bundler/src/utils/Types.ts @@ -1,19 +1,25 @@ -import { UserOperationStruct } from "@alchemy/aa-core"; +import { UserOperationStruct, UserOperationReceipt } from "@alchemy/aa-core"; import { Chain, Hex } from "viem"; -export type Bundlerconfig = { +export type BundlerConfig = { + /** The URL of the bundler service */ bundlerUrl: string; + /** entryPointAddress: address of the entry point */ entryPointAddress?: string; + /** the network id */ chainId?: number; - // eslint-disable-next-line no-unused-vars + /** The polling interval per chain for the tx receipt in milliseconds. Default value is 5 seconds */ userOpReceiptIntervals?: { [key in number]?: number }; + /** The polling interval per chain for the tx result in milliseconds. Default value is 0.5 seconds */ userOpWaitForTxHashIntervals?: { [key in number]?: number }; + /** The maximum duration in milliseconds per chain to wait for the tx receipt. Default value is 30 seconds */ userOpReceiptMaxDurationIntervals?: { [key in number]?: number }; + /** The maximum duration in milliseconds per chain to wait for the tx hash. Default value is 20 seconds */ userOpWaitForTxHashMaxDurationIntervals?: { [key in number]?: number }; /** Can be used to optionally override the chain with a custom chain if it doesn't already exist in viems list of supported chains */ viemChain?: Chain; }; -export type BundlerConfigWithChainId = Bundlerconfig & { chainId: number }; +export type BundlerConfigWithChainId = BundlerConfig & { chainId: number }; export type UserOpReceipt = { /* The request hash of the UserOperation. */ @@ -31,9 +37,9 @@ export type UserOpReceipt = { /* In case of revert, this is the revert reason. */ reason: string; /* The logs generated by this UserOperation (not including logs of other UserOperations in the same bundle). */ - logs: Array; // The logs generated by this UserOperation (not including logs of other UserOperations in the same bundle) + logs: Array; /* The TransactionReceipt object for the entire bundle, not only for this UserOperation. */ - receipt: any; + receipt: UserOperationReceipt["receipt"]; }; // review @@ -43,28 +49,42 @@ export type UserOpStatus = { userOperationReceipt?: UserOpReceipt; }; +/** + * @description SimulationType + * "validation_and_execution" is recommended during development for improved debugging & devEx, but will add some additional latency to calls. + * "validation" can be used in production mode to remove this latency after flows have been tested. + */ export type SimulationType = "validation" | "validation_and_execution"; // Converted to JsonRpcResponse with strict type export type GetUserOperationReceiptResponse = { + /** The JSON-RPC url */ jsonrpc: string; + /** Request id */ id: number; result: UserOpReceipt; + /** The error if the request failed */ error?: JsonRpcError; }; export type GetUserOperationStatusResponse = { + /** The JSON-RPC url */ jsonrpc: string; + /** Request id */ id: number; result: UserOpStatus; + /** The error if the request failed */ error?: JsonRpcError; }; // Converted to JsonRpcResponse with strict type export type SendUserOpResponse = { + /** The JSON-RPC url */ jsonrpc: string; + /** Request id */ id: number; result: string; + /** The error if the request failed */ error?: JsonRpcError; }; @@ -77,17 +97,26 @@ export type UserOpResponse = { // Converted to JsonRpcResponse with strict type export type EstimateUserOpGasResponse = { + /** The JSON-RPC url */ jsonrpc: string; + /** Request id */ id: number; + /** The result of the estimate gas request */ result: UserOpGasResponse; + /** The error if the request failed */ error?: JsonRpcError; }; export type UserOpGasResponse = { + /* The amount of gas to pay to compensate the bundler for pre-verification execution and calldata */ preVerificationGas: string; + /*The amount of gas to allocate for the verification step. */ verificationGasLimit: string; + /* The amount of gas to allocate for the main execution call */ callGasLimit: string; + /* Maximum priority fee per gas (similar to EIP-1559 max_priority_fee_per_gas) */ maxPriorityFeePerGas: string; + /* Maximum fee per gas (similar to EIP-1559 max_fee_per_gas) */ maxFeePerGas: string; }; @@ -100,9 +129,13 @@ export type GetUserOpByHashResponse = { }; export type UserOpByHashResponse = UserOperationStruct & { + /** The transaction hash of the UserOperation. */ transactionHash: string; + /** The block number of the UserOperation. */ blockNumber: number; + /** The block hash of the UserOperation. */ blockHash: string; + /** The entrypoint used */ entryPoint: string; }; /* eslint-disable @typescript-eslint/no-explicit-any */ diff --git a/packages/paymaster/src/BiconomyPaymaster.ts b/packages/paymaster/src/BiconomyPaymaster.ts index aa0af3f86..003fd42c2 100644 --- a/packages/paymaster/src/BiconomyPaymaster.ts +++ b/packages/paymaster/src/BiconomyPaymaster.ts @@ -20,11 +20,9 @@ import { getTimestampInSeconds } from "./utils/Helpers.js"; const defaultPaymasterConfig: PaymasterConfig = { paymasterUrl: "", - strictMode: false, // Set your desired default value for strictMode here + strictMode: false, }; -/** - * @dev Hybrid - Generic Gas Abstraction paymaster - */ + export class BiconomyPaymaster implements IHybridPaymaster { paymasterConfig: PaymasterConfig; @@ -37,9 +35,9 @@ export class BiconomyPaymaster implements IHybridPaymaster The partial user operation. + * @returns Promise> - A Promise that resolves to the prepared partial user operation. */ private async prepareUserOperation(userOp: Partial): Promise> { const userOperation = { ...userOp }; @@ -65,23 +63,16 @@ export class BiconomyPaymaster implements IHybridPaymaster - A Promise that resolves to the built transaction object. */ async buildTokenApprovalTransaction(tokenPaymasterRequest: BiconomyTokenPaymasterRequest): Promise { const feeTokenAddress: string = tokenPaymasterRequest.feeQuote.tokenAddress; const spender = tokenPaymasterRequest.spender; - // logging provider object isProvider - // Logger.log("provider object passed - is provider", provider?._isProvider); - - // 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 = BigInt(0); if (tokenPaymasterRequest.maxApproval && tokenPaymasterRequest.maxApproval == true) { @@ -98,19 +89,6 @@ export class BiconomyPaymaster implements IHybridPaymaster - A Promise that resolves to the fee quotes or data response. */ async getPaymasterFeeQuotesOrData( userOp: Partial, @@ -241,10 +219,12 @@ export class BiconomyPaymaster implements IHybridPaymaster, @@ -332,8 +312,8 @@ export class BiconomyPaymaster implements IHybridPaymaster + */ public static async create(config: PaymasterConfig): Promise { return new BiconomyPaymaster(config); } diff --git a/packages/paymaster/src/utils/Types.ts b/packages/paymaster/src/utils/Types.ts index 9d5f849d4..70d9fdc21 100644 --- a/packages/paymaster/src/utils/Types.ts +++ b/packages/paymaster/src/utils/Types.ts @@ -21,7 +21,9 @@ export type JsonRpcError = { }; export type PaymasterConfig = { + /** Read about at https://docs.biconomy.io/dashboard/paymaster */ paymasterUrl: string; + /** Whether or not to enforce abiding by the paymaster policies */ strictMode?: boolean; }; diff --git a/yarn.lock b/yarn.lock index a4e0e28a9..093a5c8a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2111,9 +2111,9 @@ integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== "@types/node@*", "@types/node@^20.11.10": - version "20.11.26" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.26.tgz#3fbda536e51d5c79281e1d9657dcb0131baabd2d" - integrity sha512-YwOMmyhNnAWijOBQweOJnQPl068Oqd4K3OFbTc6AHJwzweUwwWG3GIFY74OKks2PJUDkQPeddOQES9mLn1CTEQ== + version "20.11.27" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.27.tgz#debe5cfc8a507dd60fe2a3b4875b1604f215c2ac" + integrity sha512-qyUZfMnCg1KEz57r7pzFtSGt49f6RPkPBis3Vo4PbS7roQEDn22hiHzl/Lo1q4i4hDEgBJmBF/NTNg2XR0HbFg== dependencies: undici-types "~5.26.4" @@ -3765,9 +3765,9 @@ ejs@^3.1.7: jake "^10.8.5" electron-to-chromium@^1.4.668: - version "1.4.701" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.701.tgz#7335e5761331774b4dea54cd24a1b84861d45cdf" - integrity sha512-K3WPQ36bUOtXg/1+69bFlFOvdSm0/0bGqmsfPDLRXLanoKXdA+pIWuf/VbA9b+2CwBFuONgl4NEz4OEm+OJOKA== + version "1.4.703" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.703.tgz#786ab0c8cfe548b9da03890f923e69b1ae522741" + integrity sha512-094ZZC4nHXPKl/OwPinSMtLN9+hoFkdfQGKnvXbY+3WEAYtVDpz9UhJIViiY6Zb8agvqxiaJzNG9M+pRZWvSZw== elliptic@6.5.4: version "6.5.4" @@ -9324,9 +9324,9 @@ validate-npm-package-name@^3.0.0: builtins "^1.0.3" viem@^2.7.12, viem@^2.7.8: - version "2.8.4" - resolved "https://registry.yarnpkg.com/viem/-/viem-2.8.4.tgz#553996181d2d22237e31476218178d3fe96e2e3d" - integrity sha512-zBC8+YNKzo+XeUsCyXdrFzimH9Ei/nRfUKldPmVRoR/lR56/sqkDPyfCE1yvzwwmA9AJ9m9m2HtSPgl9NiTReA== + version "2.8.5" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.8.5.tgz#c0120e7f228fbb5be760c625bb2637c7cfa1b8df" + integrity sha512-EAmZ/jP7Ycmu72zRrB+N/fINEXMVBaLLsUaHwu5LoIjAzmE/f/33RV9AlhUA6RNZ5kujomylSytIyDakzQYXCQ== dependencies: "@adraffy/ens-normalize" "1.10.0" "@noble/curves" "1.2.0"