UNPKG

hardhat

Version:

Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.

149 lines 6.65 kB
var _a; import { assertHardhatInvariant } from "@nomicfoundation/hardhat-errors"; import { hexStringToBigInt, numberToHexString, } from "@nomicfoundation/hardhat-utils/hex"; import { isObject } from "@nomicfoundation/hardhat-utils/lang"; import { getRequestParams } from "../../../json-rpc.js"; /** * This class automatically adjusts transaction requests to include appropriately estimated gas prices. * It ensures that gas prices are set correctly. */ export class AutomaticGasPriceHandler { #methods = new Set(["eth_sendTransaction"]); #provider; // We pay the max base fee that can be required if the next // EIP1559_BASE_FEE_MAX_FULL_BLOCKS_PREFERENCE are full. static EIP1559_BASE_FEE_MAX_FULL_BLOCKS_PREFERENCE = 3n; // See eth_feeHistory for an explanation of what this means static EIP1559_REWARD_PERCENTILE = 50; constructor(provider) { this.#provider = provider; } #nodeHasFeeHistory; #nodeSupportsEIP1559; isSupportedMethod(jsonRpcRequest) { return this.#methods.has(jsonRpcRequest.method); } async handle(jsonRpcRequest) { if (!this.isSupportedMethod(jsonRpcRequest)) { return jsonRpcRequest; } const params = getRequestParams(jsonRpcRequest); const [tx] = params; if (!isObject(tx)) { return jsonRpcRequest; } // We don't need to do anything in these cases if (tx.gasPrice !== undefined || (tx.maxFeePerGas !== undefined && tx.maxPriorityFeePerGas !== undefined)) { return jsonRpcRequest; } let suggestedEip1559Values = await this.#suggestEip1559FeePriceValues(); // eth_feeHistory failed, so we send a legacy one if (tx.maxFeePerGas === undefined && tx.maxPriorityFeePerGas === undefined && suggestedEip1559Values === undefined) { tx.gasPrice = numberToHexString(await this.#getGasPrice()); return jsonRpcRequest; } // If eth_feeHistory failed, but the user still wants to send an EIP-1559 tx // we use the gasPrice as default values. if (suggestedEip1559Values === undefined) { const gasPrice = await this.#getGasPrice(); suggestedEip1559Values = { maxFeePerGas: gasPrice, maxPriorityFeePerGas: gasPrice, }; } let maxFeePerGas = typeof tx.maxFeePerGas === "string" ? hexStringToBigInt(tx.maxFeePerGas) : suggestedEip1559Values.maxFeePerGas; const maxPriorityFeePerGas = typeof tx.maxPriorityFeePerGas === "string" ? hexStringToBigInt(tx.maxPriorityFeePerGas) : suggestedEip1559Values.maxPriorityFeePerGas; if (maxFeePerGas < maxPriorityFeePerGas) { maxFeePerGas += maxPriorityFeePerGas; } tx.maxFeePerGas = numberToHexString(maxFeePerGas); tx.maxPriorityFeePerGas = numberToHexString(maxPriorityFeePerGas); return jsonRpcRequest; } async #getGasPrice() { const response = await this.#provider.request({ method: "eth_gasPrice", }); assertHardhatInvariant(typeof response === "string", "response should return a string"); return hexStringToBigInt(response); } async #suggestEip1559FeePriceValues() { if (this.#nodeSupportsEIP1559 === undefined) { const block = await this.#provider.request({ method: "eth_getBlockByNumber", params: ["latest", false], }); assertHardhatInvariant(isObject(block), "block should be a non null object"); this.#nodeSupportsEIP1559 = "baseFeePerGas" in block && block.baseFeePerGas !== undefined; } if (this.#nodeHasFeeHistory === false || this.#nodeSupportsEIP1559 === false) { return; } try { const response = await this.#provider.request({ method: "eth_feeHistory", params: [ "0x1", "latest", [_a.EIP1559_REWARD_PERCENTILE], ], }); assertHardhatInvariant(typeof response === "object" && response !== null && "baseFeePerGas" in response && "reward" in response && Array.isArray(response.baseFeePerGas) && Array.isArray(response.reward), "response should be an object with baseFeePerGas and reward properties"); let maxPriorityFeePerGas = hexStringToBigInt(response.reward[0][0]); if (maxPriorityFeePerGas === 0n) { try { const suggestedMaxPriorityFeePerGas = await this.#provider.request({ method: "eth_maxPriorityFeePerGas", params: [], }); assertHardhatInvariant(typeof suggestedMaxPriorityFeePerGas === "string", "suggestedMaxPriorityFeePerGas should be a string"); maxPriorityFeePerGas = hexStringToBigInt(suggestedMaxPriorityFeePerGas); } catch { // if eth_maxPriorityFeePerGas does not exist, use 1 wei maxPriorityFeePerGas = 1n; } } // If after all of these we still have a 0 wei maxPriorityFeePerGas, we // use 1 wei. This is to improve the UX of the automatic gas price // on chains that are very empty (i.e local testnets). This will be very // unlikely to trigger on a live chain. if (maxPriorityFeePerGas === 0n) { maxPriorityFeePerGas = 1n; } return { // Each block increases the base fee by 1/8 at most, when full. // We have the next block's base fee, so we compute a cap for the // next N blocks here. maxFeePerGas: (hexStringToBigInt(response.baseFeePerGas[response.baseFeePerGas.length - 1]) * 9n ** (_a.EIP1559_BASE_FEE_MAX_FULL_BLOCKS_PREFERENCE - 1n)) / 8n ** (_a.EIP1559_BASE_FEE_MAX_FULL_BLOCKS_PREFERENCE - 1n), maxPriorityFeePerGas, }; } catch { this.#nodeHasFeeHistory = false; return undefined; } } } _a = AutomaticGasPriceHandler; //# sourceMappingURL=automatic-gas-price-handler.js.map