From 9e422a64395dafa9f27065bba07f054bd7b64fa2 Mon Sep 17 00:00:00 2001 From: Mike Rolish Date: Thu, 16 Oct 2025 16:04:57 -0500 Subject: [PATCH 1/2] feat(price_pusher): Optionally use median of recent gas prices --- apps/price_pusher/src/evm/command.ts | 19 ++++++++++- apps/price_pusher/src/evm/evm.ts | 49 ++++++++++++++++++++++++---- apps/price_pusher/src/metrics.ts | 25 ++++++++++++++ 3 files changed, 86 insertions(+), 7 deletions(-) diff --git a/apps/price_pusher/src/evm/command.ts b/apps/price_pusher/src/evm/command.ts index c965e7b6ad..07903bf94c 100644 --- a/apps/price_pusher/src/evm/command.ts +++ b/apps/price_pusher/src/evm/command.ts @@ -68,6 +68,12 @@ export default { type: "number", required: false, } as Options, + "use-recent-gas-price-estimate": { + description: "Use gas price based on recent blocks", + type: "boolean", + required: false, + default: false, + } as Options, "update-fee-multiplier": { description: "Multiplier for the fee to update the price. It is useful in networks " + @@ -76,6 +82,11 @@ export default { type: "number", required: false, default: 1, + } as Options, "disable-push": { + description: "Dry run without pushing", + type: "boolean", + required: false, + default: false, } as Options, ...options.priceConfigFile, ...options.priceServiceEndpoint, @@ -104,11 +115,13 @@ export default { overrideGasPriceMultiplierCap, gasLimit, gasPrice, + useRecentGasPriceEstimate, updateFeeMultiplier, logLevel, controllerLogLevel, enableMetrics, metricsPort, + disablePush, } = argv; const logger = pino({ @@ -151,6 +164,7 @@ export default { ); const client = await createClient(endpoint, mnemonic); + const network = await client.getChainId().then((id) => id.toString()); const pythContract = createPythContract(client, pythContractAddress); logger.info( @@ -185,6 +199,9 @@ export default { overrideGasPriceMultiplier, overrideGasPriceMultiplierCap, updateFeeMultiplier, + network, + disablePush, + useRecentGasPriceEstimate, gasLimit, gasStation, gasPrice, @@ -207,7 +224,7 @@ export default { const balanceTracker = createEvmBalanceTracker({ client, address: client.account.address, - network: await client.getChainId().then((id) => id.toString()), + network, updateInterval: pushingFrequency, metrics, logger, diff --git a/apps/price_pusher/src/evm/evm.ts b/apps/price_pusher/src/evm/evm.ts index 8dbf9ab05f..b66d0aaeda 100644 --- a/apps/price_pusher/src/evm/evm.ts +++ b/apps/price_pusher/src/evm/evm.ts @@ -32,6 +32,7 @@ import { import { PythContract } from "./pyth-contract"; import { SuperWalletClient } from "./super-wallet"; +import { PricePusherMetrics } from "../metrics"; export class EvmPriceListener extends ChainPriceListener { constructor( @@ -135,9 +136,13 @@ export class EvmPricePusher implements IPricePusher { private overrideGasPriceMultiplier: number, private overrideGasPriceMultiplierCap: number, private updateFeeMultiplier: number, + private network: string, + private disablePush: boolean, + private useRecentGasPriceEstimate: boolean, private gasLimit?: number, private customGasStation?: CustomGasStation, private gasPrice?: number, + private metrics?: PricePusherMetrics, ) {} // The pubTimes are passed here to use the values that triggered the push. @@ -191,8 +196,11 @@ export class EvmPricePusher implements IPricePusher { this.gasPrice ?? Number( await (this.customGasStation?.getCustomGasPrice() ?? - this.client.getGasPrice()), + (this.useRecentGasPriceEstimate + ? this.getMedianRecentGasPrice() + : this.client.getGasPrice())), ); + this.metrics?.updateGasPrice(this.network, gasPrice); // Try to re-use the same nonce and increase the gas if the last tx is not landed yet. if (this.pusherAddress === undefined) { @@ -258,11 +266,13 @@ export class EvmPricePusher implements IPricePusher { this.logger.debug({ request }, "Simulated request successfully"); - const hash = await this.client.writeContract(request); - - this.logger.info({ hash }, "Price update sent"); - - this.waitForTransactionReceipt(hash); + if (!this.disablePush) { + const hash = await this.client.writeContract(request); + this.logger.info({ hash }, "Price update sent"); + this.waitForTransactionReceipt(hash); + } else { + this.logger.debug("Push disabled, not attempting"); + } } catch (err: any) { this.logger.debug({ err }, "Simulating or sending transactions failed."); @@ -423,4 +433,31 @@ export class EvmPricePusher implements IPricePusher { }); return response.binary.data; } + + async getMedianRecentGasPrice(blockCount = 5): Promise { + this.logger.info({ blockCount }); + const { baseFeePerGas, reward } = await this.client.getFeeHistory({ + blockCount, + rewardPercentiles: [50], + blockTag: "latest", + }); + this.logger.info({ baseFeePerGas, reward }, "feeHistory"); + // remove the next block base fee + const trimmedBaseFees = baseFeePerGas.slice(0, -1) + const gasPrices = trimmedBaseFees.map((base, i) => { + const medianTip = reward?.[i]?.[0] ?? 0n; + return base + medianTip; + }) + this.logger.info({gasPrices}, "gasPrices:"); + + if (gasPrices.length === 0) { + return 0n; + } else { + const sorted = [...gasPrices].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)) + const medianIndex = Math.floor(sorted.length / 2) + const medianPrice = sorted[medianIndex]; + this.logger.info({ medianPrice }, "medianPrice:"); + return medianPrice; + } + } } diff --git a/apps/price_pusher/src/metrics.ts b/apps/price_pusher/src/metrics.ts index abea497d3a..1ebe350213 100644 --- a/apps/price_pusher/src/metrics.ts +++ b/apps/price_pusher/src/metrics.ts @@ -19,6 +19,8 @@ export class PricePusherMetrics { public targetPriceValue: Gauge; // Wallet metrics public walletBalance: Gauge; + // gas price + public gasPrice: Gauge; constructor(logger: Logger) { this.logger = logger; @@ -85,6 +87,13 @@ export class PricePusherMetrics { registers: [this.registry], }); + this.gasPrice = new Gauge({ + name: "pyth_gas_price", + help: "Gas price estimate for this chain", + labelNames: ["network"], + registers: [this.registry], + }); + // Setup the metrics endpoint this.server.get("/metrics", async (req, res) => { res.set("Content-Type", this.registry.contentType); @@ -212,4 +221,20 @@ export class PricePusherMetrics { `Updated wallet balance metric: ${walletAddress} = ${balanceNum}`, ); } + + public updateGasPrice( + network: string, + gasPrice: bigint | number, + ): void { + // Convert to number for compatibility with prometheus + const gasPriceNum = + typeof gasPrice === "bigint" ? Number(gasPrice) : gasPrice; + this.gasPrice.set( + { network }, + gasPriceNum, + ); + this.logger.debug( + `Updated gas price metric: ${network} = ${gasPriceNum}`, + ); + } } From e9649ad9e26483db3800b9c697a385d76f08e0ca Mon Sep 17 00:00:00 2001 From: Mike Rolish Date: Fri, 17 Oct 2025 13:45:28 -0500 Subject: [PATCH 2/2] prettier fixes --- apps/price_pusher/src/evm/command.ts | 3 ++- apps/price_pusher/src/evm/evm.ts | 18 ++++++++++-------- apps/price_pusher/src/metrics.ts | 16 ++++------------ 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/apps/price_pusher/src/evm/command.ts b/apps/price_pusher/src/evm/command.ts index 07903bf94c..f2d0525e59 100644 --- a/apps/price_pusher/src/evm/command.ts +++ b/apps/price_pusher/src/evm/command.ts @@ -82,7 +82,8 @@ export default { type: "number", required: false, default: 1, - } as Options, "disable-push": { + } as Options, + "disable-push": { description: "Dry run without pushing", type: "boolean", required: false, diff --git a/apps/price_pusher/src/evm/evm.ts b/apps/price_pusher/src/evm/evm.ts index b66d0aaeda..4682604878 100644 --- a/apps/price_pusher/src/evm/evm.ts +++ b/apps/price_pusher/src/evm/evm.ts @@ -196,9 +196,9 @@ export class EvmPricePusher implements IPricePusher { this.gasPrice ?? Number( await (this.customGasStation?.getCustomGasPrice() ?? - (this.useRecentGasPriceEstimate - ? this.getMedianRecentGasPrice() - : this.client.getGasPrice())), + (this.useRecentGasPriceEstimate + ? this.getMedianRecentGasPrice() + : this.client.getGasPrice())), ); this.metrics?.updateGasPrice(this.network, gasPrice); @@ -443,18 +443,20 @@ export class EvmPricePusher implements IPricePusher { }); this.logger.info({ baseFeePerGas, reward }, "feeHistory"); // remove the next block base fee - const trimmedBaseFees = baseFeePerGas.slice(0, -1) + const trimmedBaseFees = baseFeePerGas.slice(0, -1); const gasPrices = trimmedBaseFees.map((base, i) => { const medianTip = reward?.[i]?.[0] ?? 0n; return base + medianTip; - }) - this.logger.info({gasPrices}, "gasPrices:"); + }); + this.logger.info({ gasPrices }, "gasPrices:"); if (gasPrices.length === 0) { return 0n; } else { - const sorted = [...gasPrices].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)) - const medianIndex = Math.floor(sorted.length / 2) + const sorted = [...gasPrices].sort((a, b) => + a < b ? -1 : a > b ? 1 : 0, + ); + const medianIndex = Math.floor(sorted.length / 2); const medianPrice = sorted[medianIndex]; this.logger.info({ medianPrice }, "medianPrice:"); return medianPrice; diff --git a/apps/price_pusher/src/metrics.ts b/apps/price_pusher/src/metrics.ts index 1ebe350213..8bfe5658f5 100644 --- a/apps/price_pusher/src/metrics.ts +++ b/apps/price_pusher/src/metrics.ts @@ -222,19 +222,11 @@ export class PricePusherMetrics { ); } - public updateGasPrice( - network: string, - gasPrice: bigint | number, - ): void { + public updateGasPrice(network: string, gasPrice: bigint | number): void { // Convert to number for compatibility with prometheus const gasPriceNum = - typeof gasPrice === "bigint" ? Number(gasPrice) : gasPrice; - this.gasPrice.set( - { network }, - gasPriceNum, - ); - this.logger.debug( - `Updated gas price metric: ${network} = ${gasPriceNum}`, - ); + typeof gasPrice === "bigint" ? Number(gasPrice) : gasPrice; + this.gasPrice.set({ network }, gasPriceNum); + this.logger.debug(`Updated gas price metric: ${network} = ${gasPriceNum}`); } }