diff --git a/src/cli/config/bundler.ts b/src/cli/config/bundler.ts index 2f1a506c..16e0a53f 100644 --- a/src/cli/config/bundler.ts +++ b/src/cli/config/bundler.ts @@ -200,6 +200,10 @@ export const compatibilityArgsSchema = z.object({ .string() .transform((val) => parseGwei(val)) .optional(), + "max-priority-fee-per-gas-cap": z + .string() + .transform((val) => parseGwei(val)) + .optional(), "supports-eip7623": z.boolean().default(false) }) diff --git a/src/cli/config/options.ts b/src/cli/config/options.ts index e9e6ce77..3bfbb016 100644 --- a/src/cli/config/options.ts +++ b/src/cli/config/options.ts @@ -560,6 +560,12 @@ export const compatibilityOptions: CliCommandOptions = type: "string", require: false }, + "max-priority-fee-per-gas-cap": { + description: + "Maximum value for maxPriorityFeePerGas to enforce (in gwei). Caps the tip taken from the network gas oracle. On low-traffic chains the oracle can feedback-loop on the bundler's own past tips and report an absurd value; without a ceiling the bundler bids it verbatim and overpays. maxFeePerGas is reduced by the same amount the tip is capped.", + type: "string", + require: false + }, "supports-eip7623": { description: "Whether the chain supports EIP-7623 (Increase calldata cost to reduce maximum block size)", diff --git a/src/handlers/gasPriceManager.ts b/src/handlers/gasPriceManager.ts index 0f7ac7dc..e5ef86ed 100644 --- a/src/handlers/gasPriceManager.ts +++ b/src/handlers/gasPriceManager.ts @@ -137,29 +137,49 @@ export class GasPriceManager { maxPriorityFeePerGas: (maxPriorityFeePerGas * bumpAmount) / 100n } - if ( - this.config.floorMaxFeePerGas || - this.config.floorMaxPriorityFeePerGas - ) { - const maxFeePerGas = this.config.floorMaxFeePerGas - ? maxBigInt(this.config.floorMaxFeePerGas, result.maxFeePerGas) - : result.maxFeePerGas - - const maxPriorityFeePerGas = this.config.floorMaxPriorityFeePerGas - ? maxBigInt( - this.config.floorMaxPriorityFeePerGas, - result.maxPriorityFeePerGas - ) - : result.maxPriorityFeePerGas + const finalMaxFeePerGas = this.config.floorMaxFeePerGas + ? maxBigInt(this.config.floorMaxFeePerGas, result.maxFeePerGas) + : result.maxFeePerGas + + const finalMaxPriorityFeePerGas = this.config.floorMaxPriorityFeePerGas + ? maxBigInt( + this.config.floorMaxPriorityFeePerGas, + result.maxPriorityFeePerGas + ) + : result.maxPriorityFeePerGas + + return { + // Ensure that maxFeePerGas is always greater or equal than maxPriorityFeePerGas + maxFeePerGas: maxBigInt( + finalMaxFeePerGas, + finalMaxPriorityFeePerGas + ), + maxPriorityFeePerGas: finalMaxPriorityFeePerGas + } + } + + private applyMaxPriorityFeePerGasCap( + gasPriceParameters: GasPriceParameters + ): GasPriceParameters { + // Apply after final lower-bound checks so the oracle value cannot restore + // an uncapped priority fee. + if (this.config.maxPriorityFeePerGasCap !== undefined) { + const cappedPriorityFee = minBigInt( + gasPriceParameters.maxPriorityFeePerGas, + this.config.maxPriorityFeePerGasCap + ) + const priorityFeeDelta = + gasPriceParameters.maxPriorityFeePerGas - cappedPriorityFee + const maxFeePerGas = + gasPriceParameters.maxFeePerGas - priorityFeeDelta return { - // Ensure that maxFeePerGas is always greater or equal than maxPriorityFeePerGas - maxFeePerGas: maxBigInt(maxFeePerGas, maxPriorityFeePerGas), - maxPriorityFeePerGas + maxFeePerGas: maxBigInt(maxFeePerGas, cappedPriorityFee), + maxPriorityFeePerGas: cappedPriorityFee } } - return result + return gasPriceParameters } private async getFallBackMaxPriorityFeePerGas( @@ -285,7 +305,7 @@ export class GasPriceManager { maxPriorityFeePerGas: polygonEstimate.maxPriorityFeePerGas }) - return { + return this.applyMaxPriorityFeePerGasCap({ maxFeePerGas: maxBigInt( gasPrice.maxFeePerGas, maxFeePerGas @@ -294,7 +314,7 @@ export class GasPriceManager { gasPrice.maxPriorityFeePerGas, maxPriorityFeePerGas ) - } + }) } } @@ -302,13 +322,13 @@ export class GasPriceManager { const gasPrice = this.bumpTheGasPrice( await this.getLegacyTransactionGasPrice() ) - return { + return this.applyMaxPriorityFeePerGasCap({ maxFeePerGas: maxBigInt(gasPrice.maxFeePerGas, maxFeePerGas), maxPriorityFeePerGas: maxBigInt( gasPrice.maxPriorityFeePerGas, maxPriorityFeePerGas ) - } + }) } const estimatedPrice = await this.estimateGasPrice() @@ -320,13 +340,13 @@ export class GasPriceManager { maxFeePerGas, maxPriorityFeePerGas }) - return { + return this.applyMaxPriorityFeePerGasCap({ maxFeePerGas: maxBigInt(gasPrice.maxFeePerGas, maxFeePerGas), maxPriorityFeePerGas: maxBigInt( gasPrice.maxPriorityFeePerGas, maxPriorityFeePerGas ) - } + }) } // This method throws if it can't get a valid RPC response.