-
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.
- Loading branch information
Showing
10 changed files
with
277 additions
and
62 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
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 |
---|---|---|
@@ -1,13 +1,25 @@ | ||
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 }; | ||
import { providers } from "ethers"; | ||
import { BigNumber, bnOne, parseUnits } from "../../utils"; | ||
import { GasPriceEstimate } from "../types"; | ||
import * as ethereum from "./ethereum"; | ||
|
||
let DEFAULT_PRIORITY_FEE: BigNumber | undefined = undefined; | ||
|
||
// Arbitrum Nitro implements EIP-1559 pricing, but the priority fee is always refunded to the caller. Further, | ||
// ethers typically hardcodes the priority fee to 1.5 Gwei. So, confirm that the priority fee supplied was 1.5 | ||
// Gwei, and then drop it to 1 Wei. Reference: https://developer.arbitrum.io/faqs/gas-faqs#q-priority | ||
export async function eip1559(provider: providers.Provider, chainId: number): Promise<GasPriceEstimate> { | ||
DEFAULT_PRIORITY_FEE ??= parseUnits("1.5", 9); | ||
const { maxFeePerGas: _maxFeePerGas, maxPriorityFeePerGas } = await ethereum.eip1559(provider, chainId); | ||
|
||
// If this throws, ethers default behaviour has changed, or Arbitrum RPCs are returning something more sensible. | ||
if (!maxPriorityFeePerGas.eq(DEFAULT_PRIORITY_FEE)) { | ||
throw new Error(`Expected hardcoded 1.5 Gwei priority fee on Arbitrum, got ${maxPriorityFeePerGas}`); | ||
} | ||
|
||
// eip1559() sets maxFeePerGas = lastBaseFeePerGas + maxPriorityFeePerGas, so revert that. | ||
// The caller may apply scaling as they wish afterwards. | ||
const maxFeePerGas = _maxFeePerGas.sub(maxPriorityFeePerGas).add(bnOne); | ||
|
||
return { maxPriorityFeePerGas: bnOne, maxFeePerGas }; | ||
} |
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 |
---|---|---|
@@ -1,19 +1,28 @@ | ||
import { PublicClient } from "viem"; | ||
import { InternalGasPriceEstimate } from "../types"; | ||
import { providers } from "ethers"; | ||
import { BigNumber, bnZero } from "../../utils"; | ||
import { GasPriceEstimate } from "../types"; | ||
import { gasPriceError } from "../util"; | ||
|
||
export function eip1559(provider: PublicClient, _chainId: number): Promise<InternalGasPriceEstimate> { | ||
return provider.estimateFeesPerGas(); | ||
export async function eip1559(provider: providers.Provider, chainId: number): Promise<GasPriceEstimate> { | ||
const feeData = await provider.getFeeData(); | ||
|
||
[feeData.lastBaseFeePerGas, feeData.maxPriorityFeePerGas].forEach((field: BigNumber | null) => { | ||
if (!BigNumber.isBigNumber(field) || field.lt(bnZero)) gasPriceError("getFeeData()", chainId, feeData); | ||
}); | ||
|
||
const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas as BigNumber; | ||
const maxFeePerGas = maxPriorityFeePerGas.add(feeData.lastBaseFeePerGas as BigNumber); | ||
|
||
return { maxPriorityFeePerGas, maxFeePerGas }; | ||
} | ||
|
||
export async function legacy( | ||
provider: PublicClient, | ||
_chainId: number, | ||
_test?: number | ||
): Promise<InternalGasPriceEstimate> { | ||
export async function legacy(provider: providers.Provider, chainId: number): Promise<GasPriceEstimate> { | ||
const gasPrice = await provider.getGasPrice(); | ||
|
||
if (!BigNumber.isBigNumber(gasPrice) || gasPrice.lt(bnZero)) gasPriceError("getGasPrice()", chainId, gasPrice); | ||
|
||
return { | ||
maxFeePerGas: gasPrice, | ||
maxPriorityFeePerGas: BigInt(0), | ||
maxPriorityFeePerGas: bnZero, | ||
}; | ||
} |
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,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 }; | ||
} |
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
Oops, something went wrong.