DEV Community

Cover image for Building a Gas Optimization Tool: Technical Deep Dive
Gentian
Gentian

Posted on

Building a Gas Optimization Tool: Technical Deep Dive

Ethereum gas prices follow predictable daily patterns, but most users have no visibility into how much poor timing costs them. Here's how I built a tool to solve this problem.

The Problem

Ethereum gas prices vary dramatically throughout the day:

  • Peak hours (2-6pm EST): 40-100 Gwei
  • Off-peak (nights/weekends): 5-15 Gwei

Most users transact when convenient for them, not when economically optimal. Testing shows users commonly overpay 60-80% due to timing alone.

The Solution: GasGuard

A tool that analyzes wallet history and shows gas overpayment due to poor timing.

Core Features

  1. Historical transaction analysis
  2. Optimal vs actual gas comparison
  3. Telegram alerts for cheap gas
  4. Pattern visualization

Technical Implementation

Tech Stack

  • Frontend: React + Vite + TailwindCSS
  • Backend: Node.js + Express
  • Database: MongoDB
  • Web3: ethers.js
  • Notifications: Telegram Bot API

Architecture

// Simplified gas analysis flow
const analyzeTransaction = async (tx) => {
  // Get historical gas prices for that day
  const dailyGasPrices = await getHistoricalGas(tx.timestamp);

  // Find optimal (minimum) gas that day
  const optimalGas = Math.min(...dailyGasPrices);

  // Calculate overpayment
  const overpayment = tx.gasPrice - optimalGas;
  const overpaymentPercent = (overpayment / optimalGas) * 100;

  return {
    ...tx,
    optimalGas,
    overpayment,
    overpaymentPercent
  };
};
Enter fullscreen mode Exit fullscreen mode

Key Technical Challenges

1. Etherscan API Rate Limiting

Problem: Etherscan limits to 5 calls/second.

Solution: Implemented a queue with exponential backoff:

class RateLimitedQueue {
  constructor(maxPerSecond = 5) {
    this.queue = [];
    this.processing = false;
    this.maxPerSecond = maxPerSecond;
  }

  async add(fn) {
    return new Promise((resolve, reject) => {
      this.queue.push({ fn, resolve, reject });
      if (!this.processing) this.process();
    });
  }

  async process() {
    this.processing = true;
    while (this.queue.length > 0) {
      const batch = this.queue.splice(0, this.maxPerSecond);
      await Promise.all(batch.map(async ({ fn, resolve, reject }) => {
        try {
          const result = await fn();
          resolve(result);
        } catch (error) {
          reject(error);
        }
      }));
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
    this.processing = false;
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Historical Gas Data

Problem: Historical gas prices before 2021 are sparse.

Solution: Statistical interpolation based on block difficulty and network congestion.

3. Real-Time Notifications

Problem: WebSocket connections expensive at scale.

Solution: Telegram Bot API for push notifications instead.

// Telegram alert when gas drops
const sendGasAlert = async (userId, gasPrice) => {
  const message = `⛽ Gas Alert!\n\nGas is now ${gasPrice} Gwei - good time to transact!`;
  await bot.sendMessage(userId, message);
};
Enter fullscreen mode Exit fullscreen mode

Database Schema

// User preferences
const userSchema = new Schema({
  walletAddress: { type: String, required: true, unique: true },
  telegramId: String,
  gasThreshold: { type: Number, default: 10 },
  alertsEnabled: { type: Boolean, default: false }
});

// Cached gas prices
const gasPriceSchema = new Schema({
  timestamp: { type: Date, required: true },
  price: { type: Number, required: true },
  source: String
});

// Analyzed transactions
const transactionSchema = new Schema({
  hash: { type: String, required: true, unique: true },
  walletAddress: String,
  gasPaid: Number,
  optimalGas: Number,
  overpayment: Number,
  overpaymentPercent: Number,
  timestamp: Date
});
Enter fullscreen mode Exit fullscreen mode

Frontend: Making It User-Friendly

The challenge was presenting complex data simply.

Transaction List Component

const TransactionList = ({ transactions }) => {
  return (
    <div className="space-y-3">
      {transactions.map(tx => (
        <div key={tx.hash} className="p-4 rounded-xl bg-card">
          <div className="flex justify-between items-center">
            <span className="font-mono text-sm">
              {tx.hash.slice(0, 10)}...{tx.hash.slice(-8)}
            </span>
            <span className={`px-2 py-1 rounded text-xs font-medium ${
              tx.overpaymentPercent < 30 
                ? 'bg-green-500/20 text-green-400'
                : 'bg-red-500/20 text-red-400'
            }`}>
              {tx.overpaymentPercent < 30 ? 'Good' : 'Poor'} - {tx.overpaymentPercent.toFixed(0)}%
            </span>
          </div>
          <div className="mt-2 text-sm">
            Paid: {tx.gasPaid.toFixed(2)} (Opt: {tx.optimalGas.toFixed(2)})
          </div>
        </div>
      ))}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Key Learnings

  1. UX matters more than features - Simple visualization beat complex analytics
  2. Trust is critical - Read-only wallet access still scares users
  3. Education is hard - Many users don't believe timing matters
  4. Telegram > Email - Much higher engagement with Telegram alerts

Try It

Live at: https://gasguard.gen-a.dev

Source code considerations: Thinking about open-sourcing the analysis engine. Thoughts?

Tech Stack Summary

  • React for UI
  • Node.js backend
  • MongoDB for caching
  • ethers.js for Web3
  • Telegram Bot API for notifications

Next Steps

  1. Multi-chain support (BNB, Polygon)
  2. Predictive modeling
  3. Browser extension
  4. Open-source analysis library

Questions? Happy to discuss the technical implementation or help if you're building something similar!

Follow the project: https://x.com/asani_gentian

Top comments (0)