diff --git a/package.json b/package.json index 6b6de2028..3d0a83295 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "test": "concurrently -k --success first 'yarn start:ganache' 'yarn test:run'", "start:ganache": "ganache -m 'direct buyer cliff train rice spirit census refuse glare expire innocent quote'", "test:ci": "FORCE_COLOR=1 lerna run test:ci --stream --npm-client=yarn", - "test:coverage": "yarn jest --coverage", + "test:coverage": "concurrently -k --success first 'yarn start:ganache' 'yarn jest --runInBand --coverage'", "diff": "lerna diff", "release": "lerna version patch --no-git-tag-version --no-push --conventional-commits --yes" }, @@ -53,10 +53,12 @@ ] }, "dependencies": { + "ganache": "^7.9.1", "node-gyp": "^9.4.0", "typescript": "^5.2.2" }, "devDependencies": { + "@types/debug": "^4.1.9", "@types/jest": "^29.5.4", "@typescript-eslint/eslint-plugin": "^6.7.0", "@typescript-eslint/parser": "^6.6.0", diff --git a/packages/account/src/BaseSmartAccount.ts b/packages/account/src/BaseSmartAccount.ts index fd2d62f4e..eb0974031 100644 --- a/packages/account/src/BaseSmartAccount.ts +++ b/packages/account/src/BaseSmartAccount.ts @@ -7,11 +7,12 @@ import { calcPreVerificationGas, DefaultGasLimits } from "./utils/Preverificaito import { NotPromise, packUserOp, Logger, RPC_PROVIDER_URLS } from "@biconomy/common"; import { IBundler, UserOpResponse } from "@biconomy/bundler"; import { IPaymaster, PaymasterAndDataResponse } from "@biconomy/paymaster"; +import { SponsorUserOperationDto, BiconomyPaymaster, PaymasterMode, IHybridPaymaster } from "@biconomy/paymaster"; import { BaseSmartAccountConfig, Overrides, SendUserOpOptions, TransactionDetailsForUserOp } from "./utils/Types"; import { GasOverheads } from "./utils/Preverificaiton"; import { EntryPoint, EntryPoint__factory } from "@account-abstraction/contracts"; import { DEFAULT_ENTRYPOINT_ADDRESS } from "./utils/Constants"; -import { LRUCache } from 'lru-cache' +import { LRUCache } from "lru-cache"; type UserOperationKey = keyof UserOperation; @@ -71,7 +72,7 @@ export abstract class BaseSmartAccount implements IBaseSmartAccount { validateUserOp(userOp: Partial, requiredFields: UserOperationKey[]): boolean { for (const field of requiredFields) { if (!userOp[field]) { - throw new Error(`${field} is missing`); + throw new Error(`${String(field)} is missing in the UserOp`); } } return true; @@ -205,10 +206,24 @@ export abstract class BaseSmartAccount implements IBaseSmartAccount { async calculateUserOpGasValues(userOp: Partial): Promise> { if (!this.provider) throw new Error("Provider is not present for making rpc calls"); - const feeData = await this.provider.getFeeData(); - userOp.maxFeePerGas = userOp.maxFeePerGas ?? feeData.maxFeePerGas ?? feeData.gasPrice ?? (await this.provider.getGasPrice()); - userOp.maxPriorityFeePerGas = - userOp.maxPriorityFeePerGas ?? feeData.maxPriorityFeePerGas ?? feeData.gasPrice ?? (await this.provider.getGasPrice()); + 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 = feeData?.maxFeePerGas ?? feeData?.gasPrice ?? (await this.provider.getGasPrice()); + } + + if (userOp.maxPriorityFeePerGas === undefined || userOp.maxPriorityFeePerGas === null) { + userOp.maxPriorityFeePerGas = feeData?.maxPriorityFeePerGas ?? feeData?.gasPrice ?? (await this.provider.getGasPrice()); + } if (userOp.initCode) userOp.verificationGasLimit = userOp.verificationGasLimit ?? (await this.getVerificationGasLimit(userOp.initCode)); userOp.callGasLimit = userOp.callGasLimit ?? @@ -221,16 +236,18 @@ export abstract class BaseSmartAccount implements IBaseSmartAccount { return userOp; } + // TODO // Should make this a Dto async estimateUserOpGas( userOp: Partial, overrides?: Overrides, skipBundlerGasEstimation?: boolean, + paymasterServiceData?: SponsorUserOperationDto, ): Promise> { const requiredFields: UserOperationKey[] = ["sender", "nonce", "initCode", "callData"]; this.validateUserOp(userOp, requiredFields); let finalUserOp = userOp; - const skipBundlerCall = skipBundlerGasEstimation ?? false; + const skipBundlerCall = skipBundlerGasEstimation ?? true; // Override gas values in userOp if provided in overrides params if (overrides) { userOp = { ...userOp, ...overrides }; @@ -238,12 +255,29 @@ export abstract class BaseSmartAccount implements IBaseSmartAccount { Logger.log("userOp in estimation", userOp); - if (!this.bundler || skipBundlerCall) { - if (!this.provider) throw new Error("Provider is not present for making rpc calls"); - // if no bundler url is provided run offchain logic to assign following values of UserOp - // maxFeePerGas, maxPriorityFeePerGas, verificationGasLimit, callGasLimit, preVerificationGas - finalUserOp = await this.calculateUserOpGasValues(userOp); + if (skipBundlerCall) { + // Review: instead of checking mode it could be assumed or just pass gasless flag and use it + // make pmService data locally and pass the object with default values + if (this.paymaster && this.paymaster instanceof BiconomyPaymaster && paymasterServiceData?.mode === PaymasterMode.SPONSORED) { + if (!userOp.maxFeePerGas && !userOp.maxPriorityFeePerGas) { + throw new Error("maxFeePerGas and maxPriorityFeePerGas are required for skipBundlerCall mode"); + } + // Making call to paymaster to get gas estimations for userOp + const { callGasLimit, verificationGasLimit, preVerificationGas, paymasterAndData } = await ( + this.paymaster as IHybridPaymaster + ).getPaymasterAndData(userOp, paymasterServiceData); + finalUserOp.verificationGasLimit = verificationGasLimit ?? userOp.verificationGasLimit; + finalUserOp.callGasLimit = callGasLimit ?? userOp.callGasLimit; + finalUserOp.preVerificationGas = preVerificationGas ?? userOp.preVerificationGas; + finalUserOp.paymasterAndData = paymasterAndData ?? userOp.paymasterAndData; + } else { + Logger.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"); + // TODO: is this still needed to delete? delete userOp.maxFeePerGas; delete userOp.maxPriorityFeePerGas; // Making call to bundler to get gas estimations for userOp @@ -261,6 +295,7 @@ export abstract class BaseSmartAccount implements IBaseSmartAccount { finalUserOp.verificationGasLimit = verificationGasLimit ?? userOp.verificationGasLimit; finalUserOp.callGasLimit = callGasLimit ?? userOp.callGasLimit; finalUserOp.preVerificationGas = preVerificationGas ?? userOp.preVerificationGas; + finalUserOp.paymasterAndData = "0x"; } return finalUserOp; } diff --git a/packages/account/src/BiconomySmartAccount.ts b/packages/account/src/BiconomySmartAccount.ts index 2420f9017..40504b2b6 100644 --- a/packages/account/src/BiconomySmartAccount.ts +++ b/packages/account/src/BiconomySmartAccount.ts @@ -256,25 +256,59 @@ export class BiconomySmartAccount extends SmartAccount implements IBiconomySmart return "0x"; } - async buildUserOp(transactions: Transaction[], overrides?: Overrides, skipBundlerGasEstimation?: boolean): Promise> { + private async getNonce(): Promise { + let nonce = BigNumber.from(0); + try { + nonce = await this.nonce(); + } catch (error) { + // Not throwing this error as nonce would be 0 if this.nonce() throw exception, which is expected flow for undeployed account + Logger.log("Error while getting nonce for the account. This is expected for undeployed accounts set nonce to 0"); + } + return nonce; + } + + private async getGasFeeValues( + overrides: Overrides | undefined, + skipBundlerGasEstimation: boolean | undefined, + ): Promise<{ maxFeePerGas?: BigNumberish | undefined; maxPriorityFeePerGas?: BigNumberish | undefined }> { + const gasFeeValues = { + maxFeePerGas: overrides?.maxFeePerGas, + maxPriorityFeePerGas: overrides?.maxPriorityFeePerGas, + }; + try { + if (this.bundler && !gasFeeValues.maxFeePerGas && !gasFeeValues.maxPriorityFeePerGas && (skipBundlerGasEstimation ?? true)) { + const gasFeeEstimation = await this.bundler.getGasFeeValues(); + gasFeeValues.maxFeePerGas = gasFeeEstimation.maxFeePerGas; + gasFeeValues.maxPriorityFeePerGas = gasFeeEstimation.maxPriorityFeePerGas; + } + return gasFeeValues; + } catch (error: any) { + Logger.error("Error while getting gasFeeValues from bundler. Provided bundler might not have getGasFeeValues endpoint", error); + return gasFeeValues; + } + } + + async buildUserOp( + transactions: Transaction[], + overrides?: Overrides, + skipBundlerGasEstimation?: boolean, + paymasterServiceData?: SponsorUserOperationDto, + ): Promise> { this.isInitialized(); const to = transactions.map((element: Transaction) => element.to); const data = transactions.map((element: Transaction) => element.data ?? "0x"); const value = transactions.map((element: Transaction) => element.value ?? BigNumber.from("0")); this.isProxyDefined(); + const [nonce, gasFeeValues] = await Promise.all([this.getNonce(), this.getGasFeeValues(overrides, skipBundlerGasEstimation)]); + let callData = ""; if (transactions.length === 1) { callData = this.getExecuteCallData(to[0], value[0], data[0]); } else { callData = this.getExecuteBatchCallData(to, value, data); } - let nonce = BigNumber.from(0); - try { - nonce = await this.nonce(); - } catch (error) { - // Not throwing this error as nonce would be 0 if this.nonce() throw exception, which is expected flow for undeployed account - } + let isDeployed = true; if (nonce.eq(0)) { @@ -286,16 +320,14 @@ export class BiconomySmartAccount extends SmartAccount implements IBiconomySmart nonce, initCode: !isDeployed ? this.initCode : "0x", callData: callData, + maxFeePerGas: gasFeeValues.maxFeePerGas || undefined, + maxPriorityFeePerGas: gasFeeValues.maxPriorityFeePerGas || undefined, + signature: this.getDummySignature(), }; - // for this Smart Account dummy ECDSA signature will be used to estimate gas - userOp.signature = this.getDummySignature(); - - userOp = await this.estimateUserOpGas(userOp, overrides, skipBundlerGasEstimation); - Logger.log("userOp after estimation ", userOp); - - // Do not populate paymasterAndData as part of buildUserOp as it may not have all necessary details - userOp.paymasterAndData = "0x"; // await this.getPaymasterAndData(userOp) + // Note: Can change the default behaviour of calling estimations using bundler/local + userOp = await this.estimateUserOpGas(userOp, overrides, skipBundlerGasEstimation, paymasterServiceData); + Logger.log("UserOp after estimation ", userOp); return userOp; } @@ -394,36 +426,11 @@ export class BiconomySmartAccount extends SmartAccount implements IBiconomySmart newCallData = this.getExecuteBatchCallData(batchTo, batchValue, batchData); } - let finalUserOp: Partial = { + const finalUserOp: Partial = { ...userOp, callData: newCallData, }; - // Requesting to update gas limits again (especially callGasLimit needs to be re-calculated) - try { - delete finalUserOp.callGasLimit; - delete finalUserOp.verificationGasLimit; - delete finalUserOp.preVerificationGas; - - // Maybe send paymasterAndData since we know it's for Token paymaster - /*finalUserOp.paymasterAndData = - '0x00000f7365ca6c59a2c93719ad53d567ed49c14c000000000000000000000000000000000000000000000000000000000064e3d3890000000000000000000000000000000000000000000000000000000064e3cc81000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e583100000000000000000000000000000f7748595e46527413574a9327942e744e910000000000000000000000000000000000000000000000000000000063ac7f6c000000000000000000000000000000000000000000000000000000000010c8e07bf61410b71700f943499adfd23e50fb16040d587acb0a5e60ac8576cdbb4c8044f00579a1fc3f294e7dc4a5eb557a7193008343aa36225bddcfbd4fd15646031c'*/ - - // Review: and handle the case when mock pnd fails with AA31 during simulation. - - finalUserOp = await this.estimateUserOpGas(finalUserOp); - const cgl = ethers.BigNumber.from(finalUserOp.callGasLimit); - if (finalUserOp.callGasLimit && cgl.lt(ethers.BigNumber.from("21000"))) { - return { - ...userOp, - callData: newCallData, - }; - } - Logger.log("userOp after estimation ", finalUserOp); - } catch (error) { - Logger.error("Failed to estimate gas for userOp with updated callData ", error); - Logger.log("sending updated userOp. calculateGasLimit flag should be sent to the paymaster to be able to update callGasLimit"); - } return finalUserOp; } } catch (error) { diff --git a/packages/account/src/BiconomySmartAccountV2.ts b/packages/account/src/BiconomySmartAccountV2.ts index 5a8889c6b..ed2a6143e 100644 --- a/packages/account/src/BiconomySmartAccountV2.ts +++ b/packages/account/src/BiconomySmartAccountV2.ts @@ -16,6 +16,8 @@ import { CounterFactualAddressParam, BuildUserOpOptions, SendUserOpOptions, + Overrides, + NonceOptions, } from "./utils/Types"; import { BaseValidationModule, ModuleInfo, SendUserOpParams } from "@biconomy/modules"; import { UserOperation, Transaction } from "@biconomy/core-types"; @@ -99,6 +101,7 @@ export class BiconomySmartAccountV2 extends BaseSmartAccount { instance.nodeClient = new NodeClient({ txServiceUrl: nodeClientUrl ?? NODE_CLIENT_URL }); await instance.init(); + return instance; } @@ -314,24 +317,61 @@ export class BiconomySmartAccountV2 extends BaseSmartAccount { return bundlerResponse; } + private async getBuildUserOpNonce(nonceOptions: NonceOptions | undefined): Promise { + let nonce = BigNumber.from(0); + try { + if (nonceOptions?.nonceOverride) { + nonce = BigNumber.from(nonceOptions?.nonceOverride); + } else { + const _nonceSpace = nonceOptions?.nonceKey ?? 0; + nonce = await this.getNonce(_nonceSpace); + } + } catch (error) { + // Not throwing this error as nonce would be 0 if this.getNonce() throw exception, which is expected flow for undeployed account + Logger.log("Error while getting nonce for the account. This is expected for undeployed accounts set nonce to 0"); + } + return nonce; + } + + private async getGasFeeValues( + overrides: Overrides | undefined, + skipBundlerGasEstimation: boolean | undefined, + ): Promise<{ maxFeePerGas?: BigNumberish | undefined; maxPriorityFeePerGas?: BigNumberish | undefined }> { + const gasFeeValues = { + maxFeePerGas: overrides?.maxFeePerGas, + maxPriorityFeePerGas: overrides?.maxPriorityFeePerGas, + }; + try { + if (this.bundler && !gasFeeValues.maxFeePerGas && !gasFeeValues.maxPriorityFeePerGas && (skipBundlerGasEstimation ?? true)) { + const gasFeeEstimation = await this.bundler.getGasFeeValues(); + gasFeeValues.maxFeePerGas = gasFeeEstimation.maxFeePerGas; + gasFeeValues.maxPriorityFeePerGas = gasFeeEstimation.maxPriorityFeePerGas; + } + return gasFeeValues; + } catch (error: any) { + Logger.error("Error while getting gasFeeValues from bundler. Provided bundler might not have getGasFeeValues endpoint", error); + return gasFeeValues; + } + } + async buildUserOp(transactions: Transaction[], buildUseropDto?: BuildUserOpOptions): Promise> { const to = transactions.map((element: Transaction) => element.to); const data = transactions.map((element: Transaction) => element.data ?? "0x"); const value = transactions.map((element: Transaction) => element.value ?? BigNumber.from("0")); - // Queue promises to fetch independent data. - const nonceFetchPromise = (async () => { - const _nonceSpace = buildUseropDto?.nonceOptions?.nonceKey ?? 0; - const nonce = await this.getNonce(_nonceSpace); - return nonce; - })(); const initCodeFetchPromise = this.getInitCode(); const dummySignatureFetchPromise = this.getDummySignature(buildUseropDto?.params); + const [nonceFromFetch, initCode, signature, finalGasFeeValue] = await Promise.all([ + this.getBuildUserOpNonce(buildUseropDto?.nonceOptions), + initCodeFetchPromise, + dummySignatureFetchPromise, + this.getGasFeeValues(buildUseropDto?.overrides, buildUseropDto?.skipBundlerGasEstimation), + ]); + if (transactions.length === 0) { throw new Error("Transactions array cannot be empty"); } - let callData = ""; if (transactions.length > 1 || buildUseropDto?.forceEncodeForBatch) { callData = await this.encodeExecuteBatch(to, value, data); @@ -340,34 +380,27 @@ export class BiconomySmartAccountV2 extends BaseSmartAccount { callData = await this.encodeExecute(to[0], value[0], data[0]); } - let nonce = BigNumber.from(0); - try { - if (buildUseropDto?.nonceOptions?.nonceOverride) { - nonce = BigNumber.from(buildUseropDto?.nonceOptions?.nonceOverride); - } else { - nonce = await nonceFetchPromise; - } - } catch (error) { - // Not throwing this error as nonce would be 0 if this.getNonce() throw exception, which is expected flow for undeployed account - } - let userOp: Partial = { sender: await this.getAccountAddress(), - nonce, - initCode: await initCodeFetchPromise, + nonce: nonceFromFetch, + initCode, callData: callData, + maxFeePerGas: finalGasFeeValue.maxFeePerGas || undefined, + maxPriorityFeePerGas: finalGasFeeValue.maxPriorityFeePerGas || undefined, }; // for this Smart Account current validation module dummy signature will be used to estimate gas - userOp.signature = await dummySignatureFetchPromise; + userOp.signature = signature; // Note: Can change the default behaviour of calling estimations using bundler/local - userOp = await this.estimateUserOpGas(userOp, buildUseropDto?.overrides, buildUseropDto?.skipBundlerGasEstimation); + userOp = await this.estimateUserOpGas( + userOp, + buildUseropDto?.overrides, + buildUseropDto?.skipBundlerGasEstimation, + buildUseropDto?.paymasterServiceData, + ); Logger.log("UserOp after estimation ", userOp); - // Do not populate paymasterAndData as part of buildUserOp as it may not have all necessary details - userOp.paymasterAndData = "0x"; // await this.getPaymasterAndData(userOp) - return userOp; } @@ -466,26 +499,11 @@ export class BiconomySmartAccountV2 extends BaseSmartAccount { newCallData = await this.encodeExecuteBatch(batchTo, batchValue, batchData); } - let finalUserOp: Partial = { + const finalUserOp: Partial = { ...userOp, callData: newCallData, }; - // Requesting to update gas limits again (especially callGasLimit needs to be re-calculated) - try { - finalUserOp = await this.estimateUserOpGas(finalUserOp); - const callGasLimit = ethers.BigNumber.from(finalUserOp.callGasLimit); - if (finalUserOp.callGasLimit && callGasLimit.lt(ethers.BigNumber.from("21000"))) { - return { - ...userOp, - callData: newCallData, - }; - } - Logger.log("UserOp after estimation ", finalUserOp); - } catch (error) { - Logger.error("Failed to estimate gas for userOp with updated callData ", error); - Logger.log("Sending updated userOp. calculateGasLimit flag should be sent to the paymaster to be able to update callGasLimit"); - } return finalUserOp; } } catch (error) { diff --git a/packages/account/src/SmartAccount.ts b/packages/account/src/SmartAccount.ts index 065d1f52e..6b52b2539 100644 --- a/packages/account/src/SmartAccount.ts +++ b/packages/account/src/SmartAccount.ts @@ -10,6 +10,7 @@ import { IBundler, UserOpResponse } from "@biconomy/bundler"; import { IPaymaster, PaymasterAndDataResponse } from "@biconomy/paymaster"; import { Logger } from "@biconomy/common"; import { IEntryPoint } from "@account-abstraction/contracts"; +import { SponsorUserOperationDto, BiconomyPaymaster, IHybridPaymaster, PaymasterMode } from "@biconomy/paymaster"; import { SmartAccountConfig, Overrides, SendUserOpDto } from "./utils/Types"; type UserOperationKey = keyof UserOperation; @@ -48,7 +49,7 @@ export abstract class SmartAccount implements ISmartAccount { private validateUserOp(userOp: Partial, requiredFields: UserOperationKey[]): boolean { for (const field of requiredFields) { if (!userOp[field]) { - throw new Error(`${field} is missing`); + throw new Error(`${String(field)} is missing in the UserOp`); } } return true; @@ -102,12 +103,13 @@ export abstract class SmartAccount implements ISmartAccount { userOp: Partial, overrides?: Overrides, skipBundlerGasEstimation?: boolean, + paymasterServiceData?: SponsorUserOperationDto, ): Promise> { const requiredFields: UserOperationKey[] = ["sender", "nonce", "initCode", "callData"]; this.validateUserOp(userOp, requiredFields); let finalUserOp = userOp; - const skipBundlerCall = skipBundlerGasEstimation ?? false; + const skipBundlerCall = skipBundlerGasEstimation ?? true; // Override gas values in userOp if provided in overrides params if (overrides) { userOp = { ...userOp, ...overrides }; @@ -115,17 +117,35 @@ export abstract class SmartAccount implements ISmartAccount { Logger.log("userOp in estimation", userOp); - if (!this.bundler || skipBundlerCall) { - if (!this.provider) throw new Error("Provider is not present for making rpc calls"); - // if no bundler url is provided run offchain logic to assign following values of UserOp - // maxFeePerGas, maxPriorityFeePerGas, verificationGasLimit, callGasLimit, preVerificationGas - finalUserOp = await this.calculateUserOpGasValues(userOp); + if (skipBundlerCall) { + // Review: instead of checking mode it could be assumed or just pass gasless flag and use it + // make pmService data locally and pass the object with default values + if (this.paymaster && this.paymaster instanceof BiconomyPaymaster && paymasterServiceData?.mode === PaymasterMode.SPONSORED) { + if (!userOp.maxFeePerGas && !userOp.maxPriorityFeePerGas) { + throw new Error("maxFeePerGas and maxPriorityFeePerGas are required for skipBundlerCall mode"); + } + // Making call to paymaster to get gas estimations for userOp + const { callGasLimit, verificationGasLimit, preVerificationGas, paymasterAndData } = await ( + this.paymaster as IHybridPaymaster + ).getPaymasterAndData(userOp, paymasterServiceData); + finalUserOp.verificationGasLimit = verificationGasLimit ?? userOp.verificationGasLimit; + finalUserOp.callGasLimit = callGasLimit ?? userOp.callGasLimit; + finalUserOp.preVerificationGas = preVerificationGas ?? userOp.preVerificationGas; + finalUserOp.paymasterAndData = paymasterAndData ?? userOp.paymasterAndData; + } else { + Logger.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"); + // TODO: is this still needed to delete? delete userOp.maxFeePerGas; delete userOp.maxPriorityFeePerGas; // Making 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 provider if (!userOp.maxFeePerGas && !userOp.maxPriorityFeePerGas && (!maxFeePerGas || !maxPriorityFeePerGas)) { const feeData = await this.provider.getFeeData(); finalUserOp.maxFeePerGas = feeData.maxFeePerGas ?? feeData.gasPrice ?? (await this.provider.getGasPrice()); @@ -137,6 +157,7 @@ export abstract class SmartAccount implements ISmartAccount { finalUserOp.verificationGasLimit = verificationGasLimit ?? userOp.verificationGasLimit; finalUserOp.callGasLimit = callGasLimit ?? userOp.callGasLimit; finalUserOp.preVerificationGas = preVerificationGas ?? userOp.preVerificationGas; + finalUserOp.paymasterAndData = "0x"; } return finalUserOp; } diff --git a/packages/account/src/utils/Types.ts b/packages/account/src/utils/Types.ts index 9bd96981b..250d69c47 100644 --- a/packages/account/src/utils/Types.ts +++ b/packages/account/src/utils/Types.ts @@ -2,7 +2,7 @@ import { Signer } from "ethers"; import { ChainId } from "@biconomy/core-types"; import { BigNumberish } from "ethers"; import { IBundler } from "@biconomy/bundler"; -import { IPaymaster, PaymasterFeeQuote } from "@biconomy/paymaster"; +import { IPaymaster, PaymasterFeeQuote, SponsorUserOperationDto } from "@biconomy/paymaster"; import { BaseValidationModule, ModuleInfo } from "@biconomy/modules"; import { Provider } from "@ethersproject/providers"; import { GasOverheads } from "./Preverificaiton"; @@ -80,6 +80,7 @@ export type BuildUserOpOptions = { params?: ModuleInfo; nonceOptions?: NonceOptions; forceEncodeForBatch?: boolean; + paymasterServiceData?: SponsorUserOperationDto; }; export type NonceOptions = { diff --git a/packages/account/tests/SmartAccountV2.local.spec.ts b/packages/account/tests/SmartAccountV2.local.spec.ts index 99e0b5caa..cea9f406c 100644 --- a/packages/account/tests/SmartAccountV2.local.spec.ts +++ b/packages/account/tests/SmartAccountV2.local.spec.ts @@ -81,10 +81,6 @@ describe("BiconomySmartAccountV2 API Specs", () => { // console.log('account api provider ', accountAPI.provider) - accountAPI = await accountAPI.init(); - - console.log("Account address ", accountAPI.accountAddress); - const counterFactualAddress = await accountAPI.getAccountAddress(); console.log("Counterfactual address ", counterFactualAddress); @@ -144,7 +140,7 @@ describe("BiconomySmartAccountV2 API Specs", () => { // ((await expect(entryPoint.handleOps([signedUserOp], beneficiary))) as any).to.emit(recipient, "Sender"); expect(await provider.getCode(accountAddress).then((code) => code.length)).toBeGreaterThan(0); - }); + }, 10000); // on github runner it takes more time than 5000ms // TODO // possibly use local bundler API from image @@ -338,13 +334,12 @@ describe("BiconomySmartAccountV2 API Specs", () => { }, 10000); // on github runner it takes more time than 5000ms it("Creates another replicated instance using void signer", async () => { - - let newmodule = await ECDSAOwnershipValidationModule.create({ + const newmodule = await ECDSAOwnershipValidationModule.create({ signer: new VoidSigner(await owner.getAddress()), moduleAddress: ecdsaModule.address, }); - let accountAPI2 = await BiconomySmartAccountV2.create({ + const accountAPI2 = await BiconomySmartAccountV2.create({ chainId: ChainId.GANACHE, rpcUrl: "http://127.0.0.1:8545", // paymaster: paymaster, @@ -358,7 +353,7 @@ describe("BiconomySmartAccountV2 API Specs", () => { }); const address = await accountAPI2.getAccountAddress(); - console.log('account address ', address); + console.log("account address ", address); expect(address).toBe(accountAPI.accountAddress); }, 10000); diff --git a/packages/bundler/src/Bundler.ts b/packages/bundler/src/Bundler.ts index 37a48d8e5..99e72fd04 100644 --- a/packages/bundler/src/Bundler.ts +++ b/packages/bundler/src/Bundler.ts @@ -11,6 +11,8 @@ import { UserOpGasResponse, UserOpByHashResponse, SendUserOpOptions, + GetGasFeeValuesResponse, + GasFeeValues, } from "./utils/Types"; import { resolveProperties } from "ethers/lib/utils"; import { deepHexlify, sendRequest, getTimestampInSeconds, HttpMethod, Logger, RPC_PROVIDER_URLS } from "@biconomy/common"; @@ -174,4 +176,22 @@ export class Bundler implements IBundler { const userOpByHashResponse: UserOpByHashResponse = response.result; return userOpByHashResponse; } + + /** + * @description This function will return the gas fee values + */ + async getGasFeeValues(): Promise { + const bundlerUrl = this.getBundlerUrl(); + const response: GetGasFeeValuesResponse = await sendRequest({ + url: bundlerUrl, + method: HttpMethod.Post, + body: { + method: "biconomy_getGasFeeValues", + params: [], + id: getTimestampInSeconds(), + jsonrpc: "2.0", + }, + }); + return response.result; + } } diff --git a/packages/bundler/src/interfaces/IBundler.ts b/packages/bundler/src/interfaces/IBundler.ts index d34c1321a..5fb00a2a7 100644 --- a/packages/bundler/src/interfaces/IBundler.ts +++ b/packages/bundler/src/interfaces/IBundler.ts @@ -1,4 +1,4 @@ -import { UserOpResponse, UserOpGasResponse, UserOpReceipt, UserOpByHashResponse, SendUserOpOptions } from "../utils/Types"; +import { UserOpResponse, UserOpGasResponse, UserOpReceipt, UserOpByHashResponse, SendUserOpOptions, GasFeeValues } from "../utils/Types"; import { UserOperation } from "@biconomy/core-types"; export interface IBundler { @@ -6,4 +6,5 @@ export interface IBundler { sendUserOp(_userOp: UserOperation, _simulationParam?: SendUserOpOptions): Promise; getUserOpReceipt(_userOpHash: string): Promise; getUserOpByHash(_userOpHash: string): Promise; + getGasFeeValues(): Promise; } diff --git a/packages/bundler/src/utils/Types.ts b/packages/bundler/src/utils/Types.ts index 2cd6c4e91..b9e1ffe5f 100644 --- a/packages/bundler/src/utils/Types.ts +++ b/packages/bundler/src/utils/Types.ts @@ -86,3 +86,14 @@ export type JsonRpcError = { message: string; data: any; }; + +export type GetGasFeeValuesResponse = { + jsonrpc: string; + id: number; + result: GasFeeValues; + error?: JsonRpcError; +}; +export type GasFeeValues = { + maxPriorityFeePerGas: string; + maxFeePerGas: string; +}; diff --git a/packages/common/CHANGELOG.md b/packages/common/CHANGELOG.md index 6b9a5ff10..704bd526f 100644 --- a/packages/common/CHANGELOG.md +++ b/packages/common/CHANGELOG.md @@ -82,7 +82,7 @@ VERSION bump only * Type added for PaymasterServiceData + reordering of params ([fae8b3a](https://github.com/bcnmy/biconomy-client-sdk/commit/fae8b3a02a5e810a9a40674d27f389b89199bb62)) * logic changed to accept paymasterServiceData ([6daaf37](https://github.com/bcnmy/biconomy-client-sdk/commit/6daaf37855a13fa6e12fdbab16a7e980b4631475)) * UserOpGasFields for fetching gas and gas prices from bundler (https://github.com/bcnmy/biconomy-client-sdk/commit/a0c070b04bb6e249388a7d304dad7d08e97810e1) -* skipEstimation added in dto update CreateUserPaidTransactionDto ([487f3ae](https://github.com/bcnmy/biconomy-client-sdk/commit/487f3aefe21b2dd4fd46e18bef7168eae3c1ecc1)) +* skipBundlerGasEstimation added in dto update CreateUserPaidTransactionDto ([487f3ae](https://github.com/bcnmy/biconomy-client-sdk/commit/487f3aefe21b2dd4fd46e18bef7168eae3c1ecc1)) * fetching gas prices from bundler ([a0c070b](https://github.com/bcnmy/biconomy-client-sdk/commit/a0c070b04bb6e249388a7d304dad7d08e97810e1)) diff --git a/packages/common/package.json b/packages/common/package.json index 1890c8719..5ce64628d 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -51,6 +51,7 @@ "concurrently": "^7.4.0", "debug": "^4.3.4", "ethers": "^5.7.0", + "node-fetch": "^2.7.0", "typechain": "^8.1.1" }, "devDependencies": { diff --git a/packages/common/src/httpRequests.ts b/packages/common/src/httpRequests.ts index 40f414ad7..d4923d2b6 100644 --- a/packages/common/src/httpRequests.ts +++ b/packages/common/src/httpRequests.ts @@ -1,3 +1,4 @@ +import fetch from "node-fetch"; import { Logger } from "./Logger"; export enum HttpMethod { diff --git a/packages/core-types/CHANGELOG.md b/packages/core-types/CHANGELOG.md index 59ab27456..3eca80798 100644 --- a/packages/core-types/CHANGELOG.md +++ b/packages/core-types/CHANGELOG.md @@ -67,7 +67,7 @@ VERSION bump only - Type added for PaymasterServiceData + reordering of params ([fae8b3a](https://github.com/bcnmy/biconomy-client-sdk/commit/fae8b3a02a5e810a9a40674d27f389b89199bb62)) - logic changed to accept paymasterServiceData ([6daaf37](https://github.com/bcnmy/biconomy-client-sdk/commit/6daaf37855a13fa6e12fdbab16a7e980b4631475)) - UserOpGasFields for fetching gas and gas prices from bundler (https://github.com/bcnmy/biconomy-client-sdk/commit/a0c070b04bb6e249388a7d304dad7d08e97810e1) -- skipEstimation added in dto update CreateUserPaidTransactionDto ([487f3ae](https://github.com/bcnmy/biconomy-client-sdk/commit/487f3aefe21b2dd4fd46e18bef7168eae3c1ecc1)) +- skipBundlerGasEstimation added in dto update CreateUserPaidTransactionDto ([487f3ae](https://github.com/bcnmy/biconomy-client-sdk/commit/487f3aefe21b2dd4fd46e18bef7168eae3c1ecc1)) - fetching gas prices from bundler ([a0c070b](https://github.com/bcnmy/biconomy-client-sdk/commit/a0c070b04bb6e249388a7d304dad7d08e97810e1)) ## 2.0.0 (2023-04-07) diff --git a/packages/node-client/CHANGELOG.md b/packages/node-client/CHANGELOG.md index 479c1cffd..b2f9ab90e 100644 --- a/packages/node-client/CHANGELOG.md +++ b/packages/node-client/CHANGELOG.md @@ -37,7 +37,7 @@ Version Bump Only. ### Features -* skipEstimation flag for gas ([487f3ae](https://github.com/bcnmy/biconomy-client-sdk/commit/487f3aefe21b2dd4fd46e18bef7168eae3c1ecc1)) +* skipBundlerGasEstimation flag for gas ([487f3ae](https://github.com/bcnmy/biconomy-client-sdk/commit/487f3aefe21b2dd4fd46e18bef7168eae3c1ecc1)) diff --git a/packages/paymaster/src/BiconomyPaymaster.ts b/packages/paymaster/src/BiconomyPaymaster.ts index 405a0e50b..3413f1100 100644 --- a/packages/paymaster/src/BiconomyPaymaster.ts +++ b/packages/paymaster/src/BiconomyPaymaster.ts @@ -19,7 +19,7 @@ import { IHybridPaymaster } from "./interfaces/IHybridPaymaster"; const defaultPaymasterConfig: PaymasterConfig = { paymasterUrl: "", - strictMode: true, // Set your desired default value for strictMode here + strictMode: false, // Set your desired default value for strictMode here }; /** * @dev Hybrid - Generic Gas Abstraction paymaster @@ -42,20 +42,24 @@ export class BiconomyPaymaster implements IHybridPaymaster): Promise> { userOp = await resolveProperties(userOp); - if (userOp.nonce !== null || userOp.nonce !== undefined) { + if (userOp.nonce !== null && userOp.nonce !== undefined) { userOp.nonce = BigNumber.from(userOp.nonce).toHexString(); } - if (userOp.callGasLimit !== null || userOp.callGasLimit !== undefined) { + if (userOp.callGasLimit !== null && userOp.callGasLimit !== undefined) { userOp.callGasLimit = BigNumber.from(userOp.callGasLimit).toString(); } - if (userOp.verificationGasLimit !== null || userOp.verificationGasLimit !== undefined) { + if (userOp.verificationGasLimit !== null && userOp.verificationGasLimit !== undefined) { userOp.verificationGasLimit = BigNumber.from(userOp.verificationGasLimit).toString(); } - if (userOp.preVerificationGas !== null || userOp.preVerificationGas !== undefined) { + if (userOp.preVerificationGas !== null && userOp.preVerificationGas !== undefined) { userOp.preVerificationGas = BigNumber.from(userOp.preVerificationGas).toString(); } - userOp.maxFeePerGas = BigNumber.from(userOp.maxFeePerGas).toHexString(); - userOp.maxPriorityFeePerGas = BigNumber.from(userOp.maxPriorityFeePerGas).toHexString(); + if (userOp.maxFeePerGas !== null && userOp.maxFeePerGas !== undefined) { + userOp.maxFeePerGas = BigNumber.from(userOp.maxFeePerGas).toString(); + } + if (userOp.maxPriorityFeePerGas !== null && userOp.maxPriorityFeePerGas !== undefined) { + userOp.maxPriorityFeePerGas = BigNumber.from(userOp.maxPriorityFeePerGas).toString(); + } userOp.signature = userOp.signature || "0x"; userOp.paymasterAndData = userOp.paymasterAndData || "0x"; return userOp; @@ -136,14 +140,14 @@ export class BiconomyPaymaster implements IHybridPaymaster