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
JavaScript
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