gasOracle.ts


Overview

The `gasOracle.ts` file implements a **Gas Oracle** for Ethereum Virtual Machine (EVM) compatible blockchains. Its main purpose is to collect, analyze, and estimate gas fees based on recent blockchain data to provide accurate and dynamic gas fee recommendations.

Gas fees on EVM chains fluctuate with network demand, and this module continuously tracks recent blocks, extracting gas prices and priority fees from transactions to generate percentile-based fee estimates (e.g., slow, average, fast). It supports multiple chains (Ethereum, Avalanche, Polygon, Optimism, etc.) with chain-specific adaptations such as different RPC querying behaviors and fee buffering.

The Gas Oracle maintains an internal sliding window cache of recent block fee data, synchronizes with the blockchain state, and updates dynamically as new blocks arrive (e.g., via WebSocket events). It provides methods to start the oracle, process new blocks, estimate fees at requested percentiles, and expose the current base fee per gas.


Classes and Interfaces

Interfaces


Class: GasOracle

The core class responsible for managing gas fee data collection and estimation.

class GasOracle {
  // Properties
  public readonly coinstack: string
  private readonly totalBlocks: number
  private readonly logger: Logger
  private readonly client: PublicClient
  private feesByBlock: Map<string, BlockFees>
  private newBlocksQueue: NewBlock[]
  private baseFeePerGas?: string
  private baseFeeBuffer: boolean
  private latestBlockTag: BlockTag
  private canQueryPendingBlockByHeight: boolean

  // Constructor
  constructor(args: GasOracleArgs)

  // Methods
  async start(): Promise<void>
  getBaseFeePerGas(): string | undefined
  async estimateFees(percentiles: number[], blockCount?: number): Promise<EstimatedFees>
  async onBlock(newBlock: NewBlock): Promise<void>
  async processBlocks(): Promise<void>

  // Private methods
  private async update(blockNumber: number, blockTag?: BlockTag, retryCount?: number): Promise<void>
  private async sync(): Promise<void>
  private averageAtPercentile(blockFees: BlockFees[], percentile: number): { gasPrice: number, maxPriorityFee: number }
}

Constructor

constructor(args: GasOracleArgs)

**Example:**

const gasOracle = new GasOracle({
  logger: myLogger,
  client: viemClient,
  coinstack: 'ethereum',
  totalBlocks: 30,
})

Method: start

async start(): Promise<void>

Method: getBaseFeePerGas

getBaseFeePerGas(): string | undefined

Method: estimateFees

async estimateFees(percentiles: number[], blockCount = 20): Promise<EstimatedFees>
const fees = await gasOracle.estimateFees([1, 60, 90])
console.log(fees['60']) // fee estimate at 60th percentile

Method: onBlock

async onBlock(newBlock: NewBlock): Promise<void>

Method: processBlocks

async processBlocks(): Promise<void>

Private Method: update

private async update(blockNumber: number, blockTag?: BlockTag, retryCount = 0): Promise<void>

Private Method: sync

private async sync(): Promise<void>

Private Method: averageAtPercentile

private averageAtPercentile(blockFees: BlockFees[], percentile: number): { gasPrice: number, maxPriorityFee: number }

Utility Function: getFeeThreshold

const getFeeThreshold = (arr: number[]): number => {
  const magnitudeThreshold = 10
  arr = arr.sort((a, b) => a - b)
  const mid = Math.floor(arr.length / 2)
  const median = arr.length % 2 === 0 ? (arr[mid - 1] + arr[mid]) / 2 : arr[mid]
  return median * magnitudeThreshold
}

Important Implementation Details and Algorithms


Interaction with Other System Components


Visual Diagram: Class Structure of GasOracle

classDiagram
    class GasOracle {
        +coinstack: string
        -totalBlocks: number
        -logger: Logger
        -client: PublicClient
        -feesByBlock: Map<string, BlockFees>
        -newBlocksQueue: NewBlock[]
        -baseFeePerGas?: string
        -baseFeeBuffer: boolean
        -latestBlockTag: BlockTag
        -canQueryPendingBlockByHeight: boolean
        +constructor(args: GasOracleArgs)
        +start(): Promise<void>
        +getBaseFeePerGas(): string | undefined
        +estimateFees(percentiles: number[], blockCount?: number): Promise<EstimatedFees>
        +onBlock(newBlock: NewBlock): Promise<void>
        +processBlocks(): Promise<void>
        -update(blockNumber: number, blockTag?: BlockTag, retryCount?: number): Promise<void>
        -sync(): Promise<void>
        -averageAtPercentile(blockFees: BlockFees[], percentile: number): { gasPrice: number, maxPriorityFee: number }
    }

Usage Example

import { GasOracle } from './gasOracle'
import { Logger } from '@shapeshiftoss/logger'
import { PublicClient } from 'viem'

// Initialize dependencies
const logger = new Logger({ level: 'info' })
const client = new PublicClient({ /* ...config... */ })

// Create oracle instance for Ethereum
const gasOracle = new GasOracle({
  logger,
  client,
  coinstack: 'ethereum',
  totalBlocks: 20,
})

// Start the oracle
await gasOracle.start()

// Subscribe to new blocks (e.g., via WebSocket)
async function onNewBlock(newBlock: NewBlock) {
  await gasOracle.onBlock(newBlock)
}

// Estimate fees at 10th, 50th and 90th percentiles
const fees = await gasOracle.estimateFees([10, 50, 90])
console.log('Fee estimates:', fees)

// Get current base fee per gas
const baseFee = gasOracle.getBaseFeePerGas()
console.log('Current base fee per gas:', baseFee)

Summary

The `gasOracle.ts` file implements a robust and extensible gas fee oracle for EVM-compatible blockchains. It continuously ingests recent block data, extracts transaction fees, filters outliers, and calculates percentile-based gas fee estimates. Through chain-specific adaptations, retry logic, and event-driven block processing, it provides reliable and up-to-date gas price recommendations critical for transaction fee management in blockchain applications.

This module integrates with blockchain RPC clients and event sources, serving as a foundational component for higher-level API services and user-facing wallets that require dynamic gas fee estimation.


Appendix: Core Workflow Flowchart

flowchart TD
  Start[Start Oracle] --> FetchBlocks[Fetch Latest N Blocks]
  FetchBlocks --> ExtractFees[Extract Tx Gas Prices & Priority Fees]
  ExtractFees --> UpdateCache[Update Fee Cache per Block]
  UpdateCache --> SyncState[Sync & Prune Cache]
  SyncState --> Estimate[Estimate Fees at Requested Percentiles]
  Estimate --> ReturnFees[Return Fee Estimates]
  ReturnFees --> AwaitNewBlocks[Wait for New Blocks]
  AwaitNewBlocks -->|New Block via WebSocket| EnqueueBlock[Enqueue New Block]
  EnqueueBlock --> ProcessBlock[Process Next Block]
  ProcessBlock --> UpdateCache
  ProcessBlock --> SyncState
  SyncState --> Estimate

This diagram illustrates the continuous cycle of data collection, caching, synchronization, and fee estimation in the Gas Oracle.


If you need further clarification or examples, feel free to ask!