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
- Historical transaction analysis
- Optimal vs actual gas comparison
- Telegram alerts for cheap gas
- 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
};
};
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;
}
}
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);
};
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
});
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>
);
};
Key Learnings
- UX matters more than features - Simple visualization beat complex analytics
- Trust is critical - Read-only wallet access still scares users
- Education is hard - Many users don't believe timing matters
- 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
- Multi-chain support (BNB, Polygon)
- Predictive modeling
- Browser extension
- 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)