-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Support opt-in use of viem for gas pricing (#745)
This change updates the gasPriceOracle to support querying gas via viem's gas pricing strategies. This is beneficial because viem has an active community that are actively adding support for new chains and improving support for existing chains. An example of this is Linea, where linea_estimateGas is supported out of the box after a recent update. In order to maintain some degree of compatibility with existing users of the gasPriceOracle, the external interface still accepts an ethers Provider instance. It then internally instantiates a viem PublicClient instance and uses that instead. There is still a residual issue of how to resolve the relevant provider URLs, since it's not necessarily reliable to pull from the provider due to the uncertain type (i.e. StaticJsonRpcProvider or one of the extended variants that wraps multiple providers). Viem support can be enabled per-chain via the NEW_GAS_PRICE_ORACLE_<chainId> environment variable, which must be set explicitly to true. All other queries will revert to the existing ethers implementation. Co-authored-by: Dong-Ha Kim <[email protected]>
- Loading branch information
Showing
15 changed files
with
398 additions
and
223 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { PublicClient } from "viem"; | ||
import { InternalGasPriceEstimate } from "../types"; | ||
|
||
const MAX_PRIORITY_FEE_PER_GAS = BigInt(1); | ||
|
||
// Arbitrum Nitro implements EIP-1559 pricing, but the priority fee is always refunded to the caller. | ||
// Swap it for 1 Wei to avoid inaccurate transaction cost estimates. | ||
// Reference: https://developer.arbitrum.io/faqs/gas-faqs#q-priority | ||
export async function eip1559(provider: PublicClient, _chainId: number): Promise<InternalGasPriceEstimate> { | ||
const { maxFeePerGas: _maxFeePerGas, maxPriorityFeePerGas } = await provider.estimateFeesPerGas(); | ||
const maxFeePerGas = BigInt(_maxFeePerGas) - maxPriorityFeePerGas + MAX_PRIORITY_FEE_PER_GAS; | ||
return { maxFeePerGas, maxPriorityFeePerGas: MAX_PRIORITY_FEE_PER_GAS }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { PublicClient } from "viem"; | ||
import { InternalGasPriceEstimate } from "../types"; | ||
|
||
export function eip1559(provider: PublicClient, _chainId: number): Promise<InternalGasPriceEstimate> { | ||
return provider.estimateFeesPerGas(); | ||
} | ||
|
||
export async function legacy( | ||
provider: PublicClient, | ||
_chainId: number, | ||
_test?: number | ||
): Promise<InternalGasPriceEstimate> { | ||
const gasPrice = await provider.getGasPrice(); | ||
|
||
return { | ||
maxFeePerGas: gasPrice, | ||
maxPriorityFeePerGas: BigInt(0), | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import { PublicClient } from "viem"; | ||
import { BaseHTTPAdapter, BaseHTTPAdapterArgs } from "../../priceClient/adapters/baseAdapter"; | ||
import { isDefined } from "../../utils"; | ||
import { CHAIN_IDs } from "../../constants"; | ||
import { InternalGasPriceEstimate } from "../types"; | ||
import { gasPriceError } from "../util"; | ||
import { eip1559 } from "./ethereum-viem"; | ||
|
||
type Polygon1559GasPrice = { | ||
maxPriorityFee: number | string; | ||
maxFee: number | string; | ||
}; | ||
|
||
type GasStationV2Response = { | ||
safeLow: Polygon1559GasPrice; | ||
standard: Polygon1559GasPrice; | ||
fast: Polygon1559GasPrice; | ||
estimatedBaseFee: number | string; | ||
blockTime: number | string; | ||
blockNumber: number | string; | ||
}; | ||
|
||
type GasStationArgs = BaseHTTPAdapterArgs & { | ||
chainId?: number; | ||
host?: string; | ||
}; | ||
|
||
const { POLYGON } = CHAIN_IDs; | ||
|
||
const GWEI = BigInt(1_000_000_000); | ||
class PolygonGasStation extends BaseHTTPAdapter { | ||
readonly chainId: number; | ||
|
||
constructor({ chainId = POLYGON, host, timeout = 1500, retries = 1 }: GasStationArgs = {}) { | ||
host = host ?? chainId === POLYGON ? "gasstation.polygon.technology" : "gasstation-testnet.polygon.technology"; | ||
|
||
super("Polygon Gas Station", host, { timeout, retries }); | ||
this.chainId = chainId; | ||
} | ||
|
||
async getFeeData(strategy: "safeLow" | "standard" | "fast" = "fast"): Promise<InternalGasPriceEstimate> { | ||
const gas = await this.query("v2", {}); | ||
|
||
const gasPrice = (gas as GasStationV2Response)?.[strategy]; | ||
if (!this.isPolygon1559GasPrice(gasPrice)) { | ||
// @todo: generalise gasPriceError() to accept a reason/cause? | ||
gasPriceError("getFeeData()", this.chainId, gasPrice); | ||
} | ||
|
||
const maxPriorityFeePerGas = BigInt(gasPrice.maxPriorityFee) * GWEI; | ||
const maxFeePerGas = BigInt(gasPrice.maxFee) * GWEI; | ||
|
||
return { maxPriorityFeePerGas, maxFeePerGas }; | ||
} | ||
|
||
protected isPolygon1559GasPrice(gasPrice: unknown): gasPrice is Polygon1559GasPrice { | ||
if (!isDefined(gasPrice)) { | ||
return false; | ||
} | ||
const _gasPrice = gasPrice as Polygon1559GasPrice; | ||
return [_gasPrice.maxPriorityFee, _gasPrice.maxFee].every((field) => ["number", "string"].includes(typeof field)); | ||
} | ||
} | ||
|
||
export async function gasStation(provider: PublicClient, chainId: number): Promise<InternalGasPriceEstimate> { | ||
const gasStation = new PolygonGasStation({ chainId, timeout: 2000, retries: 0 }); | ||
let maxPriorityFeePerGas: bigint; | ||
let maxFeePerGas: bigint; | ||
try { | ||
({ maxPriorityFeePerGas, maxFeePerGas } = await gasStation.getFeeData()); | ||
} catch (err) { | ||
// Fall back to the RPC provider. May be less accurate. | ||
({ maxPriorityFeePerGas, maxFeePerGas } = await eip1559(provider, chainId)); | ||
|
||
// Per the GasStation docs, the minimum priority fee on Polygon is 30 Gwei. | ||
// https://docs.polygon.technology/tools/gas/polygon-gas-station/#interpretation | ||
const minPriorityFee = BigInt(30) * GWEI; | ||
if (minPriorityFee > maxPriorityFeePerGas) { | ||
const priorityDelta = minPriorityFee - maxPriorityFeePerGas; | ||
maxPriorityFeePerGas = minPriorityFee; | ||
maxFeePerGas = maxFeePerGas + priorityDelta; | ||
} | ||
} | ||
|
||
return { maxPriorityFeePerGas, maxFeePerGas }; | ||
} |
Oops, something went wrong.