diff --git a/packages/ethereum-storage/src/ethereum-tx-submitter.ts b/packages/ethereum-storage/src/ethereum-tx-submitter.ts index e8625cbd0a..3fdacbc702 100644 --- a/packages/ethereum-storage/src/ethereum-tx-submitter.ts +++ b/packages/ethereum-storage/src/ethereum-tx-submitter.ts @@ -7,7 +7,21 @@ import { SimpleLogger, isEip1559Supported } from '@requestnetwork/utils'; export type SubmitterProps = { signer: Signer; + /** + * The minimum value for maxPriorityFeePerGas and maxFeePerGas. + * The default is zero. + */ gasPriceMin?: BigNumber; + /** + * The maximum value for maxFeePerGas. + * There is no limit if no value is set. + */ + gasPriceMax?: BigNumber; + /** + * A multiplier for the computed maxFeePerGas. + * The default is 100, which does not change the value (100 is equal to x1, 200 is equal to x2). + */ + gasPriceMultiplier?: number; network: CurrencyTypes.EvmChainName; logger?: LogTypes.ILogger; debugProvider?: boolean; @@ -23,7 +37,15 @@ export class EthereumTransactionSubmitter implements StorageTypes.ITransactionSu private readonly provider: providers.JsonRpcProvider; private readonly gasFeeDefiner: GasFeeDefiner; - constructor({ network, signer, logger, gasPriceMin, debugProvider }: SubmitterProps) { + constructor({ + network, + signer, + logger, + gasPriceMin, + gasPriceMax, + gasPriceMultiplier, + debugProvider, + }: SubmitterProps) { this.logger = logger || new SimpleLogger(); const provider = signer.provider as providers.JsonRpcProvider; this.provider = provider; @@ -31,7 +53,13 @@ export class EthereumTransactionSubmitter implements StorageTypes.ITransactionSu network, signer, ) as RequestOpenHashSubmitter; // type mismatch with ethers. - this.gasFeeDefiner = new GasFeeDefiner({ provider, gasPriceMin, logger: this.logger }); + this.gasFeeDefiner = new GasFeeDefiner({ + provider, + gasPriceMin, + gasPriceMax, + gasPriceMultiplier, + logger: this.logger, + }); if (debugProvider) { this.provider.on('debug', (event) => { this.logger.debug('JsonRpcProvider debug event', event); diff --git a/packages/ethereum-storage/src/gas-fee-definer.ts b/packages/ethereum-storage/src/gas-fee-definer.ts index 7cdf857b4f..7515fea149 100644 --- a/packages/ethereum-storage/src/gas-fee-definer.ts +++ b/packages/ethereum-storage/src/gas-fee-definer.ts @@ -1,31 +1,41 @@ import { suggestFees } from '@rainbow-me/fee-suggestions'; -import { BigNumber, providers, constants } from 'ethers'; +import { BigNumber, providers } from 'ethers'; import { normalizeGasFees } from '@requestnetwork/utils'; import { FeeTypes, LogTypes } from '@requestnetwork/types'; export class GasFeeDefiner { private readonly logger: LogTypes.ILogger; private readonly provider: providers.JsonRpcProvider; - private readonly gasPriceMin: BigNumber; + private readonly gasPriceMin?: BigNumber; + private readonly gasPriceMax?: BigNumber; + private readonly gasPriceMultiplier?: number; constructor({ logger, provider, gasPriceMin, + gasPriceMax, + gasPriceMultiplier, }: { logger: LogTypes.ILogger; gasPriceMin?: BigNumber; + gasPriceMax?: BigNumber; + gasPriceMultiplier?: number; provider: providers.JsonRpcProvider; }) { this.logger = logger; this.provider = provider; - this.gasPriceMin = gasPriceMin || constants.Zero; + this.gasPriceMin = gasPriceMin; + this.gasPriceMax = gasPriceMax; + this.gasPriceMultiplier = gasPriceMultiplier; } public async getGasFees(): Promise { return normalizeGasFees({ logger: this.logger, gasPriceMin: this.gasPriceMin, + gasPriceMax: this.gasPriceMax, + gasPriceMultiplier: this.gasPriceMultiplier, suggestFees: async () => { const { baseFeeSuggestion, maxPriorityFeeSuggestions } = await suggestFees(this.provider); return { diff --git a/packages/utils/src/normalize-gas-fees.ts b/packages/utils/src/normalize-gas-fees.ts index 24a6c960b6..aeaf64ad15 100644 --- a/packages/utils/src/normalize-gas-fees.ts +++ b/packages/utils/src/normalize-gas-fees.ts @@ -1,6 +1,6 @@ import { BigNumber, constants } from 'ethers'; -import { maxBigNumber } from './index'; +import { maxBigNumber, minBigNumber } from './index'; import { LogTypes, FeeTypes } from '@requestnetwork/types'; /** @@ -14,22 +14,31 @@ import { LogTypes, FeeTypes } from '@requestnetwork/types'; async function normalizeGasFees({ logger, gasPriceMin, + gasPriceMax, + gasPriceMultiplier, suggestFees, }: { logger: LogTypes.ILogger; gasPriceMin?: BigNumber; + gasPriceMax?: BigNumber; + gasPriceMultiplier?: number; suggestFees: () => Promise; }): Promise { try { const suggestedFee = await suggestFees(); - const baseFee = maxBigNumber(suggestedFee.baseFee, gasPriceMin || constants.Zero); - const maxPriorityFeePerGas = maxBigNumber( suggestedFee.maxPriorityFee, gasPriceMin || constants.Zero, ); - const maxFeePerGas = baseFee.add(maxPriorityFeePerGas); + + const maxFeePerGasInit = baseFee + .add(maxPriorityFeePerGas) + .mul(gasPriceMultiplier || 100) + .div(100); + const maxFeePerGas = gasPriceMax + ? minBigNumber(maxFeePerGasInit, gasPriceMax) + : maxFeePerGasInit; if (maxPriorityFeePerGas.eq(0) || maxFeePerGas.eq(0)) { logger.warn( diff --git a/packages/utils/test/normalize-gas-fees.test.ts b/packages/utils/test/normalize-gas-fees.test.ts index 72d63a002c..67ff97fe8f 100644 --- a/packages/utils/test/normalize-gas-fees.test.ts +++ b/packages/utils/test/normalize-gas-fees.test.ts @@ -18,4 +18,30 @@ describe('Normalize Gas Fees', () => { expect(normalizedGasFees.maxPriorityFeePerGas?.toString()).toBe('1000000000'); expect(normalizedGasFees.maxFeePerGas?.toString()).toBe('2000000000'); }); + + it('should respect maximum gas fee', async () => { + const normalizedGasFees = await normalizeGasFees({ + logger: console, + suggestFees: async () => ({ + baseFee: '400000000000', // 400 Gwei + maxPriorityFee: '2000000000', // 2 Gwei + }), + gasPriceMax: BigNumber.from('250000000000'), // 250 Gwei + }); + expect(normalizedGasFees.maxPriorityFeePerGas?.toString()).toBe('2000000000'); // 2 Gwei + expect(normalizedGasFees.maxFeePerGas?.toString()).toBe('250000000000'); // 250 Gwei + }); + + it('should respect gas multiplier', async () => { + const normalizedGasFees = await normalizeGasFees({ + logger: console, + suggestFees: async () => ({ + baseFee: '20000000000', // 20 Gwei + maxPriorityFee: '2000000000', // 2 Gwei + }), + gasPriceMultiplier: 200, // x2 + }); + expect(normalizedGasFees.maxPriorityFeePerGas?.toString()).toBe('2000000000'); // 2 Gwei + expect(normalizedGasFees.maxFeePerGas?.toString()).toBe('44000000000'); // (20 + 2) x 2 = 44 Gwei + }); });