import { FeeData as EtherFeeData, Provider, } from "@ethersproject/abstract-provider"; import { BigNumber } from "@ethersproject/bignumber"; import { parseUnits } from "@ethersproject/units"; import axios from "axios"; import { HardhatRuntimeEnvironment } from "hardhat/types"; export type FeeData = Partial; /** * Gas estimator interface. */ export interface IGasEstimator { /** * Computes the estimated gas price for a given transaction. */ gasPriceEstimate(): Promise; /** * Computes the optimal transaction gas price options to use. */ txGasPrice(): Promise; } export function createGasEstimator( hre: HardhatRuntimeEnvironment, options: { blockNative: boolean; }, ): IGasEstimator { if (options.blockNative) { if (hre.network.name !== "mainnet") { throw new Error(`BlockNative does not support ${hre.network.name}`); } return new BlockNativeGasEstimator(); } else { return new ProviderGasEstimator(hre.ethers.provider); } } export class ProviderGasEstimator implements IGasEstimator { constructor(private provider: Provider) {} gasPriceEstimate(): Promise { return this.provider.getGasPrice(); } txGasPrice(): Promise { return Promise.resolve({}); } } // We just use the API that the gas price browser page uses to avoid dealing // with API keys and rate limiting. const BLOCKNATIVE_URL = "https://blocknative-api.herokuapp.com/data"; interface EstimatedPrice { confidence: number; price: number; maxPriorityFeePerGas: number; maxFeePerGas: number; } interface BlockPrices { estimatedPrices: EstimatedPrice[]; } export class BlockNativeGasEstimator implements IGasEstimator { constructor(public confidence: number = 90) {} private async queryEstimatedPrice(): Promise { const response = await axios.get(BLOCKNATIVE_URL); const { estimatedPrices }: BlockPrices = response.data; estimatedPrices.sort((a, b) => a.confidence - b.confidence); const price = estimatedPrices.find( (price) => price.confidence >= this.confidence, ); if (price === undefined) { throw new Error( `no price with confidence greater than ${this.confidence}`, ); } return price; } async gasPriceEstimate(): Promise { const { price } = await this.queryEstimatedPrice(); return gweiToWei(price); } async txGasPrice(): Promise { const { maxFeePerGas, maxPriorityFeePerGas } = await this.queryEstimatedPrice(); return { maxFeePerGas: gweiToWei(maxFeePerGas), maxPriorityFeePerGas: gweiToWei(maxPriorityFeePerGas), }; } } function gweiToWei(amount: number): BigNumber { return parseUnits(amount.toFixed(9), 9); }