On 2 November 2026 the Brazilian exchange Mercado Bitcoin paused deposits and withdrawals after funds moved out of wallets linked to it. According to CertiK, the attacker spent about four hours on eight smaller chains, including Base, Optimism and Arbitrum, before reaching the central hot wallets. Hypernative estimated the total outflow at about $40 million.
Four hours on side chains is a long window. If your alerting only watches the busiest wallet, that's time you don't see. Here's a small script that watches outgoing native transfers from a list of addresses on several EVM chains and flags anything over a threshold.
It uses only Node 18+ (built-in fetch) and public JSON-RPC endpoints. Swap in your own RPC URLs and addresses.
// outflow-watch.mjs (run: node outflow-watch.mjs) const CHAINS = { base: 'https://mainnet.base.org', optimism: 'https://mainnet.optimism.io', arbitrum: 'https://arb1.arbitrum.io/rpc', }; // addresses you operate (lowercase) const WATCH = new Set([ '0x0000000000000000000000000000000000000000', ]); const THRESHOLD_ETH = 0.5; async function rpc(url, method, params) { const r = await fetch(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }), }); const j = await r.json(); if (j.error) throw new Error(`${method}: ${j.error.message}`); return j.result; } const last = {}; async function scan(name, url) { const head = parseInt(await rpc(url, 'eth_blockNumber', []), 16); const from = last[name] ?? head; // start at the head on first run for (let n = from + 1; n <= head; n++) { const block = await rpc(url, 'eth_getBlockByNumber', ['0x' + n.toString(16), true]); for (const tx of block.transactions) { if (!tx.from || !WATCH.has(tx.from.toLowerCase())) continue; const eth = Number(BigInt(tx.value)) / 1e18; if (eth >= THRESHOLD_ETH) { console.log(`[ALERT] ${name} block ${n}: ${eth} ETH from ${tx.from} to ${tx.to} (${tx.hash})`); } } } last[name] = head; } async function loop() { for (const [name, url] of Object.entries(CHAINS)) { try { await scan(name, url); } catch (e) { console.error(name, e.message); } } } await loop(); setInterval(loop, 15_000); A few notes on taking this further:
- Native transfers are only part of it. ERC-20 outflows show up as
Transferlogs, so add aneth_getLogscall filtered on theTransfertopic with your addresses as thefromtopic. - Fast chains produce a lot of blocks. For production, use
eth_getLogsover ranges, or a provider's webhook, rather than pulling full blocks one at a time. - Alert on rate as well as size. Many small transfers from many addresses can matter more than one big one. In this incident, CryptoTicker reported small amounts swept from about 27,160 addresses into one.
- Give every chain the same thresholds and on-call routing. The smaller chain is often where nobody is looking.
What does your team monitor on the chains that see the least traffic?
Sources: CryptoTicker (citing CertiK and Cointelegraph), CriptoNoticias and Tecnoblog, all 2 Nov 2026.