Building a Honeypot Scanner That Handles 2M Scans/Day for $0
I built HoneypotScan to detect malicious crypto tokens that trap your funds. Here's how I architected it to handle massive scale without spending a dime on infrastructure.
The Problem: Honeypot Tokens
A honeypot token is a scam smart contract that lets you buy tokens but blocks you from selling. Scammers exploit the difference between tx.origin and msg.sender in Solidity:
// Malicious code example
function transfer(address to, uint256 amount) public returns (bool) {
require(tx.origin == msg.sender, "No DEX sells allowed");
// Transfer logic...
}
When you buy directly: tx.origin == msg.sender ✅
When you sell via Uniswap: tx.origin != msg.sender ❌
The transaction reverts. Your funds are trapped forever.
Architecture Overview
┌─────────────────┐
│ Next.js 16 │ Frontend (Cloudflare Pages)
│ React 19 │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Cloudflare │ Edge API (300+ locations)
│ Workers │ - Input validation
└────────┬────────┘ - Pattern detection
│ - Response caching
▼
┌─────────────────┐
│ Cloudflare KV │ Global cache (24hr TTL)
└────────┬────────┘ 95% hit rate
│
▼
┌─────────────────┐
│ Etherscan API │ Source code retrieval
│ (6 keys) │ Rotation for rate limits
└─────────────────┘
Why Cloudflare Workers + KV?
1. Edge Computing = Consistent Latency
Workers run at 300+ edge locations globally. Whether you're in Tokyo or London, you get ~2 second response times. No cold starts, no regional bottlenecks.
2. KV Caching = Massive Cost Savings
Smart contracts are immutable after deployment. This means aggressive caching:
// Check cache first
const cached = await env.HONEYPOT_CACHE.get(address);
if (cached) {
return JSON.parse(cached);
}
// Fetch from Etherscan
const sourceCode = await fetchFromEtherscan(address);
// Cache for 24 hours
await env.HONEYPOT_CACHE.put(
address,
JSON.stringify(result),
{ expirationTtl: 86400 }
);
With 95% cache hit rate:
- 100k Worker requests/day (free tier)
- 100k KV reads/day (free tier)
- = 2M potential scans/day
- = $0/month
3. Built-in DDoS Protection
Cloudflare's edge network handles DDoS attacks automatically. When someone tried to spam my API, I didn't even notice until I checked the logs.
Detection Algorithm
Pattern-Based Static Analysis
I use 13 regex patterns to detect honeypot techniques:
const patterns = [
// Core ERC20 abuse
/function\s+balanceOf[^}]*tx\.origin/,
/function\s+allowance[^}]*tx\.origin/,
/function\s+transfer[^}]*tx\.origin/,
// Hidden helpers
/function\s+_taxPayer[^}]*tx\.origin/,
/function\s+_isSuper[^}]*tx\.origin/,
// Auth bypasses
/require\s*\([^)]*tx\.origin/,
/if\s*\([^)]*tx\.origin[^)]*==|!=/,
/assert\s*\([^)]*tx\.origin/,
/\[tx\.origin\]/,
// Transfer blocks
/_isSuper\s*\(\s*recipient\s*\)/,
/_canTransfer[^}]*return\s+false/,
/require\s*\([^)]*_whitelisted\[.*\]\s*&&\s*_whitelisted\[/,
/if\s*\([^)]*isPair\[.*\][^}]*\)\s*{\s*taxAmount\s*=.*\*\s*9[5-9]/
];
Confidence Scoring
const patternCount = patterns.filter(p => p.test(sourceCode)).length;
if (patternCount >= 2) {
return { isHoneypot: true, confidence: 95 };
} else if (patternCount === 1) {
return { isHoneypot: false, confidence: 50, warning: "Suspicious" };
} else {
return { isHoneypot: false, confidence: 100 };
}
Why threshold = 2?
- Real honeypots typically show 3-7 patterns
- Legitimate contracts rarely show >1 pattern
- Minimizes false positives while maintaining 98% sensitivity
Implementation Details
1. Input Validation
Proper EIP-55 checksum validation using keccak256:
import { keccak256 } from '@noble/hashes/sha3';
import { bytesToHex } from '@noble/hashes/utils';
function isValidChecksum(address: string): boolean {
const addr = address.slice(2).toLowerCase();
const hash = bytesToHex(keccak256(addr));
for (let i = 0; i < 40; i++) {
const hashChar = parseInt(hash[i], 16);
if (hashChar >= 8 && addr[i] !== addr[i].toUpperCase()) return false;
if (hashChar < 8 && addr[i] !== addr[i].toLowerCase()) return false;
}
return true;
}
2. API Key Rotation
6 Etherscan API keys with random selection to distribute load:
const API_KEYS = [
env.ETHERSCAN_API_KEY_1,
env.ETHERSCAN_API_KEY_2,
env.ETHERSCAN_API_KEY_3,
env.ETHERSCAN_API_KEY_4,
env.ETHERSCAN_API_KEY_5,
env.ETHERSCAN_API_KEY_6,
];
const randomKey = API_KEYS[Math.floor(Math.random() * API_KEYS.length)];
3. Request Timeout Protection
10-second timeout with AbortController:
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
try {
const response = await fetch(url, { signal: controller.signal });
// Process response...
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('Request timeout');
}
throw error;
} finally {
clearTimeout(timeoutId);
}
4. Code Sanitization
Remove comments and normalize whitespace before pattern matching:
function sanitizeCode(code: string): string {
return code
.replace(/\/\*[\s\S]*?\*\//g, '') // Block comments
.replace(/\/\/.*/g, '') // Line comments
.replace(/\s+/g, ' ') // Normalize whitespace
.trim();
}
Security Features
CORS Whitelist
const ALLOWED_ORIGINS = [
'https://honeypotscan.pages.dev',
'https://www.honeypotscan.com',
'http://localhost:3000',
];
function handleCORS(request: Request): Response | null {
const origin = request.headers.get('Origin');
if (origin && !ALLOWED_ORIGINS.includes(origin)) {
return new Response('Forbidden', { status: 403 });
}
return null;
}
Content Security Policy
const CSP = [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval'",
"style-src 'self' 'unsafe-inline'",
"connect-src 'self' https://honeypotscan-api.teycircoder4.workers.dev",
"frame-ancestors 'none'",
].join('; ');
response.headers.set('Content-Security-Policy', CSP);
Rate Limiting
Simple in-memory rate limiting (resets every minute):
const rateLimits = new Map<string, number>();
function checkRateLimit(ip: string): boolean {
const count = rateLimits.get(ip) || 0;
if (count >= 30) return false;
rateLimits.set(ip, count + 1);
return true;
}
Frontend Features
Share Results via URL Hash
Results encoded in URL hash (no server needed):
function shareResult(result: ScanResult) {
const encoded = btoa(JSON.stringify(result));
const url = `${window.location.origin}#result=${encoded}`;
navigator.clipboard.writeText(url);
}
// On page load
const hash = window.location.hash;
if (hash.startsWith('#result=')) {
const encoded = hash.slice(8);
const result = JSON.parse(atob(encoded));
displayResult(result);
}
Local Scan History
Last 10 scans stored in localStorage:
function saveToHistory(result: ScanResult) {
const history = JSON.parse(localStorage.getItem('scanHistory') || '[]');
history.unshift(result);
history.splice(10); // Keep only last 10
localStorage.setItem('scanHistory', JSON.stringify(history));
}
Export as JSON
function exportResult(result: ScanResult) {
const data = {
scanner: 'HoneypotScan',
version: '1.0',
scannedAt: new Date().toISOString(),
result,
};
const blob = new Blob([JSON.stringify(data, null, 2)], {
type: 'application/json',
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `honeypot-scan-${result.address}.json`;
a.click();
}
Performance Optimizations
1. Parallel Pattern Matching
const matches = await Promise.all(
patterns.map(async (pattern) => ({
pattern: pattern.name,
matched: pattern.regex.test(sourceCode),
}))
);
2. Early Exit on Cache Hit
// Check cache before any processing
const cached = await env.HONEYPOT_CACHE.get(address);
if (cached) {
return new Response(cached, {
headers: { 'Content-Type': 'application/json' },
});
}
3. Lazy Loading Components
const ScanHistory = dynamic(() => import('./ScanHistory'), {
ssr: false,
loading: () => <Skeleton />,
});
Deployment
Cloudflare Workers
# Install Wrangler CLI
npm install -g wrangler
# Login to Cloudflare
wrangler login
# Deploy
wrangler deploy
Environment Variables
# wrangler.toml
name = "honeypotscan-api"
main = "src/worker.ts"
compatibility_date = "2024-01-01"
[[kv_namespaces]]
binding = "HONEYPOT_CACHE"
id = "your-kv-namespace-id"
[vars]
ETHERSCAN_API_KEY_1 = "your-key-1"
ETHERSCAN_API_KEY_2 = "your-key-2"
# ... more keys
Cloudflare Pages
# Build Next.js app
npm run build
# Deploy to Pages
npx wrangler pages deploy out
Lessons Learned
What Worked Well
✅ KV caching is magic - 95% hit rate = massive cost savings
✅ Edge computing eliminates latency issues - Consistent 2s response times globally
✅ Pattern-based detection is fast - Regex matching takes <100ms
✅ Immutable contracts = aggressive caching - No cache invalidation needed
Gotchas
❌ KV eventual consistency - Writes take ~60s to propagate globally
❌ 10ms CPU limit - Had to optimize regex patterns
❌ No WebSocket support - Can't do real-time updates
❌ Cold start on first deploy - Takes ~30s for global propagation
What I'd Do Differently
- Add WebAssembly for faster pattern matching
- Implement machine learning for novel pattern detection
- Build a feedback loop to improve patterns over time
- Add support for more chains (BSC, Avalanche, etc.)
Results
- 2 second average response time
- 95% cache hit rate
- $0/month infrastructure cost
- 2M scans/day capacity on free tier
- 98% sensitivity, 97% specificity
Try It Yourself
Live: honeypotscan.pages.dev
Source: github.com/Teycir/honeypotscan
The entire stack is open source. Feel free to fork, modify, or use it as a reference for your own edge computing projects.
Conclusion
Cloudflare Workers + KV is perfect for:
- High read, low write workloads
- Global low-latency requirements
- Projects that need to start free and scale
- Immutable data (aggressive caching)
For HoneypotScan, it's been the ideal architecture. Fast, free, and I haven't thought about infrastructure once since deploying.
Questions? Drop them in the comments! 👇
Top comments (0)