<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[SmartAlert]]></title><description><![CDATA[SmartAlert]]></description><link>https://smartalert.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6adbdcb39d064b5ab1b4ea35/f38a9ad8-dde2-4a0e-8ad5-08707208cb0b.png</url><title>SmartAlert</title><link>https://smartalert.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 04 Nov 2026 11:08:44 GMT</lastBuildDate><atom:link href="https://smartalert.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Migrating off OpenZeppelin Defender Monitor: what replaces each kind of monitor]]></title><description><![CDATA[Defender shut down July 1, 2026. How each Defender monitor maps to OpenZeppelin Monitor, Tenderly, Forta and SmartAlert, plus a migration checklist.]]></description><link>https://smartalert.hashnode.dev/migrating-off-openzeppelin-defender-monitor-what-replaces-each-kind-of-monitor</link><guid isPermaLink="true">https://smartalert.hashnode.dev/migrating-off-openzeppelin-defender-monitor-what-replaces-each-kind-of-monitor</guid><category><![CDATA[Ethereum]]></category><category><![CDATA[Web3]]></category><category><![CDATA[Security]]></category><category><![CDATA[monitoring]]></category><dc:creator><![CDATA[Aleksei Nazarov]]></dc:creator><pubDate>Thu, 29 Oct 2026 11:40:03 GMT</pubDate><content:encoded><![CDATA[<p>OpenZeppelin closed new Defender sign-ups on June 30, 2025 and retired the hosted service on July 1, 2026. If your contracts were watched by Defender Monitor, those alerts have stopped. The service is off, so there is nothing left to export: every monitor has to be rebuilt by hand in whatever comes next.</p>
<p>This post compares four places a Defender Monitor setup can move to: OpenZeppelin's own open-source Monitor, Tenderly Alerts, Forta, and SmartAlert. It covers monitoring only. Relayer, Actions and Deploy need separate answers.</p>
<p><em>Disclosure: I build SmartAlert, one of the four options. I've tried to keep the comparison to what each project documents, and to say where SmartAlert is the wrong choice.</em></p>
<h2>The four options in one paragraph each</h2>
<p><strong>OpenZeppelin Monitor</strong> is OpenZeppelin's recommended path. It is open source (AGPL-3.0), written in Rust, and you run it yourself in Docker or as a binary. Networks, monitors and triggers live in JSON config files. It matches on events, function calls and transactions, can run filter scripts in Python, JavaScript or Bash, and notifies Slack, Discord, Telegram, email, webhooks or a custom script. It supports EVM chains, Stellar and Solana. OpenZeppelin also offers a managed version on request.</p>
<p><strong>Tenderly Alerts</strong> is part of the Tenderly platform (SaaS). It has twelve trigger types, among them event emitted, event parameter, function call, failed transaction, ERC-20 transfer, balance change, state change and view function. Alerts go to email, Slack, Telegram, Discord, webhooks, PagerDuty or Sentry, and an alert can trigger a Tenderly Web3 Action, which is the closest thing to Defender's Monitor → Action pairing.</p>
<p><strong>Forta</strong> is a different model: detection bots, written with the Forta SDK in JavaScript/TypeScript or Python, run on a network of scan nodes, and you consume their alerts via API. Forta's newer product, Forta Firewall, screens transactions before they execute rather than alerting after. It fits teams that want threat detection or pre-execution blocking more than a 1:1 rebuild of their Defender monitors.</p>
<p><strong>SmartAlert</strong> is a hosted monitor for Ethereum, BNB Chain, Base and Arbitrum. You paste a contract address; it reads the ABI and the contract's recent history and suggests rules. Before you turn a rule on, it shows where that rule would have fired on past data. Thresholds can be fixed or compared with the same hour on previous days. Alerts go to Telegram, Slack, Discord, PagerDuty, email or webhooks. It has a free plan; paid plans are $30 and $99 per month.</p>
<h2>Defender monitor → replacement</h2>
<table>
<thead>
<tr>
<th>What you had in Defender</th>
<th>OpenZeppelin Monitor</th>
<th>Tenderly Alerts</th>
<th>Forta</th>
<th>SmartAlert</th>
</tr>
</thead>
<tbody><tr>
<td>Event condition</td>
<td>Event match in config</td>
<td>Event Emitted / Event Parameter</td>
<td>Write or use a detection bot</td>
<td>Event rule, ready sets for ERC-20, Ownable, Pausable, AccessControl, proxies, Safe</td>
</tr>
<tr>
<td>Function condition</td>
<td>Function match in config</td>
<td>Function Call</td>
<td>Detection bot</td>
<td>Calls by sender: "Allowed senders" rule; view-function answers: "Contract state" rule</td>
</tr>
<tr>
<td>Transaction status or value</td>
<td>Transaction match</td>
<td>Successful / Failed Transaction, Transaction Value</td>
<td>Detection bot</td>
<td>Failed-transaction rule; token transfers above an amount in USD</td>
</tr>
<tr>
<td>Custom filter code</td>
<td>Filter scripts (Python, JS, Bash)</td>
<td>Web3 Actions on alert</td>
<td>The bot is the code</td>
<td>None; a webhook can start your own code</td>
</tr>
<tr>
<td>Automated response after an alert</td>
<td>Custom script trigger (e.g. your own relayer call)</td>
<td>Web3 Action</td>
<td>Firewall blocks before execution</td>
<td>None — alerts only</td>
</tr>
<tr>
<td>Hosting</td>
<td>Self-hosted; managed on request</td>
<td>SaaS</td>
<td>Network of scan nodes</td>
<td>SaaS only</td>
</tr>
<tr>
<td>Chains</td>
<td>EVM, Stellar, Solana</td>
<td>Many EVM networks</td>
<td>EVM</td>
<td>Ethereum, BNB Chain, Base, Arbitrum</td>
</tr>
</tbody></table>
<h2>How to choose</h2>
<ul>
<li><strong>You have someone to run servers and want full control:</strong> OpenZeppelin Monitor. It's the most direct successor, free, and covers the most chains. Budget for RPC endpoints, upgrades and keeping the process alive.</li>
<li><strong>You already use Tenderly for simulation or debugging:</strong> Tenderly Alerts. Twelve trigger types and Web3 Actions cover most Defender setups, including Monitor → Action flows.</li>
<li><strong>You care about attack detection, not just your own events:</strong> Forta. Its bots and Firewall answer "is this an exploit?", which a plain event monitor doesn't.</li>
<li><strong>You want alerts back on the four chains above without running anything, and want to see how noisy a rule is before trusting it:</strong> SmartAlert. It doesn't run code or send transactions, so it can't replace Actions or Relayer.</li>
</ul>
<h2>A migration checklist that works with any of them</h2>
<ol>
<li><strong>Inventory from your own notes.</strong> Defender is gone, so the list comes from your repo, runbooks and old alerts in Slack or Telegram: contract, network, condition, channel, who acted on it.</li>
<li><strong>Rebuild the critical monitors first.</strong> Ownership changes, proxy upgrades, pauses, role grants and large outflows. They are rare and expensive to miss.</li>
<li><strong>Check every threshold against history before you rely on it.</strong> A threshold that would have fired forty times last month will be muted within a week.</li>
<li><strong>Send a test alert to every channel</strong> and confirm a person with the keys receives it, not only a shared channel someone reads in the morning.</li>
<li><strong>Replace Actions separately.</strong> If a monitor used to pause a contract automatically, that needs OZ Relayer, a Tenderly Web3 Action or your own bot. A pure monitor won't do it.</li>
</ol>
<p>If you've already moved, I'd like to hear what you picked and what didn't map cleanly.</p>
]]></content:encoded></item><item><title><![CDATA[Smart contract monitoring in 5 minutes: what to watch and how to set it up]]></title><description><![CDATA[Alerts for Ethereum, BNB Chain, Base and Arbitrum contracts: admin changes, big transfers, keeper gas. Check rules on history, get alerts in Telegram.]]></description><link>https://smartalert.hashnode.dev/smart-contract-monitoring-in-5-minutes-what-to-watch-and-how-to-set-it-up</link><guid isPermaLink="true">https://smartalert.hashnode.dev/smart-contract-monitoring-in-5-minutes-what-to-watch-and-how-to-set-it-up</guid><category><![CDATA[Ethereum]]></category><category><![CDATA[Web3]]></category><category><![CDATA[Security]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Solidity]]></category><dc:creator><![CDATA[Aleksei Nazarov]]></dc:creator><pubDate>Sun, 25 Oct 2026 20:56:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6adbdcb39d064b5ab1b4ea35/6a45e4d6-2ba0-43d4-920d-e358d5c38160.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A contract goes live after an audit. The audit checked the code on one day. From then on, the contract changes owner, gets upgraded, moves money and talks to bots, and somebody has to notice when one of those things is not supposed to happen.</p>
<p>Most small teams notice from users, from a block explorer tab left open, or from a post on X. <a href="https://smart-alert.io/?utm_source=hashnode&amp;utm_medium=owner&amp;utm_campaign=article-combined">SmartAlert</a> is a monitor for that gap: you paste a contract address, and it tells your team in Telegram, Slack or Discord when something on the contract is off.</p>
<p>This post covers what it watches, and then takes a contract from "nobody is watching" to working alerts in about five minutes.</p>
<h2>What it watches</h2>
<p>SmartAlert reads public chain data on <strong>Ethereum, BNB Chain, Base and Arbitrum</strong>. It does not touch your contract and needs no code change.</p>
<ul>
<li><strong>Admin changes.</strong> Ownership transferred, a role granted or revoked, a proxy upgraded, the proxy admin changed, Safe owners or threshold changed, the contract paused. These are the first on-chain signs of most takeovers.</li>
<li><strong>Money, in dollars.</strong> A transfer above $X, priced at the block and optionally filtered by sender. The total value of a treasury below a floor, including when a price drop crosses it. Prices come from Chainlink, with CoinGecko as a fallback.</li>
<li><strong>Operations.</strong> A keeper or relayer wallet with gas for fewer than N days. A balance that is low and got no top-up in an hour. Failed transactions from your bots.</li>
<li><strong>Activity.</strong> Any event count or value per hour: transfers, swaps, deposits, claims. Spikes, and a contract that suddenly goes quiet.</li>
<li><strong>State.</strong> Values read through view functions, such as <code>paused()</code> returning true.</li>
</ul>
<p>Two things keep the alerts useful:</p>
<ul>
<li><strong>A norm for each hour.</strong> Activity on most contracts is low at night and high during the day. One fixed threshold is either noisy at peak hours or silent at night. SmartAlert can compare each hour with the same hour on previous days instead, so a burst at 3 a.m. is caught and a busy afternoon is not an alert.</li>
<li><strong>A check on history before a rule runs.</strong> Every rule shows, on a chart of the contract's real past, where it would have fired. If it would have fired forty times last month, you see that before it wakes anyone up.</li>
</ul>
<h2>Setup</h2>
<p>You need a contract address on one of the four networks and a Telegram account. The free plan is enough; the first 14 days of a project work with Pro limits.</p>
<h3>Step 1. Add the contract (1 minute)</h3>
<ol>
<li><a href="https://smart-alert.io/go/sign-up?ref=article-combined&amp;utm_source=hashnode&amp;utm_medium=owner&amp;utm_campaign=article-combined">Sign up</a> and create a project.</li>
<li>Choose <strong>Add contract</strong>, pick the network and paste the address.</li>
<li>For a verified contract, the ABI is loaded for you. For an unverified one, paste the ABI.</li>
</ol>
<p>If SmartAlert has not seen the contract before, it first collects its history. The contract shows a "backfilling" status, and rules wait until it is done. If the contract is already tracked, its history is there at once.</p>
<img src="https://smart-alert.io/docs/contracts.en.png" alt="The project's contracts: network, address and indexing state" style="display:block;margin:0 auto" />

<h3>Step 2. Turn on the first rules (2 minutes)</h3>
<p>Open the contract. SmartAlert suggests rules from its ABI: ERC-20, ERC-721 and ERC-1967 proxies get matching templates. Select the ones that fit and choose <strong>Enable selected</strong>.</p>
<p>If you are not sure what to start with, these six cover the most common ways a contract gets taken over or drained:</p>
<table>
<thead>
<tr>
<th>Rule</th>
<th>Why</th>
<th>Setting</th>
</tr>
</thead>
<tbody><tr>
<td><a href="https://smart-alert.io/recipes/ownership-transferred?utm_source=hashnode&amp;utm_medium=owner&amp;utm_campaign=article-combined">Ownership transferred</a></td>
<td>A new owner can call every admin function</td>
<td>Any event, Critical</td>
</tr>
<tr>
<td><a href="https://smart-alert.io/recipes/role-granted?utm_source=hashnode&amp;utm_medium=owner&amp;utm_campaign=article-combined">Role granted</a></td>
<td>A new admin on an <code>AccessControl</code> contract</td>
<td>Any event, Critical</td>
</tr>
<tr>
<td><a href="https://smart-alert.io/recipes/proxy-upgraded?utm_source=hashnode&amp;utm_medium=owner&amp;utm_campaign=article-combined">Proxy upgraded</a></td>
<td>The code behind the address changed</td>
<td>Any event, Critical</td>
</tr>
<tr>
<td><a href="https://smart-alert.io/recipes/large-transfer-usd?utm_source=hashnode&amp;utm_medium=owner&amp;utm_campaign=article-combined">Transfer above $X</a></td>
<td>The drain itself, priced at the block</td>
<td><code>from</code> = your contract, a threshold above normal flow</td>
</tr>
<tr>
<td><a href="https://smart-alert.io/recipes/transfer-spike?utm_source=hashnode&amp;utm_medium=owner&amp;utm_campaign=article-combined">Unusual transfer activity</a></td>
<td>Bursts that a fixed number misses at night</td>
<td>Against the norm for each hour</td>
</tr>
<tr>
<td><a href="https://smart-alert.io/recipes/contract-went-quiet?utm_source=hashnode&amp;utm_medium=owner&amp;utm_campaign=article-combined">Contract went quiet</a></td>
<td>A broken integration or a stuck bot</td>
<td>Against the norm for each hour</td>
</tr>
</tbody></table>
<p>If you run keepers or relayers, add their wallets and a <a href="https://smart-alert.io/recipes/keeper-gas-runway?utm_source=hashnode&amp;utm_medium=owner&amp;utm_campaign=article-combined">gas runway</a> rule. For a treasury, add its address and a <a href="https://smart-alert.io/recipes/treasury-below-usd?utm_source=hashnode&amp;utm_medium=owner&amp;utm_campaign=article-combined">total below $X</a> rule.</p>
<h3>Step 3. Check each rule on history (1 minute)</h3>
<p>Open a rule and look at the chart. It shows the metric, the norm or your threshold, and marks where the rule would have fired.</p>
<img src="https://smart-alert.io/docs/rule-history.en.png" alt="A rule on a 7-day chart with the norm band and a mark where it would have fired" style="display:block;margin:0 auto" />

<ul>
<li>Admin rules should show zero or a few marks, each one a change you remember making.</li>
<li>For value and activity rules, too many marks means the threshold is too low. Raise it, or set a target such as "no more than 2 a week" and let SmartAlert pick the threshold.</li>
</ul>
<p>Use <strong>against the norm for each hour</strong> (the default) for activity, and <strong>own threshold</strong> for amounts in dollars and admin events.</p>
<h3>Step 4. Connect Telegram (1 minute)</h3>
<ol>
<li>Open <strong>Channels</strong>, enter a name and press <strong>Add</strong>.</li>
<li>Open the link SmartAlert gives you and press <strong>Start</strong> in the bot. For a team chat, add the bot to the group and run the command there.</li>
<li>The bot replies with the project name. The channel is active.</li>
</ol>
<p>The link works for 15 minutes. On Pro you can also add Slack, Discord, email, PagerDuty or a webhook.</p>
<h3>What an alert looks like</h3>
<p>The message has the value, the threshold or this hour's norm, the time and a link to the transaction. When the condition clears, a "Back to normal" message follows, and PagerDuty incidents close on their own.</p>
<img src="https://smart-alert.io/docs/alert.en.png" alt="An alert with the value, this hour's norm and a chart" style="display:block;margin:0 auto" />

<h2>What it does not do</h2>
<ul>
<li><strong>It does not pause or block anything.</strong> It alerts. Send Critical alerts to people who hold a pause key; an alert that reaches nobody who can act is a log entry.</li>
<li><strong>It does not read the mempool.</strong> It reads confirmed blocks. On Ethereum that is 12 confirmations, about 144 seconds, then a rule check: every 5 minutes on Free, every minute on Pro, every 30 seconds on Business. If an attacker drains a contract within two minutes of the first sign, the first loss is gone before any confirmed-block monitor can speak. Later steps of the same attack are not.</li>
<li><strong>It does not replace an audit.</strong> It is the part that notices.</li>
</ul>
<p>Two more habits:</p>
<ul>
<li><strong>Repeat on every chain you are deployed on.</strong> Attackers often go chain by chain, and the first chain's alert is the warning for the others.</li>
<li><strong>Watch contracts you depend on, not only your own.</strong> A locker, a bridge or a vault that holds your tokens is public, and you can watch it without anyone's permission.</li>
</ul>
<h2>Price</h2>
<p>$0 gets 2 contracts, 3 rules and a Telegram channel, checked every 5 minutes. Pro is $30 a month, Business $99.</p>
<h2>Next</h2>
<ul>
<li><a href="https://smart-alert.io/guides/alert-recipes?utm_source=hashnode&amp;utm_medium=owner&amp;utm_campaign=article-combined">Alert recipes</a></li>
<li><a href="https://smart-alert.io/guides/post-audit-monitoring?utm_source=hashnode&amp;utm_medium=owner&amp;utm_campaign=article-combined">Post-audit monitoring checklist</a></li>
<li><a href="https://smart-alert.io/guides/proxy-contract-monitoring?utm_source=hashnode&amp;utm_medium=owner&amp;utm_campaign=article-combined">How to monitor an upgradeable proxy</a></li>
</ul>
<hr />
<p><em>I build SmartAlert. Questions about a specific contract are welcome in the comments.</em></p>
]]></content:encoded></item><item><title><![CDATA[Ronin Bridge, August 2024: the vote threshold was 0 for 48 minutes before the first withdrawal, and no event said so]]></title><description><![CDATA[On August 6, 2024 the Ronin team upgraded its bridge on Ethereum. 48 minutes later an MEV bot withdrew about 4,000 ETH from it, and half an hour after that…]]></description><link>https://smartalert.hashnode.dev/ronin-bridge-august-2024-the-vote-threshold-was-0-for-48-minutes-before-the-first-withdrawal-and-no-event-said-so</link><guid isPermaLink="true">https://smartalert.hashnode.dev/ronin-bridge-august-2024-the-vote-threshold-was-0-for-48-minutes-before-the-first-withdrawal-and-no-event-said-so</guid><category><![CDATA[Web3]]></category><category><![CDATA[Security]]></category><category><![CDATA[Ethereum]]></category><category><![CDATA[Blockchain]]></category><dc:creator><![CDATA[Aleksei Nazarov]]></dc:creator><pubDate>Fri, 23 Oct 2026 22:27:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6adbdcb39d064b5ab1b4ea35/d4591ae7-e0e8-4177-8abb-ebb456b96814.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>On August 6, 2024 the Ronin team upgraded its bridge on Ethereum. 48 minutes later an MEV bot withdrew about 4,000 ETH from it, and half an hour after that another address took 2M USDC. In total about $12M left. The bot belonged to a whitehat, and the funds came back the same day<sup>[1][2][3]</sup>.</p>
<p>Most bridge hacks start with a stolen key. This one did not. Nobody broke in, and nothing unusual was emitted. The upgrade was the team's own, and the <code>Upgraded</code> event was expected. What broke was a number inside the contract, and only a question to the contract could show it.</p>
<h2>How the Ronin bridge approves a withdrawal</h2>
<p>Ronin is the blockchain behind Axie Infinity. Its bridge to Ethereum has two parts that matter here<sup>[2][3]</sup>:</p>
<ul>
<li><strong>The gateway</strong> <a href="https://etherscan.io/address/0x64192819Ac13Ef72bF6b5AE239AC672B43a9AF08"><code>0x6419…AF08</code></a> on Ethereum holds the bridged ETH, USDC and other tokens. It is a proxy: the address stays, the code behind it can be replaced.</li>
<li><strong>Bridge operators</strong> watch Ronin. When a user withdraws to Ethereum, operators sign the withdrawal. Each operator has a vote weight.</li>
</ul>
<p>The gateway pays out only when the signers' total weight reaches a threshold. The view function <code>minimumVoteWeight()</code> returns it. Before August 6 it returned <strong>1540</strong>, 70% of the operators' total weight<sup>[3][7]</sup>.</p>
<p>The gateway also limits how much one withdrawal may take, about 4,000 ETH or 2M USDC<sup>[2]</sup>. Keep that in mind. It capped the size of each withdrawal.</p>
<p>Changes to the gateway go through <strong>MainchainBridgeManager</strong> <a href="https://etherscan.io/address/0xa71456fA88a5f6a4696D0446E690Db4a5913fab0"><code>0xa714…fab0</code></a>: a proposal needs operator signatures, then anyone can relay it on-chain. For emergencies there is a separate <strong>PauseEnforcer</strong> <a href="https://etherscan.io/address/0xe514d9DEB7966c8BE0ca922de8a064264eA6bcd4"><code>0xe514…bcd4</code></a> that can stop the gateway in one call.</p>
<h2>What went wrong in the upgrade</h2>
<p>An upgradeable contract keeps its state in the proxy. New code often needs new state, so it ships with an initializer that fills it. The gateway had several, numbered by version. Version 3 set <code>_totalOperatorWeight</code>, the total weight the threshold is computed from. Version 4 set a new helper contract for WETH<sup>[2][3]</sup>.</p>
<p>The upgrade on August 6 jumped straight to version 4. The proxy had never run version 3. So <code>_totalOperatorWeight</code> stayed 0, and 70% of 0 is 0<sup>[3]</sup>.</p>
<p>The upgrade logs show this. They contain <code>Upgraded</code> and <code>Initialized(4)</code>, but no <code>Initialized(3)</code><sup>[7]</sup>.</p>
<h2>The attack, step by step</h2>
<p>All times UTC, block times from Ethereum. We read every transaction below on-chain ourselves<sup>[7]</sup>.</p>
<h3>08:48:47 — the planned upgrade</h3>
<p>Address <code>0xe880…f059</code> relayed proposal round 5 to MainchainBridgeManager with 16 operator signatures (<a href="https://etherscan.io/tx/0x855dd3b1194e3b889f4667b6a0996220e350e034d35d3eab29b4f23bc205767e"><code>0x855d…767e</code></a>, block 20468438). The proposal made six calls to the gateway. Among them: add WBTC, switch the code to <a href="https://etherscan.io/address/0xfc274EC92bBb1A1472884558d1B5CaaC6F8220Ee"><code>0xfc27…20Ee</code></a> and run <code>initializeV4</code>, then change the proxy admin. The last two calls failed, the first four succeeded<sup>[7]</sup>.</p>
<p>We read <code>minimumVoteWeight()</code> with <code>eth_call</code> on archive blocks<sup>[7]</sup>:</p>
<table>
<thead>
<tr>
<th>Block</th>
<th>When</th>
<th><code>minimumVoteWeight()</code></th>
</tr>
</thead>
<tbody><tr>
<td>20468437</td>
<td>the block before the upgrade</td>
<td><strong>1540</strong></td>
</tr>
<tr>
<td>20468438</td>
<td>08:48:47, the upgrade</td>
<td><strong>0</strong></td>
</tr>
<tr>
<td>20468678</td>
<td>the block before the first withdrawal</td>
<td><strong>0</strong></td>
</tr>
</tbody></table>
<p>From this block, a withdrawal from the gateway needed no operator signatures at all. Anyone who noticed could take up to the per-withdrawal limit, as many times as they liked.</p>
<blockquote>
<p><strong>🔔 SmartAlert</strong></p>
<p><strong>Rule:</strong> "Contract state" on the gateway: call <code>minimumVoteWeight()</code> every minute, fire when the answer is below 1. It is the same rule as the <a href="https://smart-alert.io/recipes/contract-state-paused">paused() recipe</a>, with a number instead of true/false.</p>
<p><strong>Alert:</strong> about 08:51–08:53. The rule reads the block 12 confirmations behind the head (about 144 seconds), so the first call that sees 0 lands 2.5–4 minutes after the upgrade<sup>[8]</sup>.</p>
<p><strong>Time left:</strong> about 45 minutes before the first withdrawal (09:37:23) and 79 minutes before the second (10:11:47).</p>
<p><strong>What the team could do:</strong> the PauseEnforcer was there. One <code>triggerPause()</code> stops the gateway. The team made exactly that call later, at 10:15:23.</p>
</blockquote>
<blockquote>
<p><strong>⚠️ What it misses</strong></p>
<p><strong>An</strong> <a href="https://smart-alert.io/recipes/proxy-upgraded"><code>Upgraded</code></a> <strong>rule would fire here too, and it would not help.</strong> The team was waiting for this event. An alert that says "the gateway was upgraded" reads as "yes, that is ours". Nothing in the event stream says the threshold is now 0. Only the contract knows that, and only if someone asks it.</p>
</blockquote>
<h3>09:37:23 — 3,996 ETH</h3>
<p>Address <code>0x1a56…3cc1</code> called its MEV contract <a href="https://etherscan.io/address/0x4Ab12E7CE31857Ee022f273e8580F73335a73c0B"><code>0x4Ab1…3c0B</code></a>, which called <code>submitWithdrawal</code> on the gateway. The gateway paid out 3,996.09 ETH (<a href="https://etherscan.io/tx/0x2619570088683e6cc3a38d93c3d98899e5783864e15525d5f5810c11189ba6cb"><code>0x2619…a6cb</code></a>, block 20468679)<sup>[7]</sup>. This was 48 minutes after the upgrade.</p>
<blockquote>
<p><strong>🔔 SmartAlert</strong></p>
<p><strong>Rule:</strong> <a href="https://smart-alert.io/recipes/treasury-below-usd">"Total address balance in $ below threshold"</a> on the gateway, with a floor a little under the normal total. Native ETH does not emit <code>Transfer</code>, so a balance rule is how you see it leave.</p>
<p><strong>Alert:</strong> about 09:40–09:41.</p>
<p><strong>Time left:</strong> about 31 minutes before the 2M USDC.</p>
<p><strong>The honest part:</strong> this is the alert that confirms the first one. On its own it comes after $10M has left. With the state alert in front of it, it says "it is happening now".</p>
</blockquote>
<h3>10:11:47 — 2M USDC</h3>
<p>Address <code>0xFDe0…455A</code> withdrew 1,998,046.88 USDC to <code>0x6980…BDD0</code> (<a href="https://etherscan.io/tx/0xbce5b8548db486c561948e8a177c8ccaa72810f972cee3909ea50af015a60ad8"><code>0xbce5…0ad8</code></a>, block 20468848)<sup>[7]</sup>.</p>
<blockquote>
<p><strong>🔔 SmartAlert</strong></p>
<p><strong>Rule:</strong> <a href="https://smart-alert.io/recipes/large-transfer-usd">Transfer above an amount in $</a> on USDC, <code>from</code> = the gateway, threshold $1,000,000.</p>
<p><strong>Alert:</strong> about 10:14–10:15:30, about when the team paused the bridge. This one tells you the size of the loss, not the time left.</p>
</blockquote>
<h3>10:15:23 — the pause</h3>
<p>Address <code>0x8B35…8f57</code> called <code>triggerPause()</code> on the PauseEnforcer (<a href="https://etherscan.io/tx/0x90a1a9011dc2c246631da893f5f9f17ea4e27dbd9a334052a1d53359dfe090c9"><code>0x90a1…c090</code></a>, block 20468866)<sup>[7]</sup>. That is 38 minutes after the first withdrawal and 86 minutes after the upgrade. Ronin said it paused the bridge about 40 minutes after the first withdrawal<sup>[1]</sup>.</p>
<h3>After — the money came back</h3>
<p>The whitehat behind the MEV bot returned the ETH, then the USDC about two hours later. Ronin paid a $500K bounty. According to The Block, the bridge held more than $850M at the time<sup>[1][4]</sup>. The per-withdrawal limit capped each transaction at about 4,000 ETH or 2M USDC<sup>[2]</sup>. SEAL 911 helped with the response<sup>[3]</sup>.</p>
<h2>What no rule would have changed</h2>
<blockquote>
<p><strong>⚠️ What it misses</strong></p>
<p><strong>A rule created after the upgrade.</strong> A contract-state rule starts recording answers when you create it. It does not read past blocks, so you cannot check it on history the way you can an event rule. To show the moment of the change, it had to exist before 08:48:47 for Ronin. A "below 1" rule created later would still fire, on its first call.</p>
</blockquote>
<blockquote>
<p><strong>⚠️ What it misses</strong></p>
<p><strong>Knowing which number matters.</strong> Here it is <code>minimumVoteWeight()</code>. For a lending market it may be a price from an oracle, for a token <code>paused()</code> or <code>owner()</code>. A monitor checks the numbers you point it at. It does not guess your invariants.</p>
</blockquote>
<blockquote>
<p><strong>⚠️ What it misses</strong></p>
<p><strong>Attacks in one transaction.</strong> If the bad value and the withdrawal land in the same transaction, a state check sees the result, not the warning. At <a href="https://www.certik.com/resources/blog/bybit-incident-technical-analysis">Bybit</a> in February 2025, a signed Safe transaction emptied about $1.46B two and a half minutes after the first sign<sup>[5]</sup>. At <a href="https://medium.com/@KiloEx/kiloex-security-incident-root-cause-analysis-post-mortem-3d899caac08c">KiloEx</a> in April 2025, pools were drained in the same second<sup>[6]</sup>. Ronin had 48 minutes. Not every incident does.</p>
</blockquote>
<h2>How to set up this rule in SmartAlert</h2>
<ol>
<li><strong>Add the contract</strong> and open it. Contract-state rules are on Pro and above.</li>
<li><strong>Create a "Contract state" rule.</strong> Pick the function, for example <code>minimumVoteWeight()</code>, and press "Check function" to see the current answer.</li>
<li><strong>Choose the condition</strong> "Below threshold" with a threshold under the normal value, for example 1. For a bridge, call every minute.</li>
<li><strong>Create it before your next upgrade.</strong> Then the first call after the upgrade shows whether the number survived.</li>
<li><strong>Add the event rules too.</strong> <a href="https://smart-alert.io/recipes/proxy-upgraded"><code>Upgraded</code></a> tells you when the code changed, the state rule tells you what it changed. For native ETH, add the gateway in "Addresses" and create a <a href="https://smart-alert.io/recipes/treasury-below-usd">"Total address balance in $ below threshold"</a> rule.</li>
<li><strong>Choose where alerts go.</strong> Telegram is free.</li>
</ol>
<p>See all <a href="https://smart-alert.io/guides/alert-recipes">alert recipes</a>.</p>
<h2>Sources</h2>
<ol>
<li>Ronin Network on X, <a href="https://x.com/Ronin_Network/status/1820804772917588339">incident statement</a>: pause, about 40 minutes, the whitehat.</li>
<li>Rekt, <a href="https://rekt.news/roninnetwork-rektII">Ronin Network — Rekt II</a>: the skipped initializer, the per-withdrawal limit, the amounts.</li>
<li>Three Sigma, <a href="https://threesigma.xyz/blog/ronin-network-12m-exploit-analysis">analysis of the Ronin Network exploit</a>: <code>initializeV3</code> and <code>_totalOperatorWeight</code>, the 70% threshold, SEAL 911.</li>
<li>The Block, <a href="https://www.theblock.co/news/regulation/2024-08-06-ronin-bridge-pauses-309707">report on the Ronin bridge pause</a>: returned funds, the $500K bounty, more than $850M in the bridge.</li>
<li>CertiK, <a href="https://www.certik.com/resources/blog/bybit-incident-technical-analysis">Bybit incident technical analysis</a>.</li>
<li>KiloEx, <a href="https://medium.com/@KiloEx/kiloex-security-incident-root-cause-analysis-post-mortem-3d899caac08c">Security incident root cause analysis</a>.</li>
<li><strong>Checked by SmartAlert on-chain:</strong> <code>minimumVoteWeight()</code> with archive <code>eth_call</code> at blocks 20468437, 20468438 and 20468678; the upgrade transaction, its six calls, their results and its logs (<code>Upgraded</code>, <code>Initialized(4)</code>, no <code>Initialized(3)</code>); the two withdrawals and the pause, with block times, senders and amounts, through the Blockscout API.</li>
<li>SmartAlert, alert delay on Ethereum: 12 confirmations (about 144 s), head polling up to 12 s, storage up to 15 s (P95), rule check every 60 s on Pro.</li>
</ol>
<hr />
<p><em>Originally published on the</em> <a href="https://smart-alert.io/blog/ronin-bridge-vote-weight"><em>SmartAlert blog</em></a><em>.</em></p>
]]></content:encoded></item></channel></rss>