<?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>Forem: Midnight Aliit Fellowship</title>
    <description>The latest articles on Forem by Midnight Aliit Fellowship (@midnight-aliit).</description>
    <link>https://forem.com/midnight-aliit</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.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F12781%2Fd7b48f1d-42ed-4b7a-9dd2-6595b3b750f7.png</url>
      <title>Forem: Midnight Aliit Fellowship</title>
      <link>https://forem.com/midnight-aliit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/midnight-aliit"/>
    <language>en</language>
    <item>
      <title>I Built a Midnight dApp Like an ETH Dev. Here's Everything I Got Wrong.</title>
      <dc:creator>Tushar Pamnani</dc:creator>
      <pubDate>Thu, 07 May 2026 09:51:16 +0000</pubDate>
      <link>https://forem.com/midnight-aliit/i-built-a-midnight-dapp-like-an-eth-dev-heres-everything-i-got-wrong-3fif</link>
      <guid>https://forem.com/midnight-aliit/i-built-a-midnight-dapp-like-an-eth-dev-heres-everything-i-got-wrong-3fif</guid>
      <description>&lt;p&gt;&lt;em&gt;A brutally honest post-mortem on migrating from a server-managed wallet to the 1AM browser extension on the Midnight Network.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Decision That Cost Me Two Days
&lt;/h2&gt;

&lt;p&gt;I want to be specific about the moment I made the mistake, because it's a very recognizable moment.&lt;/p&gt;

&lt;p&gt;Contracts written. Deployed on preprod. ZK proofs generating correctly through the CLI. I was looking at green checkmarks across the board and thinking: &lt;em&gt;frontend time.&lt;/em&gt; The hard part was done.&lt;/p&gt;

&lt;p&gt;That's the exact mental state where bad architectural decisions happen. You're riding the momentum of something working, and the last thing you want is to slow down and think carefully about something that feels like plumbing.&lt;/p&gt;

&lt;p&gt;So I didn't. I looked at Midnight's SDK, saw a server-side wallet pattern that worked perfectly in Node.js, and made a decision I immediately told myself I'd "clean up later":&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;For simplicity, I'll just use a single server-side wallet for all transactions.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I've been writing software long enough to know that "clean up later" is a specific kind of lie developers tell themselves. But I told it anyway.&lt;/p&gt;

&lt;p&gt;Later arrived. It cost me two days. Here's the full damage report.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "Server Wallet" Actually Meant
&lt;/h2&gt;

&lt;p&gt;Here's the thing about this mistake: it's completely understandable. If you come from Ethereum, you've probably written backend scripts that interact with contracts using a private key stored in a &lt;code&gt;.env&lt;/code&gt; file. Hardhat tasks, deployment scripts, admin functions, server-side signing is normal. Expected, even.&lt;/p&gt;

&lt;p&gt;Midnight has an equivalent pattern that works perfectly in Node.js environments. It goes something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;levelPrivateStateProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@midnight-ntwrk/midnight-js-level-private-state-provider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NodeZkConfigProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@midnight-ntwrk/midnight-js-node-zk-config-provider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;httpClientProofProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@midnight-ntwrk/midnight-js-http-client-proof-provider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WalletFacade&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@midnight-ntwrk/wallet-sdk-facade&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;zkConfigProvider&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;NodeZkConfigProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;zkConfigPath&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;proofProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;httpClientProofProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://127.0.0.1:6300&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;zkConfigProvider&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;await&lt;/span&gt; &lt;span class="nc"&gt;WalletFacade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&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 is great for scripts. For CLI tools. For testing. It reads ZK artifacts from the filesystem, talks to a local proof server on port 6300, and persists private state using LevelDB.&lt;/p&gt;

&lt;p&gt;What it is &lt;strong&gt;not&lt;/strong&gt; is a browser-compatible architecture.&lt;/p&gt;

&lt;p&gt;But I didn't think about that. I was in "get it working" mode. I stuffed this into my Next.js backend, created a server action that would sign and submit every transaction with &lt;em&gt;my&lt;/em&gt; wallet, and called it done.&lt;/p&gt;

&lt;p&gt;This worked. Demos looked fine. The contract calls went through. Nobody watching the demo could tell anything was architecturally broken.&lt;/p&gt;

&lt;p&gt;That's the insidious part. The mistake is invisible until you try to ship to real users, and by that point, you've built an entire frontend on top of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It's Wrong (Beyond the Obvious "Users Can't Sign")
&lt;/h2&gt;

&lt;p&gt;The most glaring problem is one everyone will call out immediately: users aren't signing their own transactions. That's not a dApp. That's a centralized backend with a ZK circuit in the middle. You've recreated a custodial wallet and called it Web3.&lt;/p&gt;

&lt;p&gt;But there are deeper, more practical problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem 1: &lt;code&gt;levelPrivateStateProvider&lt;/code&gt; doesn't exist in browsers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;LevelDB is a Node.js library. The moment you try to bundle this in a Next.js client component, you get a wall of webpack errors about missing native modules. You can't polyfill your way out of this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem 2: &lt;code&gt;NodeZkConfigProvider&lt;/code&gt; reads from the filesystem.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fs.readFileSync&lt;/code&gt;. In a browser. You know where this goes. The ZK artifacts (&lt;code&gt;.verifier&lt;/code&gt; files and &lt;code&gt;.bzkir&lt;/code&gt; files) need to be fetched over HTTP, not read from disk, but &lt;code&gt;NodeZkConfigProvider&lt;/code&gt; doesn't know how to do that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem 3: You're depending on a local proof server.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;httpClientProofProvider&lt;/code&gt; calls &lt;code&gt;http://127.0.0.1:6300&lt;/code&gt;. That's a proof server that needs to be running locally. Your users don't have one. Your Vercel deployment definitely doesn't. And even if you ran it remotely, you'd immediately hit CORS hell trying to call it from the browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem 4: You hold all the keys.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every transaction goes through your server wallet. Your seed phrase. Your keys. If your server goes down, the dApp breaks. If you rotate keys, every active session breaks. You've become the single point of failure for a supposedly trustless application.&lt;/p&gt;

&lt;p&gt;I knew all of this, in theory. I just thought I'd "clean it up later."&lt;/p&gt;

&lt;p&gt;Later arrived.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Migration: What Actually Has to Change
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://1am.xyz" rel="noopener noreferrer"&gt;1AM wallet&lt;/a&gt; is a browser extension for Midnight, think MetaMask but for ZK-native transactions. It injects a &lt;code&gt;window.midnight['1am']&lt;/code&gt; object and handles proof generation, transaction balancing, and signing internally.&lt;/p&gt;

&lt;p&gt;To migrate, I had to rip out four things and replace them all.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. ZK Config Provider
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; &lt;code&gt;NodeZkConfigProvider&lt;/code&gt; reading from filesystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt; &lt;code&gt;FetchZkConfigProvider&lt;/code&gt; loading artifacts over HTTP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&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;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/contract/collection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;zkConfigProvider&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;FetchZkConfigProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This expects your compiled artifacts to be served statically. The provider constructs URLs like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{baseUrl}/keys/{circuitId}.verifier&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{baseUrl}/zkir/{circuitId}.bzkir&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I had to compile my contracts and drop the output into &lt;code&gt;public/contract/collection/&lt;/code&gt;. Then verify they were actually accessible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:3000/contract/collection/keys/mint.verifier
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that returns HTML, you've got a path problem. If it returns binary data, you're good.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The gotcha I hit:&lt;/strong&gt; I had my artifacts in the right directory but wrong structure. The provider expects &lt;code&gt;keys/&lt;/code&gt; and &lt;code&gt;zkir/&lt;/code&gt; subdirectories with specific naming. My files were dumped flat into the folder. This gave me a cryptic &lt;code&gt;Failed to fetch ZK config for circuit 'mint'&lt;/code&gt; error that took me two hours to trace back to a missing subdirectory.&lt;/p&gt;

&lt;p&gt;Two hours. For a missing folder. The error message gave no indication that the problem was directory structure, it just said fetch failed. This is a category of debugging that ages you.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Proof Provider
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; &lt;code&gt;httpClientProofProvider&lt;/code&gt; pointing to a local proof server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt; The wallet's own proving capability.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&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;wallet1AM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;networkId&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;provingProvider&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;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProvingProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;zkConfigProvider&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;proofProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;proveTx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;unprovenTx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CostModel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@midnight-ntwrk/ledger-v8&lt;/span&gt;&lt;span class="dl"&gt;'&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;unprovenTx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;provingProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CostModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialCostModel&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;The 1AM extension bundles its own ZK prover. You don't need a proof server running anywhere. This is the cleanest part of the migration, once you have a connected wallet API, proof generation just works.&lt;/p&gt;

&lt;p&gt;The dynamic import on &lt;code&gt;CostModel&lt;/code&gt; is necessary. The &lt;code&gt;ledger-v8&lt;/code&gt; package ships a WASM module that Next.js tries to bundle at build time, and it fails. Dynamic &lt;code&gt;import()&lt;/code&gt; defers it to runtime in the browser where WebAssembly is available. You'll still see warnings in your build logs about async/await not being supported in the target environment, those are non-blocking, but they're loud and they'll worry you the first time you see them.&lt;/p&gt;

&lt;p&gt;I spent an embarrassing stretch of time trying to silence those warnings before accepting they were cosmetic. The build was fine. The app ran fine. I was just pattern-matching on red text in terminals. Move on faster than I did.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Private State Provider
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; &lt;code&gt;levelPrivateStateProvider&lt;/code&gt; with LevelDB persistence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt; An in-memory map with contract address scoping.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createPrivateStateProvider&lt;/span&gt;&lt;span class="p"&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;contractAddressScope&lt;/span&gt; &lt;span class="o"&gt;=&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;stateStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setContractAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;contractAddressScope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;privateStateId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;stateStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&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;contractAddressScope&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;privateStateId&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;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;privateStateId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&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;stateStore&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;contractAddressScope&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;privateStateId&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="o"&gt;??&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="c1"&gt;// signing key methods...&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;The scoping by contract address is not optional. If you deploy multiple contracts in the same session without scoping, private state bleeds between them. I deployed two collection contracts during testing and couldn't figure out why the second one was behaving like the first, private state was resolving to the wrong contract's data. This one took longer to diagnose than I'd like to admit, because "state leaking between contracts" isn't where your brain goes first when a minting flow misbehaves.&lt;/p&gt;

&lt;p&gt;The honest trade-off: this is ephemeral. Page reload wipes it. For production you'd want IndexedDB or syncing with the wallet's own private state store. That's a future problem. Right now, scoped in-memory state is correct enough to ship and test against real users.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Transaction Balancing and Submission
&lt;/h3&gt;

&lt;p&gt;This is the most finicky part and where most of the debugging time went.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; &lt;code&gt;WalletFacade.balanceTx()&lt;/code&gt; returned a &lt;code&gt;Transaction&lt;/code&gt; object directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt; &lt;code&gt;api.balanceUnsealedTransaction()&lt;/code&gt; returns a hex string. You have to deserialize it yourself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;walletProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WalletProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;balanceTx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;txHex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;toHex&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="nf"&gt;serialize&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;balanced&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;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balanceUnsealedTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;txHex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// The wallet returns { tx: "hexstring" } — you must deserialize&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Transaction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@midnight-ntwrk/ledger-v8&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;bytes&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;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;balanced&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="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/.&lt;/span&gt;&lt;span class="se"&gt;{2}&lt;/span&gt;&lt;span class="sr"&gt;/g&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="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;))&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;Transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deserialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;signature&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="s1"&gt;proof&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="s1"&gt;binding&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;getCoinPublicKey&lt;/span&gt;&lt;span class="p"&gt;:&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;shieldedAddress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shieldedCoinPublicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;getEncryptionPublicKey&lt;/span&gt;&lt;span class="p"&gt;:&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;shieldedAddress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shieldedEncryptionPublicKey&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;The error I got before adding the deserialization step was &lt;code&gt;Error: balanceUnsealedTransaction returned invalid result&lt;/code&gt;. Not "type mismatch," not "expected Transaction object, received string." Just "invalid result." I stared at that error for longer than I should have before realizing the wallet was returning hex and I was passing it somewhere that expected a typed object.&lt;/p&gt;

&lt;p&gt;Good error messages would have made this a 5-minute fix. It was not a 5-minute fix.&lt;/p&gt;

&lt;p&gt;For submission:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;midnightProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MidnightProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;submitTx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;txHex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;toHex&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="nf"&gt;serialize&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;result&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;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submitTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;txHex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Handle multiple possible return shapes across wallet versions&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; 
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; 
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;transactionId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&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;The defensive extraction on &lt;code&gt;transactionId || id&lt;/code&gt; exists because different versions of the 1AM extension return different shapes. You'll only discover which one you have when your transaction ID comes back as &lt;code&gt;undefined&lt;/code&gt; in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Race Condition Nobody Warns You About
&lt;/h2&gt;

&lt;p&gt;Browser extensions load asynchronously. Your app boots, checks &lt;code&gt;window.midnight['1am']&lt;/code&gt;, finds nothing, and reports "wallet not found", even though the extension is installed and working.&lt;/p&gt;

&lt;p&gt;You need to poll:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DETECT_TIMEOUT_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6000&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;DETECT_INTERVAL_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&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;startedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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;intervalId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&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="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="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;midnight&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1am&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;if &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="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intervalId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// proceed with connection&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;startedAt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;DETECT_TIMEOUT_MS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intervalId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// show "install wallet" message&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;DETECT_INTERVAL_MS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six seconds is generous but reasonable. The extension usually loads within one or two. If it's not there after six, the user genuinely doesn't have it installed.&lt;/p&gt;

&lt;p&gt;Also support Lace wallet as a fallback, &lt;code&gt;window.midnight.mnLace&lt;/code&gt;, and consider a seed phrase input mode for CLI users and recovery scenarios. Real users will be less technical than you. Having multiple connection paths matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Full Diff: What the Architecture Actually Looks Like Now
&lt;/h2&gt;

&lt;p&gt;The server wallet model had everything in a single Node.js process. One wallet, one proof server, one private state database. Simple to reason about, impossible to scale to real users.&lt;/p&gt;

&lt;p&gt;The browser wallet model distributes responsibility correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User's Browser
├── 1AM Extension
│   ├── User's private keys (never leaves extension)
│   ├── Built-in ZK prover
│   └── Transaction signing
│
└── Your Next.js App
    ├── FetchZkConfigProvider (loads artifacts from /public)
    ├── In-memory PrivateStateProvider (scoped per contract)
    └── WalletProvider / MidnightProvider (delegates to extension)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app never touches private keys. The user signs everything. The proof server is gone. The LevelDB dependency is gone. CORS is gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Tell Myself If I Could Go Back
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Don't prototype with server wallets.&lt;/strong&gt; The migration cost is real. Every abstraction you build around the server wallet pattern has to be unwound. If I'd started with the 1AM integration from day one, even just stubbed out, the final code would have been cleaner and I'd have caught the ZK artifact path issues during initial development rather than during migration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read the provider interfaces, not the implementations.&lt;/strong&gt; The SDK exports clean interfaces for &lt;code&gt;WalletProvider&lt;/code&gt;, &lt;code&gt;MidnightProvider&lt;/code&gt;, &lt;code&gt;ProofProvider&lt;/code&gt;. If I'd read those first instead of copying the working server-side implementations, I'd have understood earlier that these are &lt;em&gt;swappable&lt;/em&gt;, and that swapping them is the whole point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Browser-first is table stakes.&lt;/strong&gt; You know this from Ethereum. MetaMask was non-negotiable in 2019. Apply the same instinct to Midnight immediately, don't let the novelty of ZK circuits make you forget the basics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scope your private state by contract address on day one.&lt;/strong&gt; The fix is a single string prefix. The debugging session when you skip it is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Honest Part Nobody Writes
&lt;/h2&gt;

&lt;p&gt;Here's the thing I kept thinking about while doing this migration:&lt;/p&gt;

&lt;p&gt;The error messages in Midnight's SDK are often terrible. Not "could be better", genuinely unhelpful in ways that send you in the wrong direction. &lt;code&gt;balanceUnsealedTransaction returned invalid result&lt;/code&gt; tells you nothing about what was actually invalid. &lt;code&gt;Failed to fetch ZK config for circuit 'mint'&lt;/code&gt; gives you zero signal about whether the problem is the URL, the directory structure, the file format, or something else entirely.&lt;/p&gt;

&lt;p&gt;This is a real cost. Not a dealbreaker, but a real cost, and one that compounds. Every cryptic error is an hour of debugging that a better message would have made five minutes. When you're building alone at 3 AM and you've been staring at the same error for three hours, "invalid result" starts to feel personal.&lt;/p&gt;

&lt;p&gt;I'm writing this down because the Midnight team is actively building, they read what builders publish, and error message quality is a low-effort, high-leverage improvement. The underlying architecture is sound. The ZK primitives are genuinely interesting. But right now, the gap between "this works in CLI" and "this works in a user-facing browser app" is navigated almost entirely by developer pain, and it doesn't have to be.&lt;/p&gt;

&lt;p&gt;If this post exists in the Midnight documentation six months from now as a reference for the migration path, that's a good outcome. The patterns above are correct. The architecture works. The error messages that led me to those patterns were not helpful, and I'd rather name that honestly than wrap this up with a cheerful "but overall it was worth it!"&lt;/p&gt;

&lt;p&gt;It was worth it. The honest version of that statement includes everything above.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tushar Pamnani — building at Sevryn Labs. Find me on &lt;a href="https://x.com/Tushar_Pamnani_" rel="noopener noreferrer"&gt;X&lt;/a&gt; or &lt;a href="https://github.com/tusharpamnani" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Writing about Midnight at &lt;a href="https://dev.to/tusharpamnani"&gt;dev.to/tusharpamnani&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you're starting your Midnight journey and want to skip some of these walls, reach out. The repos are open.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>midnight</category>
      <category>compact</category>
      <category>web3</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>Mathematically Prohibiting 'Cheating' in On-Chain RPS: A Midnight ZK dApp Case Study</title>
      <dc:creator>Haruki Kondo</dc:creator>
      <pubDate>Sun, 03 May 2026 23:31:12 +0000</pubDate>
      <link>https://forem.com/midnight-aliit/mathematically-prohibiting-cheating-in-on-chain-rps-a-midnight-x-zk-dapp-case-study-3l9g</link>
      <guid>https://forem.com/midnight-aliit/mathematically-prohibiting-cheating-in-on-chain-rps-a-midnight-x-zk-dapp-case-study-3l9g</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Implementing Rock-Paper-Scissors (RPS) on-chain is surprisingly tricky.&lt;/p&gt;

&lt;p&gt;The moment you choose "Rock" and send a transaction, your opponent can read your move from the public ledger. The game is over before it even starts.&lt;/p&gt;

&lt;p&gt;I tried implementing a commit-reveal pattern manually, but managing salts, preventing front-running, and ensuring fair judging logic... it quickly became a rabbit hole.&lt;/p&gt;

&lt;p&gt;That's when I turned to &lt;strong&gt;Midnight&lt;/strong&gt;. I built a full-stack RPS dApp where "cheating" (looking at the opponent's move before playing) is mathematically impossible!&lt;/p&gt;

&lt;p&gt;In this article, I'll share the ZK circuit design using Midnight's smart contract language, &lt;strong&gt;Compact&lt;/strong&gt;, and the hurdles I faced during development.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;What ZK protects&lt;/strong&gt;: The ZK proof ensures "confidentiality of the move during the commit phase" (fairness). After the reveal, both moves are recorded on the public ledger.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is what the dApp looks like:&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.amazonaws.com%2Fuploads%2Farticles%2Fgfa16txxqbm5req1spcy.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.amazonaws.com%2Fuploads%2Farticles%2Fgfa16txxqbm5req1spcy.png" alt="RPS dApp Hero" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Learn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Designing the "Hidden" vs. "Visible" split in Midnight.&lt;/li&gt;
&lt;li&gt;Mastering the commit-reveal pattern with the Compact language.&lt;/li&gt;
&lt;li&gt;Frontend UX strategies for handling ZK proof generation time (the 10-second hurdle).&lt;/li&gt;
&lt;li&gt;Robust provider configuration with the Midnight JS SDK.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Repository
&lt;/h2&gt;

&lt;p&gt;You can find the full source code here:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mashharuki" rel="noopener noreferrer"&gt;
        mashharuki
      &lt;/a&gt; / &lt;a href="https://github.com/mashharuki/midnight-rps-sample-app" rel="noopener noreferrer"&gt;
        midnight-rps-sample-app
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Midnight RPS sample dApp
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;midnight-rps-sample-app&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Midnight RPS sample dApp&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Overview&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;midnight-rps-sample-app is a sample Rock-Paper-Scissors dApp project built on Midnight, a privacy-focused blockchain.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Key Features&lt;/h3&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fair Gameplay via Zero-Knowledge Proofs (ZK Proofs)&lt;/strong&gt;&lt;br&gt;
The app utilizes a "commit/reveal" scheme powered by Compact smart contracts. Players commit to their move (Rock, Paper, or Scissors) by submitting a hashed value. Once all players have committed, the moves are revealed. This ensures a tamper-proof gaming experience where "sniping" or reacting to an opponent's move is impossible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Powered by the Midnight Blockchain&lt;/strong&gt;&lt;br&gt;
Contracts are deployed on the Midnight PreProd testnet, with all transactions recorded on-chain.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Full-Stack Architecture&lt;/strong&gt;&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pkgs/contract&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Smart contracts written in the Compact language&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pkgs/cli&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CLI tools for contract deployment and interaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pkgs/app&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Frontend UI built with React + Vite&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lace Wallet Integration&lt;/strong&gt;&lt;br&gt;
Connects with Lace Wallet via the &lt;code&gt;@midnight-ntwrk/dapp-connector-api&lt;/code&gt; for secure signing and transaction processing.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Game Flow&lt;/h3&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Commit Phase&lt;/strong&gt; — Each player commits their move…&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/mashharuki/midnight-rps-sample-app" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;

&lt;p&gt;Here are the steps to get the app running locally.&lt;/p&gt;

&lt;p&gt;To be honest, &lt;strong&gt;it takes about 15-20 minutes&lt;/strong&gt; including ZK circuit compilation and the initial Docker image pull. Get your coffee ready!&lt;/p&gt;

&lt;h3&gt;
  
  
  Environment
&lt;/h3&gt;

&lt;p&gt;Tested with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Docker version 27.4.0
compact 0.2.0
bun 1.3.13
node 23.3.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Install Lace Wallet to your browser
&lt;/h3&gt;

&lt;p&gt;If you have not yet installed Lace Wallet, you must go to below page &amp;amp; need to install Lace Wallet&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.lace.io/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.sanity.io%2Fimages%2Ftnb8oobx%2Fproduction%2F434b20b0ce779725521c1890bd857b5b2dac6c65-1201x628.jpg" height="418" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.lace.io/" rel="noopener noreferrer" class="c-link"&gt;
            Lace | The light wallet platform to explore Web3
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Discover the new light wallet platform from Input Output Global. Manage digital assets, and access NFTs, DApps, and DeFi services. Start your Web3 journey.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.lace.io%2Ffavicon.ico" width="16" height="16"&gt;
          lace.io
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Next, you need to create wallet account of Midnight&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please switch to PreProd Network&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  0. Clone the Repository
&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/mashharuki/midnight-rps-sample-app.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  1. Install Dependencies
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2. Compile and Build Contracts
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Generate ZK circuit assets (This takes the most time)&lt;/span&gt;
bun contract compact  

&lt;span class="c"&gt;# Build CLI and Frontend&lt;/span&gt;
bun cli build
bun app build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;bun contract compact&lt;/code&gt; step is where the Compact compiler generates the ZK proving and verification keys.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  3. Start the Proof Server (Initial Pull: ~3 mins)
&lt;/h3&gt;

&lt;p&gt;We need to run the server that handles ZK proof generation via Docker. &lt;/p&gt;

&lt;p&gt;Without this server, you won't be able to deploy contracts or send commit/reveal transactions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Version &lt;code&gt;8.0.3&lt;/code&gt; is verified with Compact &lt;code&gt;0.2.0&lt;/code&gt;. &lt;br&gt;
Ensure your SDK and Proof Server versions match, or proof generation will fail.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 127.0.0.1:6300:6300 midnightntwrk/proof-server:8.0.3 midnight-proof-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  4. Deploy Contract to PreProd Network
&lt;/h3&gt;

&lt;p&gt;If you don't have testnet NIGHT Token, you can get some token from below site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://faucet.preprod.midnight.network/" rel="noopener noreferrer"&gt;https://faucet.preprod.midnight.network/&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun cli preprod-pts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you deploy successfull, contract address shows like below.&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="o"&gt;[&lt;/span&gt;19:24:25.997] INFO &lt;span class="o"&gt;(&lt;/span&gt;40223&lt;span class="o"&gt;)&lt;/span&gt;: Deploying RPS contract...
  ⠇ Deploying RPS contract[19:24:51.416] INFO &lt;span class="o"&gt;(&lt;/span&gt;40223&lt;span class="o"&gt;)&lt;/span&gt;: Deployed RPS contract at: 23149945fed06aa010cc3e48e9f5df91625567300fae4e09371bb788d07a6bd8
  ✓ Deploying RPS contract
  Contract deployed at: 23149945fed06aa010cc3e48e9f5df91625567300fae4e09371bb788d07a6bd8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Essence of Midnight Architecture: "Localizing" Information
&lt;/h2&gt;

&lt;p&gt;Developing with Midnight requires a different mindset compared to &lt;strong&gt;Solidity&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;As mentioned in my previous articles, Midnight has &lt;strong&gt;two types of states&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Public State&lt;/strong&gt;: Visible to everyone (on-chain ledger).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private State&lt;/strong&gt;: Visible only to you (local storage).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;strong&gt;Compact&lt;/strong&gt; ZK circuits act as the bridge between these two.&lt;/p&gt;
&lt;h3&gt;
  
  
  App Architecture
&lt;/h3&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.amazonaws.com%2Fuploads%2Farticles%2F6karayn0s6lg82gbdbe5.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.amazonaws.com%2Fuploads%2Farticles%2F6karayn0s6lg82gbdbe5.png" alt=" " width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Code Deep Dive: Mathematically Preventing Cheating
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Security Core: Domain Separation
&lt;/h3&gt;

&lt;p&gt;When deriving a public key from a secret key, Midnight recommends using domain separation:&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="n"&gt;pure&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;derive_pk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Domain separation with a fixed string "rps:pk:v1"&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;persistentHash&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"rps:pk:v1"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;sk&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;If you use the secret key as-is, using the same key in another app would result in the same public key, risking data leakage. By adding a prefix like "app name + version", we ensure that the public keys derived from the same secret key are unique to each app.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. The Commit-Reveal Circuits
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Commit Phase (Declaring a move while keeping it hidden)
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;commit&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;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;game_over&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                 &lt;span class="s"&gt;"Game is already over"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="py"&gt;.waiting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Not in waiting state"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;sk&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;local_secret_key&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// witness: private input&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;derive_pk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;my_move&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_my_move&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;my_salt&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_my_salt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;commitment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;make_commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_move&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_salt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;store_move_and_salt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_move&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_salt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ⭐ Save move and salt to private state&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;p1_joined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;p1_key&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;p1_commit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;p1_joined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;p2_joined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Both players already committed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;p2_key&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;p2_commit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;p2_joined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="py"&gt;.committed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Transition to 'committed' when both joined&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;Pay close attention to &lt;strong&gt;&lt;code&gt;store_move_and_salt&lt;/code&gt;&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;This is a witness function that saves the selected move and salt to the browser's IndexedDB (private state). To prove that the move revealed later is the same one committed earlier, this step is absolutely necessary.&lt;/p&gt;

&lt;p&gt;The use of &lt;code&gt;disclose()&lt;/code&gt; is also critical. It allows developers to explicitly control which results of private computations are written to the public ledger.&lt;/p&gt;
&lt;h4&gt;
  
  
  Game State Transitions
&lt;/h4&gt;

&lt;p&gt;The coordination between commit and reveal phases is managed by these states:&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.amazonaws.com%2Fuploads%2Farticles%2F2qx9f9jympddv6oecgxn.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.amazonaws.com%2Fuploads%2Farticles%2F2qx9f9jympddv6oecgxn.png" alt="State Transitions" width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Reveal Phase: Where ZK Proofs Shine
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;If the commit is the "declaration," the reveal is the "proof."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is where Midnight's ZK stack really pays off.&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="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;reveal&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;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;game_over&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                   &lt;span class="s"&gt;"Game is already over"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="py"&gt;.committed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Not in committed state"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;sk&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;local_secret_key&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;derive_pk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;my_move&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_my_move&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// Restore from private state&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;my_salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_my_salt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// Restore from private state&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;computed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;make_commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_move&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_salt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;is_p1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1_key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;is_p2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p2_key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_p1&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;is_p2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Caller is not a registered player"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_p1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;p1_revealed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Player 1 already revealed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;computed&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;p1_commit&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"Commitment mismatch for P1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;p1_move&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_move&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Move is written to ledger only now&lt;/span&gt;
    &lt;span class="n"&gt;p1_revealed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_p2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;p2_revealed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Player 2 already revealed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;computed&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;p2_commit&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"Commitment mismatch for P2"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;p2_move&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_move&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;p2_revealed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1_revealed&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;p2_revealed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;who_wins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1_move&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p2_move&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Judge winner&lt;/span&gt;
    &lt;span class="n"&gt;game_over&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="py"&gt;.finished&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;The line &lt;code&gt;assert(disclose(computed == p1_commit), "Commitment mismatch for P1")&lt;/code&gt; is the heart of the logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;my_move&lt;/code&gt; and &lt;code&gt;my_salt&lt;/code&gt; exist only in your local private state.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;make_commit(my_move, my_salt)&lt;/code&gt; is re-calculated, and the ZK circuit verifies it matches the &lt;code&gt;p1_commit&lt;/code&gt; recorded on-chain during the commit phase.&lt;/li&gt;
&lt;li&gt;You mathematically prove that "this move is the one I committed to" &lt;strong&gt;without revealing the move itself until the proof is valid.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the reveal is successful, &lt;code&gt;p1_move&lt;/code&gt; and &lt;code&gt;p2_move&lt;/code&gt; are finally written on-chain, and &lt;code&gt;who_wins()&lt;/code&gt; determines the outcome.&lt;/p&gt;

&lt;p&gt;During the commit phase, your move is hidden by ZK and remains unknown to the opponent until it is revealed. This is the mechanism that &lt;strong&gt;"mathematically prohibits cheating."&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Overcoming the ZK dApp UX Hurdle
&lt;/h2&gt;

&lt;p&gt;The biggest challenge during implementation was handling the &lt;strong&gt;"ZK proof generation time."&lt;/strong&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.amazonaws.com%2Fuploads%2Farticles%2Fk0xkb1sfw157aswyepqm.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.amazonaws.com%2Fuploads%2Farticles%2Fk0xkb1sfw157aswyepqm.png" alt="ZK Proof Loading" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Filling the 10-Second Silence
&lt;/h3&gt;

&lt;p&gt;In Midnight, before sending a transaction, you must generate a proof on your machine (or the Proof Server). This takes about 5-10 seconds.&lt;/p&gt;

&lt;p&gt;UX improvements are essential to prevent users from thinking the app has frozen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Optimistic UI Updates&lt;/strong&gt;: Switch the UI state to "Generating Proof..." as soon as the process starts to communicate clearly what is happening.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent Providers&lt;/strong&gt;: Use &lt;code&gt;levelPrivateStateProvider&lt;/code&gt; to ensure that even if the user reloads the browser during proof generation, the generated data and chosen moves are not lost.&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2Fph5mtdd7r94v0ai1xqzq.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.amazonaws.com%2Fuploads%2Farticles%2Fph5mtdd7r94v0ai1xqzq.png" alt="Waiting for Opponent" width="800" height="557"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example levelPrivateStateProvider configuration&lt;/span&gt;
&lt;span class="nx"&gt;privateStateProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;levelPrivateStateProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; 
  &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rpsPrivateState&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Separate namespace for each game&lt;/span&gt;
  &lt;span class="na"&gt;privateStoragePasswordProvider&lt;/span&gt;&lt;span class="p"&gt;:&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;storagePassword&lt;/span&gt; &lt;span class="c1"&gt;// Secure storage&lt;/span&gt;
&lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Sequence: From Commit to Game End
&lt;/h2&gt;

&lt;p&gt;Let's recap the full flow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mermaid.live/edit#pako:eNrFVuFq20gQfpW5LcE2VYxlO00saOHSlOOuJJi4LaUYzFoayUukXd3uKk3O-H72AfqIfZLOrizbqZ02f45bEJZ2vpn5ZuaTV0sWqwRZxI6OlkIKG8GyZRdYYCuCVsL1TSuAeuMD14LPczQth-GxVfod3tnXKlfagZ-lfjm8EZnk-dbC_dpaHvPL-RwfNypVPmaTyuLPbOc32dbU530-rNng3xXKGK-qYo76kDOVKW65FUqeK53sYtY1rVaro6OpbCJdCJ5pXkwl0OKVVdKHrp9Lrq2IRcmlhfcGNXDjf_etv5elM14j5XcP-4jJxVuHuBSJFNnCwl-Tfcx44iBjrVQKE9S3hzJd0fQfBHIbU1kDNVJ6nc3bg14AQ7pe9Dq1xa0raiwoiuqLCJxjBFP2WhWFsFMG4wU3uIU70PGrV1RNRGxyCm0Ifa3iG8JymYCp5uRoth4EJQeqNILYB72kbG1NHjssyNyADEkSZwWBZhRvZnhuPToAd9uBb1--wp8ywTtMLs5_TOPoRXAhTJnze8fsD5SoafQyg9K1sNvtQvvfsAcGYyUT05myH1mMJxHNjJRgbO0DqdJr7u0OxELHFbXmYQe1bzthnfcld-8aZY15Dqr0BJQ0Ps6nt3XQrf94ctzUTsbxQ-NuWe_LhFs0YBUVNkGZuKqs5tI4gStJte1XUw_UoXehu-wTPG4m-u4j1SlToQtMDnNolOH6UeZo8bcmJWXYU1yf1HbiVfdrxV3jLfK8URy0eWoJMld2se69Aa5xk7dzWJPrwZlNQHBK2m3Ljh61R3g9HtbiNVotCOSDwHOvQEi1Kv4PBdZsn6bAD6hFeu-bVVlM4OVLKMNZ3ccnKu8_ks96Kk-Sj5POwF9PkY9ReeXJHZJQ3bxHJfSgjDd3GFPX4PNCzT4LadrUOieAAMq-v-n8dOiQ8YLGgabK7aY2FrACdcFFQqf00m1PmT-Qp8yxTzDlDk7NWBG08m_6m0TQfyGLUiKOAXMH0eRexiyyusIGtD6rNij0Tpf154D_KgiYVlW22CDoxPikVNGEybTjVN9rYuqOx0paFo08lEVLdseiMBx0w7PTfm90MuqHw2E4DNg9bZ_2uieD3nA4CkeDsHf24nQVsH989F737PRk9R18hbOG" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNrFVuFq20gQfpW5LcE2VYxlO00saOHSlOOuJJi4LaUYzFoayUukXd3uKk3O-H72AfqIfZLOrizbqZ02f45bEJZ2vpn5ZuaTV0sWqwRZxI6OlkIKG8GyZRdYYCuCVsL1TSuAeuMD14LPczQth-GxVfod3tnXKlfagZ-lfjm8EZnk-dbC_dpaHvPL-RwfNypVPmaTyuLPbOc32dbU530-rNng3xXKGK-qYo76kDOVKW65FUqeK53sYtY1rVaro6OpbCJdCJ5pXkwl0OKVVdKHrp9Lrq2IRcmlhfcGNXDjf_etv5elM14j5XcP-4jJxVuHuBSJFNnCwl-Tfcx44iBjrVQKE9S3hzJd0fQfBHIbU1kDNVJ6nc3bg14AQ7pe9Dq1xa0raiwoiuqLCJxjBFP2WhWFsFMG4wU3uIU70PGrV1RNRGxyCm0Ifa3iG8JymYCp5uRoth4EJQeqNILYB72kbG1NHjssyNyADEkSZwWBZhRvZnhuPToAd9uBb1--wp8ywTtMLs5_TOPoRXAhTJnze8fsD5SoafQyg9K1sNvtQvvfsAcGYyUT05myH1mMJxHNjJRgbO0DqdJr7u0OxELHFbXmYQe1bzthnfcld-8aZY15Dqr0BJQ0Ps6nt3XQrf94ctzUTsbxQ-NuWe_LhFs0YBUVNkGZuKqs5tI4gStJte1XUw_UoXehu-wTPG4m-u4j1SlToQtMDnNolOH6UeZo8bcmJWXYU1yf1HbiVfdrxV3jLfK8URy0eWoJMld2se69Aa5xk7dzWJPrwZlNQHBK2m3Ljh61R3g9HtbiNVotCOSDwHOvQEi1Kv4PBdZsn6bAD6hFeu-bVVlM4OVLKMNZ3ccnKu8_ks96Kk-Sj5POwF9PkY9ReeXJHZJQ3bxHJfSgjDd3GFPX4PNCzT4LadrUOieAAMq-v-n8dOiQ8YLGgabK7aY2FrACdcFFQqf00m1PmT-Qp8yxTzDlDk7NWBG08m_6m0TQfyGLUiKOAXMH0eRexiyyusIGtD6rNij0Tpf154D_KgiYVlW22CDoxPikVNGEybTjVN9rYuqOx0paFo08lEVLdseiMBx0w7PTfm90MuqHw2E4DNg9bZ_2uieD3nA4CkeDsHf24nQVsH989F737PRk9R18hbOG%3Ftype%3Dpng" width="1417" height="1507"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Remaining Challenges
&lt;/h2&gt;

&lt;p&gt;There's still a lot to learn by building with Midnight and Compact. Here are some issues I'm still working on:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The Abandonment Problem (Griefing Attack)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If Player 1 commits and Player 2 never joins, the contract stays locked. I need to implement a timeout/cancel mechanism in a future update.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Version Management of Build Assets&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ZK circuit assets (proving keys) generated by &lt;code&gt;bun contract compact&lt;/code&gt; can become invalid with even minor updates to the Compact version or compiler. &lt;/p&gt;

&lt;p&gt;I actually ran into this during development. If you're building a similar app, double-check your library and Proof Server versions!&lt;/p&gt;

&lt;p&gt;Solving these two will open up even more possibilities for ZK dApps on &lt;strong&gt;Midnight&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Implementing this "fair" RPS app taught me three key lessons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Verify Versions&lt;/strong&gt;: Proof generation can fail due to mismatched Proof Server versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't Forget &lt;code&gt;store_move_and_salt&lt;/code&gt;&lt;/strong&gt;: Saving private state is mandatory for the reveal phase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UX is Priority #1&lt;/strong&gt;: Designing a UI that handles the unique "waiting time" of ZK is critical.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next, I'm planning to tackle multi-round support and timeout handling!&lt;/p&gt;

&lt;p&gt;If you're interested in Midnight's privacy tech, check out the repository and my other technical blogs!&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;
&lt;h2&gt;
  
  
  Follow me on X!
&lt;/h2&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://x.com/haruki_web3" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;x.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;






&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://docs.midnight.network/" rel="noopener noreferrer"&gt;Midnight Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://docs.midnight.network/develop/reference/compact/lang-ref" rel="noopener noreferrer"&gt;Compact Language Reference&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://github.com/mashharuki/midnight-rps-sample-app" rel="noopener noreferrer"&gt;Midnight RPS Sample Code (GitHub)&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>zk</category>
      <category>blockchain</category>
      <category>typescript</category>
      <category>react</category>
    </item>
    <item>
      <title>Your Data Isn't Private. You Just Haven't Put It On-Chain Yet</title>
      <dc:creator>Tushar Pamnani</dc:creator>
      <pubDate>Sun, 03 May 2026 13:41:20 +0000</pubDate>
      <link>https://forem.com/midnight-aliit/your-data-isnt-private-you-just-havent-put-it-on-chain-yet-4oee</link>
      <guid>https://forem.com/midnight-aliit/your-data-isnt-private-you-just-havent-put-it-on-chain-yet-4oee</guid>
      <description>&lt;p&gt;There's a dangerous assumption most developers bring into Compact:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"It's a privacy-first chain. My data is private unless I explicitly expose it."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is backwards. And it's where the serious mistakes happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual model
&lt;/h2&gt;

&lt;p&gt;Compact doesn't give you automatic privacy. It gives you a hard boundary between two worlds, and a compiler that enforces it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;World&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;th&gt;Who sees it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Public&lt;/td&gt;
&lt;td&gt;On-chain, every network node&lt;/td&gt;
&lt;td&gt;Everyone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Private&lt;/td&gt;
&lt;td&gt;Your local machine&lt;/td&gt;
&lt;td&gt;Only you&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The boundary between them is explicit. You cross it deliberately, with a specific annotation, or you don't cross it at all. The compiler guarantees this. You cannot accidentally move private data into public state, it's a compile error, not a runtime surprise.&lt;/p&gt;

&lt;p&gt;But here's what trips people up: &lt;strong&gt;the guarantee goes one direction.&lt;/strong&gt; The compiler stops private data from leaking into public state. It does not prevent you from &lt;em&gt;intentionally&lt;/em&gt; putting sensitive data on-chain by slapping it in &lt;code&gt;export ledger&lt;/code&gt; and calling it a day. That's your problem to reason about.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;export ledger&lt;/code&gt; means public. Completely.
&lt;/h2&gt;

&lt;p&gt;This is the rule that doesn't have exceptions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export ledger balance: Uint&amp;lt;64&amp;gt;;
export ledger owner: Bytes&amp;lt;32&amp;gt;;
export ledger totalSupply: Counter;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every field you declare with &lt;code&gt;export ledger&lt;/code&gt; is stored on every network node in plaintext. Your dApp can read it. So can every other dApp, every indexer, every curious person running a node. There's no visibility modifier that makes it "semi-public" or "only readable by the contract." It's public.&lt;/p&gt;

&lt;p&gt;Most developers understand this in the abstract. Where it breaks down in practice is with per-user data. The classic mistake: storing individual user balances in &lt;code&gt;export ledger&lt;/code&gt;. That's a privacy violation, not a compiler error, not a proof failure, just a design error that quietly exposes data you probably meant to protect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The heuristic:&lt;/strong&gt; If removing a ledger field would break another user's ability to interact with the contract, it belongs on-chain. If only one user cares about the value, it almost certainly shouldn't be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where private data actually lives
&lt;/h2&gt;

&lt;p&gt;Private data comes in through witnesses, callback functions declared in Compact, implemented in your TypeScript dApp, and executed locally on the user's device:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;witness getBalance(): Uint&amp;lt;64&amp;gt;;
witness secretKey(): Bytes&amp;lt;32&amp;gt;;
witness userNonce(): Field;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a circuit calls a witness, the value it returns never touches the chain. Not even as an encrypted blob. The chain sees a ZK proof that the circuit ran correctly given &lt;em&gt;some&lt;/em&gt; private inputs. The inputs themselves stay local.&lt;/p&gt;

&lt;p&gt;This is what makes Compact's privacy model different from Solidity's &lt;code&gt;private&lt;/code&gt; keyword. In Solidity, &lt;code&gt;private&lt;/code&gt; is a visibility modifier, the data is still on-chain, just not directly accessible from other contracts. In Compact, witness data is structurally off-chain. It can't go on-chain, because there's no mechanism to put it there. The proof proves the computation; the input stays local.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;disclose()&lt;/code&gt; is not what you think it is
&lt;/h2&gt;

&lt;p&gt;At some point you'll need to move something from private to public, store a commitment, record a result, update global state. That's what &lt;code&gt;disclose()&lt;/code&gt; is for.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit record(): [] {
  balance = disclose(getBalance());
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what &lt;code&gt;disclose()&lt;/code&gt; is not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's not encryption. The value is not transformed.&lt;/li&gt;
&lt;li&gt;It's not a runtime operation. There's no cost.&lt;/li&gt;
&lt;li&gt;It's not a permission check. It doesn't verify you're allowed to do this.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's a compile-time annotation. You're telling the compiler: &lt;em&gt;"I know this value is private, and I am intentionally making it public."&lt;/em&gt; The compiler records the declaration and permits the data flow. Without it, witness data trying to reach the ledger is a compile error.&lt;/p&gt;

&lt;p&gt;The implication: everything after &lt;code&gt;disclose()&lt;/code&gt; is public. The value you disclose will be visible on-chain as plaintext. Disclose it only if that's what you actually want.&lt;/p&gt;

&lt;h2&gt;
  
  
  The compiler tracks the boundary, everywhere.
&lt;/h2&gt;

&lt;p&gt;What makes this model powerful is that the compiler doesn't just check assignment statements. It tracks witness data through every operation: arithmetic, type conversions, struct construction, helper circuits.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;circuit obfuscate(x: Field): Field {
  return x + 73;
}

export circuit record(): [] {
  const s = getBalance() as Field;
  const x = obfuscate(s);
  balance = x as Bytes&amp;lt;32&amp;gt;;  // compiler error
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Passing witness data through &lt;code&gt;x + 73&lt;/code&gt; doesn't make it non-private. The compiler knows the output depends on the private input, so it's still tainted. You need &lt;code&gt;disclose()&lt;/code&gt; at the boundary, not buried in the middle of the computation.&lt;/p&gt;

&lt;p&gt;This also means comparison results count:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// compiler error: result reveals information about witness data
export circuit balanceExceeds(n: Uint&amp;lt;64&amp;gt;): Boolean {
  return getBalance() &amp;gt; n;
}

// correct: explicitly declared
export circuit balanceExceeds(n: Uint&amp;lt;64&amp;gt;): Boolean {
  return disclose(getBalance()) &amp;gt; n;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even a boolean that's derived from private data is a disclosure. The compiler catches it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you need to prove something about private data without revealing it
&lt;/h2&gt;

&lt;p&gt;This is where the commitment pattern comes in. It solves a specific problem: you need to demonstrate that a private value exists and hasn't changed, without putting the value on-chain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export ledger balanceCommitments: Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Bytes&amp;lt;32&amp;gt;&amp;gt;;

export circuit commitBalance(value: Uint&amp;lt;64&amp;gt;): [] {
  const nonce = freshNonce();
  const commitment = persistentCommit&amp;lt;Uint&amp;lt;64&amp;gt;&amp;gt;(value, nonce);
  balanceCommitments.insert(
    disclose(callerAddress()),
    disclose(commitment)
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What goes on-chain: the commitment (a hash with a random nonce). What stays local: the actual balance. A later circuit can prove the balance meets some condition — exceeds a threshold, is non-negative, without revealing the number. The commitment on-chain proves the private value was committed to and hasn't changed.&lt;/p&gt;

&lt;p&gt;The nonce is critical. Two commitments with the same nonce and same value are identical on-chain — anyone watching can correlate them. Always use a fresh nonce.&lt;/p&gt;

&lt;h2&gt;
  
  
  The design shift this requires
&lt;/h2&gt;

&lt;p&gt;Once this model clicks, you stop designing contracts around functions and start designing them around data residency.&lt;/p&gt;

&lt;p&gt;Before: &lt;em&gt;"What should this circuit do?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After: &lt;em&gt;"Where does this data live? What needs to be provable publicly? What must never leave the user's device?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;These questions affect everything downstream: what goes in &lt;code&gt;export ledger&lt;/code&gt;, what stays in witnesses, where commitments make sense, which circuits need &lt;code&gt;disclose()&lt;/code&gt; and which don't. Get the data residency wrong at the design stage and you'll either have a privacy leak or a system that can't verify what it needs to verify.&lt;/p&gt;

&lt;p&gt;Most bugs in Compact contracts aren't logic errors. They're boundary errors, data ending up in the wrong world, or a contract that can't prove what it needs to because the private data that would make the proof possible was never accessible in the right form.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one question to ask before writing any ledger field
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;If this value were visible to every person on the internet, would that be a problem?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If yes: it belongs in witnesses, not in &lt;code&gt;export ledger&lt;/code&gt;. Use the commitment pattern if you need public provability without public visibility.&lt;/p&gt;

&lt;p&gt;If no: &lt;code&gt;export ledger&lt;/code&gt; is the right place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ledger State and Explicit Disclosure chapters cover this in full
&lt;/h2&gt;

&lt;p&gt;The Compact Book walks through the commitment pattern with working examples, the full &lt;code&gt;disclose()&lt;/code&gt; rules including the standard library exceptions (&lt;code&gt;transientCommit&lt;/code&gt; is a special case), and how the compiler error traces tell you exactly where a boundary violation occurred.&lt;/p&gt;

&lt;p&gt;→ &lt;strong&gt;&lt;a href="https://compact-book.vercel.app" rel="noopener noreferrer"&gt;Compact Book&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Post #4 covers witnesses in depth, specifically why witness results are untrusted inputs, and what "the proof proves correct logic, not sensible inputs" means for how you write validation. That one has teeth.&lt;/p&gt;

</description>
      <category>midnightntwrk</category>
      <category>compact</category>
      <category>web3</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>Compact vs Solidity: Limitations and Advantages</title>
      <dc:creator>Neeraj Choubisa</dc:creator>
      <pubDate>Sat, 02 May 2026 08:49:51 +0000</pubDate>
      <link>https://forem.com/midnight-aliit/compact-vs-solidity-limitations-and-advantages-3him</link>
      <guid>https://forem.com/midnight-aliit/compact-vs-solidity-limitations-and-advantages-3him</guid>
      <description>&lt;h1&gt;
  
  
  Compact vs Solidity: Limitations and Advantages
&lt;/h1&gt;

&lt;p&gt;The emergence of privacy focused blockchain systems has introduced a new way of writing smart contracts. Compact is one such language designed for Midnight, a network that prioritizes confidential computation using zero knowledge proofs. Developers who are familiar with Solidity will notice that Compact is not just a new syntax but a fundamentally different model of building applications.&lt;/p&gt;

&lt;p&gt;This article explains the core limitations of Compact when compared to Solidity, and more importantly, the advantages that make it a powerful choice for a new class of applications.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding the Core Difference
&lt;/h2&gt;

&lt;p&gt;Solidity operates in a fully transparent environment where every transaction, state change, and computation is visible on chain. This transparency enables composability and interoperability across decentralized applications.&lt;/p&gt;

&lt;p&gt;Compact is built around a different principle. It enables computation on private data where the correctness of execution is proven without revealing the underlying inputs. Instead of relying entirely on on chain execution, Compact uses off chain computation combined with verifiable proofs.&lt;/p&gt;

&lt;p&gt;This shift changes how developers design systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Limitations of Compact Compared to Solidity
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Lack of On Chain Contract Deployment
&lt;/h3&gt;

&lt;p&gt;In Solidity, contracts can deploy other contracts dynamically. This capability enables factory patterns and permissionless systems where users can create new contracts directly from existing ones.&lt;/p&gt;

&lt;p&gt;Compact does not support deploying contracts from within a contract. All deployments must be handled off chain through scripts or backend services. This limitation directly affects architectures such as token launchpads and factory based protocols.&lt;/p&gt;




&lt;h3&gt;
  
  
  Reduced Composability
&lt;/h3&gt;

&lt;p&gt;One of the biggest strengths of Solidity is composability. Contracts can interact with each other freely, forming complex systems like decentralized exchanges, lending protocols, and aggregators.&lt;/p&gt;

&lt;p&gt;Compact restricts this level of interaction. Since execution is tied to proof generation and deterministic circuits, arbitrary contract calls are limited. This makes it harder to build interconnected systems in the same way as traditional DeFi.&lt;/p&gt;




&lt;h3&gt;
  
  
  Constraints on Dynamic Logic
&lt;/h3&gt;

&lt;p&gt;Solidity allows flexible execution patterns including dynamic loops, runtime conditions, and complex state transitions.&lt;/p&gt;

&lt;p&gt;Compact requires logic to be deterministic and efficient for proof generation. Large or unpredictable computations increase proving cost and complexity. Developers must carefully design circuits to remain efficient.&lt;/p&gt;




&lt;h3&gt;
  
  
  Higher Learning Curve
&lt;/h3&gt;

&lt;p&gt;Writing Solidity contracts is primarily about understanding blockchain state and execution.&lt;/p&gt;

&lt;p&gt;Compact introduces additional layers such as circuit design, proof efficiency, and privacy preserving logic. Developers must think about how data flows through proofs rather than just how it executes on chain.&lt;/p&gt;




&lt;h3&gt;
  
  
  Immature Tooling Ecosystem
&lt;/h3&gt;

&lt;p&gt;Solidity benefits from a mature ecosystem including frameworks, testing tools, debugging environments, and extensive documentation.&lt;/p&gt;

&lt;p&gt;Compact is still evolving. Tooling, libraries, and community support are comparatively limited, which can slow down development.&lt;/p&gt;




&lt;h3&gt;
  
  
  Different Approach to Identity and Permissions
&lt;/h3&gt;

&lt;p&gt;In Solidity, concepts like msg.sender provide a straightforward way to handle identity and permissions.&lt;/p&gt;

&lt;p&gt;Compact handles identity in a privacy preserving manner, which introduces additional complexity in designing access control mechanisms.&lt;/p&gt;




&lt;h2&gt;
  
  
  Advantages of Compact Over Solidity
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Native Privacy
&lt;/h3&gt;

&lt;p&gt;The most important advantage of Compact is built in privacy. Solidity exposes all data publicly, which limits its use in applications involving sensitive information.&lt;/p&gt;

&lt;p&gt;Compact allows data to remain encrypted while still enabling computation. Only proofs of correctness are revealed, not the underlying data.&lt;/p&gt;

&lt;p&gt;This enables entirely new categories of applications that cannot exist on transparent blockchains.&lt;/p&gt;




&lt;h3&gt;
  
  
  Computation on Encrypted Data
&lt;/h3&gt;

&lt;p&gt;Compact enables computations to be performed on encrypted inputs. This is a significant advancement compared to traditional smart contract platforms.&lt;/p&gt;

&lt;p&gt;Applications can process financial data, identity information, or proprietary algorithms without exposing them publicly. This opens possibilities in areas such as private finance and secure data processing.&lt;/p&gt;




&lt;h3&gt;
  
  
  Selective Disclosure
&lt;/h3&gt;

&lt;p&gt;Compact allows developers to reveal only the information that is necessary.&lt;/p&gt;

&lt;p&gt;For example, a user can prove they meet a condition without revealing the actual value behind it. This is useful in scenarios like credit scoring, voting systems, and compliance checks.&lt;/p&gt;




&lt;h3&gt;
  
  
  Stronger Security Model
&lt;/h3&gt;

&lt;p&gt;The deterministic nature of Compact execution reduces several classes of vulnerabilities that exist in Solidity.&lt;/p&gt;

&lt;p&gt;Since execution is tied to proofs and predefined circuits, there are fewer unexpected behaviors. This leads to more predictable and secure systems.&lt;/p&gt;




&lt;h3&gt;
  
  
  Better Suitability for Sensitive Applications
&lt;/h3&gt;

&lt;p&gt;Compact is particularly well suited for applications where privacy is essential. These include private trading systems, confidential auctions, identity verification platforms, and secure data marketplaces.&lt;/p&gt;

&lt;p&gt;Such applications are difficult or impossible to build safely using Solidity due to its transparent nature.&lt;/p&gt;




&lt;h3&gt;
  
  
  Alignment with Future Technologies
&lt;/h3&gt;

&lt;p&gt;As artificial intelligence and data driven systems grow, privacy becomes increasingly important. Compact aligns well with this future by enabling secure computation over sensitive datasets.&lt;/p&gt;

&lt;p&gt;It provides a foundation for integrating blockchain with privacy preserving AI and data infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Solidity and Compact as Complementary Systems
&lt;/h2&gt;

&lt;p&gt;It is important to understand that Compact and Solidity are not direct replacements for each other. They are designed for different purposes.&lt;/p&gt;

&lt;p&gt;Solidity excels in open and composable environments where transparency is beneficial. Compact excels in environments where privacy and confidentiality are critical.&lt;/p&gt;

&lt;p&gt;A future architecture may involve both. Solidity based systems can handle public coordination and liquidity, while Compact based systems manage private computation and sensitive logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Compact introduces a new paradigm in smart contract development. While it comes with limitations such as restricted composability and lack of on chain deployment, it provides powerful capabilities that are not possible in traditional blockchain environments.&lt;/p&gt;

&lt;p&gt;Solidity laid the foundation for decentralized applications by enabling transparent and composable systems. Compact extends this vision by introducing privacy and verifiable computation.&lt;/p&gt;

&lt;p&gt;As the blockchain ecosystem evolves, both approaches will play important roles in shaping the next generation of decentralized technology.&lt;/p&gt;

</description>
      <category>midnightchallenge</category>
      <category>compact</category>
      <category>solidity</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>Building on Midnight with Windows — A Real Developer's WSL2 Setup Guide</title>
      <dc:creator>Gutopro</dc:creator>
      <pubDate>Wed, 29 Apr 2026 23:30:33 +0000</pubDate>
      <link>https://forem.com/midnight-aliit/building-on-midnight-with-windows-a-real-developers-wsl2-setup-guide-om6</link>
      <guid>https://forem.com/midnight-aliit/building-on-midnight-with-windows-a-real-developers-wsl2-setup-guide-om6</guid>
      <description>&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.amazonaws.com%2Fuploads%2Farticles%2Ft87lesyo3ld9tw50ynd9.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Ft87lesyo3ld9tw50ynd9.jpg" alt="midnight" width="310" height="163"&gt;&lt;/a&gt;&lt;br&gt;
If you're on Windows and you want to build on Midnight, the first thing you need to know is that Midnight's toolchain — the Compact compiler, proof server, and the rest — are Linux binaries. They won't run natively on Windows. Full stop.&lt;/p&gt;

&lt;p&gt;That doesn't mean you're locked out. Windows has a feature called WSL2 — Windows Subsystem for Linux — that gives you a real Linux environment running inside Windows. Once it's set up you get a proper Ubuntu terminal, access to all the Linux dev tools you need, and a stable foundation for Midnight development.&lt;/p&gt;

&lt;p&gt;I set up my entire Midnight development environment on WSL2. This guide documents exactly what I did — including the parts that aren't obvious and the parts that caught me off guard.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 1: Enable WSL2
&lt;/h2&gt;

&lt;p&gt;Microsoft has official documentation for enabling WSL2. Follow that guide to get WSL installed on your machine before continuing here.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://learn.microsoft.com/en-us/windows/wsl/install" rel="noopener noreferrer"&gt;Enable WSL2 on Windows — Official Microsoft Guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Come back here once WSL is enabled and you have Ubuntu running.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 2: Download Ubuntu from the Microsoft Store
&lt;/h2&gt;

&lt;p&gt;Open the Microsoft Store and search for &lt;strong&gt;Ubuntu&lt;/strong&gt;. Select &lt;strong&gt;Ubuntu 24.04 LTS&lt;/strong&gt; and click &lt;strong&gt;Get&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once it installs, launch it from the Start menu. The first launch will ask you to create a Linux username and password.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;br&gt;
When you type your password nothing shows on screen. That's intentional — Linux hides password input by default. Just type it and press Enter.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Verify WSL2 is active by opening PowerShell and running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Windows PowerShell&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;wsl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--verbose&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see Ubuntu listed with &lt;strong&gt;Version 2&lt;/strong&gt;. If it shows Version 1, set WSL2 as the default with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Windows PowerShell&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;wsl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--set-default-version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3: Configure WSL2 Memory — Don't Skip This
&lt;/h2&gt;

&lt;p&gt;This is the step I wish someone had told me about before I started.&lt;/p&gt;

&lt;p&gt;By default WSL2 only gets 1GB of memory. The Midnight proof server needs at least 4GB to generate ZK proofs without crashing. If you skip this configuration your proof server will run but fail silently or crash mid-proof with no obvious error message. I learned this the hard way.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;.wslconfig&lt;/code&gt; file in your Windows user directory. Open PowerShell and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Windows PowerShell&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;notepad&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;USERPROFILE&lt;/span&gt;&lt;span class="s2"&gt;\.wslconfig"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# .wslconfig
&lt;/span&gt;&lt;span class="nn"&gt;[wsl2]&lt;/span&gt;
&lt;span class="py"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;8GB&lt;/span&gt;
&lt;span class="py"&gt;processors&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;4&lt;/span&gt;
&lt;span class="py"&gt;swap&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;3GB&lt;/span&gt;
&lt;span class="py"&gt;localhostForwarding&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your machine has less than 16GB RAM adjust accordingly. The minimum that works reliably for Midnight development is &lt;code&gt;memory=4GB&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Minimum&lt;/th&gt;
&lt;th&gt;Recommended&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;memory&lt;/td&gt;
&lt;td&gt;4GB&lt;/td&gt;
&lt;td&gt;8GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;processors&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;swap&lt;/td&gt;
&lt;td&gt;1GB&lt;/td&gt;
&lt;td&gt;3GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;localhostForwarding&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Save the file then restart WSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Windows PowerShell&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;wsl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--shutdown&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reopen Ubuntu from the Start menu.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Install curl
&lt;/h2&gt;

&lt;p&gt;Start with the fundamentals. &lt;code&gt;curl&lt;/code&gt; is needed to download everything else.&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;# WSL Ubuntu terminal&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; curl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5: Install Git
&lt;/h2&gt;

&lt;p&gt;You'll need git for version control and cloning repositories.&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;# WSL Ubuntu terminal&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 6: Install Node.js 22
&lt;/h2&gt;

&lt;p&gt;Midnight requires &lt;strong&gt;Node.js 22 or higher&lt;/strong&gt;. Install it using nvm — don't use a Windows-native Node.js installation, as version conflicts will cause problems down the line.&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;# WSL Ubuntu terminal&lt;/span&gt;
curl &lt;span class="nt"&gt;-o-&lt;/span&gt; https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bashrc
nvm &lt;span class="nb"&gt;install &lt;/span&gt;22
nvm use 22
node &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;zsh users:&lt;/strong&gt; Replace &lt;code&gt;~/.bashrc&lt;/code&gt; with &lt;code&gt;~/.zshrc&lt;/code&gt; in the commands above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Why pin to 22?&lt;/strong&gt; &lt;code&gt;create-mn-app&lt;/code&gt; and the Midnight SDK have been validated against Node.js 22. Using a newer major version may work, but you could encounter unexpected compatibility issues. Check the &lt;a href="https://docs.midnight.network" rel="noopener noreferrer"&gt;Midnight documentation&lt;/a&gt; for the latest supported range&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;nvm version:&lt;/strong&gt; The installer URL above pins nvm to v0.40.3. Check the &lt;a href="https://github.com/nvm-sh/nvm/releases" rel="noopener noreferrer"&gt;nvm releases page&lt;/a&gt; before running to confirm it's still the latest stable version and update the URL if needed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Verify the installation before moving on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--version&lt;/span&gt;   &lt;span class="c"&gt;# should print v22.x.x&lt;/span&gt;
npm &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 7: Install Docker Engine
&lt;/h2&gt;

&lt;p&gt;The Midnight proof server runs as a Docker container. You need Docker Engine installed &lt;strong&gt;inside WSL&lt;/strong&gt; — not Docker Desktop.&lt;/p&gt;

&lt;p&gt;Set up Docker's apt repository:&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;# WSL Ubuntu terminal&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;ca-certificates curl
&lt;span class="nb"&gt;sudo install&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 0755 &lt;span class="nt"&gt;-d&lt;/span&gt; /etc/apt/keyrings
&lt;span class="nb"&gt;sudo &lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://download.docker.com/linux/ubuntu/gpg &lt;span class="nt"&gt;-o&lt;/span&gt; /etc/apt/keyrings/docker.asc
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;a+r /etc/apt/keyrings/docker.asc

&lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/docker.sources &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; /etc/os-release &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;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;UBUNTU_CODENAME&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;$VERSION_CODENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
Components: stable
Architectures: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dpkg &lt;span class="nt"&gt;--print-architecture&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
Signed-By: /etc/apt/keyrings/docker.asc
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install Docker packages:&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;# WSL Ubuntu terminal&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add your user to the docker group so you can run Docker without &lt;code&gt;sudo&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;# WSL Ubuntu terminal&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;
newgrp docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the Docker daemon:&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;# WSL Ubuntu terminal&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;service docker start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify Docker works:&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;# WSL Ubuntu terminal&lt;/span&gt;
docker run hello-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 8: Install the Compact Toolchain
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# WSL Ubuntu terminal&lt;/span&gt;
curl &lt;span class="nt"&gt;--proto&lt;/span&gt; &lt;span class="s1"&gt;'=https'&lt;/span&gt; &lt;span class="nt"&gt;--tlsv1&lt;/span&gt;.2 &lt;span class="nt"&gt;-LsSf&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://github.com/midnightntwrk/compact/releases/latest/download/compact-installer.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add to PATH:&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;# WSL Ubuntu terminal&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export PATH="$HOME/.local/bin:$PATH"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;zsh users:&lt;/strong&gt; Replace &lt;code&gt;~/.bashrc&lt;/code&gt; with &lt;code&gt;~/.zshrc&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;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="c"&gt;# WSL Ubuntu terminal&lt;/span&gt;
compact &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 9: Scaffold Your Project with create-mn-app
&lt;/h2&gt;

&lt;p&gt;This is where things get streamlined. The Midnight team ships &lt;code&gt;create-mn-app&lt;/code&gt; — a scaffolding tool that sets up your entire project structure including wallet scripts, contract source, compilation config, and deployment tooling.&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;# WSL Ubuntu terminal&lt;/span&gt;
npx create-mn-app my-app
&lt;span class="nb"&gt;cd &lt;/span&gt;my-app
npm run setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;npm run setup&lt;/code&gt; will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compile your Compact smart contract&lt;/li&gt;
&lt;li&gt;Check your Node.js, Docker, and Compact compiler versions for compatibility&lt;/li&gt;
&lt;li&gt;Start the proof server via Docker Compose&lt;/li&gt;
&lt;li&gt;Guide you interactively through wallet creation or restoration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Wallet creation is interactive.&lt;/strong&gt; When &lt;code&gt;npm run deploy&lt;/code&gt; runs as part of setup, it will prompt you to either create a new wallet or restore from an existing seed. If you're starting fresh, choose option 1. Your seed will be saved to &lt;code&gt;.midnight-seed&lt;/code&gt; (chmod 600) — back it up and never commit this file.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Version mismatch:&lt;/strong&gt; &lt;code&gt;create-mn-app&lt;/code&gt; checks that your &lt;code&gt;@midnight-ntwrk/compact-runtime&lt;/code&gt; version in &lt;code&gt;package.json&lt;/code&gt; matches your installed compiler version. A mismatch causes a &lt;code&gt;CompactError&lt;/code&gt; on compilation. If setup flags it, let it fix it automatically.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once setup completes, your wallet address is printed to the terminal. Copy it and head to the faucet to get tNight tokens.&lt;/p&gt;

&lt;p&gt;If you want a more complete starting point, the Counter DApp template gives you a real increment/decrement contract with ZK proofs already wired up:&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;# WSL Ubuntu terminal&lt;/span&gt;
npx create-mn-app my-app &lt;span class="nt"&gt;--template&lt;/span&gt; counter
&lt;span class="nb"&gt;cd &lt;/span&gt;my-app
npm run setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 10: Get tNight from the Faucet
&lt;/h2&gt;

&lt;p&gt;Every transaction on Midnight costs DUST as gas — and DUST is generated automatically once your wallet holds tNight. Your wallet address was printed during &lt;code&gt;npm run setup&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Visit the &lt;a href="https://faucet.preprod.midnight.network/" rel="noopener noreferrer"&gt;preprod faucet&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Paste your wallet address&lt;/li&gt;
&lt;li&gt;Request tokens and wait ~2–5 minutes&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Important:&lt;/strong&gt; Always verify your wallet is configured for the &lt;strong&gt;preprod&lt;/strong&gt; network before copying your address. The preprod faucet only recognizes addresses registered on preprod — submitting an address from a differently-configured wallet will return an invalid address error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;DUST balance:&lt;/strong&gt; DUST accumulates over time based on the tNight in your wallet. If you attempt deployment immediately after funding and see a "Not enough DUST" error, wait a few minutes and retry with &lt;code&gt;npm run deploy&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What Doesn't Work
&lt;/h2&gt;

&lt;p&gt;Save yourself the debugging time — these approaches don't work:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Why it fails&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Running Compact compiler in PowerShell or CMD&lt;/td&gt;
&lt;td&gt;Linux binary — requires WSL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Running proof server natively on Windows&lt;/td&gt;
&lt;td&gt;Linux binary — requires WSL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Using Windows-native Node.js for Midnight projects&lt;/td&gt;
&lt;td&gt;Version and path conflicts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keeping projects in &lt;code&gt;/mnt/c/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Severely degraded file I/O performance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Submitting a mainnet-configured wallet address to preprod faucet&lt;/td&gt;
&lt;td&gt;Address not registered on preprod&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Proof server crashes or produces no output&lt;/strong&gt; — Almost always a memory issue. Increase &lt;code&gt;memory&lt;/code&gt; in &lt;code&gt;.wslconfig&lt;/code&gt; to at least &lt;code&gt;4GB&lt;/code&gt;, run &lt;code&gt;wsl --shutdown&lt;/code&gt;, restart WSL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker not found&lt;/strong&gt; — Run &lt;code&gt;sudo service docker start&lt;/code&gt;. If that fails, verify Docker is installed with &lt;code&gt;docker --version&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;compact&lt;/code&gt; not found&lt;/strong&gt; — Add &lt;code&gt;export PATH="$HOME/.local/bin:$PATH"&lt;/code&gt; to &lt;code&gt;~/.bashrc&lt;/code&gt; and run &lt;code&gt;source ~/.bashrc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Port 6300 not accessible&lt;/strong&gt; — Add &lt;code&gt;localhostForwarding=true&lt;/code&gt; to &lt;code&gt;.wslconfig&lt;/code&gt; and restart WSL with &lt;code&gt;wsl --shutdown&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slow file operations&lt;/strong&gt; — Move your project out of &lt;code&gt;/mnt/c/&lt;/code&gt; into your Linux home directory at &lt;code&gt;~/&lt;/code&gt;. The performance difference is significant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;CompactError&lt;/code&gt;: Version mismatch&lt;/strong&gt; — Your compiler version and &lt;code&gt;compact-runtime&lt;/code&gt; npm package version don't match. &lt;code&gt;create-mn-app&lt;/code&gt; will flag this during setup and offer to fix it. If you hit it mid-project, update &lt;code&gt;package.json&lt;/code&gt; to the matching runtime version and run &lt;code&gt;npm install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Not enough DUST" during deployment&lt;/strong&gt; — Your wallet is new and DUST hasn't accumulated yet. Wait a few minutes and run &lt;code&gt;npm run deploy&lt;/code&gt; again. The deploy script retries automatically up to 8 times with a delay between attempts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Your environment is ready. Run &lt;code&gt;create-mn-app&lt;/code&gt; and let it scaffold your first project. The hello-world template compiles and deploys in minutes. Watch your transaction appear on chain in &lt;a href="https://midnight.a1.ventures" rel="noopener noreferrer"&gt;1AM Explorer&lt;/a&gt; — that's your proof it's working.&lt;/p&gt;

&lt;p&gt;If you hit issues not covered here, find me in the Midnight Discord as &lt;strong&gt;Guto&lt;/strong&gt; — I've been through most of them already.&lt;/p&gt;

</description>
      <category>midnightchallenge</category>
      <category>aliit</category>
      <category>web3</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>Vibe Code Your First Midnight dApp with AI Agent Skills</title>
      <dc:creator>M Zidan Fatonie</dc:creator>
      <pubDate>Wed, 29 Apr 2026 16:24:48 +0000</pubDate>
      <link>https://forem.com/midnight-aliit/vibe-code-your-first-midnight-dapp-with-ai-agent-skills-2ocn</link>
      <guid>https://forem.com/midnight-aliit/vibe-code-your-first-midnight-dapp-with-ai-agent-skills-2ocn</guid>
      <description>&lt;p&gt;Your AI coding assistant doesn't know Compact. It knows TypeScript, Solidity, Rust, but Midnight's ZK smart contract language didn't exist when most models were trained. Ask it to write a Compact contract and you'll get plausible-looking code that won't compile.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;midnight_agent_skills&lt;/code&gt; is a set of 4 agent skills that fix this. Install them once, and your AI assistant has accurate knowledge of Compact syntax, the Midnight SDK, network config, and the gotchas that trip up real builders.&lt;/p&gt;

&lt;p&gt;This is a walkthrough of using those skills to build your first Midnight dApp.&lt;/p&gt;




&lt;h2&gt;
  
  
  What are agent skills?
&lt;/h2&gt;

&lt;p&gt;Agent skills are structured knowledge files that AI coding assistants load at context time. Instead of relying on training data, the assistant reads the skill files directly, so it gets accurate, current information rather than hallucinated guesses.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;midnight_agent_skills&lt;/code&gt; package has 4 skills:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;midnight-concepts&lt;/strong&gt;: ZK architecture, DUST/NIGHT tokenomics, Kachina protocol&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;midnight-compact&lt;/strong&gt;: Compact language, circuits, ledger operations, best practices&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;midnight-api&lt;/strong&gt;: SDK integration, wallet connection, contract deployment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;midnight-network&lt;/strong&gt;: Node setup, Docker, indexer, proof server&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Install the skills
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add https://github.com/mzf11125/midnight_agent_skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or pick individual skills:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add https://github.com/mzf11125/midnight_agent_skills &lt;span class="nt"&gt;--skill&lt;/span&gt; midnight-compact
npx skills add https://github.com/mzf11125/midnight_agent_skills &lt;span class="nt"&gt;--skill&lt;/span&gt; midnight-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2: Start the proof server
&lt;/h2&gt;

&lt;p&gt;Midnight generates ZK proofs client-side, your data stays on your machine. You need a local proof server running before you can deploy anything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 6300:6300 midnightnetwork/proof-server &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'midnight-proof-server --network preprod'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check it's up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:6300
&lt;span class="c"&gt;# We're alive 🎉!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3: Scaffold your project
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-midnight-app my-first-dapp
&lt;span class="nb"&gt;cd &lt;/span&gt;my-first-dapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4: Write your contract
&lt;/h2&gt;

&lt;p&gt;Open your AI assistant and ask it to write a Midnight contract. With the skills loaded, it knows the correct syntax.&lt;/p&gt;

&lt;p&gt;Here's a simple owner-gated counter, only the deployer can increment it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pragma language_version &amp;gt;= 0.20;
import CompactStandardLibrary;

export ledger counter: Counter;
export ledger owner: Bytes&amp;lt;32&amp;gt;;

witness local_secret_key(): Bytes&amp;lt;32&amp;gt;;

export circuit initialize(): [] {
  const pk = publicKey(local_secret_key()).bytes;
  owner.write(disclose(pk));
}

export circuit increment(): [] {
  const caller = publicKey(local_secret_key()).bytes;
  assert(disclose(caller) == owner.read(), "Not authorized");
  counter.increment(1);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Things the skills teach your AI that it wouldn't otherwise know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;export circuit&lt;/code&gt; not &lt;code&gt;function&lt;/code&gt;, circuits declare constraints, they don't execute&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;disclose()&lt;/code&gt; is required when moving witness data to the public ledger, the compiler rejects code without it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Counter&lt;/code&gt; uses &lt;code&gt;.increment()&lt;/code&gt; and &lt;code&gt;.read()&lt;/code&gt;, not &lt;code&gt;.value()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;No recursion, no unbounded loops, circuits must compile to a fixed-size constraint system&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 5: Compile
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;compact build src/counter.compact src/managed/counter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Success looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fetching public parameters for k=10 [====================] 192.38 KiB
  circuit "increment" (k=10, rows=29)
Overall progress [====================] 1/1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 6: Deploy
&lt;/h2&gt;

&lt;p&gt;With the &lt;code&gt;midnight-api&lt;/code&gt; skill loaded, your AI generates the correct facade 4.x pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WalletFacade&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@midnight-ntwrk/wallet-sdk-facade&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CounterContract&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./managed/counter/contract/index.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// facade 4.x, the old WalletFacade.init() hangs silently on standalone nodes&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="nc"&gt;WalletFacade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shielded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unshielded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dust&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;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&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;contract&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;CounterContract&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;deployed&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;deploy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;privateCounter&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="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="s1"&gt;Deployed at:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deployed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deployTxData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The skills include the SDK compatibility matrix. Your AI knows that &lt;code&gt;wallet-sdk-facade@2.x&lt;/code&gt; uses &lt;code&gt;WalletFacade.init()&lt;/code&gt; which hangs silently on standalone nodes, and that &lt;code&gt;4.x&lt;/code&gt; switched to &lt;code&gt;new WalletFacade(...) + .start()&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Run a pre-flight check
&lt;/h2&gt;

&lt;p&gt;Before you spend hours debugging, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx midnight-doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It reads your &lt;code&gt;package.json&lt;/code&gt;, running Docker containers, and config files, then cross-references them against a known compatibility matrix. The &lt;code&gt;midnight-api&lt;/code&gt; skill documents the most common silent failures:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Root cause&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;waitForSyncedState()&lt;/code&gt; hangs forever&lt;/td&gt;
&lt;td&gt;facade 2.x + standalone node mismatch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transactions silently fail&lt;/td&gt;
&lt;td&gt;Duplicate &lt;code&gt;@midnight-ntwrk/ledger-v7&lt;/code&gt; in node_modules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Indexer crash-loops&lt;/td&gt;
&lt;td&gt;Missing &lt;code&gt;subscription:&lt;/code&gt; block in &lt;code&gt;indexer.yml&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What the skills know that your AI doesn't
&lt;/h2&gt;

&lt;p&gt;The skills pull from the official Midnight docs plus 19 community articles from the Midnight Aliit Fellowship, builders documenting real production failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mental model shifts:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Circuits declare constraints, they don't execute. &lt;code&gt;assert&lt;/code&gt; is a constraint declaration, not a runtime guard, if the condition is false, the proof can't be generated.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;disclose()&lt;/code&gt; is a compile-time annotation, not encryption. The compiler tracks witness data through arithmetic and rejects undeclared disclosures.&lt;/li&gt;
&lt;li&gt;Block limits are hard limits, not gas costs. &lt;code&gt;BlockLimitExceeded&lt;/code&gt; means your transaction can't execute at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;On-chain design patterns:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flat maps over struct maps, reading a struct pulls every field into the circuit&lt;/li&gt;
&lt;li&gt;Off-chain computation + Merkle root, the chain verifies, it doesn't compute&lt;/li&gt;
&lt;li&gt;Minimal on-chain state, only what the chain needs to enforce&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Common syntax gotchas:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;counter.read()&lt;/code&gt; not &lt;code&gt;counter.value()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Enum access uses &lt;code&gt;.&lt;/code&gt; not &lt;code&gt;::&lt;/code&gt;, &lt;code&gt;GameState.playing&lt;/code&gt; not &lt;code&gt;GameState::playing&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Witness functions have no body, declaration only&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;return&lt;/code&gt; inside &lt;code&gt;for&lt;/code&gt; loops is not allowed, use &lt;code&gt;fold&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Install and start building
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add https://github.com/mzf11125/midnight_agent_skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The skills are open source. If you hit a pattern that's missing, PRs are open.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/mzf11125/midnight_agent_skills" rel="noopener noreferrer"&gt;https://github.com/mzf11125/midnight_agent_skills&lt;/a&gt;&lt;/p&gt;

</description>
      <category>midnight</category>
      <category>compact</category>
      <category>web3</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Circuits Are Not Functions. That Confusion Will Break You</title>
      <dc:creator>Tushar Pamnani</dc:creator>
      <pubDate>Tue, 28 Apr 2026 08:53:14 +0000</pubDate>
      <link>https://forem.com/midnight-aliit/circuits-are-not-functions-that-confusion-will-break-you-2do6</link>
      <guid>https://forem.com/midnight-aliit/circuits-are-not-functions-that-confusion-will-break-you-2do6</guid>
      <description>&lt;p&gt;The last post in this series covered the surface illusion: Compact looks like TypeScript, but it isn't. Same syntax, completely different model.&lt;/p&gt;

&lt;p&gt;This post goes one level deeper. Into the most specific version of that mistake, the one that actually stalls people in the middle of building something.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"A circuit is just a function."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It isn't. And the gap between those two things is where most of the confusion lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the confusion is so sticky
&lt;/h2&gt;

&lt;p&gt;The syntax is deliberately familiar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit transfer(amount: Uint&amp;lt;64&amp;gt;): [] {
  assert(balance &amp;gt;= amount, "Insufficient balance");
  balance = balance - amount;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You read this and your brain fires the usual pattern. Signature, body, assertions, state update. Looks like a function. Reads like a function. Must behave like one.&lt;/p&gt;

&lt;p&gt;Here's what that assumption gets wrong: &lt;strong&gt;functions execute. Circuits constrain.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These are not the same thing. They're not even close.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual difference
&lt;/h2&gt;

&lt;p&gt;A function describes a sequence of steps. You give it inputs, it runs through statements top to bottom, it produces an output. The machine does what you told it to do.&lt;/p&gt;

&lt;p&gt;A circuit describes a set of relationships that must hold true. When you call it from your dApp, the proof system generates a zero-knowledge proof that those relationships were satisfied. What gets submitted on-chain isn't the result of running your code. It's a proof that the constraints were met.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Function:  Input → Execute steps → Output
Circuit:   Input → Declare constraints → Generate proof → Verify proof
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no "execution" in the traditional sense. There's constraint satisfaction, proof generation, and verification.&lt;/p&gt;

&lt;h2&gt;
  
  
  What &lt;code&gt;return a + b&lt;/code&gt; actually means in a circuit
&lt;/h2&gt;

&lt;p&gt;In a function, &lt;code&gt;return a + b&lt;/code&gt; says: &lt;em&gt;compute the sum and hand it back.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In a circuit, &lt;code&gt;return a + b&lt;/code&gt; says: &lt;em&gt;the output is constrained to equal a + b.&lt;/em&gt; The proof proves this relationship held for the actual inputs, without revealing what those inputs were.&lt;/p&gt;

&lt;p&gt;Same syntax. Completely different meaning.&lt;/p&gt;

&lt;p&gt;This is why Compact works for private data at all. The proof system doesn't need to see your inputs to verify that your circuit's constraints were satisfied. It can confirm correctness without exposure. That's the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  What &lt;code&gt;assert&lt;/code&gt; actually is
&lt;/h2&gt;

&lt;p&gt;In a function, &lt;code&gt;assert&lt;/code&gt; is a runtime guard. It checks a condition and throws if it fails.&lt;/p&gt;

&lt;p&gt;In a circuit, &lt;code&gt;assert&lt;/code&gt; is a constraint declaration. It doesn't "throw", it defines a requirement that the proof must satisfy. If the condition is false, the proof can't be generated. There's no proof to submit. The transaction doesn't happen.&lt;/p&gt;

&lt;p&gt;Every &lt;code&gt;assert&lt;/code&gt; in your circuit is part of the constraint system. You're not checking inputs. You're specifying what valid inputs look like.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assert(balance &amp;gt;= amount, "Insufficient balance");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This doesn't say: &lt;em&gt;check if balance is high enough, then continue.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It says: &lt;em&gt;a proof for this circuit cannot exist if balance is less than amount.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's a different tool. Use it differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the constraints exist (and why this isn't limiting)
&lt;/h2&gt;

&lt;p&gt;Compact is deliberately bounded:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No recursion&lt;/li&gt;
&lt;li&gt;No unbounded loops&lt;/li&gt;
&lt;li&gt;Fixed type sizes at compile time&lt;/li&gt;
&lt;li&gt;Immutable variable bindings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first reaction is usually: &lt;em&gt;these feel arbitrary.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;They're causal. ZK proofs require finite circuits. A circuit is a fixed structure of gates and wires determined entirely at compile time. Unbounded computation can't become a circuit. If the compiler can't determine the full structure of your constraint system before runtime, there's nothing to prove.&lt;/p&gt;

&lt;p&gt;The bounds aren't restrictions on what Compact can do. They're what makes proof generation possible at all.&lt;/p&gt;

&lt;p&gt;This is also why &lt;code&gt;return&lt;/code&gt; can't appear inside a &lt;code&gt;for&lt;/code&gt; loop, and why variables can't be reassigned after binding. In a constraint system, reassignment is ambiguous. What does it mean to constrain a value that changes? It doesn't mean anything well-defined. So it's not allowed.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;map&lt;/code&gt; and &lt;code&gt;fold&lt;/code&gt; exist specifically for this. Transformation and accumulation without the ambiguity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The debugging shift
&lt;/h2&gt;

&lt;p&gt;This has a practical implication that catches people off guard: &lt;strong&gt;you can't debug circuits the way you debug functions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With a function, you ask: &lt;em&gt;what line failed?&lt;/em&gt; You add logging. You trace execution. You find where the state went wrong.&lt;/p&gt;

&lt;p&gt;With a circuit, there's no execution to trace. You ask: &lt;em&gt;which constraint is unsatisfied?&lt;/em&gt; You look at what relationships your circuit declares and figure out which one the proof can't satisfy given the actual inputs. Then you look at whether your witness is providing what the circuit expects.&lt;/p&gt;

&lt;p&gt;Different question. Different process.&lt;/p&gt;

&lt;p&gt;Most people hit this the hard way, they try to debug a constraint failure like a runtime error and get nowhere. The shift is: treat every failure as a constraint problem, not an execution problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this leads
&lt;/h2&gt;

&lt;p&gt;Once this model is solid, the rest of Compact becomes consistent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Witnesses&lt;/strong&gt; are the private inputs the prover uses to generate the proof. They're not function arguments in the normal sense, they're the private side of the constraint system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;disclose()&lt;/code&gt;&lt;/strong&gt; is the explicit act of moving a private constraint into public view. Not encryption. Not transmission. Just a compiler-enforced boundary that marks intentional exposure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ledger state&lt;/strong&gt; is the public constraint, what everyone on-chain can see is asserted to be true.&lt;/p&gt;

&lt;p&gt;Every concept in Compact is a piece of the same constraint model. Once you see it that way, the language is internally consistent. Before that, it seems to have arbitrary rules that don't connect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one sentence that changes how you read Compact
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;You are not writing code that runs. You are describing valid states and proving you were in them.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With that sentence as the frame:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit transfer(amount: Uint&amp;lt;64&amp;gt;): [] {
  assert(balance &amp;gt;= amount, "Insufficient balance");
  balance = balance - amount;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...reads differently. You're not executing a transfer. You're describing what a valid transfer looks like and generating a proof that this one was valid. The on-chain state updates because the proof verified, not because code "ran."&lt;/p&gt;

&lt;h2&gt;
  
  
  The full circuits chapter is in the book
&lt;/h2&gt;

&lt;p&gt;Mental models, examples, how &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;fold&lt;/code&gt; replace return-in-loop patterns, how generics work, the full proof flow. It's structured to build the right frame first, then fill in the syntax.&lt;/p&gt;

&lt;p&gt;→ &lt;strong&gt;&lt;a href="https://compact-book.vercel.app" rel="noopener noreferrer"&gt;Compact Book&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Post #3 covers public vs. private state, the two-world model and what actually goes on-chain. That one connects directly to the mistakes people make once they've grasped circuits but haven't yet internalized where data lives.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Surviving Midnight SDK: a 700-line cure for the silent failure problem</title>
      <dc:creator>Fred Santana</dc:creator>
      <pubDate>Mon, 27 Apr 2026 23:09:46 +0000</pubDate>
      <link>https://forem.com/midnight-aliit/surviving-midnight-sdk-a-700-line-cure-for-the-silent-failure-problem-57p</link>
      <guid>https://forem.com/midnight-aliit/surviving-midnight-sdk-a-700-line-cure-for-the-silent-failure-problem-57p</guid>
      <description>&lt;p&gt;Why I built midnight-doctor — a pre-flight check that catches the 16 hours of debugging you don't know you're about to spend&lt;/p&gt;

&lt;p&gt;"The information you need to align your stack already exists. It's just not executable."*&lt;/p&gt;

&lt;p&gt;After five months building four applications on &lt;a href="https://midnight.network" rel="noopener noreferrer"&gt;Midnight Network&lt;/a&gt;, I've concluded the protocol isn't the problem. The protocol is genuinely good — Compact compiles ZK circuits in three seconds without a trusted setup ceremony, and the shielded/unshielded/dust split is the cleanest answer to compliance-grade privacy I've seen.&lt;/p&gt;

&lt;p&gt;The problem is &lt;strong&gt;everything that surrounds it&lt;/strong&gt;. Specifically, the unforgiving math of: a fast-moving SDK + multiple environment tracks + a single npm namespace + zero error messages when versions misalign.&lt;/p&gt;

&lt;p&gt;This post is the story of one specific 6-hour debugging session, the pattern I extracted from it, and the &lt;a href="https://github.com/fredericosanntana/midnight-doctor" rel="noopener noreferrer"&gt;~700-line tool I built&lt;/a&gt; so the next person doesn't pay the same tax.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 6-hour silent failure
&lt;/h2&gt;

&lt;p&gt;DPO2U Wallet, March 2026. I bumped &lt;code&gt;@midnight-ntwrk/wallet-sdk-facade&lt;/code&gt; to &lt;code&gt;2.0.0&lt;/code&gt; because npm marked it as &lt;code&gt;latest&lt;/code&gt;. My local stack ran &lt;code&gt;midnightntwrk/midnight-node:0.21.0&lt;/code&gt; (preprod-targeted). I wrote ~50 lines of wallet bootstrap, started the dev loop, and watched:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&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;await&lt;/span&gt; &lt;span class="nx"&gt;WalletFacade&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;shielded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;unshielded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;dust&lt;/span&gt;&lt;span class="p"&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;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSyncedState&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="s1"&gt;synced!&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;The &lt;code&gt;synced!&lt;/code&gt; line never printed.&lt;/p&gt;

&lt;p&gt;No error. No timeout. No log. The wallet just sat in &lt;code&gt;syncing&lt;/code&gt; state forever. I added more logging — got more "syncing" lines. Restarted Docker. Wiped state. Tried a fresh seed. Re-cloned the repo. Asked Discord. Read the SDK changelog (which didn't exist for that version). Stared at the indexer logs for 40 minutes.&lt;/p&gt;

&lt;p&gt;Six hours in, almost by accident, I noticed the symptom: &lt;code&gt;subscribeRuntimeVersion&lt;/code&gt; was firing once, returning, and never firing again. The standalone node closes that subscription early. The &lt;code&gt;WalletFacade.init({...})&lt;/code&gt; constructor in 2.x wires sync to that subscription. &lt;strong&gt;Result: a single missed event silently kills the entire sync loop, with zero surface.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The fix was to downgrade to facade 1.0.0 (preprod track). Forty seconds of work, after six hours of debugging. The bug was real but the cost was &lt;em&gt;information asymmetry&lt;/em&gt;: nothing in the documentation, npm metadata, or runtime told me that "this SDK version is incompatible with this node version."&lt;/p&gt;

&lt;p&gt;That asymmetry is the real bug. Not the runtime subscription closing. Not the constructor wiring. The fact that &lt;strong&gt;the system has the information needed to diagnose itself, but doesn't bother&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The pattern: silent failures from missing cross-references
&lt;/h2&gt;

&lt;p&gt;After that session, I started cataloguing other times I'd been bitten the same way. Within a week I had a list:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Root cause&lt;/th&gt;
&lt;th&gt;Time spent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;waitForSyncedState()&lt;/code&gt; hangs forever&lt;/td&gt;
&lt;td&gt;facade 2.x + node 0.21.0 mismatch&lt;/td&gt;
&lt;td&gt;~6h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;npm install&lt;/code&gt; returns ENOTFOUND&lt;/td&gt;
&lt;td&gt;community tutorial said &lt;code&gt;.npmrc&lt;/code&gt; should point at &lt;code&gt;npm.midnight.network&lt;/code&gt; (domain doesn't exist)&lt;/td&gt;
&lt;td&gt;~2h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transactions silently fail to submit&lt;/td&gt;
&lt;td&gt;Two versions of &lt;code&gt;@midnight-ntwrk/ledger-v7&lt;/code&gt; in &lt;code&gt;node_modules&lt;/code&gt; (transitive bump)&lt;/td&gt;
&lt;td&gt;~3h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Indexer crash-loops on startup&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;indexer-standalone:4.0.0-rc.4&lt;/code&gt; requires a &lt;code&gt;subscription:&lt;/code&gt; block in &lt;code&gt;indexer.yml&lt;/code&gt;, undocumented&lt;/td&gt;
&lt;td&gt;~1.5h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WalletFacade behaves differently on standalone vs preprod&lt;/td&gt;
&lt;td&gt;Same SDK, different node behavior, no warning&lt;/td&gt;
&lt;td&gt;~4h&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Across five incidents: &lt;strong&gt;~16 hours of debugging silent failures&lt;/strong&gt;. Each one had a root cause that was, with the right cross-reference, &lt;strong&gt;detectable in seconds&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;package.json&lt;/code&gt; says facade is 2.0&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker ps&lt;/code&gt; says node is 0.21.0&lt;/li&gt;
&lt;li&gt;A lookup table says those two are incompatible&lt;/li&gt;
&lt;li&gt;→ Print a clear error.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The system already has all three pieces of data. There's just no glue.&lt;/p&gt;




&lt;h2&gt;
  
  
  The tool: &lt;code&gt;midnight-doctor&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The fix isn't a new feature in the SDK. It isn't better docs (which decay). It's an &lt;strong&gt;executable cross-reference&lt;/strong&gt; — a script that reads your project, your Docker stack, and your config files; matches them against a curated compatibility table; and tells you what's wrong.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://github.com/fredericosanntana/midnight-doctor" rel="noopener noreferrer"&gt;&lt;code&gt;midnight-doctor&lt;/code&gt;&lt;/a&gt; over a weekend. ~700 lines of Node, zero runtime dependencies, single-binary install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx midnight-doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run against my own legacy &lt;code&gt;midnight-hello-world&lt;/code&gt; repo, it produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;── midnight-doctor ──
project: /root/midnight-hello-world

⚠ SDK track: Preprod 3.x (legacy, OK for existing apps)
   Detected from wallet-sdk-facade@2.0.0.
⚠ Track is deprecated
   SDK 3.2 / facade 2.0 is 2 majors behind. Current is wallet-sdk@1.0.0 /
   facade@4.0.0 (released 2026-04-23). The WalletFacade.init({...}) constructor
   used in 2.0 has been removed; 4.0 reverted to `new WalletFacade(s, u, d) +
   .start()`. Plan a migration.
⚠ WalletFacade.init() in 2.x stalls on standalone dev nodes
   In SDK 2.x, WalletFacade.init() subscribes to runtime version events that the
   standalone node closes early. The wallet hangs in 'syncing' state forever
   with no error.
   → fix: Either (a) develop against preprod once past hello-world, or
     (b) upgrade to wallet-sdk-facade@4.0.0 which reverted to constructor +
     .start() pattern.
✓ node: midnightntwrk/midnight-node:0.21.0
✓ indexer: midnightntwrk/indexer-standalone:4.0.0-rc.4
✓ proof-server: midnightntwrk/proof-server:7.0.0
✓ midnight-node:0.21.0 matches SDK track

summary: 4 ok  3 warn  0 error  0 info
Status: workable, but warnings deserve a look.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 6-hour incident from March is now a 30-second output.&lt;/p&gt;




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

&lt;p&gt;The tool is structurally trivial. Three scanners, one diagnosis engine, one report formatter. Total surface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;midnight-doctor/
├── bin/midnight-doctor.js   # CLI entry, arg parsing, exit codes
├── lib/
│   ├── scan-package.js      # reads package.json + walks node_modules
│   ├── scan-docker.js       # runs `docker ps`, parses image tags
│   ├── scan-config.js       # reads .npmrc, indexer.yml
│   ├── diagnose.js          # cross-references findings with matrix
│   ├── report.js            # ANSI-colored pretty output
│   └── index.js             # public API
├── data/compatibility-matrix.json   # the source of truth
└── test/diagnose.test.js    # node:test, no framework
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The compatibility matrix is the product
&lt;/h3&gt;

&lt;p&gt;The ~700 lines of code are scaffolding. The actual &lt;em&gt;value&lt;/em&gt; is the JSON document at &lt;code&gt;data/compatibility-matrix.json&lt;/code&gt;. It encodes three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tracks&lt;/strong&gt; — known-good combinations of SDK + Docker images, labeled (&lt;code&gt;current&lt;/code&gt;, &lt;code&gt;preprod-3x&lt;/code&gt;, &lt;code&gt;preprod-1x&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Known issues&lt;/strong&gt; — specific bugs keyed by package version range or config pattern&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-cutting checks&lt;/strong&gt; — rules that fire when two scanners disagree (e.g., node tag vs SDK track)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A snippet:&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="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tracks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"current"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Current (latest npm + preprod compatible)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.21.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"indexer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4.0.0-rc.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"proofServer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"packages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@midnight-ntwrk/wallet-sdk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@midnight-ntwrk/wallet-sdk-facade"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@midnight-ntwrk/compact-runtime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.15.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"knownIssues"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"facade-2x-init-bug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"warn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"package-version"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                 &lt;/span&gt;&lt;span class="nl"&gt;"package"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@midnight-ntwrk/wallet-sdk-facade"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                 &lt;/span&gt;&lt;span class="nl"&gt;"range"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.x"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WalletFacade.init() in 2.x stalls on standalone dev nodes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"fix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Either develop against preprod, or upgrade to facade 4.0.0..."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&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;This file is the same information that lives, scattered, across Discord pinned messages and individual developers' heads. Centralizing it in a JSON document accomplishes three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source of truth&lt;/strong&gt; — there's now a place to point people instead of "search the channel"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versioned&lt;/strong&gt; — the file has a &lt;code&gt;verifiedAt: "2026-04-27"&lt;/code&gt; field; staleness is detectable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Executable&lt;/strong&gt; — code can act on it; humans can read it&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The scanners are deliberately dumb
&lt;/h3&gt;

&lt;p&gt;Each scanner is one file, one responsibility, ~50 lines.&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="c1"&gt;// lib/scan-docker.js (excerpt)&lt;/span&gt;
&lt;span class="k"&gt;export&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;scanDocker&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;findings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;dockerAvailable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;containers&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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;docker&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;version&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="s1"&gt;--format&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="s1"&gt;{{.Server.Version}}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dockerAvailable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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="nx"&gt;findings&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;stdout&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;docker&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ps&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="s1"&gt;--format&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="s1"&gt;{{.Image}}|{{.Names}}|{{.Status}}&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="k"&gt;for &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;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&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;span class="nb"&gt;Boolean&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;|&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;imageName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;latest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;KNOWN_IMAGES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;imageName&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;KNOWN_IMAGES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;imageName&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="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;imageName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;findings&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;No Docker SDK dependency, no parsing library — just &lt;code&gt;child_process.execFile&lt;/code&gt; and &lt;code&gt;String.prototype.split&lt;/code&gt;. If Docker isn't installed, the scanner returns &lt;code&gt;{ dockerAvailable: false }&lt;/code&gt; and the diagnosis engine emits an &lt;code&gt;info&lt;/code&gt; diagnostic instead of crashing.&lt;/p&gt;

&lt;p&gt;The package scanner is similar — walks &lt;code&gt;node_modules&lt;/code&gt; recursively (capped at 5000 directories so monorepos don't hang), records every &lt;code&gt;@midnight-ntwrk/*&lt;/code&gt; it finds and what version. Duplicates fall out as a side effect: if the same package name has more than one version in the array, it's a duplicate.&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;allInstances&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;findAllInstances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MIDNIGHT_NS&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;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&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="nx"&gt;allInstances&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;unique&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;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;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duplicates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;unique&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;Three lines. That's the entire duplicate-detection logic. The remaining ~50 lines of the scanner are filesystem traversal — boring, mechanical, but it works on every Node project regardless of package manager (npm, pnpm, yarn, bun all create &lt;code&gt;node_modules&lt;/code&gt; directories with the same shape).&lt;/p&gt;

&lt;h3&gt;
  
  
  The diagnosis engine is glorified table joins
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;lib/diagnose.js&lt;/code&gt; takes the three scan results plus the matrix and emits diagnostics. The pattern repeats per check:&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;function&lt;/span&gt; &lt;span class="nf"&gt;diagnoseCrossCutting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;matrix&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;facadeVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;installed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@midnight-ntwrk/wallet-sdk-facade&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;nodeContainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;facadeVersion&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;nodeContainer&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tracks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;detectTrack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;facadeVersion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;nodeContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-track-mismatch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`midnight-node:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;nodeContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; doesn't match SDK track (expects &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&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="na"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Mixing causes silent sync failures with no error.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;fix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Either update the node container to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, or align SDK to a track that matches your node version.`&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-track-match&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`midnight-node:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;nodeContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; matches SDK track`&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;That's the entire body of the cross-cut check that would have saved my 6-hour March incident. &lt;strong&gt;Twelve lines. The information was there the whole time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The full diagnosis file is ~200 lines. Most of it is the same shape: pick two facts from the scans, compare against the matrix, emit a diagnostic. It's deliberately boring code.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this isn't
&lt;/h2&gt;

&lt;p&gt;Scope discipline is half the value of a tool like this. Things &lt;code&gt;midnight-doctor&lt;/code&gt; deliberately doesn't do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It doesn't auto-fix.&lt;/strong&gt; No &lt;code&gt;--fix&lt;/code&gt; flag yet. The cost of a wrong auto-fix in this domain (bricked node_modules, lost work) outweighs the convenience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It doesn't curl health endpoints.&lt;/strong&gt; It checks Docker container &lt;em&gt;presence&lt;/em&gt;, not &lt;em&gt;health&lt;/em&gt;. A separate health-check script (&lt;code&gt;midnight-health-check.sh&lt;/code&gt;) covers that already in my infra repo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It doesn't probe the chain.&lt;/strong&gt; No &lt;code&gt;getBlockNumber()&lt;/code&gt;, no balance lookup. Those depend on a working SDK, which is what doctor is meant to validate &lt;em&gt;before&lt;/em&gt; you try to use it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It isn't a package manager.&lt;/strong&gt; It won't run &lt;code&gt;npm dedupe&lt;/code&gt; for you. It tells you to run it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It doesn't write code.&lt;/strong&gt; It doesn't generate scaffolds, codemods, or migrations. That's a different tool (&lt;code&gt;create-midnight-app&lt;/code&gt;, eventually).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each non-feature is a deliberate choice to keep the surface small enough that the tool stays correct.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd build next, if there's appetite
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;midnight-doctor&lt;/code&gt; is the smallest useful version. The compatibility matrix as data unlocks several adjacent tools:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;create-midnight-app&lt;/code&gt;&lt;/strong&gt; — scaffold a project where &lt;code&gt;midnight-doctor&lt;/code&gt; is wired into &lt;code&gt;npm install&lt;/code&gt; lifecycle. The matrix becomes the default versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A codemod for the 2.x → 4.x facade migration.&lt;/strong&gt; The API change is mechanical: &lt;code&gt;WalletFacade.init({...})&lt;/code&gt; → &lt;code&gt;new WalletFacade(...) + .start(...)&lt;/code&gt;. AST transform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compactc compiler version check.&lt;/strong&gt; Today it's via shell-out. With a parser, doctor could read your &lt;code&gt;.compact&lt;/code&gt; source and say "this uses syntax X, requires compactc ≥ Y."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remote-fetched matrix.&lt;/strong&gt; Right now the matrix is bundled. A nightly job at &lt;code&gt;https://compatibility.midnight.network/matrix.json&lt;/code&gt; would let users not need to bump the doctor itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Editor integration.&lt;/strong&gt; A VSCode extension that runs doctor on save and surfaces diagnostics in the problems panel. The CLI is for CI; the editor is for dev loop.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first one is the highest leverage by far — it short-circuits the entire onboarding problem. But it's a much bigger project. Doctor is a Trojan horse for the matrix; once the matrix exists and people accept it as canonical, the rest is plumbing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The broader argument: tools beat docs
&lt;/h2&gt;

&lt;p&gt;Documentation rots. A README written today describes a system that, six weeks from now, has moved two majors. Anyone who builds in a fast-moving ecosystem has lived this: the official doc says "use SDK 1.0", the npm &lt;code&gt;latest&lt;/code&gt; tag says 4.0, the GitHub README says 3.0, and the Discord pinned message says "we know, sorry, the docs are stale." None of those sources is wrong. They were all true at some point. The problem is they don't update each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Executable knowledge updates itself.&lt;/strong&gt; A JSON file with a &lt;code&gt;verifiedAt&lt;/code&gt; date that's three weeks old is &lt;em&gt;visibly&lt;/em&gt; stale. A doc that's three weeks old looks identical to a doc that's three days old. Tools force the question "is this still true?" in a way that prose doesn't.&lt;/p&gt;

&lt;p&gt;This isn't unique to Midnight. Every fast-moving stack reaches the point where the gap between "what the docs claim" and "what actually works" is wide enough to swallow new developers. The fix is the same: take the lookup table out of human heads, encode it as data, ship it as a tool that runs in seconds.&lt;/p&gt;

&lt;p&gt;The Midnight protocol team doesn't need to build &lt;code&gt;midnight-doctor&lt;/code&gt;. They could. Anyone could. The information already exists — in pinned messages, in CHANGELOG.md files, in the heads of the half-dozen people on Discord who answer the same question every week. &lt;strong&gt;The work isn't producing the information. It's transcribing it once, into a format machines can act on, and committing to keeping it current.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's what this ~700-line tool does. It's not clever. It's not hard. It's just nobody had done it.&lt;/p&gt;

&lt;p&gt;If you've spent hours on a Midnight silent failure, please run &lt;code&gt;npx midnight-doctor&lt;/code&gt; against your project before your next debugging session. If you find a bug doctor missed, &lt;a href="https://github.com/fredericosanntana/midnight-doctor/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; — the matrix is the artifact, the code is just glue.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/fredericosanntana/midnight-doctor" rel="noopener noreferrer"&gt;github.com/fredericosanntana/midnight-doctor&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Install:&lt;/strong&gt; &lt;code&gt;npm install -g midnight-doctor&lt;/code&gt; or &lt;code&gt;npx midnight-doctor&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;License:&lt;/strong&gt; MIT&lt;/p&gt;

&lt;p&gt;If this was useful, the people who actually maintain Midnight (&lt;a href="https://twitter.com/MidnightNtwrk" rel="noopener noreferrer"&gt;@MidnightNtwrk&lt;/a&gt;) deserve the visibility — half this matrix came from their Discord answers. The other half came from getting burned. Both contributions are essential.&lt;/p&gt;




&lt;h2&gt;
  
  
  Appendix: the full check list
&lt;/h2&gt;

&lt;p&gt;For reference, every check &lt;code&gt;midnight-doctor&lt;/code&gt; currently runs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Package scanner:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✓ Detect SDK track (&lt;code&gt;current&lt;/code&gt;, &lt;code&gt;preprod-3x&lt;/code&gt;, &lt;code&gt;preprod-1x&lt;/code&gt;) from &lt;code&gt;wallet-sdk-facade&lt;/code&gt; major&lt;/li&gt;
&lt;li&gt;✓ Flag deprecated tracks with migration guidance&lt;/li&gt;
&lt;li&gt;✓ Detect major-version mismatch across &lt;code&gt;wallet-sdk-*&lt;/code&gt; subpackages&lt;/li&gt;
&lt;li&gt;✓ Detect duplicate &lt;code&gt;@midnight-ntwrk/ledger-v7&lt;/code&gt; in &lt;code&gt;node_modules&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;✓ Detect duplicate &lt;code&gt;@midnight-ntwrk/compact-runtime&lt;/code&gt; in &lt;code&gt;node_modules&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;✓ Flag &lt;code&gt;wallet-sdk-facade@2.x&lt;/code&gt; standalone init bug&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Docker scanner:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✓ Detect running &lt;code&gt;midnight-node&lt;/code&gt;, &lt;code&gt;indexer-standalone&lt;/code&gt;, &lt;code&gt;proof-server&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;✓ Cross-reference node tag with SDK track&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Config scanner:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✓ Flag &lt;code&gt;.npmrc&lt;/code&gt; with bogus &lt;code&gt;npm.midnight.network&lt;/code&gt; registry&lt;/li&gt;
&lt;li&gt;✓ Flag &lt;code&gt;indexer.yml&lt;/code&gt; missing &lt;code&gt;subscription:&lt;/code&gt; block&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cross-cutting:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✓ Node tag ↔ SDK track consistency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Roadmap:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☐ Compact compiler version vs runtime&lt;/li&gt;
&lt;li&gt;☐ Health probes (RPC, GraphQL, proof endpoints)&lt;/li&gt;
&lt;li&gt;☐ Monorepo workspace iteration&lt;/li&gt;
&lt;li&gt;☐ &lt;code&gt;--fix&lt;/code&gt; mode for safe auto-corrections&lt;/li&gt;
&lt;li&gt;☐ Remote-fetched matrix&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total checks today: 11. Adding more is one PR each.&lt;/p&gt;

</description>
      <category>midnightchallenge</category>
      <category>web3</category>
    </item>
    <item>
      <title>Building a Full-Stack ZK-Privacy App on Midnight: A Step-by-Step Guide</title>
      <dc:creator>Haruki Kondo</dc:creator>
      <pubDate>Sun, 26 Apr 2026 13:11:02 +0000</pubDate>
      <link>https://forem.com/midnight-aliit/building-a-full-stack-zk-privacy-app-on-midnight-a-step-by-step-guide-1nne</link>
      <guid>https://forem.com/midnight-aliit/building-a-full-stack-zk-privacy-app-on-midnight-a-step-by-step-guide-1nne</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello everyone! 🚀&lt;/p&gt;

&lt;p&gt;In this post, we are diving deep into &lt;strong&gt;Midnight&lt;/strong&gt;, the privacy-focused blockchain!&lt;/p&gt;

&lt;p&gt;Previously, we covered how to connect a frontend application to &lt;strong&gt;Lace Wallet&lt;/strong&gt;. Now, we’re taking the next big step: &lt;strong&gt;connecting to a smart contract!&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Implementing a full-stack ZK (Zero-Knowledge) application can be tricky, but I’ve navigated the pitfalls and version mismatches so you don't have to. Let’s get started!&lt;/p&gt;

&lt;h3&gt;
  
  
  What You Will Learn
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;How to develop a full-stack application on Midnight.&lt;/li&gt;
&lt;li&gt;Concrete source code for calling smart contract functions via Lace Wallet.&lt;/li&gt;
&lt;li&gt;Real-world "gotchas" and how to solve them.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Sample App: A Full-Stack Counter
&lt;/h2&gt;

&lt;p&gt;We’ll build a simple app that connects to Lace Wallet, displays balances, and interacts with a &lt;strong&gt;Counter&lt;/strong&gt; smart contract.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.lace.io/" rel="noopener noreferrer"&gt;Lace Wallet&lt;/a&gt; browser extension (Midnight-compatible version).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PreProd&lt;/strong&gt; network selected in Lace settings.&lt;/li&gt;
&lt;li&gt;Some test &lt;strong&gt;NIGHT&lt;/strong&gt; tokens from the &lt;a href="https://midnight-tmnight-preprod.nethermind.dev/" rel="noopener noreferrer"&gt;official faucet&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Docker installed (for running the Proof Server).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GitHub Repository
&lt;/h3&gt;

&lt;p&gt;Check out the full source code here:&lt;br&gt;
&lt;a href="https://github.com/mashharuki/midnight-sample-fullstack-app" rel="noopener noreferrer"&gt;https://github.com/mashharuki/midnight-sample-fullstack-app&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Application Preview
&lt;/h3&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.amazonaws.com%2Fuploads%2Farticles%2Fin5ossccqr2661fx4mbo.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.amazonaws.com%2Fuploads%2Farticles%2Fin5ossccqr2661fx4mbo.png" width="800" height="1139"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Technical Features
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contract&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Build&lt;/td&gt;
&lt;td&gt;Compiles Compact files into WASM/ZKIR/Managed Code.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contract&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Simulator&lt;/td&gt;
&lt;td&gt;Logic verification using &lt;code&gt;CounterSimulator&lt;/code&gt; without a live network.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CLI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deploy&lt;/td&gt;
&lt;td&gt;Deploys to Standalone/TestNet and retrieves the contract address.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CLI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Interaction&lt;/td&gt;
&lt;td&gt;Direct CLI commands to increment and check counter values.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;App&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Wallet Connect&lt;/td&gt;
&lt;td&gt;Integration with Lace Wallet extension to fetch account data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;App&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sync&lt;/td&gt;
&lt;td&gt;Automatically joins and fetches current state from a contract address.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;App&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ZK Increment&lt;/td&gt;
&lt;td&gt;One-click UI to generate ZK proofs and send transactions.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Frontend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;React 19, Vite 5, Tailwind CSS v4, Lucide React, RxJS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contract&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Compact (Midnight's ZK DSL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SDK&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@midnight-ntwrk/*&lt;/code&gt; (SDK v2 / DApp Connector API v4)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Midnight Node, Indexer, Proof Server (via Docker)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tooling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bun, Biome, Vitest, TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  Deep Dive: Key Implementation Logic
&lt;/h2&gt;

&lt;p&gt;The core logic resides in &lt;code&gt;src/lib/counter.ts&lt;/code&gt; and &lt;code&gt;src/hooks/useCounter.ts&lt;/code&gt;. This was the most challenging part of the build due to SDK version transitions and compatibility issues with Lace Wallet.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;src/lib/counter.ts&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This file handles the contract instance creation, state querying, and transaction calls.&lt;/p&gt;
&lt;h4&gt;
  
  
  1. Contract Instance Creation
&lt;/h4&gt;

&lt;p&gt;We use the SDK to wrap the compiled Compact contract.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;CompactJs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@midnight-ntwrk/compact-js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Initialize the compiled contract instance&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counterContractInstance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CounterContract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CompactJs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CompiledContract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;counter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Contract&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;CompactJs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CompiledContract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;withVacantWitnesses&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;h4&gt;
  
  
  2. Querying State
&lt;/h4&gt;

&lt;p&gt;Using &lt;code&gt;queryContractState&lt;/code&gt;, we can fetch the ledger data (in this case, the &lt;code&gt;round&lt;/code&gt; number of our counter).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getCounterValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CounterProviders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ContractAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;bigint&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nf"&gt;assertIsContractAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contractAddress&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;contractState&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;providers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicDataProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queryContractState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;,&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;contractState&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contractState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;round&lt;/span&gt;
    &lt;span class="p"&gt;:&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Calling Updates (Transactions)
&lt;/h4&gt;

&lt;p&gt;The magic happens in &lt;code&gt;callTx&lt;/code&gt;. This triggers the ZK proof generation and requests a signature from Lace Wallet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;incrementCounter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;counterContract&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DeployedCounterContract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;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="c1"&gt;// Call the increment() method on the contract&lt;/span&gt;
  &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counterContract&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;callTx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&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;h2&gt;
  
  
  Getting Started: Run it Locally
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Clone &amp;amp; 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/mashharuki/midnight-lace-react-sample-app.git
&lt;span class="nb"&gt;cd &lt;/span&gt;midnight-lace-react-sample-app
bun &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Start the Proof Server
&lt;/h3&gt;

&lt;p&gt;Midnight requires a &lt;strong&gt;Proof Server&lt;/strong&gt; to generate ZK Proofs locally before sending transactions to the network.&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;cd &lt;/span&gt;pkgs/cli
&lt;span class="c"&gt;# Start the Proof Server (Ensure it's version 8.0.3)&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; standalone.yml up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Build &amp;amp; Deploy the Contract
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun contract build

&lt;span class="c"&gt;# Deploy to the PreProd network&lt;/span&gt;
bun cli preprod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: Copy the contract address displayed in the terminal!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Setup &amp;amp; Start the Frontend
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set environment variables&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;pkgs/app/.env.local.example pkgs/app/.env

&lt;span class="c"&gt;# Build and start&lt;/span&gt;
bun app build
bun app dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit &lt;code&gt;localhost:5173&lt;/code&gt;. You should see the following:&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.amazonaws.com%2Fuploads%2Farticles%2Fq5dy8nm5b72k3r841vv8.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.amazonaws.com%2Fuploads%2Farticles%2Fq5dy8nm5b72k3r841vv8.png" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Connect your wallet.&lt;/li&gt;
&lt;li&gt;Enter your deployed contract address and click &lt;strong&gt;Join&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Increment&lt;/strong&gt;. You will be prompted to sign the transaction.&lt;/li&gt;
&lt;/ol&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.amazonaws.com%2Fuploads%2Farticles%2F4k71ervscjq4y4p5uqvp.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.amazonaws.com%2Fuploads%2Farticles%2F4k71ervscjq4y4p5uqvp.png" width="702" height="1296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wait a few moments for the ZK proof generation and network finalization. Once done, refresh to see the updated value!&lt;/p&gt;




&lt;h2&gt;
  
  
  Sequence Diagrams
&lt;/h2&gt;

&lt;p&gt;Unlike traditional blockchains, Midnight involves a local &lt;strong&gt;ZK Proof&lt;/strong&gt; generation step in every state-changing transaction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contract Deployment Flow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://mermaid.live/edit#pako:eNptkt9v2jAQx_8Vyw9TJ6UIktAQP1TqYJtQ12oalSZVefHsI1hN7MyxEQzxv-9MEtSW8hAud5_v_codqDASKKMt_PWgBSwULy2vC03w13DrlFAN144sYEt4G_6gMg1YcjX_sfx8yf3mVQUuoKvFff92ST1i1cA8KKlVuekcl9hSS9hhrddk7yt0h2ND17e3-GRkbvRalWQEeks-kW9eyzf1O7LzMCKhqcweJc5y4a6E8dqBXerWcVxDPxhG1JY7eJOns1-l-uU1mSsrvHJY9ztosEHzfE9-WmPW71RhUkZW_k-N-JPlug1VTD-OhA9rBtH1MOfTjtwJAY0DOUQRN1tcVOCiYUWMfKmMeMHVaad0ib2dAmh2sh475115zNq2IZ1aK8FDVygadkTupLQYpxGtwdZcSbybQ0hVULeBGgrK0JTcvhS00EfkfCNxkq9SOWMpW_OqhYhy78xqrwVlznoYoP7wBiecNA_dcZ5uNKLW-HJzToMH8mzMWVDa0E9nW8DJ7Dx8UsryE0rZge4oy7JRPEtv8kmW5DdJlqQR3VMWJ9kom6TjSZKn01k6TY4R_XfKPRmNx3E-izEynWXpOE6O_wELMw1I" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNptkt9v2jAQx_8Vyw9TJ6UIktAQP1TqYJtQ12oalSZVefHsI1hN7MyxEQzxv-9MEtSW8hAud5_v_codqDASKKMt_PWgBSwULy2vC03w13DrlFAN144sYEt4G_6gMg1YcjX_sfx8yf3mVQUuoKvFff92ST1i1cA8KKlVuekcl9hSS9hhrddk7yt0h2ND17e3-GRkbvRalWQEeks-kW9eyzf1O7LzMCKhqcweJc5y4a6E8dqBXerWcVxDPxhG1JY7eJOns1-l-uU1mSsrvHJY9ztosEHzfE9-WmPW71RhUkZW_k-N-JPlug1VTD-OhA9rBtH1MOfTjtwJAY0DOUQRN1tcVOCiYUWMfKmMeMHVaad0ib2dAmh2sh475115zNq2IZ1aK8FDVygadkTupLQYpxGtwdZcSbybQ0hVULeBGgrK0JTcvhS00EfkfCNxkq9SOWMpW_OqhYhy78xqrwVlznoYoP7wBiecNA_dcZ5uNKLW-HJzToMH8mzMWVDa0E9nW8DJ7Dx8UsryE0rZge4oy7JRPEtv8kmW5DdJlqQR3VMWJ9kom6TjSZKn01k6TY4R_XfKPRmNx3E-izEynWXpOE6O_wELMw1I%3Ftype%3Dpng" width="943" height="548"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Increment Function Call Flow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://mermaid.live/edit#pako:eNptkl1P2zAUhv-K5atOCpULSWh8gTTBNKGNKSKrkFBuvOQ0sUjszHH4WNX_jo9dSkfxjY_sx6_f87Ghla6BcjrC3wlUBVdSNEb0pSJuDcJYWclBKEtWIxgixrDP7uAPWV1_OcZ-igoQ8_ud6Dqwx9DXYUCmxn12C6KynyjlBTK50XpNCjCPYI6ZX848UjeyVrJpw0GpAohOTy4u3CecXHayeiAlvVaVgR6ULWmA3K1j0C0nt1iD0ZLfz6SQjRJ2MhAovD9xHEpyUrT6CR8a_Sg6kuthGv77MagF4IOAN4PiUJMrYcWhibx4txDS_g4KjLBSKzK7_xEOd5XKi73cjnKKHjiUxHK4_0DVmNSTtO0hg7fvWV3qSVnX29VQezHXmHUHFYZSYbNpRHswvZC1m5cNSpTUtq6aJeUurIV5wKpuHTd5jW-1tNpQvhbdCBEVk9XFi6oot2aCN2g3cHsK_KObMJV-OCNq9NS0e8J1_l7r_k2mMWgoxMZlCsZnQvk58yzlG_pMebaYJyyLGVskWZbF2TKiL5SfptmcnSUsTrMkSTHYRvSfV2fz5XmapXG8XLAzxrLT7Suq3QUO" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNptkl1P2zAUhv-K5atOCpULSWh8gTTBNKGNKSKrkFBuvOQ0sUjszHH4WNX_jo9dSkfxjY_sx6_f87Ghla6BcjrC3wlUBVdSNEb0pSJuDcJYWclBKEtWIxgixrDP7uAPWV1_OcZ-igoQ8_ud6Dqwx9DXYUCmxn12C6KynyjlBTK50XpNCjCPYI6ZX848UjeyVrJpw0GpAohOTy4u3CecXHayeiAlvVaVgR6ULWmA3K1j0C0nt1iD0ZLfz6SQjRJ2MhAovD9xHEpyUrT6CR8a_Sg6kuthGv77MagF4IOAN4PiUJMrYcWhibx4txDS_g4KjLBSKzK7_xEOd5XKi73cjnKKHjiUxHK4_0DVmNSTtO0hg7fvWV3qSVnX29VQezHXmHUHFYZSYbNpRHswvZC1m5cNSpTUtq6aJeUurIV5wKpuHTd5jW-1tNpQvhbdCBEVk9XFi6oot2aCN2g3cHsK_KObMJV-OCNq9NS0e8J1_l7r_k2mMWgoxMZlCsZnQvk58yzlG_pMebaYJyyLGVskWZbF2TKiL5SfptmcnSUsTrMkSTHYRvSfV2fz5XmapXG8XLAzxrLT7Suq3QUO%3Ftype%3Dpng" width="1185" height="579"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Building with ZK-privacy is a paradigm shift. While there were hurdles—especially around SDK versions and wallet compatibility—overcoming them allows you to build applications that truly respect user privacy.&lt;/p&gt;

&lt;p&gt;I even created a custom AI Agent skill to help debug the Midnight SDK logic, which made the final stretch much smoother! You can find my Claude skills in the repo if you're interested.&lt;/p&gt;

&lt;p&gt;Happy coding, and see you in the next one where we might tackle some hackathon challenges! 🛡️✨&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.midnight.network/" rel="noopener noreferrer"&gt;Midnight Official Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/" rel="noopener noreferrer"&gt;Midnight Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>typescript</category>
      <category>blockchain</category>
      <category>zk</category>
    </item>
    <item>
      <title>Build a Simple App That Connects to Midnight Lace Wallet</title>
      <dc:creator>Haruki Kondo</dc:creator>
      <pubDate>Fri, 24 Apr 2026 12:35:58 +0000</pubDate>
      <link>https://forem.com/midnight-aliit/build-a-simple-app-that-connects-to-midnight-lace-wallet-4682</link>
      <guid>https://forem.com/midnight-aliit/build-a-simple-app-that-connects-to-midnight-lace-wallet-4682</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Hello everyone!&lt;/p&gt;

&lt;p&gt;In this article, we will look at &lt;strong&gt;Midnight&lt;/strong&gt;, a privacy-focused blockchain, and its dedicated wallet, &lt;strong&gt;Lace Wallet&lt;/strong&gt;.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.lace.io/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.sanity.io%2Fimages%2Ftnb8oobx%2Fproduction%2F434b20b0ce779725521c1890bd857b5b2dac6c65-1201x628.jpg" height="418" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.lace.io/" rel="noopener noreferrer" class="c-link"&gt;
            Lace | The light wallet platform to explore Web3
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Discover the new light wallet platform from Input Output Global. Manage digital assets, and access NFTs, DApps, and DeFi services. Start your Web3 journey.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.lace.io%2Ffavicon.ico" width="16" height="16"&gt;
          lace.io
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The official documentation explains how to deploy and run contracts, but I could not find clear guidance on connecting &lt;strong&gt;Lace Wallet&lt;/strong&gt; to a frontend app. I ran into a few tricky points while trying it, so I created a simple beginner-friendly sample app.&lt;/p&gt;

&lt;p&gt;I hope you enjoy it!&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Will Learn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A quick overview of Lace Wallet&lt;/li&gt;
&lt;li&gt;How to build a simple app that connects to Lace Wallet&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  What Is Lace Wallet?
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Lace Wallet&lt;/strong&gt; is a lightweight wallet for storing and managing Cardano-related digital assets. It was announced by &lt;strong&gt;IOG&lt;/strong&gt;, the team behind Cardano, on June 9, 2022.&lt;/p&gt;

&lt;p&gt;It can manage digital assets on blockchains in the Cardano ecosystem, including &lt;strong&gt;Midnight&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Even though it is lightweight, it offers rich features such as staking and asset transfers.&lt;/p&gt;

&lt;p&gt;It is currently optimized for the Cardano ecosystem, and it appears support for other ecosystems is planned in the future.&lt;/p&gt;

&lt;h1&gt;
  
  
  Build a Simple App That Connects to Lace Wallet
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before running the sample code, make sure you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://www.lace.io/" rel="noopener noreferrer"&gt;Lace Wallet&lt;/a&gt; browser extension installed (a version with Midnight support)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PreProd&lt;/strong&gt; selected in Lace network settings&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sample Code Repository
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mashharuki" rel="noopener noreferrer"&gt;
        mashharuki
      &lt;/a&gt; / &lt;a href="https://github.com/mashharuki/midnight-lace-react-sample-app" rel="noopener noreferrer"&gt;
        midnight-lace-react-sample-app
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      midnight-lace-react-sample-app
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;midnight-lace-react-sample-app&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A sample dApp frontend for connecting to the Midnight Network (PreProd).&lt;br&gt;
Use Lace Wallet to connect your wallet, view your shielded address, and check your balance.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Screenshots&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/mashharuki/midnight-lace-react-sample-app/./docs/imgs/1.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmashharuki%2Fmidnight-lace-react-sample-app%2FHEAD%2F.%2Fdocs%2Fimgs%2F1.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/mashharuki/midnight-lace-react-sample-app/./docs/imgs/2.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmashharuki%2Fmidnight-lace-react-sample-app%2FHEAD%2F.%2Fdocs%2Fimgs%2F2.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lace Wallet Connection&lt;/td&gt;
&lt;td&gt;Detects and connects to &lt;code&gt;window.midnight.mnLace&lt;/code&gt; via 100ms polling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Version Validation&lt;/td&gt;
&lt;td&gt;Verifies that the Connector API version is &lt;code&gt;&amp;gt;=1.0.0&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network Detection&lt;/td&gt;
&lt;td&gt;Automatically tries networks in order: PreProd / mainnet / undeployed / preview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Address Display&lt;/td&gt;
&lt;td&gt;Shows the shielded address in a copyable format&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Balance Display&lt;/td&gt;
&lt;td&gt;Shows Shielded / Unshielded / Dust balances in tDUST units&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language Toggle&lt;/td&gt;
&lt;td&gt;Instantly switch between Japanese and English via the top-right button (persisted in localStorage)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Tech Stack&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Library / Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;React 19 + TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build&lt;/td&gt;
&lt;td&gt;Vite 8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Styling&lt;/td&gt;
&lt;td&gt;Tailwind CSS v4 (&lt;code&gt;@tailwindcss/vite&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI Components&lt;/td&gt;
&lt;td&gt;shadcn/ui (Button, Badge, Card) + Lucide React&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internationalization&lt;/td&gt;
&lt;td&gt;i18next 26 + react-i18next 17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wallet Integration&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@midnight-ntwrk/dapp-connector-api&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Async Processing&lt;/td&gt;
&lt;td&gt;RxJS 7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Package Manager&lt;/td&gt;
&lt;td&gt;Bun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Formatter&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/mashharuki/midnight-lace-react-sample-app" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  App Preview
&lt;/h2&gt;

&lt;p&gt;This sample app is intentionally simple: connect Lace Wallet and display balances.&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.amazonaws.com%2Fuploads%2Farticles%2F4g2tingzcgy201qvk6df.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.amazonaws.com%2Fuploads%2Farticles%2F4g2tingzcgy201qvk6df.png" width="800" height="443"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Ft5r4okw69gai74xgnkpx.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.amazonaws.com%2Fuploads%2Farticles%2Ft5r4okw69gai74xgnkpx.png" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature List
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lace Wallet connection&lt;/td&gt;
&lt;td&gt;Detects and connects to &lt;code&gt;window.midnight.mnLace&lt;/code&gt; using 100ms polling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Version check&lt;/td&gt;
&lt;td&gt;Confirms Connector API version is &lt;code&gt;&amp;gt;=1.0.0&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network validation&lt;/td&gt;
&lt;td&gt;Automatically tries PreProd / mainnet / undeployed / preview in order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Address display&lt;/td&gt;
&lt;td&gt;Shows a copyable shielded address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Balance display&lt;/td&gt;
&lt;td&gt;Shows Shielded / Unshielded / Dust balances in tDUST&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language switch&lt;/td&gt;
&lt;td&gt;Instantly toggles Japanese ⇆ English from the top-right button (persisted via localStorage)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Library / Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;React 19 + TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build&lt;/td&gt;
&lt;td&gt;Vite 8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Styling&lt;/td&gt;
&lt;td&gt;Tailwind CSS v4 (&lt;code&gt;@tailwindcss/vite&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI Components&lt;/td&gt;
&lt;td&gt;shadcn/ui (Button, Badge, Card) + Lucide React&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internationalization&lt;/td&gt;
&lt;td&gt;i18next 26 + react-i18next 17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wallet Integration&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@midnight-ntwrk/dapp-connector-api&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Async Processing&lt;/td&gt;
&lt;td&gt;RxJS 7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Package Manager&lt;/td&gt;
&lt;td&gt;Bun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Formatter&lt;/td&gt;
&lt;td&gt;Biome&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Key Source Code Explanations
&lt;/h2&gt;

&lt;p&gt;The overall architecture is similar to many other blockchain apps.&lt;/p&gt;

&lt;p&gt;If you understand the SDK and API specifications, you can build a working connection app quickly.&lt;/p&gt;

&lt;p&gt;The key SDK in this project is &lt;code&gt;@midnight-ntwrk/dapp-connector-api&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is used to implement the wallet connection logic.&lt;/p&gt;

&lt;p&gt;All important connection-related logic is consolidated in the file below.&lt;/p&gt;

&lt;p&gt;A key point is around line 103.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Attempt connection. If a network mismatch occurs, fall back to the next candidate.&lt;/span&gt;
&lt;span class="nx"&gt;walletAPI&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;connector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;networkId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Lace v4: getConfiguration() is on walletAPI (not connector)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;walletRaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;walletAPI&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From &lt;code&gt;walletRaw&lt;/code&gt;, you can extract wallet information such as addresses.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&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;coinPublicKey&lt;/span&gt; &lt;span class="o"&gt;=&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;encryptionPublicKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;walletRaw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getShieldedAddresses&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&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="c1"&gt;// getShieldedAddresses() may return an array (old versions) or a single object (new versions), so handle both cases&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;walletRaw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getShieldedAddresses&lt;/span&gt; &lt;span class="k"&gt;as &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)();&lt;/span&gt;
  &lt;span class="c1"&gt;// Lace v4 returns a single object (not array):&lt;/span&gt;
  &lt;span class="c1"&gt;// { shieldedAddress, shieldedCoinPublicKey, shieldedEncryptionPublicKey }&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;result&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="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shieldedAddress&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;coinPublicKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shieldedCoinPublicKey&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coinPublicKey&lt;/span&gt; &lt;span class="o"&gt;??&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="nx"&gt;encryptionPublicKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shieldedEncryptionPublicKey&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;encryptionPublicKey&lt;/span&gt; &lt;span class="o"&gt;??&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Balance retrieval is implemented as a dedicated React hook.&lt;/p&gt;

&lt;p&gt;The most important part is the &lt;code&gt;fetchBalances&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;It fetches balances from three wallet-related methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;raw.getShieldedBalances&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;raw.getUnshieldedBalances&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;raw.getDustBalance&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;formatBalance&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/utils&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BalanceState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/utils/types&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DAppConnectorWalletAPI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@midnight-ntwrk/dapp-connector-api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BalanceState&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Fetch shielded / unshielded / dust balances in parallel from the wallet API.
 *
 * The Lace SDK type definitions do not include these balance methods,
 * so this function casts to unknown and checks method availability dynamically.
 * Promise.allSettled allows us to receive remaining results even if one call fails.
 */&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;fetchBalances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;walletAPI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DAppConnectorWalletAPI&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;shielded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;unshielded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;dust&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;walletAPI&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Try fetching balances in parallel. If a method is unavailable, return null.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;shieldedResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unshieldedResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dustResult&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allSettled&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getShieldedBalances&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&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="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getShieldedBalances&lt;/span&gt; &lt;span class="k"&gt;as &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)()&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getUnshieldedBalances&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&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="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getUnshieldedBalances&lt;/span&gt; &lt;span class="k"&gt;as &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)()&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getDustBalance&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&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="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getDustBalance&lt;/span&gt; &lt;span class="k"&gt;as &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)()&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;shielded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;shieldedResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fulfilled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;formatBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shieldedResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&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;--&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;unshielded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;unshieldedResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fulfilled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;formatBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unshieldedResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&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;--&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dust&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;dustResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fulfilled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;formatBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dustResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&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;--&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;span class="cm"&gt;/**
 * Custom hook to manage loading and refreshing balances.
 *
 * @param walletAPI - Connected wallet API. If null, no action is taken.
 * @returns balanceState: current balance state / refresh: manual refresh trigger
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;walletAPI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DAppConnectorWalletAPI&lt;/span&gt; &lt;span class="o"&gt;|&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;balanceState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setBalanceState&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BalanceState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idle&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="cm"&gt;/**
   * Fetch balances from the wallet API and update state.
   * If walletAPI is null, do nothing.
   * Set status to "loading" while fetching, "loaded" on success with balances,
   * and "error" if anything fails.
   */&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;refresh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;walletAPI&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="nf"&gt;setBalanceState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loading&lt;/span&gt;&lt;span class="dl"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;balances&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchBalances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;walletAPI&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setBalanceState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loaded&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="nx"&gt;balances&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="na"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[useBalance] Failed to fetch balances:&lt;/span&gt;&lt;span class="dl"&gt;"&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="nf"&gt;setBalanceState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&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;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;walletAPI&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="nx"&gt;balanceState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refresh&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;h1&gt;
  
  
  How to Run the App
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Clone
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/mashharuki/midnight-lace-react-sample-app.git

&lt;span class="c"&gt;# Move to the frontend app directory&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Build
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;That is all for this tutorial.&lt;/p&gt;

&lt;p&gt;Now you have a clear path to connect a &lt;strong&gt;React + Vite&lt;/strong&gt; app with &lt;strong&gt;Lace Wallet&lt;/strong&gt;, which is a solid step toward building a full-stack Midnight application.&lt;/p&gt;

&lt;p&gt;In a future article, I plan to cover how to call functions on a smart contract deployed on &lt;strong&gt;Midnight&lt;/strong&gt; and continue toward a full-stack implementation.&lt;/p&gt;

&lt;p&gt;Thank you for reading!&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.midnight.network/" rel="noopener noreferrer"&gt;Midnight Official Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/" rel="noopener noreferrer"&gt;Midnight Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nagamaru-panda.blog/?p=828" rel="noopener noreferrer"&gt;What Is Lace, the New Lightweight Wallet Developed by IOG?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>web3</category>
      <category>tutorial</category>
      <category>typescript</category>
      <category>midnight</category>
    </item>
    <item>
      <title>Compact Is Not TypeScript. That's the Whole Point.</title>
      <dc:creator>Tushar Pamnani</dc:creator>
      <pubDate>Thu, 23 Apr 2026 08:44:50 +0000</pubDate>
      <link>https://forem.com/midnight-aliit/compact-is-not-typescript-thats-the-whole-point-3d9</link>
      <guid>https://forem.com/midnight-aliit/compact-is-not-typescript-thats-the-whole-point-3d9</guid>
      <description>&lt;p&gt;Most developers approach Compact with the wrong frame.&lt;/p&gt;

&lt;p&gt;They see the syntax - functions, types, imports, curly braces, and conclude: &lt;em&gt;this is basically TypeScript with some ZK stuff sprinkled in.&lt;/em&gt; Then they start writing. And things break in ways that don't make sense. Loops that should work don't compile. Logic that feels correct fails silently. Private data bleeds into places it shouldn't.&lt;/p&gt;

&lt;p&gt;The syntax didn't lie to them. Their mental model did.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Illusion Is the Lesson
&lt;/h2&gt;

&lt;p&gt;Here's a Compact circuit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit get(): Uint&amp;lt;64&amp;gt; {
  assert(state == State.SET, "Value not set");
  return value;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your brain reads this and fires the usual pattern: &lt;em&gt;function, assertion, return value.&lt;/em&gt; It looks like a getter. Call it, get a thing back.&lt;/p&gt;

&lt;p&gt;But that's not what's happening.&lt;/p&gt;

&lt;p&gt;This circuit doesn't "run." It declares a set of constraints. When you call it from your dApp, the proof system generates a zero-knowledge proof that those constraints were satisfied, without revealing the inputs. What goes on-chain isn't the output. It's the proof.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The frame shift:&lt;/strong&gt; You're not writing code that executes. You're writing a description of a valid state, and the system proves you were in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Mental Models. One Will Break You.
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Traditional code:&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;Input → Code executes → Output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You write instructions. The machine follows them. You get a result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compact circuits:&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;Input → Constraints are declared → Proof is generated → Proof is verified
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are no "instructions" in the traditional sense. The circuit defines relationships. The prover proves the relationships held. The chain verifies the proof without seeing the inputs.&lt;/p&gt;

&lt;p&gt;Nothing is executed. Correctness is proved.&lt;/p&gt;

&lt;p&gt;This is why &lt;code&gt;assert&lt;/code&gt; is your only runtime guard. It's not just input validation, it's the mechanism by which you define what a valid state even is. Every &lt;code&gt;assert&lt;/code&gt; is a constraint. Every constraint becomes part of the circuit. The circuit is the proof.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Constraints Are the Feature
&lt;/h2&gt;

&lt;p&gt;The first reaction most developers have to Compact's restrictions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No recursion&lt;/li&gt;
&lt;li&gt;Bounded loops only (bounds must be compile-time constants)&lt;/li&gt;
&lt;li&gt;Fixed type sizes&lt;/li&gt;
&lt;li&gt;No &lt;code&gt;any&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Unsigned types only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;"These seem arbitrary. Why can't I just-"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;They're not arbitrary. They're causal.&lt;/p&gt;

&lt;p&gt;ZK proofs require finite circuits. A circuit is a fixed structure; gates, wires, constraints, determined entirely at compile time. If you could write unbounded loops or recursion, the compiler couldn't produce a circuit. The circuit literally couldn't exist. The proof couldn't be generated.&lt;/p&gt;

&lt;p&gt;The constraints don't limit what Compact can do. They're what make the magic possible at all.&lt;/p&gt;

&lt;p&gt;Accept this, and the language makes complete sense. Fight it, and you'll spend weeks trying to port TypeScript patterns into a tool designed around a completely different model.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "Privacy" Actually Means Here
&lt;/h2&gt;

&lt;p&gt;Compact is not just "TypeScript with encryption." The privacy model is structural.&lt;/p&gt;

&lt;p&gt;Midnight has two worlds:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;World&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;th&gt;Who can see it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Public&lt;/td&gt;
&lt;td&gt;On-chain&lt;/td&gt;
&lt;td&gt;Everyone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Private&lt;/td&gt;
&lt;td&gt;Local&lt;/td&gt;
&lt;td&gt;Only you&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Your sensitive data; balances, credentials, secrets, never leaves your machine. What goes on-chain is a proof that you ran the computation correctly on that private data. Validators verify the proof. They never see the inputs.&lt;/p&gt;

&lt;p&gt;This has a concrete implication: &lt;strong&gt;you have to know the boundary.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;export ledger&lt;/code&gt; is public. &lt;code&gt;witness&lt;/code&gt; data is private. &lt;code&gt;disclose()&lt;/code&gt; is the explicit line you cross when you intentionally move private data into public state.&lt;/p&gt;

&lt;p&gt;The compiler enforces this. If private data tries to flow into public state without going through &lt;code&gt;disclose()&lt;/code&gt;, it fails. But knowing &lt;em&gt;why&lt;/em&gt; this boundary exists — not just that it does — is what lets you design contracts correctly from the start instead of debugging your way there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Use Cases This Unlocks
&lt;/h2&gt;

&lt;p&gt;This matters because entire categories of blockchain applications were previously impossible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Healthcare:&lt;/strong&gt; Prove you meet age or eligibility requirements without revealing your date of birth or medical history.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Finance:&lt;/strong&gt; Prove your balance exceeds a threshold for a loan without revealing the balance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identity:&lt;/strong&gt; Prove citizenship or credential validity without revealing the underlying document.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitive markets:&lt;/strong&gt; Submit bids or inventory levels that can be verified as legitimate without being revealed to competitors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't niche edge cases. They're the reason regulated industries haven't fully adopted public blockchains. With Compact, the privacy model that makes these use cases viable is built into the language, not bolted on after the fact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Comparison Breaks Down
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;TypeScript&lt;/th&gt;
&lt;th&gt;Solidity&lt;/th&gt;
&lt;th&gt;Compact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Private data&lt;/td&gt;
&lt;td&gt;Convention only&lt;/td&gt;
&lt;td&gt;On-chain (visible)&lt;/td&gt;
&lt;td&gt;Stays local, always&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Functions&lt;/td&gt;
&lt;td&gt;Execute instructions&lt;/td&gt;
&lt;td&gt;Execute instructions&lt;/td&gt;
&lt;td&gt;Declare constraints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loops&lt;/td&gt;
&lt;td&gt;Unbounded&lt;/td&gt;
&lt;td&gt;Unbounded (gas-limited)&lt;/td&gt;
&lt;td&gt;Bounded at compile time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recursion&lt;/td&gt;
&lt;td&gt;Allowed&lt;/td&gt;
&lt;td&gt;Allowed&lt;/td&gt;
&lt;td&gt;Not allowed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Privacy enforcement&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Type system + compiler&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The closest thing Compact resembles isn't TypeScript. It's a constraint satisfaction system with TypeScript syntax. The syntax is a DX choice, not a design philosophy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The One Thing to Take Away
&lt;/h2&gt;

&lt;p&gt;Every concept in Compact, witnesses, circuits, &lt;code&gt;disclose()&lt;/code&gt;, the boundedness rules, becomes clear once you have the right frame:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You're not writing programs. You're describing valid states and proving you were in them.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With that frame:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Witnesses make sense: they're the private inputs the prover uses to generate the proof.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;disclose()&lt;/code&gt; makes sense: it's the intentional act of moving a constraint into public view.&lt;/li&gt;
&lt;li&gt;Bounded loops make sense: the circuit is fixed at compile time, so everything has to be fixed at compile time.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;assert&lt;/code&gt; makes sense: it's not validation, it's constraint declaration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without that frame, everything in Compact feels like TypeScript with arbitrary restrictions. With it, everything is exactly as it should be.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built for This
&lt;/h2&gt;

&lt;p&gt;Most documentation explains the &lt;em&gt;what&lt;/em&gt;. Compact's own docs are good. But the gap isn't information, it's the conceptual layer underneath.&lt;/p&gt;

&lt;p&gt;I've been building a structured guide that goes through each concept with the execution model shift front and center: &lt;strong&gt;&lt;a href="https://github.com/tusharpamnani/Compact-Book" rel="noopener noreferrer"&gt;Compact Book&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're trying to actually understand Compact rather than just cargo-cult your way through examples, start there.&lt;/p&gt;

&lt;p&gt;The syntax is the easy part. The mental model is the work. Get that right, and the rest follows.&lt;/p&gt;

</description>
      <category>midnight</category>
      <category>compact</category>
      <category>web3</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>Working with Maps and Merkle Trees in Compact:</title>
      <dc:creator>Nasihudeen Jimoh</dc:creator>
      <pubDate>Thu, 16 Apr 2026 08:19:24 +0000</pubDate>
      <link>https://forem.com/midnight-aliit/working-with-maps-and-merkle-trees-in-compact-40i3</link>
      <guid>https://forem.com/midnight-aliit/working-with-maps-and-merkle-trees-in-compact-40i3</guid>
      <description>&lt;h2&gt;
  
  
  A Guide to the State Dichotomy
&lt;/h2&gt;

&lt;p&gt;The Midnight blockchain introduces a fundamental architectural shift in smart contract design through its "State Dichotomy." Unlike transparency-first blockchains, Midnight enables developers to partition data into public Ledger State and private Shielded State. This guide provides a comprehensive analysis of the two primary data structures used to manage these states: &lt;strong&gt;Maps&lt;/strong&gt; and &lt;strong&gt;Merkle Trees&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Through a dual-implementation approach featuring a public registry and an anonymous allowlist this guide explores the operational mechanics, security considerations, and SDK integration patterns required to build applications with Compact.&lt;/p&gt;




&lt;h2&gt;
  
  
  The State Dichotomy: Conceptual Framework
&lt;/h2&gt;

&lt;p&gt;Compact contracts operates as a decentralized state machine where transitions are governed by Zero-Knowledge (ZK) circuits. The efficiency and privacy of these transitions depend on the selection of appropriate storage structures. This bifurcation of state is not merely an optimization but a core privacy Primitive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ledger state (Public)
&lt;/h3&gt;

&lt;p&gt;The Ledger state consists of on chain data structures that are globally visible and replicated across the network nodes. Ledger state is governed by Abstract Data Types (ADTs) such as Maps, Counters, and Sets. These structures provide the shared source of truth required for applications like token supply management, administrative registries, and public consensus variables. In Compact, every ledger interaction is verifiable by any observer, ensuring that the global state remains consistent and auditable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shielded state (Private)
&lt;/h3&gt;

&lt;p&gt;Shielded state remains off chain, residing within the prover’s local environment. Users interact with the ledger by submitting Zero Knowledge (ZK) proofs that verify a state transition has occurred according to the contract's logic without revealing the underlying data. This enables features like confidential assets, anonymous membership verification, and private governance. The shielded state is protected by the prover's secret keys and is only revealed through explicit "disclosures" within a circuit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choosing the correct structure
&lt;/h3&gt;

&lt;p&gt;The choice between a Map and a Merkle Tree is determined by the required visibility of the relationship between a user and their data. Developers must ask: "Does the network need to know &lt;em&gt;who&lt;/em&gt; owns this data, or only &lt;em&gt;that&lt;/em&gt; they own it?"&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Maps&lt;/strong&gt; are used when the association between a key and a value must be public and directly queryable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merkle Trees&lt;/strong&gt; are used when the goal is to prove membership within a set or the integrity of a dataset without revealing the identity of the specific member.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Associative storage with Maps
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;Map&lt;/code&gt; ADT is the primary tool for associative storage on the Midnight ledger. It allows for O(1) lookups and insertions, functioning as a decentralized dictionary. Maps are foundational for any application where identities need to be linked to properties or permissions in a transparent way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Syntax and declaration
&lt;/h3&gt;

&lt;p&gt;In Compact, a Map is declared within a &lt;code&gt;ledger&lt;/code&gt; block. The following definition maps a 32-byte public key to a 32-byte profile hash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pragma language_version &amp;gt;= 0.16 &amp;amp;&amp;amp; &amp;lt;= 0.21;

import CompactStandardLibrary;

export ledger registry: Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Bytes&amp;lt;32&amp;gt;&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Map operators and the disclosure rule
&lt;/h3&gt;

&lt;p&gt;Every interaction with a Map must navigate the boundary between private circuit parameters and public ledger state.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. The disclosure requirement: Bridging Private and Public
&lt;/h4&gt;

&lt;p&gt;Circuit parameters are private by default. In the Compact execution model, parameters are "witnesses" known only to the prover. To store a parameter in a public Map, it must be explicitly disclosed using the &lt;code&gt;disclose()&lt;/code&gt; operator. Failure to do so results in a compilation error, as Compact prevents the accidental leakage of private witnesses into public storage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit register(profile_hash: Bytes&amp;lt;32&amp;gt;): [] {
    const user = ownPublicKey();
    const d_profile_hash = disclose(profile_hash);
    registry.insert(user.bytes, d_profile_hash);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that the user is aware of exactly what information is being pushed to the ledger. If you were to attempt &lt;code&gt;registry.insert(user.bytes, profile_hash)&lt;/code&gt;, the compiler would signal a security violation, maintaining a strict "Privacy by Default" posture.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Detailed Map Operations
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;insert(key, value)&lt;/code&gt;&lt;/strong&gt;: Creates or updates an entry. If the key already exists, the old value is overwritten. This operation generates a ledger state update that is broadcast to the network.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;lookup(key)&lt;/code&gt;&lt;/strong&gt;: Retrieves the value associated with a key. If the key is absent, it returns the type-specific default (e.g., zero bytes for &lt;code&gt;Bytes&amp;lt;32&amp;gt;&lt;/code&gt;, false for &lt;code&gt;Boolean&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;member(key)&lt;/code&gt;&lt;/strong&gt;: A Boolean operator that checks for key existence without retrieving the value. This is highly efficient for access control checks where the value itself is irrelevant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;remove(key)&lt;/code&gt;&lt;/strong&gt;: Deletes an entry from the ledger, clearing the associated storage. This is crucial for managing "state bloat" and ensuring that outdated memberships are purged.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Zero Knowledge membership with Merkle Trees
&lt;/h2&gt;

&lt;p&gt;Merkle Trees are the engine of anonymity on Midnight. By representing a large set of data with a single 32-byte root hash, they allow for membership verification that preserves the privacy of the specific leaf.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technical specification and Depth Selection
&lt;/h3&gt;

&lt;p&gt;Merkle Trees in Compact are fixed-depth. The choice of depth determines the maximum capacity of the tree ($2^{depth}$).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export ledger allowlist: MerkleTree&amp;lt;20, Bytes&amp;lt;32&amp;gt;&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A depth of 20 supports approximately 1,048,576 entries. Choosing the correct depth is an engineering trade-off:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Higher Depth&lt;/strong&gt;: Increases the capacity of the system but linearly increases the size of the ZK circuit and the time required for proof generation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lower Depth&lt;/strong&gt;: Reduces proving time but risks hitting a "Full State" where no new members can be added without a contract migration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Operational lifecycle: The path to proof
&lt;/h3&gt;

&lt;p&gt;The verification of membership involves a transition from the ledger's public root to the prover’s private Merkle path.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. The Merkle path witness
&lt;/h4&gt;

&lt;p&gt;To prove membership, a user must provide a &lt;strong&gt;Merkle Path&lt;/strong&gt; a sequence of sibling hashes representing a branch from the leaf to the root. This is defined as a &lt;code&gt;witness&lt;/code&gt; function, identifying it as data retrieved from the prover's local environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;witness get_membership_path(leaf: Bytes&amp;lt;32&amp;gt;): MerkleTreePath&amp;lt;20, Bytes&amp;lt;32&amp;gt;&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the SDK, this path is calculated by iterating over the local view of the tree and finding the siblings for the target leaf at each level of the binary structure.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Circuit-level verification logic
&lt;/h4&gt;

&lt;p&gt;Inside the ZK-circuit, the &lt;code&gt;merkleTreePathRoot&lt;/code&gt; operator reconstructs the root hash from the provided path and leaf. The circuit verifies that the hashes align at each level ($H(L, R)$).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit access_exclusive_area(leaf: Bytes&amp;lt;32&amp;gt;): [] {
    const d_leaf = disclose(leaf);
    const path = get_membership_path(d_leaf);

    // ZK Re-computation of the root
    const computed_root = merkleTreePathRoot&amp;lt;20, Bytes&amp;lt;32&amp;gt;&amp;gt;(path);

    // Verification against Ledger State
    assert(allowlist.checkRoot(disclose(computed_root)), "Access Denied");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The "Double Disclosure" security pattern
&lt;/h3&gt;

&lt;p&gt;The pattern above utilizes two disclosures that are essential for the Midnight security model:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Disclosing the Leaf&lt;/strong&gt;: Ensures the proof corresponds to the exact data provided by the user. If the leaf were not disclosed, a malicious prover could use a different leaf than the one they claim to possess.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Disclosing the Root&lt;/strong&gt;: The &lt;code&gt;checkRoot&lt;/code&gt; operator must compare the &lt;code&gt;computed_root&lt;/code&gt; against the public ledger. For this comparison to occur, the value being checked must be public. Because the root hash is already public, this disclosure does not reveal any private information about the leaf or path used. It simply confirms: "I know a secret that hashes to this public root."&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Technical comparison: Maps vs. Merkle Trees
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Map (Ledger ADT)&lt;/th&gt;
&lt;th&gt;Merkle Tree (Ledger ADT)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Visibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fully Public&lt;/td&gt;
&lt;td&gt;Root Public; Leaves/Paths Private&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Access Pattern&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Key-Based (Direct)&lt;/td&gt;
&lt;td&gt;Path-Based (Indirect)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Privacy Model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Transparent Association&lt;/td&gt;
&lt;td&gt;Set Membership Anonymity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Proof Complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low (O(1))&lt;/td&gt;
&lt;td&gt;High (O(depth) hashes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary Use&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Registries, Public Indices&lt;/td&gt;
&lt;td&gt;Anonymous Allowlists, Confidential Voting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;State Bloat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Linear per entry&lt;/td&gt;
&lt;td&gt;Fixed per depth ($O(1)$ on-chain root)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Computational overhead analyze
&lt;/h3&gt;

&lt;p&gt;Merkle Trees impose a higher verification cost. A tree of depth 20 requires 20 sequential hash computations within the ZK-circuit. Each hash operation increases the number of constraints in the proof, which correlates directly to proof generation time. While this provides anonymity, developers must consider that a user on a mobile device may take significantly longer to generate a proof for a depth 32 tree compared to a depth-16 tree. In contrast, Map operations are computationally negligible within a circuit.&lt;/p&gt;




&lt;h2&gt;
  
  
  SDK implementation: The structural validation
&lt;/h2&gt;

&lt;p&gt;When integrating Compact contracts with the Midnight TypeScript SDK, developers must align the orchestrator's output with the runtime's expected data shapes. A common failure point is the &lt;strong&gt;Structural Validation&lt;/strong&gt; of the Merkle path.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;instanceof&lt;/code&gt; requirement and Type Safety
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;@midnight-ntwrk/compact-runtime&lt;/code&gt; performs strict type checking on objects returned by witness functions. Manually constructing a Merkle path object as a JSON literal will fail even if the fields (&lt;code&gt;value&lt;/code&gt;, &lt;code&gt;alignment&lt;/code&gt;) match. This is because the runtime's ZK IR (Intermediate Representation) requires an instance of the specific internal &lt;code&gt;MerkleTreePath&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;Instead, developers must use the &lt;code&gt;findPathForLeaf&lt;/code&gt; method provided by the ledger context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/prover/allowlist_witnesses.ts&lt;/span&gt;
&lt;span class="nf"&gt;get_membership_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;leaf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// This returns an instance of the MerkleTreePath class&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findPathForLeaf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;leaf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Leaf discovery failed: State mismatch&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;path&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 ensuring that the path is derived from the current ledger state and satisfies the runtime's internal &lt;code&gt;instanceof&lt;/code&gt; checks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Detailed Implementation Walkthrough
&lt;/h2&gt;

&lt;p&gt;The following implementation show how both structures are managed in a single application lifecycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Registry contract: &lt;code&gt;contract/registry.compact&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pragma language_version &amp;gt;= 0.16 &amp;amp;&amp;amp; &amp;lt;= 0.21;

import CompactStandardLibrary;

// Map Bytes&amp;lt;32&amp;gt; address to Bytes&amp;lt;32&amp;gt; profile CID
export ledger registry: Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Bytes&amp;lt;32&amp;gt;&amp;gt;;

export circuit register(profile_hash: Bytes&amp;lt;32&amp;gt;): [] {
    const user = ownPublicKey();
    // Public disclosure for ledger storage
    const d_profile_hash = disclose(profile_hash);
    registry.insert(user.bytes, d_profile_hash);
}

export circuit remove_registration(): [] {
    const user = ownPublicKey();
    registry.remove(user.bytes);
}

export circuit get_profile(user_pk: Bytes&amp;lt;32&amp;gt;): Bytes&amp;lt;32&amp;gt; {
    // Disclosure required for circuit parameters used in lookups
    return registry.lookup(disclose(user_pk));
}

export circuit is_registered(user_pk: Bytes&amp;lt;32&amp;gt;): Boolean {
    return registry.member(disclose(user_pk));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Allowlist contract: &lt;code&gt;contract/allowlist.compact&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pragma language_version &amp;gt;= 0.16 &amp;amp;&amp;amp; &amp;lt;= 0.21;

import CompactStandardLibrary;

// Merkle Tree for anonymous membership
export ledger allowlist: MerkleTree&amp;lt;20, Bytes&amp;lt;32&amp;gt;&amp;gt;;

export circuit update_allowlist(member_hash: Bytes&amp;lt;32&amp;gt;): [] {
    const d_member = disclose(member_hash);
    allowlist.insert(d_member);
}

export circuit access_exclusive_area(leaf: Bytes&amp;lt;32&amp;gt;): [] {
    const d_leaf = disclose(leaf);
    const path = get_membership_path(d_leaf);

    // Hash up the tree to the root
    const computed_root = merkleTreePathRoot&amp;lt;20, Bytes&amp;lt;32&amp;gt;&amp;gt;(path);

    // Check if the computed root matches the current ledger state
    assert(allowlist.checkRoot(disclose(computed_root)), "Access Denied");
}

witness get_membership_path(leaf: Bytes&amp;lt;32&amp;gt;): MerkleTreePath&amp;lt;20, Bytes&amp;lt;32&amp;gt;&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  TypeScript orchestrator: &lt;code&gt;src/index.ts&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;createActionCircuitContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;dummyContractAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@midnight-ntwrk/compact-runtime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RegistryContract&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./managed/registry/contract/index.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AllowlistContract&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./managed/allowlist/contract/index.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AllowlistProver&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./prover/allowlist_witnesses.js&lt;/span&gt;&lt;span class="dl"&gt;"&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;main&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;alicePk&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;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profileHash&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;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 1. Map Interaction: Public Registry&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;registry&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;RegistryContract&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;regCtx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createActionCircuitContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;dummyContractAddress&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;alicePk&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialContractState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialPrivateState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Insert Alice into the registry Map&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;updatedRegCtx&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;circuits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;regCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;profileHash&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;Registry state updated via Map insertion.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Merkle Interaction: Anonymous Allowlist&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prover&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;AllowlistProver&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;allowlist&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;AllowlistContract&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// Resolve the witness using the prover logic&lt;/span&gt;
    &lt;span class="na"&gt;get_membership_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;leaf&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;prover&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_membership_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;leaf&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;allowCtx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createActionCircuitContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;dummyContractAddress&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;alicePk&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;allowlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialContractState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;allowlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialPrivateState&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;Updating on-chain Merkle root...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Populate the tree before proving&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;postAddCtx&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allowlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;circuits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_allowlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;allowCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;alicePk&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;Verifying anonymous membership proof...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Execute the anonymous access circuit&lt;/span&gt;
  &lt;span class="nx"&gt;allowlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;circuits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;access_exclusive_area&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postAddCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;alicePk&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;Access Granted: Membership verified anonymously.&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="nf"&gt;main&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  State Bounded Merkle Trees
&lt;/h2&gt;

&lt;p&gt;Forsystems processing high volumes, a static Merkle Tree can present challenges during concurrent updates. Production Midnight applications often utilize &lt;strong&gt;State Bounded transitions&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Synchronization window
&lt;/h3&gt;

&lt;p&gt;Because the Merkle root is global, every new addition invalidates the old root. If a user is half way through generating a 2 second proof and another transaction lands, the root will shift and the proof will fail.&lt;/p&gt;

&lt;p&gt;Midnight addresses this with &lt;strong&gt;Historic Windows&lt;/strong&gt;. A &lt;code&gt;HistoricMerkleTree&lt;/code&gt; allows a circuit to verify a proof against any root within the last $N$ blocks. This providing a "synchronization window" that makes DApps resilient to high traffic.&lt;/p&gt;

&lt;h3&gt;
  
  
  State Bounded Merkle Trees
&lt;/h3&gt;

&lt;p&gt;A State Bounded Merkle Tree allows for the separation of the state into bounded regions. This is particularly useful for optimizing storage, as old branches that are no longer being proven against can be pruned from active memory while maintaining the cryptographic root consistency.&lt;/p&gt;




&lt;h2&gt;
  
  
  Best practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Disclosure hygiene
&lt;/h3&gt;

&lt;p&gt;In Compact, the order of &lt;code&gt;disclose()&lt;/code&gt; calls can influence the circuit's logic flow. Developers should disclose values as late as possible ideally directly before the ledger operation that requires them. This maintain a clear boundary between the private computation (witnesses) and the public assertion (disclosures).&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling state lag in production
&lt;/h3&gt;

&lt;p&gt;In a live network environment, the ledger state might be several blocks ahead of your local prover’s view. When retrieving a Merkle path, ensure your client is synchronized with the specific block height that matches the Merkle root currently stored on the ledger. Outdated paths are the most common cause of &lt;code&gt;checkRoot&lt;/code&gt; assertion failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optimization: Caching witness results
&lt;/h3&gt;

&lt;p&gt;Merkle path lookups are computationally intensive for the local ledger view. If an application requires frequent verification (e.g., a private chat room), consider caching the &lt;code&gt;MerkleTreePath&lt;/code&gt; in the private state and only updating it when the ledger's Merkle root changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  API Reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operator&lt;/th&gt;
&lt;th&gt;Structure&lt;/th&gt;
&lt;th&gt;Return Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;insert(k, v)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Map&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Inserts or updates a value.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lookup(k)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Map&lt;/td&gt;
&lt;td&gt;&lt;code&gt;V&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retrieves value or default.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;member(k)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Map&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Boolean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Checks for key existence.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;remove(k)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Map&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deletes a key from the ledger.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;insert(leaf)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MerkleTree&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Appends a leaf to the tree.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;checkRoot(root)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MerkleTree&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Boolean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Verifies a root against state.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;merkleTreePathRoot(path)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Witness&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Bytes&amp;lt;32&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Computes root from path in ZK.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Maps and Merkle Trees are the fundamental storage structures that enable the state dichotomy of the Midnight blockchain. &lt;strong&gt;Maps&lt;/strong&gt; provide the efficiency and directness required for public association, while &lt;strong&gt;Merkle Trees&lt;/strong&gt; facilitate the zero-knowledge membership proofs that define privacy preserving interaction.&lt;/p&gt;

&lt;p&gt;By mastering the transition between these two domains specifically the nuances of the &lt;code&gt;disclose()&lt;/code&gt; operator and the &lt;code&gt;MerkleTreePath&lt;/code&gt; witness resolver developers can architect complex, privacy centric applications that benefit from both transparency and confidentiality. For further exploration, consult the &lt;a href="https://docs.midnight.network" rel="noopener noreferrer"&gt;official Midnight Documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The full code example is here you can check it up*&lt;em&gt;GitHub Repository&lt;/em&gt;*: &lt;a href="https://github.com/Kanasjnr/compact-maps-merkle-tutorial" rel="noopener noreferrer"&gt;Kanasjnr/compact-maps-merkle-tutorial&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>blockchain</category>
      <category>privacy</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
