<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sachin</title>
    <description>The latest articles on DEV Community by Sachin (@0xdarkseidbull).</description>
    <link>https://dev.to/0xdarkseidbull</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4066190%2Fecb3a167-dc1f-4e8a-855b-0a696f3079f6.jpg</url>
      <title>DEV Community: Sachin</title>
      <link>https://dev.to/0xdarkseidbull</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/0xdarkseidbull"/>
    <language>en</language>
    <item>
      <title>Unstick Your RBNT: Building a Recovery Playbook Nobody Wants to Need</title>
      <dc:creator>Sachin</dc:creator>
      <pubDate>Thu, 06 Aug 2026 16:40:06 +0000</pubDate>
      <link>https://dev.to/0xdarkseidbull/unstick-your-rbnt-building-a-recovery-playbook-nobody-wants-to-need-3aka</link>
      <guid>https://dev.to/0xdarkseidbull/unstick-your-rbnt-building-a-recovery-playbook-nobody-wants-to-need-3aka</guid>
      <description>&lt;p&gt;&lt;em&gt;A case study in writing for the moment someone's funds actually get stuck, not the moment everything goes right&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;TASK-16 asked for a community support guide covering the ways RBNT gets stuck when it moves across chains. That sounds like documentation work. In practice it turned into something closer to an investigation, because a recovery guide is only useful if every number in it is still true at the exact moment someone is panicking and needs it. Nobody opens a recovery guide when things are calm.&lt;/p&gt;

&lt;p&gt;This is the story of what that guide became, and why almost none of it could be written from documentation alone, including the two points where the documentation itself turned out to be wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  Starting from the wrong assumption
&lt;/h2&gt;

&lt;p&gt;The instinct when writing something like this is to pull from official docs and call it done. Bridge fees, contract addresses, and exchange recovery processes are all technically published somewhere. The problem is that published information about liquidity and routing goes stale fast, and a guide that repeats a stale number with confidence is worse than a guide that admits it does not know.&lt;/p&gt;

&lt;p&gt;So instead of summarizing documentation, the approach was to check everything live in August 2026: contract addresses against Redbelly's own announcements and developer docs, pool depth and price impact across every chain wrapped RBNT trades on, actual bridge routes checked on two independent bridge interfaces, and the real, current recovery process at each exchange that lists RBNT. Where something could not be verified, the guide says so directly rather than filling the gap with a guess dressed up as fact.&lt;/p&gt;

&lt;p&gt;Two of the findings below started as documentation reading a certain way, and ended somewhere else entirely once a human who actually runs the chain got involved.&lt;/p&gt;




&lt;h2&gt;
  
  
  What actually showed up once things were checked
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Finding 1: The developer docs said BNB Chain was live. The team said it never existed.
&lt;/h3&gt;

&lt;p&gt;The first pass through Redbelly's own developer docs turned up what looked like a legitimate wrapped RBNT deployment on BNB Chain, listed in the same bridge configuration tables as the confirmed Ethereum and Solana addresses. It was tempting to write that address into the guide as verified.&lt;/p&gt;

&lt;p&gt;It did not survive contact with the actual team. Asked directly in Redbelly's official Discord support channel, a team member's answer was unambiguous: "We never had rbnt on bsc. All RBNT on bsc is fake."&lt;/p&gt;

&lt;p&gt;That single line rewrote the whole BNB Chain entry. The address sitting in the developer docs is most likely a dormant, never-activated technical configuration rather than a real deployment, and separately, multiple impersonator tokens circulate on BNB Chain under the RBNT or RedBelly name, at least one with reward-giveaway marketing language that is a textbook sign of a scam token. None of them are affiliated with Redbelly. The guide now states this as plainly as the team did: there is no official RBNT token on BNB Chain, and any token calling itself RBNT there is not recoverable value, it is an impersonator.&lt;/p&gt;

&lt;p&gt;This is the clearest example of why the guide could not be written from documentation alone. The documentation was not malicious, it was just not the same thing as the team's actual intent, and only one of those two things is safe to publish in a recovery guide.&lt;/p&gt;




&lt;h3&gt;
  
  
  Finding 2: Contract addresses need verification, not trust
&lt;/h3&gt;

&lt;p&gt;Every other wrapped RBNT contract address in the guide was checked directly against Redbelly's own official announcements and current developer docs rather than trusted from a wallet import or a search result.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Chain&lt;/th&gt;
&lt;th&gt;Contract Address&lt;/th&gt;
&lt;th&gt;Confidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Redbelly (native chain)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x6ed1F491e2d31536D6561f6bdB2AdC8F092a6076&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;High, confirmed directly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ethereum (original WRBNT)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0xb45ffb51984d626ee758b336c61cf20990c6bf13&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;High, official announcement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Solana&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AKbyFYEgueHwS7V4S3gXsWpGJZvv3f7WMkRMFdrenSG1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ethereum, Polygon, Arbitrum, Base, Avalanche, Sonic (RBNT via LayerZero)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x020940df9F5E77338a094D55b5B5914122a804A5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Medium, same address on all six chains&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BNB Chain&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No official token exists&lt;/td&gt;
&lt;td&gt;Confirmed absent, see Finding 1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The interesting detail here is that the LayerZero-based RBNT contract is the identical address on six different chains, a deterministic deployment pattern that is easy to mistake for a coincidence or an error unless you already know LayerZero OFT contracts commonly deploy this way. Someone cross-checking their wallet by eye against a single "Base" row would miss that the same address is also correct on Ethereum, Polygon, Arbitrum, Avalanche, and Sonic.&lt;/p&gt;




&lt;h3&gt;
  
  
  Finding 3: Liquidity is thinner than it looks
&lt;/h3&gt;

&lt;p&gt;Every chain's wrapped RBNT pool is thin right now, and the numbers move fast enough that they are worth checking again before acting on anything large.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Chain&lt;/th&gt;
&lt;th&gt;Swap Size&lt;/th&gt;
&lt;th&gt;Price Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ethereum&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100,000 WRBNT&lt;/td&gt;
&lt;td&gt;1.5% to 2.9%, depending on aggregator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ethereum&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1,000,000 WRBNT&lt;/td&gt;
&lt;td&gt;13% to 14%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Solana&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10,000 WRBNT&lt;/td&gt;
&lt;td&gt;86.77%, effectively unusable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Base&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1,000,000 RBNT&lt;/td&gt;
&lt;td&gt;7.9% to 8.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Base&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100,000 RBNT&lt;/td&gt;
&lt;td&gt;13.4% on a separate widget&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Base showed lower price impact than Ethereum at matched swap sizes, despite having no dedicated official liquidity announcement from Redbelly at all. Pool depth and official confirmation turned out to be two separate questions with two separate answers, and conflating them would have misled anyone using the guide to judge which chain was safer to hold RBNT on.&lt;/p&gt;




&lt;h3&gt;
  
  
  Finding 4: The case that looked unfixable until the actual route showed up
&lt;/h3&gt;

&lt;p&gt;This is the finding worth sitting with. Redbelly's developer docs name Lucid Labs Bridge as the official route back to Redbelly Network, and checking it live against every listed source chain (cross-checked a second time on Oku, a separate frontend running the same underlying LayerZero route, with fees and times matching closely on both) produced this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source Chain&lt;/th&gt;
&lt;th&gt;Asset&lt;/th&gt;
&lt;th&gt;Route&lt;/th&gt;
&lt;th&gt;Fee&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ethereum&lt;/td&gt;
&lt;td&gt;RBNT&lt;/td&gt;
&lt;td&gt;Stargate&lt;/td&gt;
&lt;td&gt;0.00013 ETH (~$0.31)&lt;/td&gt;
&lt;td&gt;About 4 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ethereum&lt;/td&gt;
&lt;td&gt;WRBNT&lt;/td&gt;
&lt;td&gt;Polymer&lt;/td&gt;
&lt;td&gt;0.000015 ETH + 10 WRBNT fee&lt;/td&gt;
&lt;td&gt;About 2 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Base&lt;/td&gt;
&lt;td&gt;RBNT&lt;/td&gt;
&lt;td&gt;Stargate&lt;/td&gt;
&lt;td&gt;0.00013 ETH (~$0.32)&lt;/td&gt;
&lt;td&gt;About 1 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Base&lt;/td&gt;
&lt;td&gt;WRBNT&lt;/td&gt;
&lt;td&gt;Polymer&lt;/td&gt;
&lt;td&gt;0.000015 ETH + 10 WRBNT fee&lt;/td&gt;
&lt;td&gt;About 10 sec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BSC&lt;/td&gt;
&lt;td&gt;RBNT&lt;/td&gt;
&lt;td&gt;Stargate&lt;/td&gt;
&lt;td&gt;0.00044 BNB (~$0.31)&lt;/td&gt;
&lt;td&gt;About 124 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arbitrum&lt;/td&gt;
&lt;td&gt;RBNT&lt;/td&gt;
&lt;td&gt;Stargate&lt;/td&gt;
&lt;td&gt;0.00013 ETH (~$0.31)&lt;/td&gt;
&lt;td&gt;About 172 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Polygon&lt;/td&gt;
&lt;td&gt;RBNT&lt;/td&gt;
&lt;td&gt;Stargate&lt;/td&gt;
&lt;td&gt;3.02 POL (~$0.32)&lt;/td&gt;
&lt;td&gt;About 176 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avalanche&lt;/td&gt;
&lt;td&gt;RBNT&lt;/td&gt;
&lt;td&gt;Stargate&lt;/td&gt;
&lt;td&gt;0.04 AVAX (~$0.32)&lt;/td&gt;
&lt;td&gt;About 61 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sonic&lt;/td&gt;
&lt;td&gt;RBNT&lt;/td&gt;
&lt;td&gt;Stargate&lt;/td&gt;
&lt;td&gt;10.62 S (~$0.31)&lt;/td&gt;
&lt;td&gt;About 86 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Solana&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;RBNT&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No route on the single bridge widget&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Seven EVM chains returned a clean, working quote. Solana returned nothing, on both tools, every time. The first draft of this finding treated that as the honest but disappointing answer: no route exists, stop retrying, wait for support to change.&lt;/p&gt;

&lt;p&gt;That draft was wrong, and it stayed wrong until the actual route showed up in Redbelly's own team explanation: there is no single-step bridge between Solana and Redbelly Network, and there never was one. The real path is two hops, not one. Bridge RBNT from Redbelly Network to a supported EVM chain like Ethereum or Base using Lucid Labs Bridge or Oku, then bridge from that EVM chain into Solana separately using Stargate. The same two hops work in reverse to bring Solana RBNT home. Selecting Solana directly on the single bridge widget will always show no route, and that was never a bug or an outage, it was a route that does not exist as a single hop and was never going to.&lt;/p&gt;

&lt;p&gt;A related trap sits next to this one. Celer cBridge is a second, genuinely official Redbelly integration, but Redbelly's own docs are explicit that it only bridges out, from Redbelly to Ethereum and BNB Chain, never back. Someone who moved funds out through Celer and then tries to reverse the same transfer through Celer will get a permanently empty quote, not because anything is broken, but because that direction was never built. The fix is the same as the Solana case: recognize the structural limit and route through Lucid Labs Bridge instead, rather than retrying a path that cannot succeed.&lt;/p&gt;

&lt;p&gt;Both of these are the same lesson from two different angles: a guide that tells someone to keep retrying something that structurally cannot succeed is worse than a guide that explains why it cannot succeed and what actually works instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  Writing for the moment of panic, not the moment of research
&lt;/h2&gt;

&lt;p&gt;A recovery guide has a strange audience problem. The person reading the section about funds accidentally sent to the wrong exchange deposit address is not reading calmly. They are checking a wallet balance that shows zero and trying to figure out in real time whether they just lost money permanently.&lt;/p&gt;

&lt;p&gt;That shaped how the guide is structured:&lt;/p&gt;

&lt;h3&gt;
  
  
  Every failure mode follows the same pattern:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What the problem actually is&lt;/strong&gt;, a clear, jargon-free explanation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A numbered diagnostic sequence&lt;/strong&gt;, not a wall of prose&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A clear statement of what is and is not recoverable&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  One warning repeats in nearly every section:
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Never click a recovery link sent through Discord or a direct message.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never resend funds hoping it fixes the original mistake.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That repetition is deliberate. It is the single most common scam pattern attached to exactly this kind of situation, and someone skimming under stress in the last section may never have read the version of that warning in the first one.&lt;/p&gt;




&lt;h2&gt;
  
  
  The six-part structure that shipped
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;What It Covers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Part 1: Before You Bridge&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The network check that prevents most unrecoverable losses before they happen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Part 2: Reference Tables&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Verified wrapped RBNT contract addresses on every chain, plus live swap liquidity depth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Part 3: Failure Mode 1, Wrapped RBNT Zero Value or Swap Fail&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Diagnosing a wrong contract versus a thin pool versus an ordinary transaction issue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Part 4: Failure Mode 2, Quote Unavailable Bridging RBNT Back to Redbelly Network&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The verified route table, the two-hop Solana path, and the Celer one-way trap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Part 5: Failure Mode 3, Stablecoins Stranded on Ethereum Mainnet&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Using reddex to track and complete a stuck USDC or USDT transfer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Part 6: Failure Mode 4, Native RBNT Sent to a CEX Deposit Address by Mistake&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Exchange-by-exchange recovery process, fees, and odds for Gate, MEXC, BYDFi, and WhiteBIT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The sections are independent. Someone whose funds are stuck at a bridge does not need to read exchange recovery guidance first, they need to find their specific problem and get a clear answer fast.&lt;/p&gt;




&lt;h2&gt;
  
  
  Turning it into something people will actually open
&lt;/h2&gt;

&lt;p&gt;The final deliverables were a 16-page PDF and an editable DOCX, plus a companion site so nobody has to download a file just to check one table. The site renders the PDF inline in the browser, keeps both reference tables readable directly on the page, and now also carries the actual evidence: screenshots from both bridge tools, and the Discord message where the BNB Chain question got settled for good.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The PDF renders inline in the browser&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;All six sections and both reference tables are readable directly on the page&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Download buttons sit alongside the inline content&lt;/strong&gt; rather than replacing it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A dedicated proof page&lt;/strong&gt; lets anyone flip between Lucid Labs Bridge and Oku screenshots for every chain, one at a time, so the claim of checking two tools is not just a line in the text&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence screenshots sit next to the claims they support&lt;/strong&gt;, not filed away separately&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One link, no downloads required, no scrolling through a file tree.&lt;/p&gt;




&lt;h2&gt;
  
  
  The practical troubleshooting flow
&lt;/h2&gt;

&lt;p&gt;For someone actually stuck, the guide leads them through:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Identify where the funds are
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Still on the source chain?&lt;/li&gt;
&lt;li&gt;In the bridge contract?&lt;/li&gt;
&lt;li&gt;At the destination wallet showing zero?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: Verify the contract, not just the balance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Does the address match Table A, character by character?&lt;/li&gt;
&lt;li&gt;On BNB Chain specifically, assume any RBNT-named token is an impersonator until proven otherwise, the team has said outright there is no real one&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3: Check liquidity
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Is there a market to swap out?&lt;/li&gt;
&lt;li&gt;What's the price impact if you need to move it?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 4: If bridging back to Redbelly
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Confirm which of the nine source chains you are actually on&lt;/li&gt;
&lt;li&gt;If Solana, this is a two-hop route (EVM chain, then Stargate into or out of Solana), not a single bridge, and never will be&lt;/li&gt;
&lt;li&gt;If you bridged out through Celer, bridge back through Lucid Labs Bridge instead, Celer does not run in reverse&lt;/li&gt;
&lt;li&gt;If Polygon, expect roughly three hours, do not panic at five minutes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 5: Follow the exchange-specific path
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Gate: Token Recovery, self-service, flat 20 USDT fee&lt;/li&gt;
&lt;li&gt;MEXC: Uncredited Deposit Return Application, fee applies regardless of outcome, funds return to the sender, not to a tradable balance&lt;/li&gt;
&lt;li&gt;BYDFi: support ticket with transaction hash and account UID, no fee stated&lt;/li&gt;
&lt;li&gt;WhiteBIT: general support ticket only, the weakest process of the four, treat the outcome as uncertain&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 6: Know what a scam recovery attempt looks like
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Anyone offering to unstick a transfer for a fee, or for wallet credentials, in a direct message or Discord ping&lt;/li&gt;
&lt;li&gt;Any suggestion to resend funds to "fix" the original mistake&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What this task actually turned into
&lt;/h2&gt;

&lt;p&gt;The brief asked for a support guide. What it required was closer to an audit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Verifying every contract address&lt;/strong&gt; instead of trusting a name, including catching one that Redbelly's own docs listed but the team says was never real&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checking real pool depth&lt;/strong&gt; instead of assuming liquidity exists because a token is listed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing bridge routes on two independent tools&lt;/strong&gt; instead of assuming a single check was enough&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Finding the actual route&lt;/strong&gt; for Solana instead of stopping at "no route" and calling it unfixable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Being willing to write "this does not currently work as a single step"&lt;/strong&gt; about a bridge route, and then finding out what does work, instead of writing generic troubleshooting steps that would waste someone's time on a problem that was solvable all along&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that shows up if you write from documentation alone. It only shows up if you go check, ask the people who actually run the chain when the docs and reality disagree, and stay honest about what checking actually found, including the parts that were not good news on the first pass.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/0xDarkSeidBull/daotask16" rel="noopener noreferrer"&gt;github.com/0xDarkSeidBull/daotask16&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live site:&lt;/strong&gt; &lt;a href="https://daotask16.test-hub.xyz" rel="noopener noreferrer"&gt;daotask16.test-hub.xyz&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;All bridge fee and timeline data verified live in August 2026, cross-checked on two independent bridge interfaces. Exchange recovery processes confirmed against official help center documentation. The BNB Chain finding and the Solana two-hop route were both confirmed directly by Redbelly's team. This guide contains no price predictions or investment advice.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How Network Adoption Drives RBNT Value</title>
      <dc:creator>Sachin</dc:creator>
      <pubDate>Thu, 06 Aug 2026 16:37:09 +0000</pubDate>
      <link>https://dev.to/0xdarkseidbull/verifying-before-shipping-a-rbnt-token-case-study-3bc1</link>
      <guid>https://dev.to/0xdarkseidbull/verifying-before-shipping-a-rbnt-token-case-study-3bc1</guid>
      <description>&lt;p&gt;Redbelly Network is a Layer 1 blockchain built for compliant real-world&lt;br&gt;
asset (RWA) tokenization. Its native token, RBNT, isn't a side asset. It's&lt;br&gt;
the fuel every part of the network runs on. That structural link is why&lt;br&gt;
adoption, not speculation, is what actually drives demand for RBNT.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More activity means more gas burned.&lt;/strong&gt; Every transaction on Redbelly,&lt;br&gt;
whether it's an issuer registering an asset, an investor transferring a&lt;br&gt;
tokenized security, or a smart contract call, needs RBNT to pay gas.&lt;br&gt;
Redbelly prices gas at a flat $0.000000476190476 per unit, so&lt;br&gt;
cost-per-transaction stays predictable even as usage grows. A stable&lt;br&gt;
price per transaction still means total RBNT consumed scales with&lt;br&gt;
transaction count. Ten times the on-chain activity means roughly ten&lt;br&gt;
times the gas-driven demand for RBNT, independent of anything happening&lt;br&gt;
in the market.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More validators and node operators means more staking demand.&lt;/strong&gt; RBNT&lt;br&gt;
secures the network through three separate staking types: consensus,&lt;br&gt;
SEVM, and oracle staking, each locking RBNT to earn rewards for securing&lt;br&gt;
a distinct part of the system. As more institutions and platforms build&lt;br&gt;
on Redbelly, the network needs more validators, more SEVM capacity, and&lt;br&gt;
more oracle coverage to keep pace, and each of those roles requires&lt;br&gt;
staked RBNT. Adoption on the demand side (more issuers, more assets)&lt;br&gt;
pulls more RBNT into the supply side (more staked, securing capacity).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More shards means more locked RBNT.&lt;/strong&gt; Sharding is Redbelly's approach&lt;br&gt;
to scaling, and participants lock RBNT to initiate and manage shards. A&lt;br&gt;
network handling a handful of pilot programs needs far less sharded&lt;br&gt;
capacity than one settling real-world assets at institutional volume, so&lt;br&gt;
as usage climbs, so does the RBNT locked to support it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More ecosystem participants means more governance stake.&lt;/strong&gt; RBNT&lt;br&gt;
holders vote on upgrades, allocation changes, and validator-set&lt;br&gt;
reconfiguration. A growing base of node operators, oracle providers, and&lt;br&gt;
builders, all paid in RBNT for growing the network, means a growing base&lt;br&gt;
of RBNT holders with a direct stake in how the network evolves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The common thread.&lt;/strong&gt; None of this requires a story about future price.&lt;br&gt;
It's a mechanical relationship. Redbelly's whitepaper ties gas, staking,&lt;br&gt;
sharding, and governance directly to RBNT, with no substitute token able&lt;br&gt;
to fill any of those roles. As real-world-asset issuance, transaction&lt;br&gt;
volume, and validator and oracle participation on Redbelly grow, each of&lt;br&gt;
these four channels pulls more RBNT out of open circulation and into&lt;br&gt;
active use. Adoption is the input. RBNT demand is the mechanical output.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sourced from Redbelly Network's official whitepaper (August 2025). Full&lt;br&gt;
citations, the allocation table, and vesting schedule are in the&lt;br&gt;
companion Tokenomics Report (Part 1 of this task). This piece makes no&lt;br&gt;
price predictions, only claims about what network growth structurally&lt;br&gt;
requires of RBNT.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to Bridge ETH from Ethereum Sepolia to Redbelly Network Testnet: A Complete Lock-and-Mint Integration Guide</title>
      <dc:creator>Sachin</dc:creator>
      <pubDate>Thu, 06 Aug 2026 16:13:15 +0000</pubDate>
      <link>https://dev.to/0xdarkseidbull/how-to-bridge-eth-from-ethereum-sepolia-to-redbelly-network-testnet-a-complete-lock-and-mint-4i88</link>
      <guid>https://dev.to/0xdarkseidbull/how-to-bridge-eth-from-ethereum-sepolia-to-redbelly-network-testnet-a-complete-lock-and-mint-4i88</guid>
      <description>&lt;p&gt;&lt;em&gt;A step-by-step walkthrough of building, deploying, and running a working 2-of-3 multisig bridge between Sepolia and Redbelly Testnet  with real transaction proof, full code, and every error I actually hit along the way.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmja6rv4kfhkqkeob5j2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmja6rv4kfhkqkeob5j2w.png" alt=" " width="800" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Redbelly Network is a fast, deterministic-finality Layer 1 built for compliant real-world-asset tokenization  but like most young chains, it has a liquidity problem. If you're a DeFi user sitting on ETH in a MetaMask wallet, there's no obvious, well-documented path to get that value onto Redbelly and start using it.&lt;/p&gt;

&lt;p&gt;I set out to fix that gap for myself, and ended up building &lt;strong&gt;Redbridge&lt;/strong&gt;: a working, testnet-deployed lock-and-mint bridge that moves ETH from Ethereum Sepolia to Redbelly Network Testnet as a wrapped ERC-20 (&lt;code&gt;WETH.rb&lt;/code&gt;), secured by a 2-of-3 multisig relayer network.&lt;/p&gt;

&lt;p&gt;This article is the guide I wish existed when I started. By the end, you'll understand exactly how a lock-and-mint bridge works, you'll have deployed one yourself on testnet, and you'll know the seven specific errors I hit building this  with the exact fix for each, so you don't lose the hours I did.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://redbridge.test-hub.xyz" rel="noopener noreferrer"&gt;redbridge.test-hub.xyz&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live bridge history API:&lt;/strong&gt; &lt;a href="https://api.redbridge.test-hub.xyz/api/bridge-history" rel="noopener noreferrer"&gt;api.redbridge.test-hub.xyz/api/bridge-history&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full source code:&lt;/strong&gt; &lt;a href="https://github.com/0xDarkSeidBull/daotask8" rel="noopener noreferrer"&gt;https://github.com/0xDarkSeidBull/daotask8&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's get into it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Bridge architecture, explained from scratch&lt;/li&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Step-by-step: deploying your own bridge&lt;/li&gt;
&lt;li&gt;Code walkthrough: lock, verify, mint&lt;/li&gt;
&lt;li&gt;Gas cost analysis&lt;/li&gt;
&lt;li&gt;Stuck-fund recovery: support tickets&lt;/li&gt;
&lt;li&gt;Troubleshooting: seven real errors and their exact fixes&lt;/li&gt;
&lt;li&gt;Proof: verified testnet transactions&lt;/li&gt;
&lt;li&gt;Does anything already bridge into Redbelly?&lt;/li&gt;
&lt;li&gt;What I'd build next&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. Bridge architecture, explained from scratch
&lt;/h2&gt;

&lt;p&gt;If you've never looked under the hood of a cross-chain bridge before, the mental model is simpler than it sounds. There is no magic teleportation of tokens between chains  that's not how any of this works, on any bridge, ever. What actually happens is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You lock an asset on the &lt;strong&gt;source chain&lt;/strong&gt; (it never leaves that chain).&lt;/li&gt;
&lt;li&gt;Some mechanism verifies that the lock genuinely happened.&lt;/li&gt;
&lt;li&gt;An equivalent, wrapped representation of that asset gets &lt;strong&gt;minted&lt;/strong&gt; on the &lt;strong&gt;destination chain&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is called a &lt;strong&gt;lock-and-mint bridge&lt;/strong&gt;, and it's the same fundamental pattern used by Wormhole's Token Bridge, LayerZero's OFT standard, and most bridges you've used without realizing it. The differences between bridges mostly come down to &lt;em&gt;step 2&lt;/em&gt;  how do you trustlessly verify that a lock actually happened, without just trusting one central party?&lt;/p&gt;

&lt;p&gt;Redbridge's architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   Ethereum Sepolia                                    Redbelly Testnet
  ┌───────────────────┐                              ┌──────────────────────┐
  │ SepoliaLockVault   │                              │ WETHBridged (ERC-20)  │
  │  .sol               │                              │  .sol                  │
  │                      │      watches Locked event    │                        │
  │  lock(recipient) ────┼────► relayer (off-chain) ───►│  confirmMint(...)       │
  │  payable, locks ETH  │      3 independent           │  2-of-3 multisig gate   │
  └───────────────────┘      signer processes         └──────────────────────┘
                                                                  │
                                                                  ▼
                                                         mints WETH.rb 1:1
                                                         to the recipient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why a relayer, and not a full light client?
&lt;/h3&gt;

&lt;p&gt;The gold-standard answer to "how do you verify a lock happened on another chain" is to run a &lt;strong&gt;light client&lt;/strong&gt; of the source chain inside a smart contract on the destination chain  this is genuinely how LayerZero and Wormhole work under the hood, with their own decentralized validator/oracle networks doing that verification at scale.&lt;/p&gt;

&lt;p&gt;Building a custom light client for Redbelly's consensus mechanism from scratch is a serious undertaking, well beyond the scope of a reference implementation. So Redbridge uses the next-best, widely-used simplification: a &lt;strong&gt;permissioned relayer set&lt;/strong&gt;. A small number of pre-approved off-chain processes independently watch the source chain, and the destination contract only acts once enough of them agree. This is a real, production-adjacent pattern  it's just centralized around a known signer set rather than a fully decentralized validator network.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why 2-of-3, and not a single relayer?
&lt;/h3&gt;

&lt;p&gt;If a single relayer's private key gets compromised, or the relayer operator simply decides to act maliciously, a 1-of-1 design lets them mint unlimited &lt;code&gt;WETH.rb&lt;/code&gt; out of thin air  nothing locked on Sepolia would back it.&lt;/p&gt;

&lt;p&gt;Requiring &lt;strong&gt;2 of 3&lt;/strong&gt; independent signers to agree before a mint executes means a single compromised, offline, or malicious signer can neither mint fraudulently nor halt the bridge alone. It's the same threshold-signature philosophy behind the "guardian networks" you'll see described in Wormhole's and other production bridges' documentation, just scaled down to a size appropriate for a testnet reference build.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clearing up a common point of confusion: who approves what
&lt;/h3&gt;

&lt;p&gt;When I first built this, my own reflex was to think the &lt;em&gt;user&lt;/em&gt; should somehow "approve" their own mint after locking  surely there's a second click for them, right? There isn't, and understanding why is the key insight of this whole architecture.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Who&lt;/th&gt;
&lt;th&gt;What they actually do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bridge user&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Anyone&lt;/td&gt;
&lt;td&gt;Calls &lt;code&gt;lock()&lt;/code&gt; on Sepolia. That's it  full stop. No separate approval, no claim transaction.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Relayer signers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3 pre-configured wallets, set at deploy time via &lt;code&gt;updateRelayerSigner()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Independently detect the &lt;code&gt;Locked&lt;/code&gt; event, wait for confirmations, and call &lt;code&gt;confirmMint()&lt;/code&gt;. Once 2 of 3 have done so, the contract mints automatically.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If the &lt;em&gt;user&lt;/em&gt; could approve their own mint, the entire multisig concept would be pointless  anyone could self-approve a fraudulent mint with no independent verification that a lock ever occurred. The separation between "the person who locked funds" and "the independent parties who verify the lock" is the whole point.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before you start, you'll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Node.js 18 or newer.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sepolia ETH&lt;/strong&gt; in a wallet you control. Get some free from &lt;a href="https://sepoliafaucet.com" rel="noopener noreferrer"&gt;sepoliafaucet.com&lt;/a&gt; or any Sepolia faucet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redbelly Testnet RBNT&lt;/strong&gt; for gas  but only in the &lt;em&gt;relayer&lt;/em&gt; wallets, not the end-user wallet. Bridging as a user costs zero RBNT; running the relayer processes that approve mints does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An RPC provider for Sepolia whose free tier actually supports &lt;code&gt;eth_getLogs&lt;/code&gt; at usable ranges.&lt;/strong&gt; This sounds like a minor detail. It is not  it is the single biggest time-sink in this whole build, and Section 7.1 below is dedicated to walking through exactly why, with the fix.&lt;/li&gt;
&lt;li&gt;Basic familiarity with &lt;strong&gt;Hardhat&lt;/strong&gt; and &lt;strong&gt;ethers.js v6&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Step-by-step: deploying your own bridge
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1 Clone and install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/0xDarkSeidBull/dao-redbelly.git
&lt;span class="nb"&gt;cd &lt;/span&gt;dao-redbelly
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.2 Configure your environment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
nano .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fields that matter most to fill in correctly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DEPLOYER_PRIVATE_KEY&lt;/code&gt;  deploys both contracts and becomes the initial owner of each.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SEPOLIA_RPC&lt;/code&gt;  &lt;strong&gt;read Section 7.1 before picking one.&lt;/strong&gt; A wrong choice here will cost you an hour of confusing crashes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;REDBELLY_TESTNET_RPC&lt;/code&gt;  &lt;code&gt;https://governors.testnet.redbelly.network&lt;/code&gt; works out of the box.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RELAYER_PRIVATE_KEY&lt;/code&gt; and &lt;code&gt;RELAYER2_PRIVATE_KEY&lt;/code&gt;  two &lt;em&gt;independent&lt;/em&gt; wallets, each funded with a small amount of Redbelly Testnet RBNT for gas.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.3 Deploy the two contracts
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx hardhat run scripts/deploy-sepolia.js &lt;span class="nt"&gt;--network&lt;/span&gt; sepolia
npx hardhat run scripts/deploy-redbelly.js &lt;span class="nt"&gt;--network&lt;/span&gt; redbellyTestnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each script prints a deployed contract address. Copy them into &lt;code&gt;LOCK_VAULT_ADDRESS&lt;/code&gt; and &lt;code&gt;WETH_BRIDGED_ADDRESS&lt;/code&gt; in your &lt;code&gt;.env&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.4 Register your relayer signers on-chain
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;WETHBridged&lt;/code&gt; contract needs to know &lt;em&gt;which three addresses&lt;/em&gt; are allowed to call &lt;code&gt;confirmMint()&lt;/code&gt;. From the contract owner's account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx hardhat console &lt;span class="nt"&gt;--network&lt;/span&gt; redbellyTestnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;weth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContractAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WETHBridged&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WETH_BRIDGED_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;weth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateRelayerSigner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0xYourSigner1Address&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;weth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateRelayerSigner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0xYourSigner2Address&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;weth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateRelayerSigner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0xYourSigner3Address&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// fine to leave unused if you're only running 2 signers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two active signers are enough to satisfy a 2-of-3 threshold  the third slot doesn't need a live relayer process behind it for the bridge to function.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.5 Run the relayer processes
&lt;/h3&gt;

&lt;p&gt;Each signer runs its &lt;strong&gt;own independent process&lt;/strong&gt;, with its own private key, independently watching the same two contracts. Redbridge runs two of these under &lt;code&gt;pm2&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env .env.signer1   &lt;span class="c"&gt;# set RELAYER_PRIVATE_KEY to signer 1's key, SIGNER_LABEL=signer1&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; .env .env.signer2   &lt;span class="c"&gt;# set RELAYER_PRIVATE_KEY to signer 2's key, SIGNER_LABEL=signer2&lt;/span&gt;

pm2 start ecosystem.config.js
pm2 save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm it's alive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pm2 logs relayer-signer1 &lt;span class="nt"&gt;--lines&lt;/span&gt; 20 &lt;span class="nt"&gt;--nostream&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see it scan a window of recent blocks for any locks it missed, then settle into "listening for new &lt;code&gt;Locked&lt;/code&gt; events."&lt;/p&gt;

&lt;h3&gt;
  
  
  3.6 Send your first bridge transaction
&lt;/h3&gt;

&lt;p&gt;Call &lt;code&gt;lock()&lt;/code&gt; on &lt;code&gt;SepoliaLockVault&lt;/code&gt;, attaching ETH and specifying your Redbelly recipient address (the exact code is in Section 4.2 below). Within roughly 30 seconds to a couple of minutes  depending on your confirmation-depth settings  both relayer signers will independently detect the lock, verify it, and call &lt;code&gt;confirmMint()&lt;/code&gt;. The moment the second approval lands, &lt;code&gt;WETH.rb&lt;/code&gt; appears in the recipient's Redbelly Testnet wallet. No further action required from the user.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Code walkthrough: lock, verify, mint
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ethers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sepoliaProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JsonRpcProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SEPOLIA_RPC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;USER_PRIVATE_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sepoliaProvider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LOCK_VAULT_ABI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function lock(address redbellyRecipient) external payable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;event Locked(uint256 indexed nonce, address indexed sender, address indexed redbellyRecipient, uint256 amount, uint256 timestamp)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lockVault&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LOCK_VAULT_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LOCK_VAULT_ABI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.2 Bridge: lock ETH on Sepolia
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;bridgeETH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recipientOnRedbelly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amountEth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;lockVault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recipientOnRedbelly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseEther&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amountEth&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Lock tx submitted:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;receipt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lockedEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;receipt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logs&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;lockVault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Locked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Locked with nonce:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lockedEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;lockedEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;bridgeETH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0xRecipientAddressOnRedbelly&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.01&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire user-facing interaction. There is no second transaction to send.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 Checking mint status (since minting is automatic, not claimed)
&lt;/h3&gt;

&lt;p&gt;Since there's no "claim" button, checking on your bridge's progress means polling the destination contract's own state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redbellyProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JsonRpcProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDBELLY_TESTNET_RPC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WETH_BRIDGED_ABI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function computeMintKey(uint256 sourceChainId, uint256 sourceNonce) public pure returns (bytes32)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function mintRequests(bytes32) public view returns (address recipient, uint256 amount, uint256 approvalCount, bool executed)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wethBridged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WETH_BRIDGED_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;WETH_BRIDGED_ABI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;redbellyProvider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SEPOLIA_CHAIN_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;11155111&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkMintStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lockNonce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mintKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;wethBridged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;computeMintKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SEPOLIA_CHAIN_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lockNonce&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;wethBridged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mintRequests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mintKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;approvals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;approvalCount&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;executed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// Poll every 15 seconds until executed === true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a subtle but important design decision buried in that snippet: notice I'm reading &lt;code&gt;mintRequests()&lt;/code&gt; directly from the contract, rather than parsing the &lt;code&gt;approvalCount&lt;/code&gt; out of a decoded event log from a transaction receipt. That wasn't the original design  I switched to it after hitting a genuinely confusing bug, which is Section 7.4 below. If you only read one troubleshooting entry in this whole article, make it that one; it generalizes to a lot more than just bridges.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.4 If you're extending this to ERC-20 tokens instead of native ETH
&lt;/h3&gt;

&lt;p&gt;Redbridge moves native ETH, so there's no ERC-20 &lt;code&gt;approve()&lt;/code&gt; step in the base flow. If you fork this to lock a token instead (say, a testnet USDC), the user-facing flow gains exactly one extra step before locking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TOKEN_ABI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function approve(address spender, uint256 amount) external returns (bool)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TOKEN_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TOKEN_ABI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;approveTx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;approve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LOCK_VAULT_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amountToBridge&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;approveTx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// then call the vault's lock(token, amount, recipient) equivalent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Gas cost analysis
&lt;/h2&gt;

&lt;p&gt;Measured directly on Sepolia and Redbelly Testnet in August 2026. These are gas-unit and native-token figures  actual USD cost depends on the ETH/RBNT price and network congestion at the time you transact.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Chain&lt;/th&gt;
&lt;th&gt;Gas used (approx.)&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;lock()&lt;/code&gt;  0.001 ETH&lt;/td&gt;
&lt;td&gt;Sepolia&lt;/td&gt;
&lt;td&gt;~68,000&lt;/td&gt;
&lt;td&gt;A single &lt;code&gt;SSTORE&lt;/code&gt; for the incrementing nonce plus the &lt;code&gt;Locked&lt;/code&gt; event emission dominate the cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;lock()&lt;/code&gt;  0.012 ETH&lt;/td&gt;
&lt;td&gt;Sepolia&lt;/td&gt;
&lt;td&gt;~68,000&lt;/td&gt;
&lt;td&gt;Identical to the row above  gas cost here is amount-independent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;confirmMint()&lt;/code&gt;  1st approval&lt;/td&gt;
&lt;td&gt;Redbelly Testnet&lt;/td&gt;
&lt;td&gt;~85,000&lt;/td&gt;
&lt;td&gt;Writes a new &lt;code&gt;mintRequests&lt;/code&gt; entry, increments the approval counter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;confirmMint()&lt;/code&gt;  2nd approval (triggers the mint)&lt;/td&gt;
&lt;td&gt;Redbelly Testnet&lt;/td&gt;
&lt;td&gt;~140,000&lt;/td&gt;
&lt;td&gt;The extra cost comes from the ERC-20 &lt;code&gt;_mint()&lt;/code&gt; call plus the &lt;code&gt;MintExecuted&lt;/code&gt; event&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The interesting takeaway here isn't the specific numbers  it's that &lt;strong&gt;the size of the transfer has zero effect on gas cost&lt;/strong&gt;. Locking 0.001 ETH and locking 0.012 ETH cost exactly the same, because gas is a function of &lt;em&gt;state changes&lt;/em&gt;, not &lt;em&gt;value moved&lt;/em&gt;. This has a real design consequence: if this bridge were handling meaningful volume, there's no discount for batching, and a production version would want a batched-mint pattern  one &lt;code&gt;confirmMint()&lt;/code&gt; call that approves many pending locks at once  to amortize that fixed per-transaction overhead across many users. This reference implementation doesn't include that, and it's the first thing I'd build if this moved toward mainnet.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Stuck-fund recovery: support tickets
&lt;/h2&gt;

&lt;p&gt;Somewhere around transaction #9 in testing, I hit the scenario every bridge builder dreads: a user's lock sat there, fully confirmed on Sepolia, and just... never minted. No error in the UI, nothing actionable  just a transaction that looked stuck, with zero visibility into why, and, at that point, no way for a user to do anything about it except message me directly.&lt;/p&gt;

&lt;p&gt;That's a bad place to leave a live product, even a testnet one. So before calling this bridge "done," I built a self-serve recovery path: a support-ticket system, backed by the same bridge-history database, wired directly into the app's Contact Support / Reclaim flow.&lt;/p&gt;

&lt;p&gt;Here's what it actually does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Opening a ticket&lt;/strong&gt; takes just a Sepolia lock tx hash and a wallet address. The backend automatically looks up the lock's current on-chain status  no manual investigation needed on either side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If the transaction already minted successfully&lt;/strong&gt;, the ticket resolves itself, instantly, in the same request  tagged &lt;code&gt;resolved_already_bridged&lt;/code&gt;, with the destination Redbelly transaction hash attached as proof. This turned out to matter more than I expected: a good chunk of "is my bridge stuck?" reports are, on inspection, transactions that bridged completely normally and the user just didn't see it land. Those now get an immediate, correct answer with zero human involvement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Genuinely stuck locks&lt;/strong&gt; stay &lt;code&gt;active&lt;/code&gt;, with the on-chain status attached, until someone manually resolves them  so no context gets lost between "user reports it" and "someone investigates it."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-submitting the same transaction&lt;/strong&gt; while a ticket is already open returns the existing ticket rather than spawning a duplicate.&lt;/li&gt;
&lt;li&gt;A wallet can pull up &lt;strong&gt;every ticket it has opened&lt;/strong&gt;, split into Active and Solved, without needing to keep track of a ticket ID.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The API surface is small and deliberately boring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /api/support-ticket                      open a ticket (auto-resolves if already minted)
GET  /api/support-ticket/:ticketId             fetch a single ticket
GET  /api/support-ticket/wallet/:address       all tickets for a wallet, split Active / Solved
POST /api/support-ticket/:ticketId/resolve     admin-only, marks a stuck lock as recovered
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The transaction that prompted this  nonce &lt;code&gt;#9&lt;/code&gt;  is in the proof table in Section 8 below, marked as recovered. It was genuinely stuck for about 22 hours, for the reason covered in Section 7.6 next, and it's now minted, with the recovery flow this section describes having actually been exercised on a real, previously-broken transaction rather than just designed in the abstract.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Troubleshooting: seven real errors and their exact fixes
&lt;/h2&gt;

&lt;p&gt;Everything below actually happened during this build. I'm including the raw error text because that's what you'll be pasting into a search bar at 1am, and I want this article to be the thing that search turns up.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.1 &lt;code&gt;eth_getLogs&lt;/code&gt; fails with "archive request" / "requires a personal token"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What you'll see:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;-32602&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Archive requests require a personal token. Get one at: https://www.allnodes.com/publicnode"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; Free public RPC endpoints restrict how large a block range a single &lt;code&gt;eth_getLogs&lt;/code&gt; call can cover. &lt;code&gt;publicnode.com&lt;/code&gt; treats essentially &lt;em&gt;any&lt;/em&gt; historical range as an "archive request" and blocks it outright, regardless of how small the range is. Alchemy's free tier is more usable but still caps &lt;code&gt;eth_getLogs&lt;/code&gt; at a hard &lt;strong&gt;10-block range per call&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix&lt;/strong&gt; is to stop asking for one big range and instead query in small chunks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getLogsChunked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fromBlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toBlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chunkSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allEvents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fromBlock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;toBlock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;chunkSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;chunkSize&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toBlock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queryFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;allEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// stay under free-tier rate limits&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;allEvents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also keep your &lt;code&gt;HISTORICAL_LOOKBACK_BLOCKS&lt;/code&gt; setting modest (300–500) unless you're paying for a higher RPC tier  scanning 10,000 blocks in 10-block chunks means 1,000 sequential requests, and that alone will trip the next error.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.2 Alchemy 429  "exceeded compute units per second capacity"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What you'll see:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Your app has exceeded its compute units per second capacity..."&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; Running multiple relayer processes against the same free-tier API key, each independently chunk-scanning history at the same moment, multiplies your request rate. Two processes doing 400ms-apart chunked requests simultaneously will burst well past a free tier's per-second ceiling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; the same chunk-delay from 7.1 helps, plus keeping the lookback window small so there's simply less to scan. If you're moving beyond a testnet demo into anything approaching real volume, budget for a paid RPC tier  this isn't a workaround you want to lean on in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.3 &lt;code&gt;SqliteError: database is locked&lt;/code&gt; (&lt;code&gt;SQLITE_BUSY&lt;/code&gt;) on startup
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What you'll see:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SqliteError: database is locked
    at Database.pragma (.../better-sqlite3/lib/methods/pragma.js:11:44)
code: 'SQLITE_BUSY'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; Two relayer processes (one per signer) both open the same SQLite file at nearly the same startup moment. If one process is midway through setting &lt;code&gt;journal_mode = WAL&lt;/code&gt; exactly when the other tries to open the file, SQLite briefly, correctly reports it as busy, and by default that surfaces as a hard crash instead of a retry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix&lt;/strong&gt; is a one-line addition: give the connection an explicit busy-timeout so it waits and retries instead of throwing immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DB_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pragma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;busy_timeout = 10000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pragma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;journal_mode = WAL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7.4 &lt;code&gt;approvalCount&lt;/code&gt; silently stored as &lt;code&gt;0&lt;/code&gt;  the one that took longest to find
&lt;/h3&gt;

&lt;p&gt;This is the bug I mentioned back in Section 4.3, and it's worth walking through in a bit more detail because the &lt;em&gt;lesson&lt;/em&gt; is more broadly useful than the specific fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I saw:&lt;/strong&gt; the database was recording every relayer approval with &lt;code&gt;approval_count: 0&lt;/code&gt;  even though I could independently verify, by reading the contract's own state directly, that the real, on-chain approval count was correctly progressing (1, then 2).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What was actually happening:&lt;/strong&gt; my original code parsed the &lt;code&gt;MintApproved&lt;/code&gt; event's &lt;code&gt;approvalCount&lt;/code&gt; argument straight out of the transaction receipt's decoded logs. In Solidity, &lt;code&gt;uint256&lt;/code&gt; values decode to JavaScript &lt;code&gt;BigInt&lt;/code&gt; under ethers v6. Under conditions I still can't fully explain, it worked fine in isolated debugging scripts but not in the live relayer process; that decoded value occasionally came back &lt;code&gt;undefined&lt;/code&gt;, which turned &lt;code&gt;Number(undefined)&lt;/code&gt; into &lt;code&gt;NaN&lt;/code&gt;. And here's the part that made this genuinely hard to catch: &lt;code&gt;better-sqlite3&lt;/code&gt; doesn't throw on a &lt;code&gt;NaN&lt;/code&gt; integer-bind parameter. It silently coerces it to &lt;code&gt;0&lt;/code&gt;. No error, no warning, no crash  just a quietly wrong number sitting in the database until I happened to cross-check it against the chain directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix and the generalizable lesson:&lt;/strong&gt; stop trusting a value derived from log-parsing when the same value is available as a direct, authoritative contract read.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;freshState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;wethBridged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mintRequests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mintKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;approvalCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;freshState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;approvalCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// authoritative  not derived from logs&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;executed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;freshState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It costs one extra RPC call. It eliminates an entire category of "my decoded event argument is subtly wrong, and I have no idea why" bugs. If you take away nothing else from this whole troubleshooting section: &lt;strong&gt;whenever you have a choice between parsing something out of an event log and reading it directly from contract state, read it directly.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  7.5 &lt;code&gt;pm2 restart&lt;/code&gt; silently ignores your &lt;code&gt;.env&lt;/code&gt; changes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What you'll see:&lt;/strong&gt; you update &lt;code&gt;SEPOLIA_RPC&lt;/code&gt; in &lt;code&gt;.env&lt;/code&gt;, run &lt;code&gt;pm2 restart &amp;lt;name&amp;gt;&lt;/code&gt;, and the logs keep showing requests going to the &lt;em&gt;old&lt;/em&gt; RPC URL  as if nothing changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; if your &lt;code&gt;ecosystem.config.js&lt;/code&gt; reads &lt;code&gt;process.env.*&lt;/code&gt; at file-load time (via a top-level &lt;code&gt;require("dotenv").config()&lt;/code&gt;) and copies those values into each app's &lt;code&gt;env&lt;/code&gt; object, Node caches that &lt;code&gt;require()&lt;/code&gt; call the very first time it runs. &lt;code&gt;pm2 restart&lt;/code&gt; restarts the &lt;em&gt;process&lt;/em&gt;  it does not re-execute &lt;code&gt;ecosystem.config.js&lt;/code&gt;. So it keeps reusing whatever environment values were baked in at the last &lt;code&gt;pm2 start ecosystem.config.js&lt;/code&gt;, no matter how many times you &lt;code&gt;restart&lt;/code&gt; after that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix&lt;/strong&gt; has two valid versions. The reliable one, which I ended up using: skip &lt;code&gt;ecosystem.config.js&lt;/code&gt;'s environment-copying entirely, and have each process load its own &lt;code&gt;.env&lt;/code&gt; file fresh, directly, at the moment it actually starts, via a tiny shell wrapper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# start-signer1.sh&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; /path/to/.env.signer1
&lt;span class="nb"&gt;set&lt;/span&gt; +a
&lt;span class="nb"&gt;exec &lt;/span&gt;node /path/to/relayer/relayer.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ecosystem.config.js&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;relayer-signer1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./start-signer1.sh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;relayer-signer2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./start-signer2.sh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This guarantees the environment gets read fresh from disk every single time the process actually starts, completely independent of any caching behavior in &lt;code&gt;pm2&lt;/code&gt; itself. (The cruder but valid alternative: always &lt;code&gt;pm2 delete &amp;lt;name&amp;gt;&lt;/code&gt; followed by a fresh &lt;code&gt;pm2 start ecosystem.config.js&lt;/code&gt; after any &lt;code&gt;.env&lt;/code&gt; edit, never just &lt;code&gt;restart&lt;/code&gt;.)&lt;/p&gt;

&lt;h3&gt;
  
  
  7.6 A stuck lock gets permanently skipped as "possible reorg"  but it wasn't a reorg
&lt;/h3&gt;

&lt;p&gt;This is the bug behind nonce &lt;code&gt;#9&lt;/code&gt;, the transaction discussed in Section 6 above, and it's the one that worried me most  not because it was hard to fix, but because of &lt;em&gt;how&lt;/em&gt; it failed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I saw:&lt;/strong&gt; nonce #9's lock sat fully confirmed on Sepolia, but the relayer logs showed something oddly final: &lt;code&gt;Lock #9 not verifiable post-confirmation (possible reorg) -- skipping.&lt;/code&gt; Once a relayer logs that, it means "give up permanently," not "try again later." Except there was no reorg. The lock was sitting right there on Sepolia, completely untouched, hours later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; the verification function, &lt;code&gt;getLogsChunked()&lt;/code&gt;, was designed to retry on RPC failures and eventually give up after three attempts  reasonable so far. But when it gave up, it didn't throw. It logged the failure and quietly returned whatever partial (sometimes completely empty) results it had collected, as if that were a complete, successful query. The caller, &lt;code&gt;verifyLockStillValid()&lt;/code&gt;, had no way to distinguish "I successfully checked and found nothing" from "I failed to check at all." Both cases produced an identical empty result, and an empty result was interpreted as "the lock got reorged out  skip it forever."&lt;/p&gt;

&lt;p&gt;That's a dangerous class of bug, because it fails both &lt;em&gt;silently&lt;/em&gt; and &lt;em&gt;permanently&lt;/em&gt;. A transient RPC hiccup  the exact kind Sections 7.1 and 7.2 are all about  was enough to permanently orphan a real, valid, fully-locked user transaction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; make the failure loud instead of silent. Have &lt;code&gt;getLogsChunked()&lt;/code&gt; throw once its retries are genuinely exhausted, and have the caller treat a thrown error completely differently from a clean, successful, empty result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`getLogs chunk [&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] failed after 3 attempts:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`getLogsChunked exhausted retries for [&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A thrown error now means "ask again later"  the lock gets re-queued with a 30-second backoff instead of abandoned. Only a clean, successful query that genuinely returns zero matching events gets to call itself a reorg. This is the fix that eventually let nonce #9 recover on its own, and it's also what powers the "genuinely stuck" side of the support-ticket system in Section 6.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.7 The verification window checks blocks that haven't been mined yet
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What I saw:&lt;/strong&gt; even after the fix above, &lt;em&gt;every single fresh lock&lt;/em&gt;  not just ones hitting RPC trouble  went through one unnecessary retry cycle before verifying successfully. Consistently. On every transaction, not occasionally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; the verification window was calculated as &lt;code&gt;originalBlockNumber + CHUNK_SIZE&lt;/code&gt; (10 blocks past the lock), but &lt;code&gt;CONFIRMATION_BLOCKS&lt;/code&gt; was set to just 2. The relayer would attempt verification only 2 blocks after the lock, while asking for a window extending 10 blocks past it  a range that, at that point, simply didn't exist on chain yet. The relayer was asking Sepolia for blocks from the future. This wasn't an occasional RPC problem; it was a structural mismatch between two config values that were each individually reasonable, but incompatible together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; cap the query window at the chain's actual current head, never past it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentHead&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sepoliaProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBlockNumber&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;windowEnd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;originalBlockNumber&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;CHUNK_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentHead&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that in place, verification succeeds on the first attempt instead of self-healing through the retry queue 90–100 seconds later. It's a small fix, but it's the difference between "the recovery system quietly compensates for a structural bug on every single transaction" and "the structural bug doesn't exist."&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Proof: verified testnet transactions
&lt;/h2&gt;

&lt;p&gt;Talk is cheap on a crypto blog; here are real, independently checkable transactions from this exact bridge  all bridged in this testing cycle (19–20 August 2026). Nonce &lt;code&gt;#9&lt;/code&gt; is deliberately included: it's the transaction discussed in Sections 6 and 7.6 above, genuinely stuck for roughly 22 hours before the fix, and now minted  proof the recovery path works end to end on a real transaction, not just in a design doc.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Lock nonce&lt;/th&gt;
&lt;th&gt;Sepolia lock tx&lt;/th&gt;
&lt;th&gt;Redbelly mint tx&lt;/th&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;#17&lt;/td&gt;
&lt;td&gt;&lt;a href="https://sepolia.etherscan.io/tx/0xbef7522a4d15a9708f83c90034359f02e5ce035b3beae017044352450b91893a" rel="noopener noreferrer"&gt;&lt;code&gt;0xbef7522a...b91893a&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://redbelly.testnet.routescan.io/tx/0x815e7c58484ae15a6116bab2095d88c27a5bd73e261a6317621ffa5951f2345c" rel="noopener noreferrer"&gt;&lt;code&gt;0x815e7c58...f2345c&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;0.001 ETH&lt;/td&gt;
&lt;td&gt;✅ Minted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#16&lt;/td&gt;
&lt;td&gt;&lt;a href="https://sepolia.etherscan.io/tx/0x84b13ca308679a7ebb902b7c227323248fcfe86473bd92298513520a05d05737" rel="noopener noreferrer"&gt;&lt;code&gt;0x84b13ca3...d05737&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://redbelly.testnet.routescan.io/tx/0x91f30864af8227f8b3d2332c68dcc7169260f37b115d43179bd6922633d7774a" rel="noopener noreferrer"&gt;&lt;code&gt;0x91f30864...d7774a&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;0.001 ETH&lt;/td&gt;
&lt;td&gt;✅ Minted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#15&lt;/td&gt;
&lt;td&gt;&lt;a href="https://sepolia.etherscan.io/tx/0xc3d52399064fb2019ea50b764c0f1ec0ef600c853e6d7876f752fe777819b657" rel="noopener noreferrer"&gt;&lt;code&gt;0xc3d52399...19b657&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://redbelly.testnet.routescan.io/tx/0x1cca9aa129c87fa7cd86443ec578c18da8e45642558d939baef7509621b44a03" rel="noopener noreferrer"&gt;&lt;code&gt;0x1cca9aa1...b44a03&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;0.0011 ETH&lt;/td&gt;
&lt;td&gt;✅ Minted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#14&lt;/td&gt;
&lt;td&gt;&lt;a href="https://sepolia.etherscan.io/tx/0x48a5267f86c24f3ecbbfa705fbb36126013e6a17e72d5a9055cfffe79b5381b7" rel="noopener noreferrer"&gt;&lt;code&gt;0x48a5267f...5381b7&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://redbelly.testnet.routescan.io/tx/0xebac3c365f5eb195f5357a4299becf58b766c9a476bec2dbf360e47b4e082ffc" rel="noopener noreferrer"&gt;&lt;code&gt;0xebac3c36...082ffc&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;0.0012 ETH&lt;/td&gt;
&lt;td&gt;✅ Minted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#13&lt;/td&gt;
&lt;td&gt;&lt;a href="https://sepolia.etherscan.io/tx/0x626c2af8bb77ee5cf8b2f5039283ccd5360dd3d3593c3858515abe80753ba878" rel="noopener noreferrer"&gt;&lt;code&gt;0x626c2af8...3ba878&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://redbelly.testnet.routescan.io/tx/0xe9a6e5221ecf0964fd631e63efebe3bd94c1fb48f0ed5ca711aa7b3b99a48855" rel="noopener noreferrer"&gt;&lt;code&gt;0xe9a6e522...a48855&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;0.001 ETH&lt;/td&gt;
&lt;td&gt;✅ Minted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#9&lt;/td&gt;
&lt;td&gt;&lt;a href="https://sepolia.etherscan.io/tx/0x2b370c4fcf229328218cac6f9a95b463bcf1242d742d9c02b770ee9673a7889e" rel="noopener noreferrer"&gt;&lt;code&gt;0x2b370c4f...a7889e&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://redbelly.testnet.routescan.io/tx/0xe9e8f29f37409e22c36ca4f4b9b3c6c7ee11f5806d49368b518ea340154891ce" rel="noopener noreferrer"&gt;&lt;code&gt;0xe9e8f29f...4891ce&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;0.1 ETH&lt;/td&gt;
&lt;td&gt;✅ Minted  recovered from a stuck state (Section 7.6)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Deployed contracts:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SepoliaLockVault&lt;/code&gt; (Ethereum Sepolia): &lt;a href="https://sepolia.etherscan.io/address/0x130d07624d00DF30A5C30C3D237fD5d99A3DdE11" rel="noopener noreferrer"&gt;&lt;code&gt;0x130d07624d00DF30A5C30C3D237fD5d99A3DdE11&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WETHBridged&lt;/code&gt; (Redbelly Testnet): &lt;a href="https://redbelly.testnet.routescan.io/token/0x11Bef97d2d2063b41887A76403B852b52D151501?type=erc20" rel="noopener noreferrer"&gt;&lt;code&gt;0x11Bef97d2d2063b41887A76403B852b52D151501&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to see the full, live, continuously-updating history  not just the six rows above, but every transaction any user has bridged  the backend exposes it publicly:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GET https://api.redbridge.test-hub.xyz/api/bridge-history&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Screenshot placeholder: insert a screenshot of the Redbridge app's Transfer Status panel showing a completed bridge, and a second screenshot of the Bridge History table, here.)&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Does anything already bridge into Redbelly?
&lt;/h2&gt;

&lt;p&gt;Part of building this meant actually checking, rather than assuming, whether major cross-chain protocols already support Redbelly natively  because if one does, you may not need to build a custom relayer at all. Here's what I found by checking each protocol's own current, live chain-list documentation directly (not blog posts or year-old summaries, which turned out to be stale in more than one case):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Native Redbelly support?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LayerZero&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;Yes  on Mainnet.&lt;/strong&gt; LayerZero's own docs list a dedicated "Redbelly Mainnet" deployment page and an "OFT Quickstart" guide for it, right alongside every other chain they support. Of everything checked, this is the clearest path to a production-grade Redbelly bridge without maintaining your own relayer infrastructure.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Axelar&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ No confirmed support in their chain-connectivity documentation or ecosystem listings.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Wormhole&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ No. Their official Supported Networks reference table (dated July 2026, spanning all five of their products) lists roughly 40 chains; Redbelly isn't among them. This is the most exhaustive and current source of the four, so it's a high-confidence "no."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Connext&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚠️ Unconfirmed. Connext adds EVM-compatible chains on request via Discord rather than publishing a static support list, so this can't be ruled in or out from documentation alone.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multichain&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⛔ Not applicable. Multichain (formerly Anyswap) shut down in 2023.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;A quick correction worth flagging&lt;/strong&gt;, since it's an easy mistake to make: Redbelly &lt;strong&gt;Mainnet&lt;/strong&gt; is chain ID &lt;strong&gt;151&lt;/strong&gt;, and Redbelly &lt;strong&gt;Testnet&lt;/strong&gt; is chain ID &lt;strong&gt;153&lt;/strong&gt;. Mixing these up when configuring an RPC or a bridge contract will cost you a confusing debugging session.&lt;/p&gt;

&lt;p&gt;Four other integrations turned out to be more immediately relevant than the protocols above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Celer's cBridge&lt;/strong&gt; added Redbelly Network support in August 2025, connecting it with Ethereum and BNB Chain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redbelly's own official bridge&lt;/strong&gt;, &lt;a href="https://www.reddex.io/bridge" rel="noopener noreferrer"&gt;reddex.io/bridge&lt;/a&gt;, is what Redbelly's own X account points users toward for bridging USDC/USDT into the network, describing it as their official and exclusive DEX.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lucid Labs Bridge&lt;/strong&gt; is what Redbelly's own developer docs recommend for the return path  bringing RBNT from a non-Redbelly chain back to Redbelly. It also ships a "Resend Transaction" feature, for retrying a transfer that gets stuck mid-relay without contacting support first, which is the same instinct behind Section 6's ticket system above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Router Protocol's Nitro and Polymer&lt;/strong&gt; aren't bridges you'd pick directly, but they're the infrastructure behind the existing wrapped-RBNT deployments: Nitro powers the Solana wrapped-RBNT bridge, Polymer powers the Ethereum one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given that LayerZero is confirmed live on Redbelly Mainnet, the most natural "version two" of this project isn't more custom relayer code; it's replacing the relayer network entirely with LayerZero's own DVN/Executor infrastructure, while keeping a similar lock-and-mint (or OFT-based) contract pattern. That's genuinely the next thing I'd build.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. What I'd build next
&lt;/h2&gt;

&lt;p&gt;This is a testnet reference implementation, not a production system, and I want to be direct about where the real gaps are rather than overselling it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No batched minting.&lt;/strong&gt; As covered in Section 5, gas cost is currently linear in the number of transactions with no economy of scale. At meaningful volume, that's the first thing to fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native ETH only.&lt;/strong&gt; Extending to arbitrary ERC-20 tokens is a well-understood next step (Section 4.4 sketches what changes), but it isn't built yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permissioned relayer set, not a decentralized validator network.&lt;/strong&gt; This is an honest architectural trade-off for a reference build, not a production security model; see Section 9 for why LayerZero's existing Redbelly deployment is the more credible path to something trustless at scale.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build on top of any of this, or find an eighth error I haven't hit yet, I'd genuinely like to hear about it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Full source code, deployment scripts, tests, and the exact troubleshooting entries above (kept in sync with this article) live in the GitHub repository: &lt;a href="https://github.com/0xDarkSeidBull/daotask8" rel="noopener noreferrer"&gt;https://github.com/0xDarkSeidBull/daotask8&lt;/a&gt;. Live app at &lt;a href="https://redbridge.test-hub.xyz" rel="noopener noreferrer"&gt;redbridge.test-hub.xyz&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written as a submission for TASK-08 (Existing Bridge Integration Guide) on the Redbelly DAO Task Board.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Several messages in the Redbelly community channels, all asking some version of the same five questions:</title>
      <dc:creator>Sachin</dc:creator>
      <pubDate>Thu, 06 Aug 2026 16:05:08 +0000</pubDate>
      <link>https://dev.to/0xdarkseidbull/resolving-kyc-confusion-for-redbelly-network-1nld</link>
      <guid>https://dev.to/0xdarkseidbull/resolving-kyc-confusion-for-redbelly-network-1nld</guid>
      <description>&lt;ul&gt;
&lt;li&gt;When does KYC actually apply?&lt;/li&gt;
&lt;li&gt;How many wallets can one identity cover?&lt;/li&gt;
&lt;li&gt;How long does approval take?&lt;/li&gt;
&lt;li&gt;Are regional restrictions still in place?&lt;/li&gt;
&lt;li&gt;Does staking need KYC too?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That repetition is what TASK-18 on the Redbelly DAO Task Board set out to fix: one plain-language page that answers all five, with each claim either sourced to an official source or clearly marked as unconfirmed.&lt;/p&gt;

&lt;p&gt;The instinct with a task like this is to write what sounds right and move on. Five questions, five answers, ship it. But the task spec was explicit about what counts as a failure: &lt;strong&gt;any claim stated as fact with no source, and not marked unconfirmed, fails review outright.&lt;/strong&gt; That single rule shaped how the whole thing got built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting with what could actually be verified
&lt;/h2&gt;

&lt;p&gt;The first question, when KYC applies, had a clean answer sitting in Redbelly's own developer documentation. &lt;strong&gt;KYC gates native mainnet activity, transactions, staking, governance, all of it runs through the Redbelly Access identity layer, and that scope covers Redbelly's own chain specifically.&lt;/strong&gt; Wrapped RBNT already exists on multiple external chains, Ethereum (ERC-20) and Solana (SPL, bridged via Router Protocol's Nitro), both confirmed through Redbelly Network's own official X account, and any token living outside Redbelly's own chain sits outside that stated scope by definition, so trading it on an external DEX needs no Redbelly-side verification at all. That conclusion follows directly from the onboarding documentation's own scope, not a chain-by-chain guess. What is not separately checked is the token standard on any chain beyond those two confirmed deployments, so the piece does not assume a specific format for a chain it has not named.&lt;/p&gt;

&lt;p&gt;Regional restrictions were more interesting, because the task brief assumed something the evidence did not support. The brief stated restrictions had been removed. Checking Redbelly's own Terms and Conditions told a different story: &lt;strong&gt;Clause 15 lists eighteen jurisdictions still restricted from the platform, Afghanistan through Zimbabwe, with no indication anywhere of a reduction from a prior, larger list.&lt;/strong&gt; This is the same pattern that showed up on an earlier Redbelly DAO task, where a brief's assumption about a listing status turned out to be outdated by the time anyone actually checked. The fix here was the same instinct: verify directly rather than trust the brief, then report what is actually true even when it contradicts what was expected.&lt;/p&gt;

&lt;p&gt;So the explainer states the current restricted list as fact, cited to Clause 15, and does not repeat the removal claim. No source existed for removal, so it does not appear as one. An early draft also implied that residency outside the eighteen listed jurisdictions was itself enough to guarantee KYC approval. The Terms only name who is excluded, they do not separately confirm who is cleared, so that implication was removed too.&lt;/p&gt;

&lt;p&gt;Staking followed the same logic, backed up with a second source after review feedback pushed for something more direct than a whitepaper inference. Redbelly's whitepaper lists staking as one of RBNT's five core uses, running entirely on Redbelly's own chain rather than through a wrapped token. The developer portal adds the direct link: &lt;strong&gt;gaining access to the network at all requires an access credential, proved with a photo ID and biometric checks&lt;/strong&gt;, before a user can self-enable write access through a specific network smart contract, and staking is executed by calling exactly that kind of write function. Two sources, not one whitepaper line stretched to cover it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two claims with no official document
&lt;/h2&gt;

&lt;p&gt;Two of the five questions turned out to have no trace anywhere in official documentation. &lt;strong&gt;The ten-wallet-per-identity limit and the typical approval wait time simply do not appear in any published Redbelly source&lt;/strong&gt;, not the docs site, not the Access portal, not any dev reference.&lt;/p&gt;

&lt;p&gt;The task spec anticipated exactly this situation. Its own language allows a claim to be marked as community-reported and unconfirmed rather than dropped outright, as long as it is labeled honestly. That is where Discord came in. Both figures came from two Redbelly moderators, Appie and Daniel Bressoud, answering directly in the support channel, and the ten-wallet figure specifically was verified firsthand by registering ten wallets under a single identity and confirming the limit holds. Both claims went into the explainer tagged as community-reported and unconfirmed with no published document behind them, &lt;strong&gt;a label revised after review pointed out that the original mod-verified tag read as more certain than an informal Discord answer supports on its own.&lt;/strong&gt; Each tag now sits next to the actual Discord screenshot and a direct link to the message, so a reader can check the source firsthand rather than take the label's word for it.&lt;/p&gt;

&lt;p&gt;That distinction matters more than it might look. A reader skimming the page should be able to tell, at a glance, which numbers came from Redbelly's own documentation and which came from a community channel with a screenshot attached. Blurring that line to make the page look more authoritative would have been easy and would have failed the task's own stated bar. The approval-time figure got a similar tightening on review: it originally read as "most submissions," a phrasing that sounds like a Redbelly-published statistic. It is not one, so the wording now attributes it explicitly to what community members report, not to Redbelly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building it to be read in under ten seconds
&lt;/h2&gt;

&lt;p&gt;The task's quality benchmark was specific: a reader should be able to find their own answer for a stated scenario in under ten seconds. That ruled out long paragraphs up front. Each of the five sections leads with a single bolded sentence carrying the actual answer, then two or three sentences of supporting detail, then the source line in italics below it. Someone scanning for just the wallet limit or just the wait time does not need to read the whole page to find it.&lt;/p&gt;

&lt;p&gt;The deliverable itself stayed to one page on purpose, PDF and DOCX both, plus a markdown source file for the companion site. Getting genuinely five sections of sourced content onto a single printed page took a few passes of tightening margins and spacing rather than cutting content, since the goal was density without losing readability.&lt;/p&gt;

&lt;h2&gt;
  
  
  The companion website
&lt;/h2&gt;

&lt;p&gt;A site went up following the DAO Task Board's own brand kit: deep slate background, slightly lighter slate content cards, one red accent reserved for brand moments, and a separate amber status colour for anything pending or unconfirmed. The PDF sits embedded directly on the page through a proper iframe rather than a link that forces a download, so a reader can open the explainer without leaving the site or triggering an unwanted file save. All five sections render as full formatted content below it, not a collapsed accordion, with the two community-reported claims visibly tagged in amber, each paired with its Discord screenshot and message link, so the distinction from officially sourced claims stays visible and checkable on the page itself, not just described in the source document.&lt;/p&gt;

&lt;p&gt;The logo followed the same fix that has come up on every one of these builds. Referencing an image by its GitHub raw URL works fine in the editor preview and then loads inconsistently once deployed to the production domain, so the image gets uploaded directly into the project's own public folder instead and referenced locally. Small detail, but it is the difference between a logo that shows up for every visitor and one that shows up half the time depending on caching.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this task actually tested
&lt;/h2&gt;

&lt;p&gt;Underneath the formatting, TASK-18 was really testing one thing: whether a contributor will report an inconvenient finding honestly rather than smoothing it over to match what the brief expected. The brief said restrictions were removed. They were not, at least not verifiably. The brief implied five clean answers were sitting somewhere waiting to be written up. Three were. Two needed a Discord conversation with a moderator and firsthand verification instead.&lt;/p&gt;

&lt;p&gt;The honest version of the page is more useful than a confident version would have been, because someone reading it under real confusion about their own KYC status needs to know which parts of the answer are documented fact and which parts are the best information currently available. &lt;strong&gt;Both have value. Only one should be presented as certain.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/0xDarkSeidBull/daotask18" rel="noopener noreferrer"&gt;github.com/0xDarkSeidBull/daotask18&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Live site: &lt;a href="https://daotask18.test-hub.xyz/" rel="noopener noreferrer"&gt;daotask18.test-hub.xyz&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>crypto</category>
    </item>
  </channel>
</rss>
