Building production bots on free APIs sounds cool in theory. In practice, you hit walls immediately. Here are the 5 real errors I encountered while building two Ethereum monitoring bots, and exactly how I fixed them.
Error 1: Alchemy Event Listeners Timeout After 5 Minutes
The Problem:
I started monitoring Aave liquidations using ethers.js event listeners on free-tier Alchemy.
provider.on(filter, (log) => {
console.log("Liquidation detected:", log);
});
Worked perfectly for 5 minutes. Then the WebSocket died. No error message. Just silence. Listener stopped working forever.
Why it happens:
Free-tier Alchemy (and most providers) have timeout limits on WebSocket connections. Idle connections get dropped. No reconnect logic on the free tier.
The Fix:
Stop using event listeners. Use polling instead.
async function pollLiquidations() {
const currentBlock = await provider.getBlockNumber();
const logs = await provider.getLogs({
address: AAVE_LENDING_POOL,
topics: [LIQUIDATION_CALL_TOPIC],
fromBlock: currentBlock - 9,
toBlock: currentBlock
});
logs.forEach(log => handleLiquidation(log));
}
// Poll every 15 minutes
setInterval(pollLiquidations, 15 * 60 * 1000);
The lesson: On free tier, reliability > real-time. Polling every 15 minutes that works beats real-time listeners that disconnect.
Error 2: CoinGecko Rate Limit 429 (Too Many Requests)
The Problem:
I wanted to track 5 tokens: UNI, AAVE, USDC, DAI, USDT.
const tokens = ['UNI', 'AAVE', 'USDC', 'DAI', 'USDT'];
async function getPrices() {
const prices = await Promise.all(
tokens.map(token =>
axios.get(`https://api.coingecko.com/api/v3/simple/price?ids=${token}...`)
)
);
}
// Check every 15 minutes
setInterval(getPrices, 15 * 60 * 1000);
After a day, I got slammed with 429 errors. CoinGecko rate-limited me. Price alerts stopped working.
Why it happens:
Free-tier CoinGecko allows ~5 requests per second, but daily limits kick in after 50 concurrent calls per minute. 5 tokens × 96 checks/day = 480 calls. You hit the limit.
The Fix:
Reduce frequency OR reduce tokens. I chose frequency.
const tokens = ['UNI', 'AAVE']; // 2 tokens only
// Check every 2 hours (not 15 mins)
// 2 tokens × 12 checks/day = 24 calls. Under limit.
setInterval(getPrices, 2 * 60 * 60 * 1000);
Still catches all meaningful moves (5%+ happens in hours, not minutes).
The lesson: APIs are precious on free tier. Reduce scope, not reliability. 2 tokens checked reliably > 5 tokens checked intermittently.
Error 3: Supabase RLS "New Row Violates Row-Level Security Policy"
The Problem:
Bots ran fine for 2 days. Then suddenly:
Supabase error: new row violates row-level security policy for table "price_history"
Both bots stopped writing data. Data collection halted. Silent failure.
Why it happens:
I had Row-Level Security (RLS) enabled but forgot to disable it for anonymous (public) access. When the bots tried to INSERT with anon key, RLS rejected them.
// This would fail
const { error } = await supabase
.from('price_history')
.insert([{ symbol: 'UNI', price: 5.94 }])
The Fix:
Disable RLS on the tables (if you're the only user):
ALTER TABLE price_history DISABLE ROW LEVEL SECURITY;
ALTER TABLE liquidations DISABLE ROW LEVEL SECURITY;
Or enable RLS but create a policy:
CREATE POLICY "Enable insert for all" ON price_history
FOR INSERT WITH CHECK (true);
The lesson: RLS is security theater on hobby projects. Disable it for free tier. Enable it when you have real users.
Error 4: "Cannot Find Module" In Termux File Creation
The Problem:
I created a data analysis script but it wouldn't run:
$ node data-analysis.js
Error: Cannot find module '/data/data/com.termux/files/home/data-analysis.js'
File seemed to exist, but Node couldn't find it. Turned out the file never actually created properly.
Why it happens:
When copying files to Termux , file paths get weird. The tool thinks it created the file, but Termux didn't actually receive it.
The Fix:
Create files directly in Termux using nano:
nano data-analysis.js
Paste content, save with Ctrl+X → Y → Enter.
Verify with:
ls ~/*.js
The lesson: Don't rely on external file creation tools for Termux. Nano is slower but reliable.
Error 5: Can't Exit tmux On Mobile Keyboard
The Problem:
I attach to a tmux session to check bot output:
tmux attach -t price-alert
See the output, want to exit. Try Ctrl+B then D. Nothing happens. Keyboard doesn't register the key combo properly on mobile.
Stuck in the session. No escape.
Why it happens:
Mobile on-screen keyboards don't register modifier key combos well (Ctrl+B, Alt+X, etc). Long-press + tap doesn't work reliably.
The Fix:
Don't attach at all. Use capture-pane instead:
tmux capture-pane -t price-alert -p
Shows last output without entering the session. No exit needed. No stuck state.
For killing a session if you DO get stuck:
# In a new terminal
tmux kill-session -t price-alert
The lesson: Avoid interactive sessions on mobile. Use read-only commands instead.
The Pattern
All 5 errors follow the same theme:
Free tier + unconventional setup (mobile, no servers) = constraints.
The fix isn't "pay more" or "use better tools." It's work within constraints:
- Event listeners die → use polling
- Rate limits hit → reduce scope
- RLS blocks → disable it
- File creation fails → use native tools
- Keyboard combos don't work → avoid them
That's the real lesson. Not the specific fixes, but the mindset: I'm not building for ideal conditions. I'm building for free APIs, free databases, and a phone running Linux.
Ship anyway.
Running on: Termux (Android), free-tier Alchemy, free-tier CoinGecko, Supabase free tier, tmux
Timeline: Sept 1-12, 2026. Two weeks into production.
Top comments (1)
The polling-over-listeners conclusion is right on free tiers, but the failure mode to watch for is that a dropped listener, a silent throttle and 'nothing happened today' all look identical in the logs. We run the same shape on a VPS with one addition: a 'last successful poll' gauge that alerts when it goes stale, instead of trusting that a missing row means a missing event.
On RLS I'd push back slightly. What actually bit you was a policy change landing with no write test behind it, and RLS was the only thing that surfaced it. Disabling it makes the insert work and removes your one signal. Cheaper trade: keep RLS, add the INSERT policy, and put a canary write in the startup path so policy drift fails loudly at boot rather than quietly two days later.