EVM Gas Oracle
Overview
The **EVM Gas Oracle** is a specialized component designed to estimate gas fees for Ethereum and EVM-compatible blockchains (e.g., Avalanche, Polygon, Optimism). Gas fees are critical for users and applications to understand the transaction costs on these networks and to set appropriate fees for timely inclusion in blocks.
This module exists to solve the problem of accurately estimating dynamic gas fees by continuously tracking recent block and transaction gas prices on the blockchain. It provides fee recommendations at different percentiles (e.g., slow, average, fast) by analyzing historical data, enabling client applications and APIs to suggest reasonable gas prices to end users.
Core Concept and Purpose
Gas Fee Estimation Challenge: Gas fees on EVM chains fluctuate based on network congestion and block demand. Users need reliable fee estimates to avoid overpaying or having transactions stuck.
Dynamic Oracle: The Gas Oracle maintains an up-to-date cache of gas prices from recent blocks, including base fees and priority fees, factoring in chain-specific behaviors.
Multi-Chain Support: It supports multiple EVM-compatible chains, adjusting parameters like block tags and fee buffering based on chain-specific RPC behaviors.
The oracle aggregates transaction gas prices and priority fees from recent blocks, cleans the data, and computes percentile-based fee estimates. This approach balances responsiveness with historical data smoothing.
How the Gas Oracle Works
Initialization
On creation, the oracle is configured with:
A blockchain client (
PublicClient) to query blocks.The target coinstack chain name.
Number of recent blocks to track (default 20).
Logger for diagnostics.
Chain-specific parameters are set, such as:
Which block tag to query (
latestorpending).Whether to apply a buffer multiplier to base fees.
Whether querying pending blocks by height is supported.
Data Collection
The oracle begins by querying the most recent block height, then loads detailed block data for the last N blocks.
Each block's transactions are inspected to extract:
Gas prices paid by transactions.
Max priority fees where applicable.
The block's base fee per gas.
Blocks and their extracted fees are stored in a
Mapkeyed by block number.
Continuous Update
The oracle listens for new block notifications (via WebSocket or other means).
New blocks are enqueued and processed sequentially.
For each new block, fees are extracted and stored, updating the oracle's internal state.
The oracle synchronizes its state by verifying it has the latest N blocks and prunes outdated blocks.
Fee Estimation
Clients request fee estimates by specifying desired percentiles (e.g., 1%, 60%, 90%).
The oracle:
Syncs its state to ensure data freshness.
Selects recent blocks to analyze.
Filters extreme outliers using a calculated threshold based on median values.
Calculates average gas price and max priority fee at each requested percentile.
Builds fee estimates including:
gasPrice(legacy or fallback).maxFeePerGasandmaxPriorityFeePerGas(for EIP-1559).
Applies base fee buffering if configured for the chain.
Interaction with Other System Components
The Gas Oracle is instantiated and used primarily by the EVM API Service (
node/coinstacks/common/api/src/evm/service.ts).The service uses the oracle to provide gas fee information in its API endpoints:
The
getGasFees()method calls the oracle's estimateFees() to retrieve current fee estimates.
The oracle depends on a blockchain RPC client (
viem'sPublicClient) for querying blocks and base fee data.The oracle listens for new blocks, which can be delivered via WebSocket events from the indexer or node layer, ensuring prompt updates.
The oracle's fee estimates support transaction fee suggestions for sending transactions (
sendTx) and gas estimation (estimateGas) workflows in the service.
Important Concepts and Design Patterns
Chain-Specific Behavior Encapsulation
The oracle configures chain-specific parameters in its constructor, such as:
Whether to buffer base fees (e.g., Optimism doubles the base fee).
Which block tag to query (
latestorpending).Whether pending blocks can be queried by block height.
This approach isolates chain peculiarities in one place, making the oracle adaptable to multiple EVM chains.
Data Structures and Algorithms
Sliding Window of Blocks: Maintains a map of recent blocks and their fees, pruning older data to keep memory bounded.
Percentile-Based Estimation: Uses statistical percentile calculations to generate fee estimates, providing conservative and optimistic fee levels.
Outlier Filtering: Implements a threshold function to exclude gas prices that are extreme outliers, improving estimate stability.
Resilience and Retry Logic
The oracle employs retry logic with exponential backoff when fetching block data to handle temporary RPC failures.
It logs errors but continues operation to avoid crashing.
Event-Driven Updates
New block events enqueue blocks for sequential processing, ensuring serialized updates and consistent internal state.
Code References and Examples
Oracle Initialization and Chain Configuration
constructor(args: GasOracleArgs) {
// ...
switch (args.coinstack) {
case 'avalanche':
this.baseFeeBuffer = false
this.latestBlockTag = 'latest'
this.canQueryPendingBlockByHeight = true
break
case 'optimism':
this.baseFeeBuffer = true
this.latestBlockTag = 'latest'
this.canQueryPendingBlockByHeight = true
break
// ... other chains
default:
throw new Error(`no coinstack support for: ${args.coinstack}`)
}
}
Fee Estimation Method
The `estimateFees` method calculates fee estimates at requested percentiles:
async estimateFees(percentiles: Array<number>, blockCount = 20): Promise<EstimatedFees> {
await this.sync()
const blockFees = Array.from(this.feesByBlock.entries())
.sort(([blockA], [blockB]) => Number(blockB) - Number(blockA))
.slice(0, blockCount)
.map(([, fees]) => fees)
const estimatedFees = percentiles.reduce<EstimatedFees>((estimatedFees, percentile) => {
const { gasPrice, maxPriorityFee } = this.averageAtPercentile(blockFees, percentile)
estimatedFees[percentile.toString()] = {
gasPrice: gasPrice.toFixed(0),
...(this.baseFeePerGas && {
maxFeePerGas: (maxPriorityFee + Number(this.baseFeePerGas) * (this.baseFeeBuffer ? 2 : 1)).toFixed(0),
maxPriorityFeePerGas: maxPriorityFee.toFixed(0),
}),
}
return estimatedFees
}, {})
return estimatedFees
}
Integration in EVM Service
The service class uses the gas oracle to provide fee info:
async getGasFees(): Promise<GasFees> {
const baseFeePerGas = this.gasOracle.getBaseFeePerGas()
const estimatedFees = await this.gasOracle.estimateFees([1, 60, 90])
return {
baseFeePerGas,
slow: estimatedFees['1'],
average: estimatedFees['60'],
fast: estimatedFees['90'],
}
}
Visual Diagram: Flow of Gas Fee Estimation
sequenceDiagram
participant Client
participant APIService
participant GasOracle
participant BlockchainRPC
Note over GasOracle: Initialization (fetch recent blocks)
Client->>APIService: Request gas fee estimates
APIService->>GasOracle: estimateFees([1,60,90])
GasOracle->>GasOracle: sync internal state (fetch missing blocks)
GasOracle->>BlockchainRPC: getBlock(blockNumber or tag)
BlockchainRPC-->>GasOracle: Block data with tx fees
GasOracle->>GasOracle: update internal fee data
GasOracle-->>APIService: Estimated fees at requested percentiles
APIService-->>Client: Return gas fee estimates
Summary
The **EVM Gas Oracle** is a vital module that continuously monitors recent blocks on Ethereum and compatible chains to generate accurate gas fee estimates. It addresses the challenges of dynamic gas pricing by applying statistical analysis to recent transaction fees, supporting multiple chains with chain-specific configurations. The oracle integrates tightly with the API service, enabling responsive and reliable fee estimation for transaction submission workflows.