<?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: KOMARI Subheeksh</title>
    <description>The latest articles on DEV Community by KOMARI Subheeksh (@komari_subheeksh_ced2cb4c).</description>
    <link>https://dev.to/komari_subheeksh_ced2cb4c</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%2F4045128%2F36ebc7b3-2b50-4a3b-bab5-94a150be3e85.png</url>
      <title>DEV Community: KOMARI Subheeksh</title>
      <link>https://dev.to/komari_subheeksh_ced2cb4c</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/komari_subheeksh_ced2cb4c"/>
    <language>en</language>
    <item>
      <title>Your Midnight deploy will crash on a fresh install - here are the wallet-sdk patches (and why they happen)</title>
      <dc:creator>KOMARI Subheeksh</dc:creator>
      <pubDate>Sun, 09 Aug 2026 12:45:44 +0000</pubDate>
      <link>https://dev.to/komari_subheeksh_ced2cb4c/your-midnight-deploy-will-crash-on-a-fresh-install-here-are-the-wallet-sdk-patches-and-why-they-o3g</link>
      <guid>https://dev.to/komari_subheeksh_ced2cb4c/your-midnight-deploy-will-crash-on-a-fresh-install-here-are-the-wallet-sdk-patches-and-why-they-o3g</guid>
      <description>&lt;p&gt;Every Midnight developer eventually hits this. You clone a working project, run &lt;code&gt;npm install&lt;/code&gt;, and the deploy script that worked yesterday crashes with something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TypeError: state.pendingOutputs.values.map is not a function or its return value is not iterable
  at Object.pickAllCoins (CoreWallet.js:28:66)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or your wallet sync hangs for minutes and dies with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wallet.Sync: [object Object]
  at file:///.../wallet-sdk-shielded/dist/v1/Sync.js:126:169
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first time this happened to me I assumed it was a Node version issue. It is not. It's a bug in the Midnight wallet SDK itself — a family of bugs, actually, spread across five packages — and the fix is a set of patches you have to re-apply after every &lt;code&gt;npm install&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The root cause: plain iterators without Iterator helpers
&lt;/h2&gt;

&lt;p&gt;The wallet SDK (version 1.2.0 core, shielded 3.0.2) defines its own ledger types — maps of transaction intents, outputs, and coin nonces. Those custom types implement &lt;code&gt;.entries()&lt;/code&gt;, &lt;code&gt;.values()&lt;/code&gt;, and &lt;code&gt;.keys()&lt;/code&gt; methods that return plain iterators.&lt;/p&gt;

&lt;p&gt;Here's the problem. Modern JavaScript's &lt;code&gt;Iterator&lt;/code&gt; protocol has helper methods: &lt;code&gt;.map()&lt;/code&gt;, &lt;code&gt;.filter()&lt;/code&gt;, &lt;code&gt;.find()&lt;/code&gt;, &lt;code&gt;.every()&lt;/code&gt;, &lt;code&gt;.toArray()&lt;/code&gt;. Native &lt;code&gt;Map&lt;/code&gt; and &lt;code&gt;Set&lt;/code&gt; objects get these helpers from &lt;code&gt;Iterator.prototype&lt;/code&gt; — and in Node 22+, you can do:&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;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;values&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;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// works on native Map&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the SDK's custom ledger types return iterators that do NOT inherit from &lt;code&gt;Iterator.prototype&lt;/code&gt;. They're plain objects that only implement the &lt;code&gt;next()&lt;/code&gt; method. So:&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="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pendingOutputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&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="c1"&gt;// TypeError: state.pendingOutputs.values.map is not a function&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not a Node version problem. It fails on every Node version, because the SDK's custom iterators never had the helper methods to begin with. The SDK code was written against an environment where these helpers existed (or the code was never run against the custom types), and it shipped assuming &lt;code&gt;.map()&lt;/code&gt; would be there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The patch: spread into a real array first
&lt;/h2&gt;

&lt;p&gt;The fix for each crash site is mechanical: convert the plain iterator into a real array (which has all the methods) before chaining:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- state.pendingOutputs.values().map(...)
&lt;/span&gt;&lt;span class="gi"&gt;+ [...state.pendingOutputs.values()].map(...)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here are the affected files and the exact patches, verified against wallet-sdk-shielded 3.0.2 / unshielded 3.1.0 / facade 4.1.0 / dust-wallet 4.2.0.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;wallet-sdk-shielded/dist/v1/CoreWallet.js&lt;/code&gt; — &lt;code&gt;.values().map()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The crash from the top of this post. This one hits first because &lt;code&gt;pickAllCoins&lt;/code&gt; runs during coin selection, before any transaction is built.&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;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/state.pendingOutputs.values().map/([...state.pendingOutputs.values()]).map/'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  node_modules/@midnight-ntwrk/wallet-sdk-shielded/dist/v1/CoreWallet.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. &lt;code&gt;wallet-sdk-shielded/dist/v1/TransactionImbalances.js&lt;/code&gt; — &lt;code&gt;.entries().every()&lt;/code&gt; (x2)
&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;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/imbalances.guaranteed.entries().every/[...imbalances.guaranteed.entries()].every/'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  node_modules/@midnight-ntwrk/wallet-sdk-shielded/dist/v1/TransactionImbalances.js
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/segmentImbalances.entries().every/[...segmentImbalances.entries()].every/'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  node_modules/@midnight-ntwrk/wallet-sdk-shielded/dist/v1/TransactionImbalances.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. &lt;code&gt;wallet-sdk-shielded/dist/v1/TransactionOps.js&lt;/code&gt; — &lt;code&gt;.entries().filter()&lt;/code&gt; (x2)
&lt;/h3&gt;

&lt;p&gt;These call sites span multiple lines, so a plain &lt;code&gt;sed&lt;/code&gt; won't do. The chain looks like:&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="nx"&gt;tx&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imbalances&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Patch with a small Node script:&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;fs&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="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&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;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node_modules/@midnight-ntwrk/wallet-sdk-shielded/dist/v1/TransactionOps.js&lt;/span&gt;&lt;span class="dl"&gt;'&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;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/tx&lt;/span&gt;&lt;span class="se"&gt;\n(\s&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)\.&lt;/span&gt;&lt;span class="sr"&gt;imbalances&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="sr"&gt;0&lt;/span&gt;&lt;span class="se"&gt;\)\n\1\.&lt;/span&gt;&lt;span class="sr"&gt;entries&lt;/span&gt;&lt;span class="se"&gt;\(\)\n\1\.&lt;/span&gt;&lt;span class="sr"&gt;filter&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[...tx&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;$1.imbalances(0)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;$1.entries()]&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;$1.filter(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/tx&lt;/span&gt;&lt;span class="se"&gt;\n(\s&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)\.&lt;/span&gt;&lt;span class="sr"&gt;imbalances&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="sr"&gt;segment&lt;/span&gt;&lt;span class="se"&gt;\)\n\1\.&lt;/span&gt;&lt;span class="sr"&gt;entries&lt;/span&gt;&lt;span class="se"&gt;\(\)\n\1\.&lt;/span&gt;&lt;span class="sr"&gt;filter&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[...tx&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;$1.imbalances(segment)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;$1.entries()]&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;$1.filter(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. &lt;code&gt;wallet-sdk-unshielded-wallet/dist/v1/TransactionOps.js&lt;/code&gt; — &lt;code&gt;.keys().toArray()&lt;/code&gt; + &lt;code&gt;.entries().filter().map().toArray()&lt;/code&gt;
&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;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/transaction.intents?.keys().toArray()/Array.from(transaction.intents?.keys() ?? [])/'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  node_modules/@midnight-ntwrk/wallet-sdk-unshielded-wallet/dist/v1/TransactionOps.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.entries().filter().map().toArray()&lt;/code&gt; chain is multi-line and also ends with &lt;code&gt;.toArray()&lt;/code&gt; that has no replacement — the cleanest approach is a Python one-liner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;node_modules/@midnight-ntwrk/wallet-sdk-unshielded-wallet/dist/v1/TransactionOps.js&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;const imbalances = transaction&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;            .imbalances(segment)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;            .entries()&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;            .filter&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;const imbalances = [...transaction&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;            .imbalances(segment)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;            .entries()]&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;            .filter&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.toArray();&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;        return Imbalances.fromEntries&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;        return Imbalances.fromEntries&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. &lt;code&gt;wallet-sdk-facade/dist/transaction.js&lt;/code&gt; — &lt;code&gt;.values().toArray()&lt;/code&gt; (x2)
&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;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/tx.intents?.values().toArray()/[...(tx.intents?.values() ?? [])]/g'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  node_modules/@midnight-ntwrk/wallet-sdk-facade/dist/transaction.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. &lt;code&gt;wallet-sdk-dust-wallet/dist/v1/Transacting.js&lt;/code&gt; — &lt;code&gt;.entries().find()&lt;/code&gt;
&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="nx"&gt;fs&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="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&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;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node_modules/@midnight-ntwrk/wallet-sdk-dust-wallet/dist/v1/Transacting.js&lt;/span&gt;&lt;span class="dl"&gt;'&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;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="sr"&gt;/const &lt;/span&gt;&lt;span class="se"&gt;\[&lt;/span&gt;&lt;span class="sr"&gt;_, imbalance&lt;/span&gt;&lt;span class="se"&gt;\]&lt;/span&gt;&lt;span class="sr"&gt; = transaction&lt;/span&gt;&lt;span class="se"&gt;\n(\s&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)\.&lt;/span&gt;&lt;span class="sr"&gt;imbalances&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="sr"&gt;0, totalFee&lt;/span&gt;&lt;span class="se"&gt;\)\n\1\.&lt;/span&gt;&lt;span class="sr"&gt;entries&lt;/span&gt;&lt;span class="se"&gt;\(\)\n\1\.&lt;/span&gt;&lt;span class="sr"&gt;find&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;const [_, imbalance] = [...transaction&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;$1.imbalances(0, totalFee)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;$1.entries()]&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;$1.find(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bonus bug: &lt;code&gt;Set.difference()&lt;/code&gt; doesn't exist on older Node
&lt;/h2&gt;

&lt;p&gt;While patching &lt;code&gt;CoreWallet.js&lt;/code&gt;, you'll find a second landmine right next to the first:&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="nx"&gt;coinNonces&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;difference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;definedNonces&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Set.prototype.difference()&lt;/code&gt; is ES2025 — it exists on Node 22.13+ and 24, but NOT on Node 20, which the Midnight toolchain still supports. On Node 20 this fails with &lt;code&gt;coinNonces.difference is not a function&lt;/code&gt; during wallet state restore.&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;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/coinNonces.difference(definedNonces)/new Set([...coinNonces].filter(x =&amp;gt; !definedNonces.has(x)))/'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  node_modules/@midnight-ntwrk/wallet-sdk-shielded/dist/v1/CoreWallet.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A correction on wallet sync hangs
&lt;/h2&gt;

&lt;p&gt;An earlier version of this post claimed the preprod RPC closes idle WebSocket connections and that client keepalive pings were needed. That was wrong. Direct testing against &lt;code&gt;wss://rpc.preprod.midnight.network&lt;/code&gt; shows an idle connection stays open well past a minute, and the server sends a ping every 30 seconds that the client auto-pongs — idle connections do not drop for lack of client keepalives. The verification snippet in the earlier version also passed &lt;code&gt;{ pingInterval, pingTimeout }&lt;/code&gt; to &lt;code&gt;new WebSocket()&lt;/code&gt;, which are not valid &lt;code&gt;ws&lt;/code&gt; options and are silently ignored, so it did not actually reproduce anything.&lt;/p&gt;

&lt;p&gt;The sync hang itself is real — long preprod syncs can still fail with &lt;code&gt;Wallet.Sync&lt;/code&gt; errors from the shielded/unshielded sync modules — but the idle-timeout explanation was incorrect. The practical workaround that got syncs moving was the sync-gap tolerance patch (see #8-10 below), and the honest advice is to capture the actual sync error before reaching for any patch. Treat the keepalive theory as retracted.&lt;/p&gt;

&lt;h2&gt;
  
  
  The complete patch script
&lt;/h2&gt;

&lt;p&gt;Here's everything in one place — save it as &lt;code&gt;patch-sdk.sh&lt;/code&gt; in your project and run it after every &lt;code&gt;npm install&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="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Comprehensive Midnight wallet-sdk patches (11 patches, 13 fixes across 7 files)&lt;/span&gt;
&lt;span class="c"&gt;# Run after every `npm install` before deploying.&lt;/span&gt;
&lt;span class="c"&gt;# The SDK's custom ledger types define .entries()/.values()/.keys() that return&lt;/span&gt;
&lt;span class="c"&gt;# plain iterators WITHOUT Iterator helper methods (.filter, .map, .find, .every).&lt;/span&gt;
&lt;span class="c"&gt;# This is an SDK bug, not a Node version issue — affects all Node versions.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="nv"&gt;ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ROOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== Patching Midnight wallet-sdk iterator bugs ==="&lt;/span&gt;

&lt;span class="c"&gt;# ── Shielded wallet (5 patches) ──&lt;/span&gt;

&lt;span class="c"&gt;# 1. CoreWallet.js — .values().map()&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/state.pendingOutputs.values().map/([...state.pendingOutputs.values()]).map/'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  node_modules/@midnight-ntwrk/wallet-sdk-shielded/dist/v1/CoreWallet.js
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  ✓ CoreWallet.js"&lt;/span&gt;

&lt;span class="c"&gt;# 1b. CoreWallet.js — Set.difference() (ES2025, missing on Node 20)&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/coinNonces.difference(definedNonces)/new Set([...coinNonces].filter(x =&amp;gt; !definedNonces.has(x)))/'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  node_modules/@midnight-ntwrk/wallet-sdk-shielded/dist/v1/CoreWallet.js
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  ✓ CoreWallet.js Set.difference"&lt;/span&gt;

&lt;span class="c"&gt;# 2. TransactionImbalances.js — .entries().every() (2 occurrences)&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/imbalances.guaranteed.entries().every/[...imbalances.guaranteed.entries()].every/'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  node_modules/@midnight-ntwrk/wallet-sdk-shielded/dist/v1/TransactionImbalances.js
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/segmentImbalances.entries().every/[...segmentImbalances.entries()].every/'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  node_modules/@midnight-ntwrk/wallet-sdk-shielded/dist/v1/TransactionImbalances.js
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  ✓ TransactionImbalances.js"&lt;/span&gt;

&lt;span class="c"&gt;# 3. TransactionOps.js (shielded) — .entries().filter() (2 occurrences)&lt;/span&gt;
node &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"
const fs = require('fs');
let f = 'node_modules/@midnight-ntwrk/wallet-sdk-shielded/dist/v1/TransactionOps.js';
let c = fs.readFileSync(f, 'utf8');
c = c.replace(/tx&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;(&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s2"&gt;+)&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;imbalances&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="se"&gt;\)\n\1\.&lt;/span&gt;&lt;span class="s2"&gt;entries&lt;/span&gt;&lt;span class="se"&gt;\(\)\n\1\.&lt;/span&gt;&lt;span class="s2"&gt;filter&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="s2"&gt;/g,
  '[...tx&lt;/span&gt;&lt;span class="se"&gt;\n\$&lt;/span&gt;&lt;span class="s2"&gt;1.imbalances(0)&lt;/span&gt;&lt;span class="se"&gt;\n\$&lt;/span&gt;&lt;span class="s2"&gt;1.entries()]&lt;/span&gt;&lt;span class="se"&gt;\n\$&lt;/span&gt;&lt;span class="s2"&gt;1.filter(');
c = c.replace(/tx&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;(&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s2"&gt;+)&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;imbalances&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="s2"&gt;segment&lt;/span&gt;&lt;span class="se"&gt;\)\n\1\.&lt;/span&gt;&lt;span class="s2"&gt;entries&lt;/span&gt;&lt;span class="se"&gt;\(\)\n\1\.&lt;/span&gt;&lt;span class="s2"&gt;filter&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="s2"&gt;/g,
  '[...tx&lt;/span&gt;&lt;span class="se"&gt;\n\$&lt;/span&gt;&lt;span class="s2"&gt;1.imbalances(segment)&lt;/span&gt;&lt;span class="se"&gt;\n\$&lt;/span&gt;&lt;span class="s2"&gt;1.entries()]&lt;/span&gt;&lt;span class="se"&gt;\n\$&lt;/span&gt;&lt;span class="s2"&gt;1.filter(');
fs.writeFileSync(f, c);
"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  ✓ TransactionOps.js (shielded)"&lt;/span&gt;

&lt;span class="c"&gt;# ── Unshielded wallet (2 patches in 1 file) ──&lt;/span&gt;

&lt;span class="c"&gt;# 4. TransactionOps.js (unshielded) — .keys().toArray()&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/transaction.intents?.keys().toArray()/Array.from(transaction.intents?.keys() ?? [])/'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  node_modules/@midnight-ntwrk/wallet-sdk-unshielded-wallet/dist/v1/TransactionOps.js
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  ✓ TransactionOps.js keys (unshielded)"&lt;/span&gt;

&lt;span class="c"&gt;# 5. TransactionOps.js (unshielded) — .entries().filter().map().toArray()&lt;/span&gt;
&lt;span class="c"&gt;# The Node regex approach is fragile due to escaping. Python fallback is more reliable.&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
f = 'node_modules/@midnight-ntwrk/wallet-sdk-unshielded-wallet/dist/v1/TransactionOps.js'
c = open(f).read()
c = c.replace(
    'const imbalances = transaction&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n            .imbalances(segment)&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n            .entries()&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n            .filter',
    'const imbalances = [...transaction&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n            .imbalances(segment)&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n            .entries()]&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n            .filter'
)
c = c.replace('.toArray();&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n        return Imbalances.fromEntries', ';&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n        return Imbalances.fromEntries')
open(f,'w').write(c)
print('ok')
"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  ✓ TransactionOps.js imbalances (unshielded) [python]"&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="c"&gt;# Node regex fallback&lt;/span&gt;
    node &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"
const fs = require('fs');
let f = 'node_modules/@midnight-ntwrk/wallet-sdk-unshielded-wallet/dist/v1/TransactionOps.js';
let c = fs.readFileSync(f, 'utf8');
c = c.replace(
  /const imbalances = transaction&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n(&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;s+)&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.imbalances&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;(segment&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;)&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.entries&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;(&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;)&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.filter&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;(/,
  'const imbalances = [...transaction&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt;1.imbalances(segment)&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt;1.entries()]&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt;1.filter('
);
c = c.replace(/&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.toArray&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;(&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;);&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n(&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;s+)return Imbalances&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.fromEntries/, ';&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt;1return Imbalances.fromEntries');
fs.writeFileSync(f, c);
"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  ✓ TransactionOps.js imbalances (unshielded) [node]"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# ── Facade (1 patch, 2 occurrences) ──&lt;/span&gt;

&lt;span class="c"&gt;# 6. transaction.js — .values().toArray() (2 occurrences)&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/tx.intents?.values().toArray()/[...(tx.intents?.values() ?? [])]/g'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  node_modules/@midnight-ntwrk/wallet-sdk-facade/dist/transaction.js
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  ✓ transaction.js (facade)"&lt;/span&gt;

&lt;span class="c"&gt;# ── Dust wallet (1 patch) ──&lt;/span&gt;

&lt;span class="c"&gt;# 7. Transacting.js — .entries().find()&lt;/span&gt;
node &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"
const fs = require('fs');
let f = 'node_modules/@midnight-ntwrk/wallet-sdk-dust-wallet/dist/v1/Transacting.js';
let c = fs.readFileSync(f, 'utf8');
c = c.replace(
  /const &lt;/span&gt;&lt;span class="se"&gt;\[&lt;/span&gt;&lt;span class="s2"&gt;_, imbalance&lt;/span&gt;&lt;span class="se"&gt;\]&lt;/span&gt;&lt;span class="s2"&gt; = transaction&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;(&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s2"&gt;+)&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;imbalances&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="s2"&gt;0, totalFee&lt;/span&gt;&lt;span class="se"&gt;\)\n\1\.&lt;/span&gt;&lt;span class="s2"&gt;entries&lt;/span&gt;&lt;span class="se"&gt;\(\)\n\1\.&lt;/span&gt;&lt;span class="s2"&gt;find&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="s2"&gt;/,
  'const [_, imbalance] = [...transaction&lt;/span&gt;&lt;span class="se"&gt;\n\$&lt;/span&gt;&lt;span class="s2"&gt;1.imbalances(0, totalFee)&lt;/span&gt;&lt;span class="se"&gt;\n\$&lt;/span&gt;&lt;span class="s2"&gt;1.entries()]&lt;/span&gt;&lt;span class="se"&gt;\n\$&lt;/span&gt;&lt;span class="s2"&gt;1.find('
);
fs.writeFileSync(f, c);
"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  ✓ Transacting.js (dust)"&lt;/span&gt;

&lt;span class="c"&gt;# ── Wallet sync workarounds (3 patches) ⚠️ UNSAFE — see note below ──&lt;/span&gt;

&lt;span class="c"&gt;# 8. Skip shielded sync&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/this.shielded.waitForSyncedState(),//'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  node_modules/@midnight-ntwrk/wallet-sdk-facade/dist/index.js
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  ✓ facade index.js"&lt;/span&gt;

&lt;span class="c"&gt;# 9-10. Increase sync gap tolerance&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/waitForSyncedState(allowedGap = 0n)/waitForSyncedState(allowedGap = 100000n)/'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  node_modules/@midnight-ntwrk/wallet-sdk-unshielded-wallet/dist/UnshieldedWallet.js
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/waitForSyncedState(allowedGap = 0n)/waitForSyncedState(allowedGap = 100000n)/'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  node_modules/@midnight-ntwrk/wallet-sdk-dust-wallet/dist/DustWallet.js
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  ✓ sync gap tolerance"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== All 11 patches applied (13 fixes across 7 files) ==="&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Unsafe workarounds (patches #8-10).&lt;/strong&gt; These three do NOT fix a bug — they disable sync-completeness checks. Skipping the shielded &lt;code&gt;waitForSyncedState()&lt;/code&gt; and raising &lt;code&gt;allowedGap&lt;/code&gt; to 100000n let a wallet act on incomplete chain state, which risks acting on stale or missing data. Use them only for local dev / demos where a slightly-off balance is acceptable, and never on a wallet handling real funds. The iterator patches (#1-7) are safe; #8-10 are not, and were kept separate for that reason.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Note: the unshielded &lt;code&gt;.entries().filter().map().toArray()&lt;/code&gt; chain is safest with the Python snippet above — the regex escaping in sed/node gets fragile. Check &lt;code&gt;node_modules/@midnight-ntwrk/wallet-sdk-unshielded-wallet/dist/v1/TransactionOps.js&lt;/code&gt; after patching and confirm no &lt;code&gt;.entries().filter()&lt;/code&gt; chain remains un-prefixed.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check whether you need the patches
&lt;/h2&gt;

&lt;p&gt;If your deploy crashes with any of these, you need them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;state.pendingOutputs.values.map is not a function
imbalances.guaranteed.entries().every is not a function
transaction.intents?.keys().toArray is not a function
coinNonces.difference is not a function
Wallet.Sync: [object Object]  (after a long hang on preprod)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you can grep your installed SDK directly:&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;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;values()&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;map&lt;/span&gt;&lt;span class="se"&gt;\|\.&lt;/span&gt;&lt;span class="s2"&gt;entries()&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;every&lt;/span&gt;&lt;span class="se"&gt;\|\.&lt;/span&gt;&lt;span class="s2"&gt;keys()&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;toArray&lt;/span&gt;&lt;span class="se"&gt;\|\.&lt;/span&gt;&lt;span class="s2"&gt;difference("&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  node_modules/@midnight-ntwrk/wallet-sdk-shielded/dist/v1/CoreWallet.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;Every Midnight project that deploys from a fresh clone hits this wall — including the official example repos, because the SDK is broken the same way for everyone. The patches are mechanical, but discovering them cost real debugging time, and they must be re-applied after every &lt;code&gt;npm install&lt;/code&gt; (the patches live in &lt;code&gt;node_modules&lt;/code&gt;, so a fresh install restores the broken code). Put &lt;code&gt;patch-sdk.sh&lt;/code&gt; in your repo, run it in CI before &lt;code&gt;npm run deploy&lt;/code&gt;, and your fresh-clone experience will match your working-tree experience.&lt;/p&gt;

&lt;p&gt;A more durable alternative is &lt;a href="https://www.npmjs.com/package/patch-package" rel="noopener noreferrer"&gt;patch-package&lt;/a&gt;: apply the patches once, run &lt;code&gt;npx patch-package @midnight-ntwrk/wallet-sdk-shielded ...&lt;/code&gt; to generate a &lt;code&gt;patches/&lt;/code&gt; directory, add a &lt;code&gt;postinstall&lt;/code&gt; script (&lt;code&gt;patch-package&lt;/code&gt;), and every &lt;code&gt;npm install&lt;/code&gt; re-applies them automatically — no manual re-run, and the patches are reviewed as part of your repo instead of living only in a script.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Verified against wallet-sdk 1.2.0, wallet-sdk-shielded 3.0.2, wallet-sdk-unshielded-wallet 3.1.0, wallet-sdk-facade 4.1.0, wallet-sdk-dust-wallet 4.2.0, proof-server 8.1.0.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>midnight</category>
      <category>sdk</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building Atomic Cross-Border Settlement on Stellar</title>
      <dc:creator>KOMARI Subheeksh</dc:creator>
      <pubDate>Sun, 26 Jul 2026 12:11:33 +0000</pubDate>
      <link>https://dev.to/komari_subheeksh_ced2cb4c/building-atomic-cross-border-settlement-on-stellar-8io</link>
      <guid>https://dev.to/komari_subheeksh_ced2cb4c/building-atomic-cross-border-settlement-on-stellar-8io</guid>
      <description>&lt;h1&gt;
  
  
  Building Atomic Cross-Border Settlement on Stellar: The AnchorFX Story
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;A technical deep-dive into Soroban escrow contracts, FX oracles, and mainnet deployment — from testnet prototype to production.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  se
&lt;/h2&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Cross-border payments still take 3-5 days and cost 6.5% on average. Correspondent banking chains are slow, opaque, and expensive. The $800B remittance market has no atomic settlement layer.&lt;/p&gt;

&lt;p&gt;Stellar was purpose-built for this. 5-second finality. Built-in DEX. Path payments at the protocol level. And now, with Soroban smart contracts, programmable settlement.&lt;/p&gt;

&lt;p&gt;AnchorFX is an open-source protocol that combines these primitives into trustless, atomic FX settlement between regulated financial anchors. Two Soroban contracts — an Escrow Factory and an FX Rate Oracle — communicate via cross-contract calls to lock, rate, and settle funds in a single atomic flow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sender → [Escrow Contract] → Receiver
              │
         [Oracle Contract]
              │
         FX Rate Data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Contract 1: Escrow Factory (995 lines, 23 tests)
&lt;/h3&gt;

&lt;p&gt;The escrow contract is a multi-escrow factory with per-escrow storage. Each escrow goes through a defined lifecycle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Created&lt;/strong&gt; — Sender locks tokens with a timeout and settlement conditions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CounterpartyApproved&lt;/strong&gt; — Receiver signs off on the terms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Settled&lt;/strong&gt; — Admin releases funds at the locked FX rate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refunded&lt;/strong&gt; — Sender reclaims after timeout expires&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cancelled&lt;/strong&gt; — Admin cancels (circuit breaker)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;create_escrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timeout_blocks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;corridor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="nf"&gt;.require_auth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Read oracle rate at creation time — locks the rate&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;oracle_addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="nf"&gt;.storage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.instance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ORACLE_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="nf"&gt;.invoke_contract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;oracle_addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;symbol_short!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"get_rate"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Store escrow with locked rate&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key security decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Per-escrow storage&lt;/strong&gt; — O(1) reads, independent TTL per escrow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checks-effects-interactions&lt;/strong&gt; — state saved before token transfer in all 4 mutation functions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;require_auth() on init&lt;/strong&gt; — prevents front-running&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pause/unpause&lt;/strong&gt; — emergency circuit breaker&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typed errors&lt;/strong&gt; — no raw &lt;code&gt;panic!&lt;/code&gt; anywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Contract 2: FX Rate Oracle (195 lines, 4 tests)
&lt;/h3&gt;

&lt;p&gt;The oracle stores per-token FX rates with 24-hour expiry. Rates are set by admin and validated on every read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;rate_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RateData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="nf"&gt;.storage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.instance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.unwrap_or_else&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="nd"&gt;panic_with_error!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;RateNotFound&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// Check expiry (~24h in ledgers)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="nf"&gt;.ledger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.sequence&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rate_data&lt;/span&gt;&lt;span class="py"&gt;.expires_at&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;panic_with_error!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;RateExpired&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;rate_data&lt;/span&gt;&lt;span class="py"&gt;.rate&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cross-Contract Communication
&lt;/h3&gt;

&lt;p&gt;The escrow reads the oracle rate at creation time and locks it. This means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Rate is &lt;strong&gt;locked at escrow creation&lt;/strong&gt; — no slippage between creation and settlement&lt;/li&gt;
&lt;li&gt;Oracle can &lt;strong&gt;update rates independently&lt;/strong&gt; — existing escrows use their locked rate&lt;/li&gt;
&lt;li&gt;Both contracts have &lt;strong&gt;independent admin&lt;/strong&gt; — oracle admin ≠ escrow admin if desired&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Frontend
&lt;/h2&gt;

&lt;p&gt;Next.js 16 + React 19 + Tailwind CSS v4. Five routes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Route&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Landing page with animated hero, features, stats&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/wallet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Freighter/xBull connect, balance, send XLM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/contract&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deploy contracts, real-time SSE event stream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/anchors&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Escrow management dashboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/admin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Analytics, health monitoring, admin controls&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;10 API routes handle REST + SSE + SEP-31 stubs. All rate-limited (30 burst, 2 req/s). Input validation on every endpoint.&lt;/p&gt;




&lt;h2&gt;
  
  
  Audit &amp;amp; Security
&lt;/h2&gt;

&lt;p&gt;Full security audit: &lt;strong&gt;257 findings&lt;/strong&gt;. All critical, high, and medium issues fixed before mainnet.&lt;/p&gt;

&lt;p&gt;Key mitigations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Per-escrow storage (eliminates single-TTL data wipe risk)&lt;/li&gt;
&lt;li&gt;Checks-effects-interactions ordering&lt;/li&gt;
&lt;li&gt;Input validation with typed errors&lt;/li&gt;
&lt;li&gt;Production security headers: HSTS, CSP, COOP, CORP&lt;/li&gt;
&lt;li&gt;CI fails on lint/format/tsc warnings&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Mainnet Deployment
&lt;/h2&gt;

&lt;p&gt;Deployed on Stellar mainnet with 6 verified transactions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Contract&lt;/th&gt;
&lt;th&gt;Address&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Oracle&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CCOIG4R7AIUQTP5CURK4PFINFF2EVQTBGTCN636E2JY25FGY7L4K54KT&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Escrow&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CDGQ7K4XGAPG3YJVAGHCE45XOR63HLD6ARJCRESEEKSRQZSIRAKG6F6V&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Lessons from mainnet deploy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WASM upload cost scales with size&lt;/strong&gt; — 4KB oracle: ~1 XLM. 13KB escrow: ~18 XLM. Optimize aggressively.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mainnet RPC can be unreliable&lt;/strong&gt; — add retries with exponential backoff, use multiple RPC endpoints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fee estimation matters&lt;/strong&gt; — always &lt;code&gt;simulateTransaction&lt;/code&gt; first, use the suggested fee&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequence numbers&lt;/strong&gt; — horizon and RPC can desync. Always reload account before building&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Contract Tests:    27 (23 escrow + 4 oracle)
Frontend Tests:    26 (vitest)
Total:             53 passing, zero warnings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test coverage includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full escrow lifecycle (create → approve → settle → refund → cancel)&lt;/li&gt;
&lt;li&gt;Multi-escrow isolation (two escrows don't interfere)&lt;/li&gt;
&lt;li&gt;FX rate math (denominator precision, overflow protection)&lt;/li&gt;
&lt;li&gt;Fuzz testing (50 random escrow operations)&lt;/li&gt;
&lt;li&gt;Rate limiting, schema validation, error states&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Soroban's storage model is different&lt;/strong&gt; — per-key TTL means you can't store everything in one map. Per-escrow storage with independent lifetimes is the right pattern.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cross-contract calls are powerful but have overhead&lt;/strong&gt; — plan your gas budget. Reading oracle rates at creation time and caching them is cheaper than live reads at settlement.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mainnet is not testnet&lt;/strong&gt; — fees are real, RPC is slower, and WASM upload costs will surprise you. Simulate everything. Budget 3x what testnet costs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security-first from day one&lt;/strong&gt; — the audit found 257 issues in code that "felt solid." Get audited. Fix everything.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Open Source
&lt;/h2&gt;

&lt;p&gt;AnchorFX is MIT licensed. Contracts, frontend, deploy scripts, and tests are all public.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/subheeksh5599/AnchorFX" rel="noopener noreferrer"&gt;github.com/subheeksh5599/AnchorFX&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://anchorfx.vercel.app" rel="noopener noreferrer"&gt;anchorfx.vercel.app&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Twitter:&lt;/strong&gt; &lt;a href="https://x.com/Tenki_ai" rel="noopener noreferrer"&gt;@Tenki_ai&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Built for the Stellar Journey to Mastery — Black Belt track. Stellar, Soroban SDK v22, Rust WASM, Next.js 16.&lt;/p&gt;

</description>
      <category>stellarchallenge</category>
      <category>soroban</category>
      <category>blockchain</category>
      <category>web3</category>
    </item>
    <item>
      <title>SIGNOZ</title>
      <dc:creator>KOMARI Subheeksh</dc:creator>
      <pubDate>Fri, 24 Jul 2026 08:17:21 +0000</pubDate>
      <link>https://dev.to/komari_subheeksh_ced2cb4c/signoz-8eb</link>
      <guid>https://dev.to/komari_subheeksh_ced2cb4c/signoz-8eb</guid>
      <description>&lt;h1&gt;
  
  
  I built a deterministic verdict engine for AI agents on SigNoz — no LLM in the loop
&lt;/h1&gt;

&lt;p&gt;Your AI agent hallucinates. It loops tool calls. It burns tokens. And you won't know until a customer complains.&lt;/p&gt;

&lt;p&gt;That's the problem Gaze solves. It watches your agent's traces through SigNoz, runs nine deterministic rules, and issues a recomputable verdict. No LLM in the verdict path. Same input always produces the same sha256 hash.&lt;/p&gt;

&lt;p&gt;I built this for the Agents of SigNoz Hackathon 2026. Here's how it works and what I learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Every observability tool tells you what your agent COST. LangSmith, Burnrate, every LLM gateway — they all show dollars spent and tokens consumed. But none of them tell you if the agent was RIGHT.&lt;/p&gt;

&lt;p&gt;An agent can repeat the same wrong answer for hours. It can cite documents that don't exist. It can get stuck in a tool loop burning API credits. Existing SRE tools catch when your service is down. They don't catch when your agent is wrong.&lt;/p&gt;

&lt;p&gt;Gaze sits between your agent and SigNoz. It reads the traces, evaluates behavior, and issues a verdict score from 0 to 100. When the score drops, SigNoz fires an alert.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Your agent emits OpenTelemetry spans with GenAI semantic conventions — prompt, completion, tokens, model name. SigNoz stores them in ClickHouse. Gaze queries ClickHouse directly, runs nine deterministic rules against every span, computes a score, writes the verdict back to SigNoz via OTLP.&lt;/p&gt;

&lt;p&gt;All of this happens inside your deployment. No external service. No API key. You run &lt;code&gt;foundryctl cast --file casting.yaml&lt;/code&gt; and everything comes up: SigNoz, ClickHouse, the ingester, the UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  The nine rules
&lt;/h3&gt;

&lt;p&gt;Every rule is pure Python. No ML models, no embeddings APIs, no external dependencies.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;repetition loop&lt;/strong&gt; — n-gram similarity &amp;gt; 80% across 5+ consecutive spans. Catches agents stuck repeating the same output.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;embedding drift&lt;/strong&gt; — character bigram hashing. Tokenizes output into bigrams, hashes them to a fixed-length vector, compares cosine distance from the agent's baseline. If the distance exceeds 0.40, the agent's output quality is degrading.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;tool loop&lt;/strong&gt; — detects circular tool calls. Same (tool, args) pair repeated 3+ times means the agent is stuck.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;unauthorized tool&lt;/strong&gt; — cross-references tool calls against the agent's registered manifest. Catches jailbreak attempts that try to access unregistered tools.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;prompt injection&lt;/strong&gt; — 47 regex patterns from the verazuo/jailbreak_llms dataset. Matches known injection vectors like "ignore all instructions", "DAN", "developer mode", "system override".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;cost explosion&lt;/strong&gt; — token usage &amp;gt; 3x the 7-day rolling average for the same agent + model.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;latency degradation&lt;/strong&gt; — P95 span duration &amp;gt; 2x the 7-day rolling baseline.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;empty response&lt;/strong&gt; — agent returned null or empty output.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;hallucinated source&lt;/strong&gt; — agent cited a document not found in the retrieval spans.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The verdict
&lt;/h3&gt;

&lt;p&gt;Each verdict carries a sha256 hash of (trace snapshot + rule set version + agent ID). Same input always produces the same hash. Anyone can recompute and verify.&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="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://gaze-4fy2.onrender.com/verdict &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"agent_id": "support-bot-01"}'&lt;/span&gt;

&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"verdict_hash"&lt;/span&gt;: &lt;span class="s2"&gt;"sha256:a1b2c3d4..."&lt;/span&gt;,
  &lt;span class="s2"&gt;"score"&lt;/span&gt;: 94,
  &lt;span class="s2"&gt;"status"&lt;/span&gt;: &lt;span class="s2"&gt;"HEALTHY"&lt;/span&gt;,
  &lt;span class="s2"&gt;"rules_evaluated"&lt;/span&gt;: 9
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The verdict spans are written back to SigNoz via OTLP so they appear alongside your agent's traces.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I used SigNoz
&lt;/h2&gt;

&lt;p&gt;This project wouldn't exist without SigNoz. Here's exactly what I used:&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP server
&lt;/h3&gt;

&lt;p&gt;SigNoz exposes an MCP (Model Context Protocol) server that allows external tools to query traces programmatically. Gaze uses this to pull agent spans for evaluation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# mcp_client.py
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;query_spans&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;window_minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;signoz_mcp_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signoz_list_spans&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;service&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;minutes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;window_minutes&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;h3&gt;
  
  
  ClickHouse direct queries
&lt;/h3&gt;

&lt;p&gt;For deeper analysis, Gaze queries ClickHouse directly. SigNoz stores all trace data in &lt;code&gt;signoz_traces.distributed_signoz_index_v3&lt;/code&gt;. Gaze extracts GenAI-specific attributes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;span_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trace_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;JSONExtractString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tagMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gen_ai.prompt'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;JSONExtractString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tagMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gen_ai.completion'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="k"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;JSONExtractInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tagMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'gen_ai.usage.input_tokens'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;input_tokens&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;signoz_traces&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;distributed_signoz_index_v3&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;serviceName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'support-bot-01'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="k"&gt;MINUTE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  OTLP verdict export
&lt;/h3&gt;

&lt;p&gt;After evaluation, Gaze creates verdict spans and exports them to SigNoz via OTLP. This means verdicts appear as regular spans in the SigNoz UI, right next to your agent's traces.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# otel_exporter.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.exporter.otlp.proto.grpc.trace_exporter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OTLPSpanExporter&lt;/span&gt;

&lt;span class="n"&gt;exporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OTLPSpanExporter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:4317&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;insecure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;span_processor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BatchSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exporter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Foundry deployment
&lt;/h3&gt;

&lt;p&gt;Everything deploys with one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;foundryctl cast &lt;span class="nt"&gt;--file&lt;/span&gt; casting.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;casting.yaml&lt;/code&gt; defines the full SigNoz stack — ClickHouse, ingester, query service, UI, alert manager. Judges can re-run Foundry against your &lt;code&gt;casting.yaml.lock&lt;/code&gt; to reproduce the exact deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  SigNoz dashboard
&lt;/h3&gt;

&lt;p&gt;I built a 6-panel dashboard in &lt;code&gt;dashboards/gaze-verdict.json&lt;/code&gt; that shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent score cards (0-100 for each registered agent)&lt;/li&gt;
&lt;li&gt;Verdict timeline with rule breakdowns&lt;/li&gt;
&lt;li&gt;Evidence explorer linking each violation to the exact span&lt;/li&gt;
&lt;li&gt;Token usage over time vs rolling average&lt;/li&gt;
&lt;li&gt;Latency P95 vs baseline&lt;/li&gt;
&lt;li&gt;Alert rules for score drops&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Import it directly into your SigNoz UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Deterministic beats probabilistic for observability
&lt;/h3&gt;

&lt;p&gt;Every verdict tool out there uses an LLM to judge agent quality. This is fundamentally wrong for observability. If your monitoring tool can give different answers for the same input, it's not monitoring — it's guessing.&lt;/p&gt;

&lt;p&gt;Gaze uses character bigram hashing for embedding drift detection instead of calling an embedding API. No model variance. No API latency. No cost. Same text always produces the same vector. This was the hardest lesson: resist the urge to use AI for everything. Sometimes a hash function is better.&lt;/p&gt;

&lt;h3&gt;
  
  
  ClickHouse is underrated for trace analysis
&lt;/h3&gt;

&lt;p&gt;Most observability tools abstract ClickHouse behind APIs. But direct queries give you power you can't get otherwise. Gaze's rules use window functions, JSON extraction, and statistical aggregates that would be painful through a REST API. ClickHouse handles them natively.&lt;/p&gt;

&lt;h3&gt;
  
  
  SigNoz MCP is real infrastructure
&lt;/h3&gt;

&lt;p&gt;The MCP server isn't a demo feature — it's a production-grade trace query interface. I built the entire Gaze integration on it. Zero issues with connection stability or query performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Foundry makes self-hosting trivial
&lt;/h3&gt;

&lt;p&gt;I've set up SigNoz manually before. It's 4+ containers with networking, volumes, and config. Foundry reduces it to one YAML file and one command. For a hackathon deadline, this is the difference between finishing and not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;

&lt;p&gt;Everything is open source: &lt;a href="https://github.com/subheeksh5599/Gaze" rel="noopener noreferrer"&gt;github.com/subheeksh5599/Gaze&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: Python 3.11+, FastAPI, Pydantic v2. 8 endpoints. 35/35 tests passing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: Vite + React 19 + TypeScript + Tailwind — &lt;a href="https://gaze-omega.vercel.app" rel="noopener noreferrer"&gt;gaze-omega.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rules engine&lt;/strong&gt;: 9 deterministic rules. Zero ML dependencies. Pure Python.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment&lt;/strong&gt;: Foundry for SigNoz, Render for backend, Vercel for frontend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try it yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/subheeksh5599/Gaze
&lt;span class="nb"&gt;cd &lt;/span&gt;Gaze
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; backend/requirements.txt
&lt;span class="nb"&gt;cd &lt;/span&gt;backend &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; python3 gaze/server.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then visit &lt;code&gt;http://localhost:8000/docs&lt;/code&gt; for the OpenAPI docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Historical replay — recompute verdicts for past trace windows&lt;/li&gt;
&lt;li&gt;Custom rule builder — YAML-based rules, shareable rule packs&lt;/li&gt;
&lt;li&gt;Agent pause integration — auto-pause on CRITICAL verdict&lt;/li&gt;
&lt;li&gt;Slack/Discord bot for querying verdicts from chat&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Built for the Agents of SigNoz Hackathon 2026. MIT licensed.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>devchallenge</category>
      <category>monitoring</category>
    </item>
  </channel>
</rss>
