Building a Honeypot Scanner That Handles 2M Scans/Day for $0
Source: Dev.to
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...
}
- Buy directly →
tx.origin == msg.sender✅ - Sell via Uniswap →
tx.origin != msg.sender❌
The transaction reverts and 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 (24 h TTL)
│ │ 95 % hit rate
└────────┬────────┘
│
▼
┌─────────────────┐
│ Etherscan API │ Source‑code retrieval
│ (6 keys) │ Rotation for rate limits
└─────────────────┘
Why Cloudflare Workers + KV?
-
Edge Computing = Consistent Latency
Workers run at 300+ edge locations worldwide. Whether you’re in Tokyo or London you get ~2 s response times—no cold starts, no regional bottlenecks. -
KV Caching = Massive Cost Savings
Smart contracts are immutable after deployment, which makes aggressive caching safe:
// 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 h
await env.HONEYPOT_CACHE.put(
address,
JSON.stringify(result),
{ expirationTtl: 86400 }
);
With a 95 % cache‑hit rate we get:
- 100 k Worker requests/day (free tier)
- 100 k KV reads/day (free tier)
→ ≈ 2 M potential scans/day → $0 / month
- Built‑in DDoS Protection
Cloudflare’s edge network automatically mitigates DDoS attacks. When someone tried to spam the API, I didn’t even notice until I checked the logs.
Detection Algorithm
Pattern‑Based Static Analysis
13 regular‑expression patterns are used to spot 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 a threshold of 2?
- Real honeypots typically show 3‑7 patterns.
- Legitimate contracts rarely show > 1 pattern.
- This minimizes false positives while keeping ≈ 98 % sensitivity.
Implementation Details
Input Validation
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 = 8 && addr[i] !== addr[i].toUpperCase()) return false;
if (hashChar controller.abort(), 10_000);
}
Fetch with Timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10_000);
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);
}
Code Sanitization
Comments are stripped and whitespace normalized before pattern matching:
function sanitizeCode(code: string): string {
return code
.replace(/\/\*[\s\S]*?\*\//g, '') // Block comments
.replace(/\/\/.*/g, '') // Line comments
.replace(/\s+/g, ' ') // Collapse whitespace
.trim();
}
Final Thoughts
By leveraging edge computing, global KV caching, and a lightweight static‑analysis engine, HoneypotScan can safely process millions of scans per day at zero cost. The same pattern can be adapted to any on‑chain security‑checking service that needs to operate at massive scale without a budget.
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();
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
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
Parallel Pattern Matching
const matches = await Promise.all(
patterns.map(async (pattern) => ({
pattern: pattern.name,
matched: pattern.regex.test(sourceCode),
}))
);
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' },
});
}
Lazy Loading Components
const ScanHistory = dynamic(() => import('./ScanHistory'), {
ssr: false,
loading: () => ,
});
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 ~2 s response times globally
- ✅ Pattern‑based detection is fast – Regex matching takes < 100 ms
- ✅ Immutable contracts = aggressive caching – No cache invalidation needed
Gotchas
- ❌ KV eventual consistency – Writes take ~60 s to propagate globally
- ❌ 10 ms CPU limit – Had to optimise regex patterns
- ❌ No WebSocket support – Can’t do real‑time updates
- ❌ Cold start on first deploy – Takes ~30 s 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 s average response time
- 95 % cache hit rate
- $0 /month infrastructure cost
- 2 M scans/day capacity on free tier
- 98 % sensitivity, 97 % specificity
Try It Yourself
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 highly scalable.
Questions? Drop them in the comments! 👇