diff --git a/src/handlers/gasPriceManager.ts b/src/handlers/gasPriceManager.ts index d5964ea8..00cdb66c 100644 --- a/src/handlers/gasPriceManager.ts +++ b/src/handlers/gasPriceManager.ts @@ -6,7 +6,7 @@ import { } from "@alto/types" import { maxBigInt, minBigInt, type Logger } from "@alto/utils" import * as sentry from "@sentry/node" -import { parseGwei, type Chain, type PublicClient } from "viem" +import { parseGwei, type Chain, type PublicClient, maxUint128 } from "viem" import { celo, celoAlfajores, @@ -36,6 +36,77 @@ function getGasStationUrl(chainId: ChainId.Polygon | ChainId.Mumbai): string { } } +class ArbitrumManager { + private queueL1BaseFee: { timestamp: number; baseFee: bigint }[] + private queueL2BaseFee: { timestamp: number; baseFee: bigint }[] + + private maxQueueSize + private queueValidity = 15_000 + + constructor(maxQueueSize: number) { + this.maxQueueSize = maxQueueSize + this.queueL1BaseFee = [] + this.queueL2BaseFee = [] + } + + public saveL1BaseFee(baseFee: bigint) { + const queue = this.queueL1BaseFee + const last = queue.length > 0 ? queue[queue.length - 1] : null + const timestamp = Date.now() + + if (!last || timestamp - last.timestamp >= this.queueValidity) { + if (queue.length >= this.maxQueueSize) { + queue.shift() + } + queue.push({ baseFee, timestamp }) + } else if (baseFee < last.baseFee) { + last.baseFee = baseFee + last.timestamp = timestamp + } + } + + public saveL2BaseFee(baseFee: bigint) { + const queue = this.queueL2BaseFee + const last = queue.length > 0 ? queue[queue.length - 1] : null + const timestamp = Date.now() + + if (!last || timestamp - last.timestamp >= this.queueValidity) { + if (queue.length >= this.maxQueueSize) { + queue.shift() + } + queue.push({ baseFee, timestamp }) + } else if (baseFee < last.baseFee) { + last.baseFee = baseFee + last.timestamp = timestamp + } + } + + public async getMinL1BaseFee() { + const queue = this.queueL1BaseFee + + if (queue.length === 0) { + return 1n + } + return queue.reduce( + (acc: bigint, cur) => minBigInt(cur.baseFee, acc), + queue[0].baseFee + ) + } + + public async getMaxL2BaseFee() { + const queue = this.queueL2BaseFee + + if (queue.length === 0) { + return maxUint128 + } + + return queue.reduce( + (acc: bigint, cur) => maxBigInt(cur.baseFee, acc), + queue[0].baseFee + ) + } +} + export class GasPriceManager { private chain: Chain private publicClient: PublicClient @@ -53,6 +124,7 @@ export class GasPriceManager { private gasBumpMultiplier: bigint private gasPriceRefreshIntervalInSeconds: number private chainType: ChainType + public arbitrumManager: ArbitrumManager constructor( chain: Chain, @@ -83,6 +155,8 @@ export class GasPriceManager { this.updateGasPrice() }, this.gasPriceRefreshIntervalInSeconds * 1000) } + + this.arbitrumManager = new ArbitrumManager(this.maxQueueSize) } public init() { diff --git a/src/utils/validation.ts b/src/utils/validation.ts index 1c8c6a76..b36cdccc 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -331,7 +331,9 @@ export async function calcPreVerificationGas( publicClient, userOperation, entryPoint, - preVerificationGas + preVerificationGas, + gasPriceManager, + validate ) } @@ -583,7 +585,9 @@ export async function calcArbitrumPreVerificationGas( publicClient: PublicClient, op: UserOperation, entryPoint: Address, - staticFee: bigint + staticFee: bigint, + gasPriceManager: GasPriceManager, + validate: boolean ) { let selector: Hex let paramData: Hex @@ -649,7 +653,24 @@ export async function calcArbitrumPreVerificationGas( serializedTx ]) - return result[0] + staticFee + let [gasForL1, l2BaseFee, l1BaseFeeEstimate] = result + + gasPriceManager.arbitrumManager.saveL1BaseFee(l1BaseFeeEstimate) + gasPriceManager.arbitrumManager.saveL2BaseFee(l2BaseFee) + + if (validate) { + // gasEstimateL1Component source: https://github.com/OffchainLabs/nitro/blob/5cd7d6913eb6b4dedb08f6ea49d7f9802d2cc5b9/execution/nodeInterface/NodeInterface.go#L515-L551 + const feesForL1 = (gasForL1 * l2BaseFee) / l1BaseFeeEstimate + + const minL1BaseFeeEstimate = + await gasPriceManager.arbitrumManager.getMinL1BaseFee() + const maxL2BaseFee = + await gasPriceManager.arbitrumManager.getMaxL2BaseFee() + + gasForL1 = (feesForL1 * minL1BaseFeeEstimate) / maxL2BaseFee + } + + return staticFee + gasForL1 } export function parseViemError(err: unknown) {