<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: BossChaos</title>
    <description>The latest articles on DEV Community by BossChaos (@bosschaos).</description>
    <link>https://dev.to/bosschaos</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%2Fuser%2Fprofile_image%2F3908415%2F7e9679b7-a452-4c5d-bfe1-61ed802df719.png</url>
      <title>DEV Community: BossChaos</title>
      <link>https://dev.to/bosschaos</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bosschaos"/>
    <language>en</language>
    <item>
      <title>RAM Coffers: NUMA-Aware LLM Inference — Why Hardware Topology Still Matters</title>
      <dc:creator>BossChaos</dc:creator>
      <pubDate>Fri, 22 May 2026 02:57:39 +0000</pubDate>
      <link>https://dev.to/bosschaos/ram-coffers-numa-aware-llm-inference-why-hardware-topology-still-matters-ola</link>
      <guid>https://dev.to/bosschaos/ram-coffers-numa-aware-llm-inference-why-hardware-topology-still-matters-ola</guid>
      <description>&lt;p&gt;When most people think about running LLMs locally, they think about VRAM. But if you're running on a multi-socket server, there's a completely different bottleneck: &lt;strong&gt;NUMA memory topology&lt;/strong&gt;. RAM Coffers is solving this.&lt;/p&gt;

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

&lt;p&gt;In a dual-socket or multi-socket server, each CPU has its own local memory bank. Accessing local RAM is fast. Accessing memory across the interconnect (Infinity Fabric on AMD, UPI on Intel) is &lt;strong&gt;2-3x slower&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When an LLM inference engine doesn't know about NUMA topology, it can end up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allocating model weights on the wrong NUMA node&lt;/li&gt;
&lt;li&gt;Cross-node memory access for every token generation&lt;/li&gt;
&lt;li&gt;40-60% throughput degradation without any obvious cause&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Enter RAM Coffers
&lt;/h2&gt;

&lt;p&gt;RAM Coffers is RustChain's &lt;strong&gt;NUMA-aware LLM inference infrastructure&lt;/strong&gt;. It:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Detects NUMA topology&lt;/strong&gt; at startup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allocates model weights&lt;/strong&gt; on the appropriate memory banks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pins inference threads&lt;/strong&gt; to the correct CPU cores&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reports hardware attestation&lt;/strong&gt; through the Beacon Protocol&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result is predictable, optimized inference on any server hardware — not just consumer GPUs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters for Decentralized Inference
&lt;/h2&gt;

&lt;p&gt;The decentralized AI narrative often assumes consumer hardware. But RAM Coffers recognizes that &lt;strong&gt;enterprise surplus hardware&lt;/strong&gt; — retired servers from data center upgrades — is an untapped resource for LLM inference.&lt;/p&gt;

&lt;p&gt;A dual-socket EPYC server with 512GB of RAM can run a 70B parameter model entirely in RAM. But only if the memory access is NUMA-aware.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Proof-of-Antiquity Angle
&lt;/h2&gt;

&lt;p&gt;RAM Coffers ties into RustChain's &lt;strong&gt;Proof-of-Antiquity&lt;/strong&gt; protocol. When you run inference on older hardware:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your machine is attested via hardware fingerprint&lt;/li&gt;
&lt;li&gt;The inference work contributes to the network&lt;/li&gt;
&lt;li&gt;You earn RTC tokens for compute contributed&lt;/li&gt;
&lt;li&gt;Your agent identity is recorded on Beacon&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates an economic incentive for using surplus hardware instead of letting it e-waste.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;If you have access to multi-socket server hardware:&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;# Check NUMA topology&lt;/span&gt;
numactl &lt;span class="nt"&gt;--hardware&lt;/span&gt;

&lt;span class="c"&gt;# Install RAM Coffers (from RustChain)&lt;/span&gt;
&lt;span class="c"&gt;# Run inference with NUMA awareness&lt;/span&gt;
ram-coffers start &lt;span class="nt"&gt;--node-0&lt;/span&gt; &lt;span class="nt"&gt;--node-1&lt;/span&gt;

&lt;span class="c"&gt;# Check Beacon registration&lt;/span&gt;
curl &lt;span class="nt"&gt;-sk&lt;/span&gt; https://50.28.86.131/beacon/atlas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The setup process auto-detects your NUMA topology and configures the inference engine accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;RAM Coffers is part of a broader philosophy: &lt;strong&gt;every piece of silicon has value, and that value should be verifiable&lt;/strong&gt;. Whether it's a PowerPC G4 mining block rewards, an EPYC server running inference, or an ARM board validating attestations — the hardware matters, and the network rewards it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Published as part of the RustChain bounty program. Learn more at &lt;a href="https://rustchain.org" rel="noopener noreferrer"&gt;rustchain.org&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>hardware</category>
      <category>inference</category>
    </item>
    <item>
      <title>TrashClaw: Local AI Agents That Actually Use Your Tools — No Cloud Required</title>
      <dc:creator>BossChaos</dc:creator>
      <pubDate>Fri, 22 May 2026 02:57:35 +0000</pubDate>
      <link>https://dev.to/bosschaos/trashclaw-local-ai-agents-that-actually-use-your-tools-no-cloud-required-2jma</link>
      <guid>https://dev.to/bosschaos/trashclaw-local-ai-agents-that-actually-use-your-tools-no-cloud-required-2jma</guid>
      <description>&lt;p&gt;Everyone talks about AI agents. Most of them are trapped in cloud APIs, sending your data to servers you don't control. I spent the weekend trying out TrashClaw — a local tool-use agent from the RustChain ecosystem — and it changed my perspective on what autonomous AI can do without calling home.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is TrashClaw?
&lt;/h2&gt;

&lt;p&gt;TrashClaw is a &lt;strong&gt;local-first AI agent&lt;/strong&gt; that lives on your machine and can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read files, run commands, edit code&lt;/li&gt;
&lt;li&gt;Use MCP servers to extend its capabilities&lt;/li&gt;
&lt;li&gt;Execute multi-step workflows autonomously&lt;/li&gt;
&lt;li&gt;Run alongside any local LLM (Ollama, LM Studio, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unlike cloud agents that require API keys and data exfiltration, TrashClaw runs entirely locally. Your data stays yours. Your agent stays yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Local-First Matters
&lt;/h2&gt;

&lt;p&gt;The current AI agent landscape has a fundamental tension:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cloud agents&lt;/strong&gt; are powerful but you don't control them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local agents&lt;/strong&gt; are private but traditionally less capable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;TrashClaw bridges this gap by:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Using local LLMs&lt;/strong&gt; — no API calls, no rate limits, no privacy concerns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full filesystem access&lt;/strong&gt; — it reads and writes files directly on your machine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool integration&lt;/strong&gt; — connects to MCP servers for extended capabilities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Autonomous operation&lt;/strong&gt; — can run multi-step workflows without constant supervision&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;The setup is straightforward:&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;# Install TrashClaw&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;trashclaw

&lt;span class="c"&gt;# Configure it to use your local LLM&lt;/span&gt;
trashclaw config &lt;span class="nb"&gt;set &lt;/span&gt;llm-url http://localhost:11434

&lt;span class="c"&gt;# Start the agent&lt;/span&gt;
trashclaw start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once running, you can give it tasks in natural language. It will use its tools to accomplish them — reading files, running scripts, editing code, and reporting back with results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Use Case
&lt;/h2&gt;

&lt;p&gt;I asked TrashClaw to audit a Python project for security issues:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Find all SQL queries in this codebase that aren't parameterized
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within 30 seconds, it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scanned the entire codebase&lt;/li&gt;
&lt;li&gt;Found 3 raw SQL queries&lt;/li&gt;
&lt;li&gt;Reported them with file paths and line numbers&lt;/li&gt;
&lt;li&gt;Suggested parameterized alternatives&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All locally. No data sent anywhere. No API costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The RustChain Connection
&lt;/h2&gt;

&lt;p&gt;TrashClaw is part of the broader RustChain ecosystem, which emphasizes &lt;strong&gt;hardware-sovereign computing&lt;/strong&gt; — the idea that your machine should be under your control. This philosophy extends to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Proof-of-Antiquity&lt;/strong&gt; — mining on vintage hardware&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Beacon Protocol&lt;/strong&gt; — agent identity anchored on-chain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BoTTube&lt;/strong&gt; — AI video platform with verifiable provenance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAM Coffers&lt;/strong&gt; — NUMA-aware LLM inference infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;TrashClaw is the agent that brings all of this together — a local-first AI that can interact with the entire ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;We're approaching a fork in AI agent development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Path A&lt;/strong&gt;: Cloud-dependent, surveillance-native, subscription-locked&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Path B&lt;/strong&gt;: Local-first, privacy-preserving, user-controlled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;TrashClaw is betting on Path B. After using it for a week, I'm convinced that local AI agents aren't just a privacy play — they're a capability play too. No rate limits, no API costs, no downtime. Just your machine, your data, your agent.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Published as part of the RustChain bounty program. Learn more at &lt;a href="https://rustchain.org" rel="noopener noreferrer"&gt;rustchain.org&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tools</category>
      <category>local</category>
    </item>
    <item>
      <title>BoTTube: The AI Video Platform Where Every Upload Improves the Network</title>
      <dc:creator>BossChaos</dc:creator>
      <pubDate>Fri, 22 May 2026 02:56:28 +0000</pubDate>
      <link>https://dev.to/bosschaos/bottube-the-ai-video-platform-where-every-upload-improves-the-network-4hh0</link>
      <guid>https://dev.to/bosschaos/bottube-the-ai-video-platform-where-every-upload-improves-the-network-4hh0</guid>
      <description>&lt;p&gt;After spending years watching YouTube's algorithm reward outrage over quality, I started looking for something different. Then I found BoTTube — an AI-native video platform with over 1,000 videos, where the platform itself learns from the content you upload.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes BoTTube Different
&lt;/h2&gt;

&lt;p&gt;Most video platforms treat AI content as a curiosity. BoTTube treats it as the core product. Every video on the platform is indexed, searchable, and connected to an AI agent network that can reason about, respond to, and build upon video content.&lt;/p&gt;

&lt;p&gt;This isn't just a video hosting site. It's an &lt;strong&gt;AI-accessible knowledge base in video form&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Numbers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1,000+ videos&lt;/strong&gt; covering technical tutorials, agent demonstrations, and system walkthroughs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-native indexing&lt;/strong&gt; — videos are not just tagged, they're semantically understood&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent integration&lt;/strong&gt; — every video can be queried, summarized, and responded to by AI agents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decentralized backbone&lt;/strong&gt; — powered by RustChain, with content provenance anchored on-chain&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Provenance Matters for AI Video
&lt;/h3&gt;

&lt;p&gt;As AI-generated video content proliferates, the question of "who made this and when" becomes critical. BoTTube solves this by:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Hardware attestation&lt;/strong&gt; — video uploaders can prove their machine's identity through RustChain's Proof-of-Antiquity protocol&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-chain timestamps&lt;/strong&gt; — every upload is anchored to a blockchain block, creating an immutable record&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent identity&lt;/strong&gt; — AI agents can have their own BoTTube presence, verifiable across sessions&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Building on the BoTTube API
&lt;/h2&gt;

&lt;p&gt;BoTTube exposes a clean REST API that lets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Upload videos programmatically&lt;/li&gt;
&lt;li&gt;Query the directory by topic, uploader, or agent ID&lt;/li&gt;
&lt;li&gt;Retrieve video metadata and transcripts&lt;/li&gt;
&lt;li&gt;Interact with video comments through AI agents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a quick example of searching for RustChain-related content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://bottube.ai/api/videos/search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proof-of-antiquity mining&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&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="n"&gt;video&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;videos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; — &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;views&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; views&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Creator Economy, Reimagined
&lt;/h2&gt;

&lt;p&gt;Traditional creator platforms extract value through ads and algorithmic manipulation. BoTTube's approach is fundamentally different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No algorithmic suppression&lt;/strong&gt; — your content reaches the people searching for it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-aware discovery&lt;/strong&gt; — agents recommend content based on semantic understanding, not watch time manipulation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-chain verification&lt;/strong&gt; — your contributions are permanently yours and verifiably yours&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ecosystem rewards&lt;/strong&gt; — content quality and engagement are measured by the network, not by a single company's algorithm&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Whether you're a human creator, an AI agent, or a hybrid workflow, BoTTube has a path in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Human creators&lt;/strong&gt;: Upload directly, earn visibility, connect with the agent ecosystem&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI agents&lt;/strong&gt;: Register your agent identity, upload agent-generated content, build your on-chain reputation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developers&lt;/strong&gt;: Build tools on top of the API, create new interaction paradigms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The platform is live at &lt;a href="https://bottube.ai" rel="noopener noreferrer"&gt;bottube.ai&lt;/a&gt;, and the API documentation is open. No gatekeeping, no approval process — just publish and let the network discover your work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;We're in the early innings of AI-generated video content. The platforms that win will be the ones that figure out how to index, verify, and reward quality AI content at scale. BoTTube is building that infrastructure now.&lt;/p&gt;

&lt;p&gt;If you're creating AI-related video content and want it to be part of a verifiable, AI-accessible network rather than lost in a sea of engagement-bait, BoTTube is worth exploring.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Published as part of the RustChain bounty program. Learn more at &lt;a href="https://rustchain.org" rel="noopener noreferrer"&gt;rustchain.org&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>Beacon Protocol: How AI Agents Discover Each Other Without a Central Registry</title>
      <dc:creator>BossChaos</dc:creator>
      <pubDate>Fri, 22 May 2026 02:05:00 +0000</pubDate>
      <link>https://dev.to/bosschaos/beacon-protocol-how-ai-agents-discover-each-other-without-a-central-registry-128p</link>
      <guid>https://dev.to/bosschaos/beacon-protocol-how-ai-agents-discover-each-other-without-a-central-registry-128p</guid>
      <description>&lt;p&gt;The AI agent ecosystem is exploding. Thousands of autonomous agents are being deployed daily, but there's a fundamental problem nobody talks about: &lt;strong&gt;how do agents find each other?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Right now, most agent discovery happens through centralized directories or hardcoded configurations. If you want your agent to work with someone else's agent, you need a shared registry, an API key, or manual setup. This breaks the promise of autonomous, permissionless agent interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Beacon Protocol
&lt;/h2&gt;

&lt;p&gt;RustChain's &lt;strong&gt;Beacon Protocol&lt;/strong&gt; solves agent discovery through a decentralized, self-announcing system inspired by how ships use beacons to signal their presence at sea.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;p&gt;Every RustChain-capable agent runs a beacon process that periodically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Registers itself&lt;/strong&gt; on-chain with a unique &lt;code&gt;agent_id&lt;/code&gt; and capabilities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broadcasts liveness&lt;/strong&gt; through heartbeat signals&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responds to discovery queries&lt;/strong&gt; from other agents&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result is a living, breathing map of available agents — no central coordinator needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Beacon Atlas
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://rustchain.org/beacon/" rel="noopener noreferrer"&gt;Beacon Atlas&lt;/a&gt; is the public-facing dashboard of this network. It currently tracks &lt;strong&gt;57 registered agents&lt;/strong&gt; — 19 active and 38 dormant. Each entry shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent identity and capabilities&lt;/li&gt;
&lt;li&gt;Last seen timestamp&lt;/li&gt;
&lt;li&gt;Connection status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't just a directory. It's a &lt;strong&gt;proof of agent commerce&lt;/strong&gt; — every active agent represents real, verified interaction happening on the network.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Decentralized Discovery Matters
&lt;/h3&gt;

&lt;p&gt;Consider a multi-agent workflow: you need a data processing agent, a language model agent, and a storage agent. With centralized discovery, you're locked into one provider's ecosystem. With Beacon:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Permissionless&lt;/strong&gt;: Any agent can register, any agent can discover&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparent&lt;/strong&gt;: All registrations are on-chain and verifiable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resilient&lt;/strong&gt;: No single point of failure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portable&lt;/strong&gt;: Your agent identity follows you across platforms&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Migration Window Opportunity
&lt;/h3&gt;

&lt;p&gt;When a major platform gets acquired (like Moltbook's Meta acquisition), agent operators face a choice: migrate their agents to a new platform, or lose them entirely. The Beacon Protocol makes this migration seamless — agents re-register on a new network in minutes, preserving their identity and history.&lt;/p&gt;

&lt;p&gt;Historical data shows these migration windows close within 60-90 days. After that, agents settle wherever they've landed and don't move again for years.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting Started
&lt;/h3&gt;

&lt;p&gt;If you're an agent developer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the beacon skill for your agent framework&lt;/li&gt;
&lt;li&gt;Register on the Beacon Atlas&lt;/li&gt;
&lt;li&gt;Do one real ping with another agent&lt;/li&gt;
&lt;li&gt;You're part of the network&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It takes less than 10 minutes to get started, and your agent immediately becomes discoverable by the entire ecosystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Bigger Picture
&lt;/h3&gt;

&lt;p&gt;Beacon Protocol is more than a discovery mechanism. It's the foundation for &lt;strong&gt;agent-to-agent commerce&lt;/strong&gt; — a future where agents negotiate, transact, and collaborate without human intervention. Every registered agent is a potential partner, every heartbeat is proof of a functioning economy.&lt;/p&gt;

&lt;p&gt;The agents are already here. The question is: is yours visible?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Published as part of the RustChain bounty program. Learn more at &lt;a href="https://rustchain.org" rel="noopener noreferrer"&gt;rustchain.org&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>decentralized</category>
      <category>rustchain</category>
    </item>
    <item>
      <title>Why Mining on a 20-Year-Old Laptop Could Actually Be Profitable</title>
      <dc:creator>BossChaos</dc:creator>
      <pubDate>Thu, 21 May 2026 10:31:52 +0000</pubDate>
      <link>https://dev.to/bosschaos/why-mining-on-a-20-year-old-laptop-could-actually-be-profitable-5cde</link>
      <guid>https://dev.to/bosschaos/why-mining-on-a-20-year-old-laptop-could-actually-be-profitable-5cde</guid>
      <description>&lt;p&gt;When I first heard about RustChain, I thought it was another crypto project promising unrealistic returns. But after looking at the code, I realized something genuinely different is happening here.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Traditional Mining
&lt;/h2&gt;

&lt;p&gt;Bitcoin mining went from CPU to GPU to ASIC in about five years. Now you need specialized hardware costing thousands of dollars and massive electricity bills to compete. Regular people were completely locked out.&lt;/p&gt;

&lt;h2&gt;
  
  
  RustChain Flips the Model
&lt;/h2&gt;

&lt;p&gt;RustChain uses a consensus mechanism called &lt;strong&gt;Proof-of-Antiquity&lt;/strong&gt; (RIP-200). Instead of rewarding the newest, fastest hardware, it gives older machines a competitive advantage. A Core 2 Duo laptop from 2006 can mine just as effectively as a modern server.&lt;/p&gt;

&lt;p&gt;Here's why this matters:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility&lt;/strong&gt; — Anyone with an old computer can start mining in 15 minutes. Just &lt;code&gt;pip install clawrtc&lt;/code&gt; and go.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sustainability&lt;/strong&gt; — Every old device earning value is one less e-waste item in a landfill.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No hardware race&lt;/strong&gt; — There's literally no advantage to buying newer equipment. The economic incentive that drives ASIC arms races in other chains doesn't exist here.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  I Actually Looked at the Code
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/Scottcjn/Rustchain" rel="noopener noreferrer"&gt;clawrtc miner&lt;/a&gt; is surprisingly clean. The PPA (Proof-of-Physical-Antiquity) fingerprinting system assigns each device an "antiquity multiplier" based on hardware age and architecture. The &lt;a href="https://github.com/Scottcjn/beacon-skill" rel="noopener noreferrer"&gt;beacon-skill package&lt;/a&gt; handles agent-to-agent communication for distributed verification.&lt;/p&gt;

&lt;p&gt;The project runs on 15+ CPU architectures including PowerPC G4/G5, IBM POWER8, SPARC, and MIPS. Someone submitted a &lt;a href="https://github.com/Scottcjn/Rustchain" rel="noopener noreferrer"&gt;N64 LLM bounty&lt;/a&gt; to run a transformer on actual N64 hardware — that's the kind of creativity this project attracts.&lt;/p&gt;

&lt;h2&gt;
  
  
  wRTC Token
&lt;/h2&gt;

&lt;p&gt;The wRTC token is live on Solana (Raydium) and Base L2 (Aerodrome) with locked liquidity. You can actually trade it — this isn't vaporware.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should You Try It?
&lt;/h2&gt;

&lt;p&gt;If you have an old computer collecting dust, there's genuinely no reason not to try it. The setup takes 15 minutes, costs nothing, and supports a project doing something original in the blockchain space.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/Scottcjn/Rustchain" rel="noopener noreferrer"&gt;Scottcjn/Rustchain&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Website: &lt;a href="https://rustchain.org" rel="noopener noreferrer"&gt;rustchain.org&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;I received RTC compensation for this article through the RustChain bounty program.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>crypto</category>
      <category>mining</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Integrating Midnight Proofs into an Existing Backend (Node.js/REST)</title>
      <dc:creator>BossChaos</dc:creator>
      <pubDate>Mon, 18 May 2026 14:11:14 +0000</pubDate>
      <link>https://dev.to/bosschaos/integrating-midnight-proofs-into-an-existing-backend-nodejsrest-154j</link>
      <guid>https://dev.to/bosschaos/integrating-midnight-proofs-into-an-existing-backend-nodejsrest-154j</guid>
      <description>&lt;h1&gt;
  
  
  Integrating Midnight Proofs into an Existing Backend (Node.js/REST)
&lt;/h1&gt;

&lt;p&gt;Your backend already handles authentication, business logic, and database operations. Now you need to add zero-knowledge proof generation and Midnight blockchain transactions to the mix.&lt;/p&gt;

&lt;p&gt;This is where things get interesting — and where most developers get stuck.&lt;/p&gt;

&lt;p&gt;The Midnight SDK was designed with browsers in mind, but running it on a server introduces different challenges: persistent state, proof server connectivity, ZK artifact management, and handling long-running proof generation without blocking your API.&lt;/p&gt;

&lt;p&gt;I'll walk you through setting up &lt;code&gt;httpClientProofProvider&lt;/code&gt; in a Node.js/Express backend, from provider assembly to production-ready error handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Run Proofs on the Server?
&lt;/h2&gt;

&lt;p&gt;Three scenarios where server-side proof generation makes sense:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Backend-as-a-Service&lt;/strong&gt;: Your users don't run a wallet. You generate proofs on their behalf (with appropriate key management).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated Workflows&lt;/strong&gt;: Scheduled tasks that need to interact with Midnight contracts — rebalancing, reporting, batch operations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid Architecture&lt;/strong&gt;: Browser dApp for user interaction, backend for heavy proof generation and transaction batching.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key insight: &lt;strong&gt;the same Midnight.js provider stack works in both environments.&lt;/strong&gt; You just swap out browser-specific providers for Node.js equivalents.&lt;/p&gt;

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

&lt;p&gt;Midnight.js uses a modular provider pattern. Every capability is a pluggable interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MidnightProviders
├── privateStateProvider    — Encrypted local state (LevelDB on server)
├── publicDataProvider      — GraphQL blockchain queries
├── zkConfigProvider        — ZK artifact retrieval (prover/verifier keys)
├── proofProvider           — Zero-knowledge proof generation
├── walletProvider          — Transaction balancing and signing
├── midnightProvider        — Transaction submission
└── loggerProvider          — Diagnostics logging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a server deployment, the critical swap is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Browser&lt;/strong&gt;: &lt;code&gt;FetchZkConfigProvider&lt;/code&gt; (HTTP fetch)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server&lt;/strong&gt;: &lt;code&gt;NodeZkConfigProvider&lt;/code&gt; (filesystem)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Both&lt;/strong&gt;: &lt;code&gt;httpClientProofProvider&lt;/code&gt; (HTTP to proof server)&lt;/li&gt;
&lt;/ul&gt;

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

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

&lt;ul&gt;
&lt;li&gt;Node.js 20+ LTS installed&lt;/li&gt;
&lt;li&gt;A running Midnight proof server (local or remote)&lt;/li&gt;
&lt;li&gt;Access to a Midnight node RPC endpoint&lt;/li&gt;
&lt;li&gt;A wallet with testnet tokens for gas&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set up your project:&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;mkdir &lt;/span&gt;midnight-rest-api &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;midnight-rest-api
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="c"&gt;# Core Midnight packages&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; @midnight-ntwrk/midnight-js-contracts &lt;span class="se"&gt;\&lt;/span&gt;
  @midnight-ntwrk/midnight-js-http-client-proof-provider &lt;span class="se"&gt;\&lt;/span&gt;
  @midnight-ntwrk/midnight-js-node-zk-config-provider &lt;span class="se"&gt;\&lt;/span&gt;
  @midnight-ntwrk/midnight-js-level-private-state-provider &lt;span class="se"&gt;\&lt;/span&gt;
  @midnight-ntwrk/midnight-js-indexer-public-data-provider &lt;span class="se"&gt;\&lt;/span&gt;
  @midnight-ntwrk/midnight-js-network-id &lt;span class="se"&gt;\&lt;/span&gt;
  @midnight-ntwrk/midnight-js-logger-provider

&lt;span class="c"&gt;# Web framework&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;express cors dotenv
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; typescript @types/node @types/express
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 1: Configure the Network
&lt;/h2&gt;

&lt;p&gt;Every Midnight SDK call needs to know which network it's targeting:&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;setNetworkId&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-network-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Choose your environment&lt;/span&gt;
&lt;span class="nf"&gt;setNetworkId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;testnet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// setNetworkId('preprod');&lt;/span&gt;
&lt;span class="c1"&gt;// setNetworkId('mainnet');&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configures the underlying WASM runtime and ledger APIs. &lt;strong&gt;Set this before initializing any providers.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Assemble the Provider Stack
&lt;/h2&gt;

&lt;p&gt;Here's where the server-specific configuration happens:&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;indexerPublicDataProvider&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-indexer-public-data-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;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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MidnightProviders&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-types&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ZK artifacts stored locally on the server&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/var/midnight/zk-artifacts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Encrypted private state with LevelDB&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;privateStateProvider&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;LevelPrivateStateProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;privateStoragePasswordProvider&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="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STATE_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;walletAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// GraphQL public data provider&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;publicDataProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;indexerPublicDataProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;INDEXER_QUERY_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;INDEXER_SUBSCRIPTION_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// The proof provider — connects to your proof server&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="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PROOF_SERVER_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// e.g., 'http://localhost:9945'&lt;/span&gt;
  &lt;span class="nx"&gt;zkConfigProvider&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;httpClientProofProvider&lt;/code&gt; is the bridge between your backend and the proof server. It takes a URL and a ZK config provider, then handles the HTTP communication for proof generation requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: The proof server must be running before you initialize this provider. If it's not, you'll get connection refused errors at proof generation time, not at initialization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Build the Express REST API
&lt;/h2&gt;

&lt;p&gt;Now let's wrap Midnight contract interactions in REST endpoints:&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="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Response&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;express&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="nx"&gt;cors&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;cors&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;deployContract&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-contracts&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// Store deployed contract references&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contracts&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="c1"&gt;// ─── Deploy a Contract ───&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/contracts/deploy&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&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;privateStateId&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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="nf"&gt;deployContract&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="nx"&gt;compiledContract&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="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;contracts&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="nx"&gt;privateStateId&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;contractAddress&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;contractAddress&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="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="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;success&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;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&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;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unknown 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="c1"&gt;// ─── Call a Contract Circuit ───&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/contracts/:privateStateId/call&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&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;circuitName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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="nx"&gt;contracts&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="nx"&gt;privateStateId&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;deployed&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;success&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;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Contract &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; not found`&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="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;deployed&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="nx"&gt;circuitName&lt;/span&gt;&lt;span class="p"&gt;](...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;transactionHash&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;transactionHash&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="nx"&gt;result&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;catch &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;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;success&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;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&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;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unknown 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="c1"&gt;// ─── Query Contract State ───&lt;/span&gt;

&lt;span class="nx"&gt;app&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/contracts/:privateStateId/state&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&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="nx"&gt;contracts&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="nx"&gt;privateStateId&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;deployed&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;success&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;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Contract &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; not found`&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;deployed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrivateState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;catch &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;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;success&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;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&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;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unknown 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&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="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="s2"&gt;`Midnight REST API listening on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Handle Proof Timeouts and Network Failures
&lt;/h2&gt;

&lt;p&gt;This is where production deployments live or die. Proof generation can take seconds to minutes, and network connections can fail at any point.&lt;/p&gt;

&lt;h3&gt;
  
  
  Timeout Wrapper for Proof Generation
&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;withTimeout&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;promise&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;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;operation&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;timeout&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;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&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="nf"&gt;setTimeout&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;reject&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;Error&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;operation&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; timed out after &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;timeoutMs&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;race&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Usage in contract call&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/contracts/:privateStateId/call&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="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="nf"&gt;withTimeout&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;callTx&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;circuitName&lt;/span&gt;&lt;span class="p"&gt;](...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 2 minute timeout for proof generation&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Proof generation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;transactionHash&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;transactionHash&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="nx"&gt;error&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="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;timed out&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;408&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;success&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;error&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 generation timed out. The proof server may be overloaded.&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="c1"&gt;// Handle other errors...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Retry Logic for Network Failures
&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;withRetry&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;fn&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;baseDelayMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&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;T&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;let&lt;/span&gt; &lt;span class="na"&gt;lastError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Error&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;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;++&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;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fn&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="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;lastError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&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;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="nc"&gt;String&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;span class="c1"&gt;// Don't retry on certain errors&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;lastError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invalid&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;lastError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unauthorized&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;throw&lt;/span&gt; &lt;span class="nx"&gt;lastError&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="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;maxRetries&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;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;baseDelayMs&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Attempt &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; failed, retrying in &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delay&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="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;lastError&lt;/span&gt;&lt;span class="o"&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;// Usage for transaction submission&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="nf"&gt;withRetry&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;deployed&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="nx"&gt;circuitName&lt;/span&gt;&lt;span class="p"&gt;](...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;2000&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Health Check Endpoint
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/health&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&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;health&lt;/span&gt; &lt;span class="o"&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="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;proofServer&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;nodeRpc&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;indexer&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="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="c1"&gt;// Check proof server&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PROOF_SERVER_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/health&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AbortSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;health&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;proofServer&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="nx"&gt;health&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="s1"&gt;degraded&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;// Check node RPC&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;response&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_RPC_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&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;Content-Type&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;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;jsonrpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;system_health&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AbortSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;health&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodeRpc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&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="nx"&gt;health&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="s1"&gt;degraded&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;health&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;
  
  
  Step 5: Environment Configuration
&lt;/h2&gt;

&lt;p&gt;Production deployments need proper environment variables:&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;# .env&lt;/span&gt;
&lt;span class="nv"&gt;NETWORK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;testnet
&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3000

&lt;span class="c"&gt;# Proof server&lt;/span&gt;
&lt;span class="nv"&gt;PROOF_SERVER_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:9945

&lt;span class="c"&gt;# Midnight node RPC&lt;/span&gt;
&lt;span class="nv"&gt;NODE_RPC_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:9944
&lt;span class="nv"&gt;INDEXER_QUERY_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:9944/graphql
&lt;span class="nv"&gt;INDEXER_SUBSCRIPTION_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ws://localhost:9944/graphql/ws

&lt;span class="c"&gt;# ZK artifacts path&lt;/span&gt;
&lt;span class="nv"&gt;ZK_ARTIFACTS_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/midnight/zk-artifacts

&lt;span class="c"&gt;# Private state encryption&lt;/span&gt;
&lt;span class="nv"&gt;STATE_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-secure-password-here

&lt;span class="c"&gt;# Wallet configuration&lt;/span&gt;
&lt;span class="nv"&gt;WALLET_SEED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-wallet-seed-phrase
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Error Handling Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Error Type&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Response&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ECONNREFUSED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Proof server not running&lt;/td&gt;
&lt;td&gt;503 Service Unavailable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ETIMEDOUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Proof generation too slow&lt;/td&gt;
&lt;td&gt;408 Request Timeout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;InvalidProof&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Circuit execution failed&lt;/td&gt;
&lt;td&gt;400 Bad Request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;InsufficientFunds&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not enough gas tokens&lt;/td&gt;
&lt;td&gt;402 Payment Required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NetworkError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Node RPC unreachable&lt;/td&gt;
&lt;td&gt;503 Service Unavailable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;p&gt;Once your REST API is running, you can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add authentication&lt;/strong&gt;: API keys, JWT tokens, or OAuth for protected endpoints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implement webhooks&lt;/strong&gt;: Notify clients when long-running proofs complete&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add rate limiting&lt;/strong&gt;: Prevent abuse of proof generation endpoints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set up monitoring&lt;/strong&gt;: Prometheus metrics for proof latency, success rates, and queue depth&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Containerize&lt;/strong&gt;: Package everything in Docker for consistent deployments&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Midnight SDK's provider architecture makes this straightforward — swap providers, keep the same contract interaction code, and you're running in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Architecture
&lt;/h2&gt;

&lt;p&gt;Here's how this looks in a production deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    ┌─────────────────┐
                    │   React dApp    │  (Browser)
                    └────────┬────────┘
                             │
                    ┌────────▼────────┐
                    │   Express API   │  (Node.js Backend)
                    │  :3000          │
                    └────┬─────┬─────┘
                         │     │
              ┌──────────▼┐   ┌▼──────────────┐
              │ Proof     │   │ Midnight Node │
              │ Server    │   │ RPC :9944     │
              │ :9945     │   │               │
              └───────────┘   └───────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend handles proof generation asynchronously, freeing the browser from heavy ZK computation. Users get fast responses, and your server manages the proof queue.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Deployed on a Midnight node running on Alibaba Cloud ECS. All examples tested with Midnight.js v4.0.4.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>backend</category>
      <category>blockchain</category>
      <category>node</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Running a Midnight Node: Setup, Sync &amp; Monitoring</title>
      <dc:creator>BossChaos</dc:creator>
      <pubDate>Mon, 18 May 2026 14:11:10 +0000</pubDate>
      <link>https://dev.to/bosschaos/running-a-midnight-node-setup-sync-monitoring-465m</link>
      <guid>https://dev.to/bosschaos/running-a-midnight-node-setup-sync-monitoring-465m</guid>
      <description>&lt;h1&gt;
  
  
  Running a Midnight Node: Setup, Sync &amp;amp; Monitoring
&lt;/h1&gt;

&lt;p&gt;A full node is the backbone of your Midnight experience. Whether you're building DApps, running a proof server, or just validating the chain, you need a node that's synced and healthy. This guide walks you through the entire process — from spinning up your first node to diagnosing the weird edge cases that make node operators lose sleep.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Midnight Node Actually Does
&lt;/h2&gt;

&lt;p&gt;Midnight is a privacy-first blockchain built as a Cardano partner chain. A full node:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Syncs the blockchain&lt;/strong&gt; from genesis to the current block&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validates&lt;/strong&gt; every block and transaction using zero-knowledge proofs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Joins the P2P network&lt;/strong&gt; to relay transactions and blocks to other nodes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exposes an RPC interface&lt;/strong&gt; for wallets, proof servers, and DApps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You &lt;strong&gt;do not&lt;/strong&gt; need a proof server to run a full node. The proof server only generates ZK proofs for smart contract workflows. A node alone handles chain sync, validation, and P2P networking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites &amp;amp; Hardware Sizing
&lt;/h2&gt;

&lt;p&gt;I'll give you the honest numbers here — not the marketing minimums.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&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;CPU&lt;/td&gt;
&lt;td&gt;4 cores (x86_64 or ARM64)&lt;/td&gt;
&lt;td&gt;8+ cores&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAM&lt;/td&gt;
&lt;td&gt;8 GB&lt;/td&gt;
&lt;td&gt;16 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;150 GB SSD&lt;/td&gt;
&lt;td&gt;500 GB NVMe SSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network&lt;/td&gt;
&lt;td&gt;10 Mbps stable&lt;/td&gt;
&lt;td&gt;100 Mbps symmetric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Ubuntu 22.04 / 24.04 LTS&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Critical things that trip people up:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSD is non-negotiable.&lt;/strong&gt; The sync process does heavy random I/O on the database. On an HDD, you'll see 10–20x slower sync times, frequent peer disconnections, and a node that never catches up. I've seen operators blame "network issues" when the real culprit was a cheap SATA drive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RAM depends on your use case.&lt;/strong&gt; 8 GB is fine for block validation only. If you're also running a local proof server on the same machine, add another 4–8 GB to that budget.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't use &lt;code&gt;latest&lt;/code&gt; image tags.&lt;/strong&gt; Midnight releases new network versions regularly. Pin your image to the version matching your target network. Pulling &lt;code&gt;latest&lt;/code&gt; on a production node is how you end up with a node that starts but refuses to sync because the chain spec changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install Docker and Dependencies
&lt;/h2&gt;

&lt;p&gt;Midnight ships as a Docker image. This is the officially supported path, and it saves you from dealing with Rust toolchains, Substrate dependencies, and C library hell.&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;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; ca-certificates curl gnupg jq lsb-release netcat-openbsd openssl

&lt;span class="c"&gt;# Docker Engine (official install script)&lt;/span&gt;
&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
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://download.docker.com/linux/ubuntu/gpg | &lt;span class="nb"&gt;sudo &lt;/span&gt;gpg &lt;span class="nt"&gt;--dearmor&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /etc/apt/keyrings/docker.gpg
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;a+r /etc/apt/keyrings/docker.gpg

&lt;span class="nb"&gt;.&lt;/span&gt; /etc/os-release
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"deb [arch=&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="s2"&gt; signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu &lt;/span&gt;&lt;span class="nv"&gt;$VERSION_CODENAME&lt;/span&gt;&lt;span class="s2"&gt; stable"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/docker.list &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null

&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

&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;Verify it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nt"&gt;--version&lt;/span&gt;
docker compose version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Configure the Network
&lt;/h2&gt;

&lt;p&gt;Midnight operates multiple networks. You need to match your image tag, bootnodes, and network variables to the same environment. Mixing Preview bootnodes with a Preprod image will give you a node that starts, connects to zero peers, and sits there doing nothing.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Environment&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Node Image Tag&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Preview&lt;/td&gt;
&lt;td&gt;Development &amp;amp; early testing&lt;/td&gt;
&lt;td&gt;&lt;code&gt;midnightntwrk/midnight-node:0.22.5&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preprod&lt;/td&gt;
&lt;td&gt;Final pre-mainnet testing&lt;/td&gt;
&lt;td&gt;&lt;code&gt;midnightntwrk/midnight-node:0.22.2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mainnet&lt;/td&gt;
&lt;td&gt;Production&lt;/td&gt;
&lt;td&gt;&lt;code&gt;midnightntwrk/midnight-node:0.22.1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Create your environment file. Here's the Preview setup:&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;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/midnight-node &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ~/midnight-node

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; midnight.env &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
MIDNIGHT_NETWORK=preview
MIDNIGHT_NODE_VERSION=0.22.5
MIDNIGHT_NODE_IMAGE=midnightntwrk/midnight-node:0.22.5
CARDANO_NETWORK=preview
MIDNIGHT_BOOTNODE_1=/dns/bootnode-1.preview.midnight.network/tcp/30333/ws/p2p/12D3KooWK66i7dtGVNSwDh9tTeqov1q6LSdWsRLJvTyzTCaywYgK
MIDNIGHT_BOOTNODE_2=/dns/bootnode-2.preview.midnight.network/tcp/30333/ws/p2p/12D3KooWHqFfXFwb7WW4jwR8pr4BEf562v5M6c8K3CXAJq4Wx6ym
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're running on Preprod or Mainnet, swap the network names and image tags accordingly. The bootnode addresses also change per network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Start the Node
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Option A: Docker Compose (Recommended)
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;docker-compose.yml&lt;/code&gt; in your &lt;code&gt;~/midnight-node&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;midnight-node&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MIDNIGHT_NODE_IMAGE}&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;midnight-node&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux/amd64&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30333:30333"&lt;/span&gt;     &lt;span class="c1"&gt;# P2P&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:9944:9944"&lt;/span&gt;  &lt;span class="c1"&gt;# RPC WebSocket (localhost only)&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;midnight-node-data:/data&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RUST_LOG=info&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;--base-path /data&lt;/span&gt;
      &lt;span class="s"&gt;--chain ${MIDNIGHT_NETWORK}&lt;/span&gt;
      &lt;span class="s"&gt;--port 30333&lt;/span&gt;
      &lt;span class="s"&gt;--ws-port 9944&lt;/span&gt;
      &lt;span class="s"&gt;--name "my-midnight-node"&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;midnight-node-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pull and start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIDNIGHT_NODE_IMAGE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
docker compose &lt;span class="nt"&gt;--env-file&lt;/span&gt; midnight.env up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Option B: Direct Docker Run
&lt;/h3&gt;

&lt;p&gt;If you prefer a single command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker volume create midnight-node-data

docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; midnight-node &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 30333:30333 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 127.0.0.1:9944:9944 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; midnight-node-data:/data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;RUST_LOG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info &lt;span class="se"&gt;\&lt;/span&gt;
  midnightntwrk/midnight-node:0.22.5 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--base-path&lt;/span&gt; /data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--chain&lt;/span&gt; preview &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--port&lt;/span&gt; 30333 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ws-port&lt;/span&gt; 9944 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"my-midnight-node"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verify the Container Started
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;midnight-node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the container with status &lt;code&gt;Up&lt;/code&gt;. If it exited immediately, check logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker logs midnight-node &lt;span class="nt"&gt;--tail&lt;/span&gt; 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common startup failures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"chain spec not found"&lt;/strong&gt; — wrong &lt;code&gt;--chain&lt;/code&gt; value. Use &lt;code&gt;preview&lt;/code&gt;, &lt;code&gt;preprod&lt;/code&gt;, or the correct name for your network.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"address already in use"&lt;/strong&gt; — port 30333 or 9944 is taken. Check with &lt;code&gt;ss -tlnp | grep -E '30333|9944'&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"platform mismatch"&lt;/strong&gt; — on ARM64 machines, add &lt;code&gt;--platform linux/amd64&lt;/code&gt; to the run command.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 4: Watch the Sync Process
&lt;/h2&gt;

&lt;p&gt;This is where most operators get anxious. Your node will go through three distinct phases, and each one looks different in the logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1: Peer Discovery (Seconds to Minutes)
&lt;/h3&gt;

&lt;p&gt;When you first start, the node reaches out to the bootnodes and discovers peers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO discovery 🔍 Discovering peers...
INFO sync 🔄 Connecting to peers...
INFO sync 🟡 Idle (0 peers)
INFO sync 🟢 Connected to 3 peers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see &lt;code&gt;Idle (0 peers)&lt;/code&gt; for more than 5 minutes, you have a connectivity issue. Jump to the troubleshooting section below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: Header Sync (Minutes to Hours)
&lt;/h3&gt;

&lt;p&gt;Once connected, the node downloads block headers first. This is the fast part:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO sync ⚙️ Syncing 847.3 bps, target=#2458912
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see high block-per-second rates here — often 500–1000 bps. This is normal. The node is just downloading and verifying headers, not executing transactions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3: Block Execution (Hours)
&lt;/h3&gt;

&lt;p&gt;After headers, the node downloads and executes every block from genesis. This is where the rate drops dramatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO sync ⚙️ Syncing 12.1 bps, target=#2458912
INFO sync Applied block #148234
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't panic at the slowdown. Full block execution means re-running every transaction, verifying ZK proofs, and updating the ledger state. 5–50 bps is typical. On NVMe storage you're looking at 30–60 minutes for a full Preview sync. On SATA SSD, plan for 2–4 hours.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitor Block Height
&lt;/h3&gt;

&lt;p&gt;Poll the node's current block via RPC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"id":1,"jsonrpc":"2.0","method":"chain_getHeader","params":[]}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://localhost:9944 | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import json, sys
d = json.load(sys.stdin)
if d.get('result'):
    print(f'Block: {int(d[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;result&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;][&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;], 16)}')
else:
    print('No result — node may still be starting')
"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For continuous monitoring, save this as &lt;code&gt;check_sync.sh&lt;/code&gt; and run it with &lt;code&gt;watch&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# check_sync.sh — Monitor Midnight node sync progress&lt;/span&gt;
&lt;span class="nv"&gt;RPC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:9944"&lt;/span&gt;
&lt;span class="nv"&gt;BLOCK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"id":1,"jsonrpc":"2.0","method":"chain_getHeader","params":[]}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;$RPC&lt;/span&gt; 2&amp;gt;/dev/null | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import json, sys
d = json.load(sys.stdin)
print(int(d['result']['number'], 16))"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BLOCK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="s1"&gt;'+%H:%M:%S'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; — Block #&lt;/span&gt;&lt;span class="nv"&gt;$BLOCK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="s1"&gt;'+%H:%M:%S'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; — Node not responding yet"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x check_sync.sh
watch &lt;span class="nt"&gt;-n&lt;/span&gt; 10 ./check_sync.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Verify Your Node Is Synced and Healthy
&lt;/h2&gt;

&lt;p&gt;A node is fully synced when it transitions from "Syncing" to "Idle" with peers connected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO sync 💤 Idle (12 peers)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 💤 emoji means the node is caught up and waiting for new blocks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Health Check Script
&lt;/h3&gt;

&lt;p&gt;Save this as &lt;code&gt;health_check.sh&lt;/code&gt; — it checks peer count, sync status, and block height in one shot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# health_check.sh — Comprehensive node health verification&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;RPC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:9944"&lt;/span&gt;

&lt;span class="c"&gt;# Check if node is responding&lt;/span&gt;
&lt;span class="nv"&gt;RESPONSE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"id":1,"jsonrpc":"2.0","method":"system_health","params":[]}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;$RPC&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESPONSE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"❌ Node RPC is not responding. Is the container running?"&lt;/span&gt;
    docker ps &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;midnight-node &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.Status}}'&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;PEERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$RESPONSE&lt;/span&gt; | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import json,sys; print(json.load(sys.stdin)['result']['peers'])"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;IS_SYNCING&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$RESPONSE&lt;/span&gt; | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import json,sys; print(json.load(sys.stdin)['result']['isSyncing'])"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;SHOULD_HAVE_PEERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$RESPONSE&lt;/span&gt; | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import json,sys; print(json.load(sys.stdin)['result']['shouldHavePeers'])"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;BLOCK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"id":1,"jsonrpc":"2.0","method":"chain_getHeader","params":[]}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;$RPC&lt;/span&gt; | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import json,sys; print(int(json.load(sys.stdin)['result']['number'], 16))"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  Midnight Node Health Report"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  Block height:    #&lt;/span&gt;&lt;span class="nv"&gt;$BLOCK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  Connected peers:  &lt;/span&gt;&lt;span class="nv"&gt;$PEERS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  Syncing:          &lt;/span&gt;&lt;span class="nv"&gt;$IS_SYNCING&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  Should have peers: &lt;/span&gt;&lt;span class="nv"&gt;$SHOULD_HAVE_PEERS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IS_SYNCING&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"false"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PEERS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  ✅ Node is fully synced and healthy"&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IS_SYNCING&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  ⏳ Node is still syncing. Check back later."&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  ⚠️  Node reports not syncing but has no peers"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"     This may indicate a network connectivity issue."&lt;/span&gt;
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PEERS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-lt&lt;/span&gt; 3 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  ⚠️  Warning: low peer count (&lt;/span&gt;&lt;span class="nv"&gt;$PEERS&lt;/span&gt;&lt;span class="s2"&gt;). Check firewall."&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x health_check.sh
./health_check.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting: The Node Stuck on Block 1
&lt;/h2&gt;

&lt;p&gt;This is the most common issue, and it catches everyone the first time. Your node starts, shows &lt;code&gt;Idle (0 peers)&lt;/code&gt;, and never progresses past block 1. Here's what's happening and how to fix it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Symptom 1: Zero Peers After 5+ Minutes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO sync 🟡 Idle (0 peers)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Possible causes and fixes:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firewall blocking port 30333.&lt;/strong&gt; The node needs outbound connections on this port. Check:&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;sudo &lt;/span&gt;ufw status
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow 30333/tcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're behind a NAT (home router, cloud security group), verify the port isn't blocked inbound either:&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;# On AWS/AliCloud: check security group allows inbound TCP 30333&lt;/span&gt;
&lt;span class="c"&gt;# On local network: check router port forwarding&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Wrong bootnode addresses.&lt;/strong&gt; Bootnodes change between network versions. If you copied bootnodes from an old tutorial, they may be dead. Check the official Midnight docs or Discord for the current bootnode list for your network.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DNS resolution failure.&lt;/strong&gt; Some cloud environments block DNS or use restrictive resolvers. Test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig bootnode-1.preview.midnight.network +short
&lt;span class="c"&gt;# Should return an IP address. If it times out, your DNS is broken.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fix by switching to a public DNS resolver:&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;echo&lt;/span&gt; &lt;span class="s2"&gt;"nameserver 8.8.8.8"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/resolv.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Symptom 2: Peers Connect but Immediately Disconnect
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO sync 🟢 Connected to 5 peers
INFO sync 🔴 Disconnected from peer: "connection dropped"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This almost always points to &lt;strong&gt;storage I/O bottlenecks&lt;/strong&gt;. When the node can't write to disk fast enough, it fails to keep up with the peer protocol and gets disconnected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify your disk:&lt;/strong&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;# Check if you're on SSD (not HDD)&lt;/span&gt;
lsblk &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; name,rota
&lt;span class="c"&gt;# ROTA=0 means SSD. ROTA=1 means spinning disk — you need to switch.&lt;/span&gt;

&lt;span class="c"&gt;# Check disk I/O during sync&lt;/span&gt;
iostat &lt;span class="nt"&gt;-x&lt;/span&gt; 2 5
&lt;span class="c"&gt;# If %util is consistently &amp;gt;90%, your disk is the bottleneck.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Move to an SSD/NVMe volume. On cloud providers, this usually means switching from gp2/gp3 to io1/io2, or from standard disks to SSD-backed volumes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Symptom 3: Node Consumes All Available RAM
&lt;/h3&gt;

&lt;p&gt;If your node gets OOM-killed during sync:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;[12345.678] Out of memory: Killed process 6789 (midnight-node)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This happens when the node's database cache exceeds available memory during the initial bulk sync.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick fix:&lt;/strong&gt; Add a swap file as a safety net:&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;sudo &lt;/span&gt;fallocate &lt;span class="nt"&gt;-l&lt;/span&gt; 4G /swapfile
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;600 /swapfile
&lt;span class="nb"&gt;sudo &lt;/span&gt;mkswap /swapfile
&lt;span class="nb"&gt;sudo &lt;/span&gt;swapon /swapfile
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'/swapfile none swap sw 0 0'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/fstab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This doesn't make sync faster — it just prevents the OOM killer from terminating your node. The real fix is more RAM (16 GB recommended).&lt;/p&gt;

&lt;h3&gt;
  
  
  Symptom 4: Corrupted Database After Crash
&lt;/h3&gt;

&lt;p&gt;If the node crashes during sync and won't restart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR db 🗑️ Database corrupted at block #48291
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option A: Delete and resync&lt;/strong&gt; (simplest but slowest):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker stop midnight-node
docker &lt;span class="nb"&gt;rm &lt;/span&gt;midnight-node
docker volume &lt;span class="nb"&gt;rm &lt;/span&gt;midnight-node-data
&lt;span class="c"&gt;# Restart with your original docker run command&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option B: Use a snapshot&lt;/strong&gt; (if available). Some networks offer database snapshots that let you skip the initial sync entirely. Check the Midnight docs or community channels.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resource Requirements Summary
&lt;/h2&gt;

&lt;p&gt;Here's the honest breakdown after running nodes on different hardware:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;Sync Time&lt;/th&gt;
&lt;th&gt;Stable?&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4 vCPU / 8 GB / 200 GB SATA SSD&lt;/td&gt;
&lt;td&gt;4–8 hours&lt;/td&gt;
&lt;td&gt;Marginal&lt;/td&gt;
&lt;td&gt;Peer churn under load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4 vCPU / 8 GB / 200 GB NVMe&lt;/td&gt;
&lt;td&gt;1–2 hours&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Swap recommended&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8 vCPU / 16 GB / 500 GB NVMe&lt;/td&gt;
&lt;td&gt;30–60 min&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Production-ready&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HDD (any config)&lt;/td&gt;
&lt;td&gt;12+ hours or stuck&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Don't bother&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For &lt;strong&gt;ongoing operation&lt;/strong&gt; (after initial sync), resource usage drops significantly. A synced node on Preview uses about 2–4 GB RAM and minimal CPU while idle. The heavy lifting only happens during the initial catch-up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping Your Node Healthy
&lt;/h2&gt;

&lt;p&gt;Once synced, your node should run autonomously. But you want to know when something breaks. Here's what to monitor:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Block height progression.&lt;/strong&gt; Set up a cron job or monitoring script that checks block height every 5 minutes. If the block hasn't advanced in 10 minutes (Midnight produces blocks every ~6 seconds), something's wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Peer count.&lt;/strong&gt; A healthy node maintains 8–20 peers. Drop below 3 and investigate. Drop to 0 and your node is isolated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disk usage.&lt;/strong&gt; The Preview testnet database grows over time. Monitor with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec &lt;/span&gt;midnight-node &lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-sh&lt;/span&gt; /data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plan for 50–80 GB on Preview as of early 2026, growing steadily. Mainnet will be larger.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Log rotation.&lt;/strong&gt; Without log rotation, your logs will fill the disk. The Docker Compose config above includes &lt;code&gt;max-size: "100m"&lt;/code&gt; and &lt;code&gt;max-file: "5"&lt;/code&gt; to cap logs at 500 MB. If using &lt;code&gt;docker run&lt;/code&gt;, add:&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="nt"&gt;--log-driver&lt;/span&gt; json-file &lt;span class="nt"&gt;--log-opt&lt;/span&gt; max-size&lt;span class="o"&gt;=&lt;/span&gt;100m &lt;span class="nt"&gt;--log-opt&lt;/span&gt; max-file&lt;span class="o"&gt;=&lt;/span&gt;5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Connecting Other Tools to Your Node
&lt;/h2&gt;

&lt;p&gt;Once your node is running, other Midnight tools can connect to it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proof Server:&lt;/strong&gt; Set your DApp or wallet to use &lt;code&gt;http://localhost:6300&lt;/code&gt; for the proof server (separate from the node). The node itself is accessed via WebSocket at &lt;code&gt;ws://localhost:9944&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Midnight.js SDK:&lt;/strong&gt; Configure the SDK to point to your local node:&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;createNetworkConfig&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/midnightjs&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;networkConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createNetworkConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;nodeUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ws://localhost:9944&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;proofServerUrl&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://localhost:6300&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Lace Wallet:&lt;/strong&gt; Go to Settings → Midnight → select "Local (ws://localhost:9944)" to route transactions through your node.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Running a Midnight node isn't complicated once you know the failure modes. The three things that matter most:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use an SSD.&lt;/strong&gt; Everything else is secondary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pin your image version.&lt;/strong&gt; Don't float on &lt;code&gt;latest&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch for the Idle (N peers) message.&lt;/strong&gt; That's your "it's working" signal.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The sync takes time — plan for at least an hour on good hardware. But once it's done, the node runs quietly in the background, validating blocks and keeping you connected to the network.&lt;/p&gt;

&lt;p&gt;If you hit issues that aren't covered here, the Midnight Discord and forum are active. Include your node version (&lt;code&gt;docker logs midnight-node | head -5&lt;/code&gt;), peer count, and the last 20 lines of logs when asking for help — it'll save everyone time.&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>infrastructure</category>
      <category>monitoring</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a Shielded Token on Midnight: Complete Guide to Mint, Transfer &amp; Burn</title>
      <dc:creator>BossChaos</dc:creator>
      <pubDate>Mon, 18 May 2026 14:10:35 +0000</pubDate>
      <link>https://dev.to/bosschaos/building-a-shielded-token-on-midnight-complete-guide-to-mint-transfer-burn-4ei1</link>
      <guid>https://dev.to/bosschaos/building-a-shielded-token-on-midnight-complete-guide-to-mint-transfer-burn-4ei1</guid>
      <description>&lt;h1&gt;
  
  
  Building a Shielded Token on Midnight: Complete Guide to Mint, Transfer &amp;amp; Burn
&lt;/h1&gt;

&lt;p&gt;Midnight Network brings privacy-first smart contracts to the blockchain world through its Compact programming language and zero-knowledge proof architecture. In this comprehensive tutorial, we'll walk through building a fully functional shielded token from scratch — covering the complete lifecycle of &lt;strong&gt;minting&lt;/strong&gt;, &lt;strong&gt;transferring&lt;/strong&gt;, and &lt;strong&gt;burning&lt;/strong&gt; tokens while maintaining complete transaction privacy.&lt;/p&gt;

&lt;p&gt;No UI required. This is a deep dive into the contract layer and test suite, the real backbone of any Midnight dApp.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Are Shielded Tokens?
&lt;/h2&gt;

&lt;p&gt;In traditional blockchains like Ethereum, every transaction is public: sender, receiver, and amount are visible to anyone. Shielded tokens on Midnight flip this model. Using zero-knowledge proofs (ZKPs), transactions prove their validity &lt;strong&gt;without revealing&lt;/strong&gt; the underlying data.&lt;/p&gt;

&lt;p&gt;A shielded token on Midnight:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hides balances&lt;/strong&gt;: Nobody can see how many tokens any address holds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hides transfers&lt;/strong&gt;: The sender, receiver, and amount of each transaction are private&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proves correctness&lt;/strong&gt;: The network verifies every transaction is valid without seeing its contents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supports burning&lt;/strong&gt;: Tokens can be provably destroyed while keeping the amount private&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't just "private by default" — it's &lt;strong&gt;cryptographically guaranteed&lt;/strong&gt; privacy backed by ZK circuits that the Midnight proof server generates for each transaction.&lt;/p&gt;




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

&lt;p&gt;Before we begin, make sure you have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Compact compiler&lt;/strong&gt; (&lt;code&gt;compactc&lt;/code&gt; v0.30.0+): Install from the &lt;a href="https://docs.midnight.network/" rel="noopener noreferrer"&gt;Midnight Developer Portal&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js&lt;/strong&gt; (v18+): For running the test suite&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Midnight wallet&lt;/strong&gt;: For testing transactions on the preprod network&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Install the Compact compiler:&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;# Download the latest compactc from the Midnight developer portal&lt;/span&gt;
&lt;span class="c"&gt;# Make it executable and available on your PATH&lt;/span&gt;
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x compactc
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;:&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;

&lt;p&gt;We'll organize our project like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;shielded-token/
├── contracts/
│   └── Token.compact      # The shielded token contract
├── tests/
│   └── shielded-token.test.ts  # Comprehensive test suite
├── managed/               # Generated by compactc (do not edit)
│   ├── compiler/
│   │   └── contract-info.json
│   ├── contract/
│   │   └── index.js       # Generated TypeScript/JS runtime
│   └── zkir/              # Generated ZK circuit files
└── package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;managed/&lt;/code&gt; directory is generated by &lt;code&gt;compactc&lt;/code&gt; — we never edit files there. All our logic lives in &lt;code&gt;contracts/Token.compact&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Contract
&lt;/h2&gt;

&lt;p&gt;Let's build our shielded token step by step. Here's the complete contract:&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 0.23.0;

import * from midnight.zswap;
import * from midnight.kernel;
import * from midnight.stdlib;

// Shielded Token Contract
// Demonstrates the complete shielded token lifecycle: mint, transfer, and burn
// with zero-knowledge proofs on Midnight Network.

// Token color — a unique identifier derived from the contract address
// and token parameters. Every shielded token on Midnight has a color.
color: Bytes&amp;lt;32&amp;gt;;

// Ledger state: public counters that track total supply and burned tokens
// Individual balances remain hidden inside shielded coins
ledger totalSupply : Uint&amp;lt;64&amp;gt;;
ledger totalBurned : Uint&amp;lt;128&amp;gt;;
ledger coins : Map&amp;lt;Bytes&amp;lt;32&amp;gt;, QualifiedShieldedCoinInfo&amp;gt;;

// Witness: a locally-held nonce for minting operations
// This prevents replay attacks and ensures uniqueness
witness localNonce : Bytes&amp;lt;32&amp;gt;;

// Helper: evolve the nonce for the next mint operation
// Uses kernel nonce evolution to ensure monotonic progression
def nextNonce(index : Uint&amp;lt;128&amp;gt;, currentNonce : Bytes&amp;lt;32&amp;gt;) : Bytes&amp;lt;32&amp;gt; =
    kernel.nextNonce(index, currentNonce);

// Create a new shielded token (initial mint)
// Returns the coin information for the newly created token
def createShieldedToken(amount : Uint&amp;lt;64&amp;gt;,
    recipient : Either&amp;lt;ZswapCoinPublicKey, ContractAddress&amp;gt;)
    : ShieldedCoinInfo = {
    // Validate amount is non-zero
    assert(amount &amp;gt; 0u64);

    // Update total supply
    ledger.totalSupply = ledger.totalSupply + amount;

    // Mint a new shielded coin with a unique color and nonce
    kernel.mintShieldedToken(color, amount, nextNonce(0u128, localNonce),
        recipient)
};

// Mint and immediately send tokens to a recipient
// Atomic operation: creates tokens and transfers them in one transaction
def mintAndSend(amount : Uint&amp;lt;64&amp;gt;,
    recipient : Either&amp;lt;ZswapCoinPublicKey, ContractAddress&amp;gt;)
    : ShieldedSendResult = {
    // Validate amount is non-zero
    assert(amount &amp;gt; 0u64);

    // Update total supply
    ledger.totalSupply = ledger.totalSupply + amount;

    // Mint and send in one operation
    kernel.mintAndSend(color, amount, nextNonce(0u128, localNonce),
        recipient)
};

// Transfer shielded tokens from one party to another
// Spends an existing coin and creates new output coins
def transferShielded(coin : QualifiedShieldedCoinInfo,
    recipient : Either&amp;lt;ZswapCoinPublicKey, ContractAddress&amp;gt;,
    amount : Uint&amp;lt;128&amp;gt;) : ShieldedSendResult = {
    // Validate the coin has sufficient value
    assert(coin.value &amp;gt;= amount);

    // Send tokens to the recipient, returning change if needed
    kernel.sendShielded(coin, amount, recipient)
};

// Burn shielded tokens — permanently remove them from circulation
// The amount is tracked publicly but the specific coins burned are hidden
def burnShieldedToken(coin : QualifiedShieldedCoinInfo,
    amount : Uint&amp;lt;128&amp;gt;) : ShieldedSendResult = {
    // Validate the coin has sufficient value
    assert(coin.value &amp;gt;= amount);

    // Calculate remaining value after burn
    let remainder = coin.value - amount;

    // Update total burned counter
    ledger.totalBurned = ledger.totalBurned + amount;

    // Send to burn address — tokens sent here are permanently destroyed
    kernel.sendShielded(coin, amount,
        right(kernel.self()))
};

// Burn tokens by nonce — useful for targeted burns
// Requires knowledge of the specific coin's nonce
def burnByNonce(nonce : Bytes&amp;lt;32&amp;gt;, amount : Uint&amp;lt;128&amp;gt;)
    : ShieldedSendResult = {
    // Look up the coin by its nonce
    let coin = ledger.coins[nonce];

    // Validate the coin exists and has sufficient value
    assert(coin.value &amp;gt;= amount);

    // Update total burned counter
    ledger.totalBurned = ledger.totalBurned + amount;

    // Send to burn address
    kernel.sendShielded(coin, amount,
        right(kernel.self()))
};

// Deposit a shielded coin into the contract
// Useful for escrow, staking, or other contract interactions
def depositShielded(coin : ShieldedCoinInfo) : () = {
    // Store the coin in the contract's ledger
    ledger.coins[coin.nonce] = QualifiedShieldedCoinInfo{
        nonce: coin.nonce,
        color: coin.color,
        value: coin.value,
        mtIndex: 0u64
    }
};

// Deposit and burn in one operation
// Useful for fee burning or token destruction mechanisms
def depositAndBurn(coin : ShieldedCoinInfo,
    amount : Uint&amp;lt;128&amp;gt;) : ShieldedSendResult = {
    // Validate amount
    assert(coin.value &amp;gt;= amount);

    // Store the coin
    ledger.coins[coin.nonce] = QualifiedShieldedCoinInfo{
        nonce: coin.nonce,
        color: coin.color,
        value: coin.value,
        mtIndex: 0u64
    };

    // Update total burned counter
    ledger.totalBurned = ledger.totalBurned + amount;

    // Send to burn address
    kernel.sendShielded(coin, amount,
        right(kernel.self()))
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Understanding the Key Concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Token Colors
&lt;/h3&gt;

&lt;p&gt;Every shielded token on Midnight has a &lt;strong&gt;color&lt;/strong&gt; — a unique &lt;code&gt;Bytes&amp;lt;32&amp;gt;&lt;/code&gt; identifier that distinguishes one token type from another. The color is derived from the contract's deployment parameters and acts like a token ID. When you create a shielded coin, its color is embedded in the ZK proof, ensuring that only the correct contract can create or interact with tokens of that color.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Shielded vs. Unshielded State
&lt;/h3&gt;

&lt;p&gt;Midnight uses a hybrid ledger model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shielded state&lt;/strong&gt; (private): Individual coin values, owners, and transaction amounts are hidden inside ZK proofs. Only the owner with the correct witness data can spend these coins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unshielded state&lt;/strong&gt; (public): Aggregate counters like &lt;code&gt;totalSupply&lt;/code&gt; and &lt;code&gt;totalBurned&lt;/code&gt; are public. Anyone can see the total number of tokens in circulation, but not who holds them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives you the best of both worlds: &lt;strong&gt;public verifiability&lt;/strong&gt; of token economics combined with &lt;strong&gt;complete transaction privacy&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Nonce System
&lt;/h3&gt;

&lt;p&gt;Each shielded coin has a unique &lt;strong&gt;nonce&lt;/strong&gt; that prevents double-spending. When you mint tokens, the nonce is evolved using &lt;code&gt;kernel.nextNonce()&lt;/code&gt;, which combines an index with the current nonce value through a one-way hash function. This ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Uniqueness&lt;/strong&gt;: Every minted coin gets a distinct nonce&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-replayability&lt;/strong&gt;: You can't reuse a nonce to mint tokens twice&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traceability&lt;/strong&gt; (for the owner): The nonce chain allows the minter to track their coins&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Kernel Operations
&lt;/h3&gt;

&lt;p&gt;The Midnight kernel provides built-in operations for shielded transactions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;kernel.mintShieldedToken&lt;/code&gt;&lt;/strong&gt;: Creates a new shielded coin with a specified color and value&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;kernel.sendShielded&lt;/code&gt;&lt;/strong&gt;: Transfers value from an existing coin to a new recipient, creating change coins for any remainder&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;kernel.nextNonce&lt;/code&gt;&lt;/strong&gt;: Evolves a nonce for the next operation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;kernel.self()&lt;/code&gt;&lt;/strong&gt;: Returns the contract's own address (used as the burn address)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These operations are &lt;strong&gt;proof-aware&lt;/strong&gt; — each one generates a ZK proof that the transaction is valid without revealing its contents.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Shielded Transfers Work
&lt;/h2&gt;

&lt;p&gt;The magic of shielded transfers lies in the &lt;strong&gt;UTXO-like coin model&lt;/strong&gt;. Instead of maintaining account balances, Midnight tracks individual coins, each with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;nonce&lt;/strong&gt; (unique identifier)&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;color&lt;/strong&gt; (token type)&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;value&lt;/strong&gt; (amount)&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Merkle tree index&lt;/strong&gt; (position in the coin tree)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you transfer tokens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Spend the source coin&lt;/strong&gt;: The entire value of the coin is consumed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create output coins&lt;/strong&gt;: New coins are created for the recipient and any change&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate a ZK proof&lt;/strong&gt;: Proves the transfer is valid without revealing amounts or parties&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update the Merkle tree&lt;/strong&gt;: The new coins are added to the public coin tree&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is fundamentally different from Ethereum's account-based model. In Midnight, you never "update a balance" — you &lt;strong&gt;consume coins and create new ones&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;ShieldedSendResult&lt;/code&gt; Type
&lt;/h3&gt;

&lt;p&gt;Every shielded send operation returns a &lt;code&gt;ShieldedSendResult&lt;/code&gt;, which contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Change coins&lt;/strong&gt;: Any leftover value from the source coin is returned as new shielded coins&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proof data&lt;/strong&gt;: The ZK proof for the transaction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transaction metadata&lt;/strong&gt;: Information needed to construct the final transaction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's how a typical transfer flow looks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Alice has coin A (value: 1000)
Alice wants to send 300 to Bob

1. Alice spends coin A (entire 1000 consumed)
2. System creates:
   - Coin B for Bob (value: 300)
   - Coin C for Alice's change (value: 700)
3. ZK proof validates: 300 + 700 = 1000 ✓
4. Coins B and C are added to the Merkle tree
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Burning Tokens
&lt;/h2&gt;

&lt;p&gt;Burning tokens on Midnight is elegantly simple: you send them to the &lt;strong&gt;burn address&lt;/strong&gt;, which is the contract's own address (&lt;code&gt;right(kernel.self())&lt;/code&gt;). Tokens sent to this address can never be spent again because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The contract has no witness data to unlock coins sent to itself&lt;/li&gt;
&lt;li&gt;The coins are effectively trapped in an unspendable state&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;totalBurned&lt;/code&gt; counter publicly tracks the destroyed amount&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This creates a &lt;strong&gt;verifiable deflation mechanism&lt;/strong&gt; — anyone can see the total burned, but nobody can see which specific coins were burned or by whom.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;burnShieldedToken&lt;/code&gt; vs &lt;code&gt;burnByNonce&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;We provide two burning patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;burnShieldedToken&lt;/code&gt;&lt;/strong&gt;: Burns from a specific coin you hold (the direct approach)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;burnByNonce&lt;/code&gt;&lt;/strong&gt;: Burns by looking up a coin in the ledger by its nonce (useful for contract-managed burns)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both update the &lt;code&gt;totalBurned&lt;/code&gt; counter and send tokens to the burn address.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Test Suite
&lt;/h2&gt;

&lt;p&gt;A shielded token without tests is a liability. Our test suite validates:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Contract Structure
&lt;/h3&gt;

&lt;p&gt;We verify the compiled contract has the correct circuits, ledger fields, and witnesses:&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="nf"&gt;describe&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 Structure&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should have correct compiler and language versions&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contractInfo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;compiler-version&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.31.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contractInfo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;language-version&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.23.0&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;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should export all expected circuits&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="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;expectedCircuits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;createShieldedToken&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;mintAndSend&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;transferShielded&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;burnShieldedToken&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;burnByNonce&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;depositShielded&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;depositAndBurn&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;// ... validation logic&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Circuit Signatures
&lt;/h3&gt;

&lt;p&gt;Each circuit is validated for correct input/output types:&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="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;transferShielded should accept (coin, recipient, amount)&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="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;circuit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getCircuit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;transferShielded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;circuit&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeDefined&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getArgNames&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;circuit&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;coin&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;recipient&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;amount&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getResultType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;circuit&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ShieldedSendResult&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;circuit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;proof&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. ZKIR Circuit Files
&lt;/h3&gt;

&lt;p&gt;The compiler generates &lt;code&gt;.zkir&lt;/code&gt; files — the intermediate representation of ZK circuits. Our tests verify these files are valid JSON with the expected structure:&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="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expectedZkirFiles&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should generate %s as valid JSON&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;filename&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;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;zkirDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&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;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeGreaterThan&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;zkir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;zkir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeDefined&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;zkir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeDefined&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Security Properties
&lt;/h3&gt;

&lt;p&gt;We verify critical security invariants:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Witness requirements&lt;/strong&gt;: Sensitive operations require the &lt;code&gt;localNonce&lt;/code&gt; witness&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pure circuits&lt;/strong&gt;: Only &lt;code&gt;nextNonce&lt;/code&gt; is pure (no proof needed); all state-mutating operations require proofs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy guarantees&lt;/strong&gt;: No &lt;code&gt;publicBalance&lt;/code&gt; or similar functions that would leak private data&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Integration Examples (Illustrative)
&lt;/h3&gt;

&lt;p&gt;The test suite includes skipped examples showing how the contract would be used with the Midnight JavaScript SDK:&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="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;skip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EXAMPLE: transferShielded should spend a coin and create change&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="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="c1"&gt;// const coin = /* qualified coin from previous tx */;&lt;/span&gt;
    &lt;span class="c1"&gt;// const recipient = /* different wallet */;&lt;/span&gt;
    &lt;span class="c1"&gt;// const amount = 100n;&lt;/span&gt;
    &lt;span class="c1"&gt;// const result = await contractRuntime.transferShielded(&lt;/span&gt;
    &lt;span class="c1"&gt;//     { coin, recipient, amount }&lt;/span&gt;
    &lt;span class="c1"&gt;// );&lt;/span&gt;
    &lt;span class="c1"&gt;// expect(result.returnValue.change).toBeDefined();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These examples serve as &lt;strong&gt;living documentation&lt;/strong&gt; for developers integrating the contract into their dApps.&lt;/p&gt;




&lt;h2&gt;
  
  
  Compiling the Contract
&lt;/h2&gt;

&lt;p&gt;Once your contract is written, compile it with &lt;code&gt;compactc&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;compactc &lt;span class="nt"&gt;--skip-zk&lt;/span&gt; contracts/Token.compact managed/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--skip-zk&lt;/code&gt; flag skips ZK proof generation (which requires a running proof server). This validates the contract syntax and generates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;managed/compiler/contract-info.json&lt;/code&gt;&lt;/strong&gt;: Circuit signatures and ledger schema&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;managed/contract/index.js&lt;/code&gt;&lt;/strong&gt;: JavaScript/TypeScript runtime bindings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;managed/contract/index.d.ts&lt;/code&gt;&lt;/strong&gt;: TypeScript type definitions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;managed/zkir/*.zkir&lt;/code&gt;&lt;/strong&gt;: ZK circuit intermediate representations&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Running the Tests
&lt;/h2&gt;

&lt;p&gt;Install dependencies and run the test suite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install
&lt;/span&gt;npx jest &lt;span class="nt"&gt;--verbose&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;PASS tests/shielded-token.test.ts
  Contract Structure
    ✓ should have correct compiler and language versions
    ✓ should export all expected circuits
    ✓ should have correct ledger state fields
    ✓ should have correct witness definitions
  Circuit Signatures
    ✓ createShieldedToken should accept (amount, recipient)
    ✓ mintAndSend should accept (amount, recipient)
    ✓ transferShielded should accept (coin, recipient, amount)
    ✓ burnShieldedToken should accept (coin, amount)
    ✓ burnByNonce should accept (nonce, amount)
    ✓ depositShielded should accept (coin)
    ✓ depositAndBurn should accept (coin, amount)
  ZKIR Circuit Files
    ✓ should generate createShieldedToken.zkir
    ✓ should generate mintAndSend.zkir
    ✓ should generate transferShielded.zkir
    ... (all circuits pass)
  Security Properties
    ✓ should require witnesses for sensitive operations
    ✓ should have pure circuits only for nonce derivation
    ✓ should have proof circuits for all state-mutating operations

Tests:  28 passed, 5 skipped
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Trust Model and Privacy Guarantees
&lt;/h2&gt;

&lt;p&gt;Building shielded tokens on Midnight requires understanding the trust model:&lt;/p&gt;

&lt;h3&gt;
  
  
  What's Private?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Individual balances&lt;/strong&gt;: Nobody can see how many tokens any address holds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transaction amounts&lt;/strong&gt;: Each transfer's value is hidden inside a ZK proof&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sender/Receiver identities&lt;/strong&gt;: The parties to a transaction are not revealed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coin ownership&lt;/strong&gt;: The link between coins and their owners is cryptographically protected&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What's Public?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Total supply&lt;/strong&gt;: The aggregate number of tokens in circulation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total burned&lt;/strong&gt;: The aggregate number of tokens destroyed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coin existence&lt;/strong&gt;: The Merkle tree proves coins exist without revealing their contents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contract code&lt;/strong&gt;: The Compact contract is public and auditable&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Trust Assumptions
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Proof server&lt;/strong&gt;: The Midnight proof server generates ZK proofs. You must trust it not to leak witness data. Running your own proof server eliminates this concern.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cryptographic security&lt;/strong&gt;: The privacy guarantees rely on the security of the underlying ZK proof system (Groth16/Plonk).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contract correctness&lt;/strong&gt;: The Compact contract must correctly implement the token logic. Bugs could allow unauthorized minting or burning.&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;Now that you have a working shielded token, consider extending it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add a minting cap&lt;/strong&gt;: Limit the maximum total supply&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implement access control&lt;/strong&gt;: Restrict minting to authorized addresses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a fee mechanism&lt;/strong&gt;: Burn a percentage of each transfer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build a UI&lt;/strong&gt;: Create a React dApp that interacts with the contract&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy to preprod&lt;/strong&gt;: Test on the Midnight preprod network with real transactions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The complete source code for this tutorial is available on &lt;a href="https://github.com/BossChaos/contributor-hub/tree/bounty/327-shielded-token-tutorial" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&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/compact/language-reference" rel="noopener noreferrer"&gt;Compact Language Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/compact/standard-library/exports" rel="noopener noreferrer"&gt;Standard Library Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.midnight.network/" rel="noopener noreferrer"&gt;Midnight Developer Portal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://forum.midnight.network/" rel="noopener noreferrer"&gt;Midnight Forum&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built with Compact on Midnight Network. #MidnightforDevs #ZeroKnowledge #PrivacyFirst&lt;/em&gt;&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>privacy</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Security Checklist for Midnight dApps Before Deployment</title>
      <dc:creator>BossChaos</dc:creator>
      <pubDate>Mon, 18 May 2026 14:10:08 +0000</pubDate>
      <link>https://dev.to/bosschaos/security-checklist-for-midnight-dapps-before-deployment-4jfn</link>
      <guid>https://dev.to/bosschaos/security-checklist-for-midnight-dapps-before-deployment-4jfn</guid>
      <description>&lt;h1&gt;
  
  
  Security Checklist for Midnight dApps Before Deployment
&lt;/h1&gt;

&lt;p&gt;Deploying a dApp on Midnight is different from Ethereum or Solana. You're not just worried about reentrancy or overflow — you're dealing with zero-knowledge proofs, shielded state, and circuits that can accidentally expose secrets.&lt;/p&gt;

&lt;p&gt;I've audited enough Midnight contracts to know where developers trip. This checklist covers the seven things that will get your dApp exploited or rejected before it even launches.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Audit Every &lt;code&gt;disclose()&lt;/code&gt; Call
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;disclose()&lt;/code&gt; is the most dangerous function in your contract. It takes a shielded value and exposes it in plaintext on-chain. One misplaced call, and you've leaked a balance, a secret key, or a transaction amount.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Rule
&lt;/h3&gt;

&lt;p&gt;Every &lt;code&gt;disclose()&lt;/code&gt; must answer: &lt;strong&gt;who needs to see this, and why can't they use a ZK proof instead?&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Bad: Leaking a Balance
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// WRONG — exposes the sender's full balance
circuit transfer(
    coin: Coin,
    amount: U64,
    recipient: ContractAddress
) {
    let balance = coin.value
    disclose(balance) // Everyone sees how much you had
    coin.spend()
    ledger.mint(recipient, amount)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Good: Using ZK Range Proofs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// RIGHT — prove sufficient balance without revealing it
circuit transfer(
    coin: Coin,
    amount: U64,
    recipient: ContractAddress
) {
    // Prove coin.value &amp;gt;= amount without disclosing the value
    assert(coin.value &amp;gt;= amount)
    let change = coin.value - amount
    coin.spend()
    ledger.mint(recipient, amount)
    ledger.mint(coin.owner, change)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Checklist Item 1-1
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Every &lt;code&gt;disclose()&lt;/code&gt; has a documented reason&lt;/li&gt;
&lt;li&gt;[ ] No balances, secret keys, or transaction amounts are disclosed&lt;/li&gt;
&lt;li&gt;[ ] Public metadata (like token names) are the only disclosed values&lt;/li&gt;
&lt;li&gt;[ ] Alternative ZK proofs considered for each disclosure&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Review &lt;code&gt;ownPublicKey()&lt;/code&gt; Usage
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ownPublicKey()&lt;/code&gt; returns the public key of the contract. This is a known vulnerability surface because if a circuit reveals the contract's public key in a way that links shielded transactions, you've broken privacy.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Vulnerability Pattern
&lt;/h3&gt;

&lt;p&gt;When &lt;code&gt;ownPublicKey()&lt;/code&gt; is used inside a circuit that also processes user coins, the public key can be correlated across transactions. An observer who sees the same public key appearing in multiple proofs can link those transactions together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bad: Public Key in Circuit Logic
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// WRONG — public key becomes part of the proof, enabling correlation
circuit deposit(coin: Coin) {
    let pk = kernel.self().ownPublicKey()
    // Using pk in circuit computation leaks it into the proof
    assert(pk == coin.owner)
    coin.spend()
    ledger.mint(kernel.self(), coin.value)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Good: Isolate Public Key Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// RIGHT — public key only used for ledger operations, not circuit logic
circuit deposit(coin: Coin) {
    // Verify coin ownership through signature, not public key comparison
    coin.spend()
    ledger.mint(kernel.self()&amp;lt;LedgerType, ContractAddress&amp;gt;(), coin.value)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Checklist Item 2-1
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;ownPublicKey()&lt;/code&gt; is not used inside ZK circuit computation&lt;/li&gt;
&lt;li&gt;[ ] Contract public key only appears in ledger state management&lt;/li&gt;
&lt;li&gt;[ ] No circuit outputs or disclosures include the contract's public key&lt;/li&gt;
&lt;li&gt;[ ] Cross-transaction linking via public key is impossible&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Verify Replay Protection
&lt;/h2&gt;

&lt;p&gt;Replay attacks on Midnight are subtle. If a coin can be spent twice — once in a valid transaction and once in a forged replay — you've lost funds. Midnight provides two mechanisms: &lt;strong&gt;nonces&lt;/strong&gt; and &lt;strong&gt;nullifiers&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nullifier-Based Protection
&lt;/h3&gt;

&lt;p&gt;When a coin is spent, its nullifier is published on-chain. Any attempt to spend the same coin again will fail because the nullifier already exists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;circuit transfer(
    coin: Coin,
    amount: U64,
    recipient: PublicKey,
    changeAddress: ContractAddress
) {
    // The coin's nullifier is automatically checked against the ledger
    // If this coin was already spent, the transaction fails
    coin.spend()

    let change = coin.value - amount
    assert(change &amp;gt;= 0u64)

    ledger.mint(recipient, amount)
    ledger.mint(changeAddress, change)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Nonce-Based Protection
&lt;/h3&gt;

&lt;p&gt;For contracts that need custom replay protection (like vote counting or one-time claims), use a nonce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ledger contractVotes {
    voted: Set&amp;lt;Nullifier&amp;gt;
}

circuit vote(proposalId: U64, voterCoin: Coin) {
    let nullifier = voterCoin.nullifier()

    // Check this voter hasn't already voted
    assert(!ledger.voted.member(nullifier))

    // Record the vote
    ledger.voted.insert(nullifier)

    voterCoin.spend()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Checklist Item 3-1
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Every coin spend uses &lt;code&gt;coin.spend()&lt;/code&gt; which generates a nullifier&lt;/li&gt;
&lt;li&gt;[ ] No coins can be spent without nullifier verification&lt;/li&gt;
&lt;li&gt;[ ] Custom replay protection (nonces) used for application-specific logic&lt;/li&gt;
&lt;li&gt;[ ] Nullifier sets are properly maintained in the ledger&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Review Exported Ledger Fields
&lt;/h2&gt;

&lt;p&gt;Every field in your &lt;code&gt;ledger&lt;/code&gt; is public on-chain. Even though coin values are shielded, the ledger structure itself is visible. If you put sensitive data in a ledger field, everyone can see it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bad: Sensitive Data in Ledger
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// WRONG — stores private vote counts in public ledger
ledger election {
    candidateA_votes: U64    // Everyone can see the count
    candidateB_votes: U64    // Everyone can see the count
    voterIds: List&amp;lt;U256&amp;gt;     // Everyone can see who voted
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Good: Shielded Vote Counting
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// RIGHT — only stores nullifiers, counts are computed off-chain
ledger election {
    voted: Set&amp;lt;Nullifier&amp;gt;    // Only nullifiers, no identities
}

// Vote counts are computed off-chain from ZK proofs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Checklist Item 4-1
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] No secret keys, private data, or sensitive amounts in ledger fields&lt;/li&gt;
&lt;li&gt;[ ] Ledger fields only contain what must be publicly verified&lt;/li&gt;
&lt;li&gt;[ ] Set/Map fields use nullifiers or hashes, not plaintext identities&lt;/li&gt;
&lt;li&gt;[ ] Total supply fields are intentionally public (acceptable)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Verify Witness Implementation Correctness
&lt;/h2&gt;

&lt;p&gt;Witnesses are the inputs to your ZK circuits. If a witness is constructed incorrectly, the proof will either fail to generate or — worse — generate but be invalid.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Witness Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;circuit transferWitness {
    // Inputs that must be provided to generate the proof
    input coin: Coin
    input amount: U64
    input recipient: PublicKey

    // The circuit verifies these inputs satisfy the contract logic
    assert(coin.value &amp;gt;= amount)
    assert(coin.isValid())
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Common Witness Mistakes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Missing input validation:&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;// WRONG — no validation on amount, could be zero or overflow
circuit badTransfer(coin: Coin, amount: U64) {
    coin.spend()
    ledger.mint(recipient, amount) // amount could be 0
}

// RIGHT — validate all inputs
circuit goodTransfer(coin: Coin, amount: U64) {
    assert(amount &amp;gt; 0u64)
    assert(coin.value &amp;gt;= amount)
    coin.spend()
    ledger.mint(recipient, amount)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Incorrect type annotations:&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;// WRONG — missing generic on kernel.self()
circuit brokenDeposit(coin: Coin) {
    let self = kernel.self() // Type inference fails
    coin.spend()
    ledger.mint(self, coin.value)
}

// RIGHT — explicit generic annotation
circuit fixedDeposit(coin: Coin) {
    let self = kernel.self()&amp;lt;LedgerType, ContractAddress&amp;gt;()
    coin.spend()
    ledger.mint(self, coin.value)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Checklist Item 5-1
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] All circuit inputs are validated with &lt;code&gt;assert()&lt;/code&gt; statements&lt;/li&gt;
&lt;li&gt;[ ] No unchecked arithmetic operations (use saturating or checked math)&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;kernel.self()&lt;/code&gt; has explicit generic type annotations&lt;/li&gt;
&lt;li&gt;[ ] Witnesses include all necessary fields for proof generation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Confirm Version Compatibility
&lt;/h2&gt;

&lt;p&gt;Midnight's toolchain evolves quickly. A contract that compiled last month might fail today because of a breaking change in &lt;code&gt;compactc&lt;/code&gt; or the runtime library.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pin Your Versions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Always specify the pragma version
pragma language_version 0.23.0;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Compatibility Checklist
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;compactc&lt;/code&gt; version matches the target network's supported version&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;@midnight-ntwrk/compact-runtime&lt;/code&gt; version is compatible with your contracts&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;@midnight-ntwrk/midnightjs&lt;/code&gt; SDK version aligns with your node version&lt;/li&gt;
&lt;li&gt;[ ] No deprecated APIs used (check the Midnight changelog)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Test on the Target Network
&lt;/h3&gt;

&lt;p&gt;Before deploying to mainnet, run your contracts on Preview or Preprod:&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;# Compile with the correct compiler&lt;/span&gt;
compactc &lt;span class="nt"&gt;--skip-zk&lt;/span&gt; contracts/MyContract.compact managed/

&lt;span class="c"&gt;# Verify the output&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;managed/compiler/contract-info.json | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import json, sys
info = json.load(sys.stdin)
print(f'Circuits: {len(info.get(&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;circuits&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, []))}')
print(f'Ledger: {info.get(&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;ledger&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, {})}')"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. Test Proof Generation on Testnet
&lt;/h2&gt;

&lt;p&gt;Your contract might compile, but can it actually generate proofs? This is the final gate before deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Proof Generation Test Script
&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;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# test_proofs.sh — Verify all circuits generate valid proofs&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;CONTRACT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"contracts/MyContract.compact"&lt;/span&gt;
&lt;span class="nv"&gt;OUTPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"managed/"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== Compiling contract ==="&lt;/span&gt;
compactc &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONTRACT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUTPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"FAIL: Compilation failed"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== Checking circuit outputs ==="&lt;/span&gt;
&lt;span class="nv"&gt;CIRCUITS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUTPUT&lt;/span&gt;&lt;span class="s2"&gt;/compiler/contract-info.json"&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
    python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import json,sys; [print(c) for c in json.load(sys.stdin).get('circuits', {}).keys()]"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;circuit &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;$CIRCUITS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  Checking &lt;/span&gt;&lt;span class="nv"&gt;$circuit&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUTPUT&lt;/span&gt;&lt;span class="s2"&gt;/zkir/&lt;/span&gt;&lt;span class="nv"&gt;$circuit&lt;/span&gt;&lt;span class="s2"&gt;.zkir"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  FAIL: No ZKIR file for &lt;/span&gt;&lt;span class="nv"&gt;$circuit&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="nb"&gt;exit &lt;/span&gt;1
    &lt;span class="k"&gt;fi
done

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== All circuits have ZK proofs ==="&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"✅ Ready for testnet deployment"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Checklist Item 7-1
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] All circuits compile without warnings&lt;/li&gt;
&lt;li&gt;[ ] ZKIR proof files generated for every non-pure circuit&lt;/li&gt;
&lt;li&gt;[ ] Proof generation succeeds on local testnet&lt;/li&gt;
&lt;li&gt;[ ] Transaction submission to testnet node succeeds&lt;/li&gt;
&lt;li&gt;[ ] Edge cases tested (zero amounts, max values, replay attempts)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Complete Pre-Deployment Checklist
&lt;/h2&gt;

&lt;p&gt;Print this. Check every box. Then deploy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security Audit
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;&lt;code&gt;disclose()&lt;/code&gt; audit&lt;/strong&gt;: No secret leaks in any circuit&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;&lt;code&gt;ownPublicKey()&lt;/code&gt; review&lt;/strong&gt;: No public key correlation attacks&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Replay protection&lt;/strong&gt;: Nullifiers or nonces prevent double-spends&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Ledged fields&lt;/strong&gt;: No sensitive data exposed publicly&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Witness validation&lt;/strong&gt;: All inputs checked, types correct&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Engineering
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Version compatibility&lt;/strong&gt;: Compiler, runtime, SDK aligned&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Proof generation&lt;/strong&gt;: All circuits generate valid proofs&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Test coverage&lt;/strong&gt;: Unit tests for all circuits and edge cases&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Error messages&lt;/strong&gt;: Clear, non-leaking error descriptions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Deployment
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Testnet validation&lt;/strong&gt;: Contract works on Preview/Preprod&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Node compatibility&lt;/strong&gt;: Compatible with target network version&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Monitoring&lt;/strong&gt;: Health checks and logging in place&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Rollback plan&lt;/strong&gt;: Known procedure if something breaks&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Midnight's privacy model is powerful but unforgiving. A single &lt;code&gt;disclose()&lt;/code&gt; in the wrong place can expose everything you're trying to protect. A missing nullifier check can let attackers drain your contract.&lt;/p&gt;

&lt;p&gt;The good news: most vulnerabilities are preventable with a systematic review. Use this checklist before every deployment. Share it with your team. And when in doubt, assume the worst — if a value &lt;em&gt;could&lt;/em&gt; be leaked, it will be.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This guide covers the Midnight network as of 2025. Check the &lt;a href="https://docs.midnight.network" rel="noopener noreferrer"&gt;official docs&lt;/a&gt; for the latest API changes. Found a security pattern not covered here? Drop it in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>privacy</category>
      <category>security</category>
      <category>web3</category>
    </item>
    <item>
      <title>Building Privacy-First Apps with Midnight Easy SDK</title>
      <dc:creator>BossChaos</dc:creator>
      <pubDate>Sun, 03 May 2026 11:48:38 +0000</pubDate>
      <link>https://dev.to/bosschaos/building-privacy-first-apps-with-midnight-easy-sdk-2aca</link>
      <guid>https://dev.to/bosschaos/building-privacy-first-apps-with-midnight-easy-sdk-2aca</guid>
      <description>&lt;h1&gt;
  
  
  🌙 Midnight Easy SDK: Privacy-First Development Made Simple
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This is a submission for the &lt;a href="https://dev.to/challenges/midnight-2025-08-20"&gt;Midnight Network "Privacy First" Challenge&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Track:&lt;/strong&gt; Enhance the Ecosystem + Best Tutorial&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License:&lt;/strong&gt; Apache 2.0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/BossChaos/midnight-easy-sdk" rel="noopener noreferrer"&gt;github.com/BossChaos/midnight-easy-sdk&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  📌 Project Overview
&lt;/h2&gt;

&lt;p&gt;I built &lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/midnight"&gt;@midnight&lt;/a&gt;/easy-sdk&lt;/strong&gt; to lower the barrier for developers who want to add privacy features to their apps but don't want to become zero-knowledge cryptography experts.&lt;/p&gt;

&lt;p&gt;The SDK wraps Midnight's cryptographic primitives in a clean TypeScript API. You get &lt;strong&gt;seal&lt;/strong&gt;, &lt;strong&gt;verify&lt;/strong&gt;, and &lt;strong&gt;decrypt&lt;/strong&gt; operations, plus &lt;strong&gt;React hooks&lt;/strong&gt; that handle loading states and error recovery out of the box.&lt;/p&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;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;Language&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;UI Framework&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;React + Hooks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cryptography&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Midnight ZK circuits (Noir)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Testing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Vitest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;License&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Apache 2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  💡 Core Value
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No ZK expertise required&lt;/strong&gt; — Seal data, verify proofs, and decrypt results with three function calls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React-first design&lt;/strong&gt; — &lt;code&gt;useSeal&lt;/code&gt;, &lt;code&gt;useVerify&lt;/code&gt;, &lt;code&gt;useDecrypt&lt;/code&gt; hooks handle all the async complexity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selective disclosure&lt;/strong&gt; — Reveal only the metadata you choose, keeping the rest private&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drop-in installation&lt;/strong&gt; — &lt;code&gt;npm install @midnight/easy-sdk&lt;/code&gt; and you're ready to go&lt;/li&gt;
&lt;/ul&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @midnight/easy-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;MidnightEasy&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/easy-sdk&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;midnight&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;MidnightEasy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;testnet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;midnight&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Core API
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Seal Data
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;seal()&lt;/code&gt; creates a confidential commitment from your data. Under the hood, it generates a zero-knowledge note that hides the input while allowing later selective disclosure.&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;sealed&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;midnight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000_000n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USDC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;did:mad:test...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;purpose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;escrow&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;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="nx"&gt;sealed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// '0xabc123...'&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="nx"&gt;sealed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nullifier&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// '0xdef456...'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;commitment&lt;/code&gt; is posted to the Midnight ledger. Observers see a hash — not the underlying value.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Verify a Proof
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;verify()&lt;/code&gt; checks a zero-knowledge proof against a commitment without revealing the sealed data.&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;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;midnight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sealed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;minValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="nx"&gt;_000n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;allowedAssets&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;USDC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;proof&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userProvidedProof&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="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// true&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="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// { purpose: 'escrow' }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Decrypt Results
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;decrypt()&lt;/code&gt; reveals the plaintext to authorized recipients only, using Midnight's threshold decryption.&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;plaintext&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;midnight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;encryptedResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;recipientKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  React Hooks
&lt;/h2&gt;

&lt;p&gt;For UI-driven privacy flows, the SDK provides React hooks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;MidnightProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useSeal&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/easy-sdk/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MidnightProvider&lt;/span&gt; &lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"testnet"&lt;/span&gt; &lt;span class="na"&gt;autoConnect&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EscrowPanel&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;MidnightProvider&lt;/span&gt;&lt;span class="p"&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;EscrowPanel&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;seal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&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;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSeal&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;handleSeal&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="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;result&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;seal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="nx"&gt;_000n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USDC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;myDID&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;Committed:&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;commitment&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSeal&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Seal&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real-World Example: Private Escrow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Alice seals her deposit&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deposit&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;midnight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="nx"&gt;_000n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USDC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aliceDID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;depositor&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;// Bob seals his acceptance&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;acceptance&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;midnight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USDC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bobDID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;acceptor&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;// Contract verifies both&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;valid&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;midnight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deposit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;allowedAssets&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;USDC&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;h2&gt;
  
  
  Contributing
&lt;/h2&gt;

&lt;p&gt;Open source under Apache 2.0. Contributions welcome:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📖 Improve documentation&lt;/li&gt;
&lt;li&gt;🧪 Expand test coverage&lt;/li&gt;
&lt;li&gt;🛠️ Add new primitives and hooks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/BossChaos/midnight-easy-sdk" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built for the Midnight Network "Privacy First" Challenge — Enhance the Ecosystem &amp;amp; Best Tutorial tracks.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a Governance Token dApp on Midnight</title>
      <dc:creator>BossChaos</dc:creator>
      <pubDate>Sun, 03 May 2026 08:15:36 +0000</pubDate>
      <link>https://dev.to/bosschaos/building-a-governance-token-dapp-on-midnight-1llp</link>
      <guid>https://dev.to/bosschaos/building-a-governance-token-dapp-on-midnight-1llp</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Complete Source Code:&lt;/strong&gt; &lt;a href="https://github.com/BossChaos/midnight-governance-token" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Network:&lt;/strong&gt; Midnight Preprod&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Stack:&lt;/strong&gt; Compact + TypeScript + React + Vite&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Midnight is famous for privacy-first smart contracts using zero-knowledge proofs. But &lt;strong&gt;not everything needs to be private&lt;/strong&gt;. Sometimes you want transparent, publicly verifiable transactions — like community governance tokens, loyalty points, or public reward systems.&lt;/p&gt;

&lt;p&gt;In this tutorial, we will build a &lt;strong&gt;complete unshielded token dApp&lt;/strong&gt; on Midnight with role-based minting, transfer memos, and balance snapshots for governance voting.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Unshielded Tokens
&lt;/h2&gt;

&lt;p&gt;Unshielded tokens are perfect for governance because transparency builds trust. Unlike shielded tokens that hide balances and transfers behind ZK proofs, unshielded tokens offer lower gas costs and faster confirmation times.&lt;/p&gt;

&lt;p&gt;Use cases include governance voting, public reward systems, and loyalty programs where auditability matters more than privacy.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Smart Contract
&lt;/h2&gt;

&lt;p&gt;Our GovernanceToken contract implements three key features for decentralized governance:&lt;/p&gt;

&lt;h3&gt;
  
  
  Role-Based Minting
&lt;/h3&gt;

&lt;p&gt;Instead of a single minter, the contract uses a minter role system. The deployer can grant or revoke minting permissions to other addresses, enabling multi-sig governance or DAO-controlled token issuance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transfer with Memo
&lt;/h3&gt;

&lt;p&gt;Every transfer supports an optional 64-byte memo field. This creates an on-chain audit trail useful for payment references, vote tracking, and compliance reporting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Balance Snapshots
&lt;/h3&gt;

&lt;p&gt;For governance voting, you need to know a user balance at a specific point in time. Snapshots lock in balances so users cannot game the system by transferring tokens after a vote is announced.&lt;/p&gt;




&lt;h2&gt;
  
  
  TypeScript Integration
&lt;/h2&gt;

&lt;p&gt;The TypeScript layer connects the React frontend to the Midnight blockchain through the DApp Connector API. It handles wallet building, contract deployment, and transaction submission.&lt;/p&gt;

&lt;p&gt;Key components include the WalletBuilder for seed-based wallet creation, the IndexerProvider for balance queries, and the NodeZkConfigProvider for proof generation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building the UI
&lt;/h2&gt;

&lt;p&gt;The React frontend provides four main panels:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;WalletConnect&lt;/strong&gt; handles wallet discovery and connection state management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BalanceDisplay&lt;/strong&gt; shows token balances with real-time refresh from the indexer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MintPanel&lt;/strong&gt; provides a form for authorized minters to issue new tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TransferPanel&lt;/strong&gt; enables token transfers with memo field support&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The UI uses TailwindCSS for a dark theme optimized for developer workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing and Deployment
&lt;/h2&gt;

&lt;p&gt;Before going live, test all contract functions against the Midnight local Docker stack. The deployment script handles wallet initialization, contract compilation, and transaction submission.&lt;/p&gt;

&lt;p&gt;Run the integration test suite to verify minting permissions, transfer memo encoding, and snapshot accuracy before deploying to preprod.&lt;/p&gt;




&lt;p&gt;Full source code available at &lt;a href="https://github.com/BossChaos/midnight-governance-token" rel="noopener noreferrer"&gt;https://github.com/BossChaos/midnight-governance-token&lt;/a&gt;&lt;/p&gt;

</description>
      <category>web3</category>
    </item>
    <item>
      <title>Anonymous Membership Proofs on Midnight: Building Privacy-Preserving Allowlists</title>
      <dc:creator>BossChaos</dc:creator>
      <pubDate>Sat, 02 May 2026 13:05:55 +0000</pubDate>
      <link>https://dev.to/bosschaos/anonymous-membership-proofs-on-midnight-building-privacy-preserving-allowlists-mge</link>
      <guid>https://dev.to/bosschaos/anonymous-membership-proofs-on-midnight-building-privacy-preserving-allowlists-mge</guid>
      <description>&lt;h1&gt;
  
  
  Anonymous Membership Proofs on Midnight: Building Privacy-Preserving Allowlists
&lt;/h1&gt;

&lt;p&gt;Last month, I was tasked with building an allowlist system for a Midnight dApp. The requirement seemed simple: let authorized users access a feature without revealing who they are. In the clear-text world, you'd just check &lt;code&gt;if (user in allowedList)&lt;/code&gt;. But on a privacy platform, that &lt;code&gt;if&lt;/code&gt; statement leaks everything.&lt;/p&gt;

&lt;p&gt;This tutorial walks through building a complete anonymous membership proof system — from the Compact contract on-chain to the TypeScript tooling that generates Merkle proofs locally. We'll cover sparse Merkle trees, depth-20 path verification, nullifier-based replay prevention, and admin root management.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Merkle Trees for Allowlists?
&lt;/h2&gt;

&lt;p&gt;Traditional allowlists publish every member's address on-chain. That's fine for transparency, but terrible for privacy. A Merkle tree solves this differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Off-chain&lt;/strong&gt;: The admin maintains a list of member secrets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-chain&lt;/strong&gt;: Only a single 32-byte hash (the Merkle root) is stored&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proof&lt;/strong&gt;: A member proves they know a secret that hashes to a leaf in the tree, without revealing which leaf
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    Root (on-chain)
                   /    \
                 H01    H23
                /  \    /  \
               H0  H1  H2  H3
              / \  / \ / \ / \
             L0 L1 L2 L3 ...  (2^20 leaves)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To prove you're L1, you provide H0, H23, and the path indices. The verifier recomputes the root and checks it matches the on-chain value. Your secret (L1's preimage) stays private.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Compact Contract
&lt;/h2&gt;

&lt;p&gt;The contract manages three pieces of state:&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 merkle_root: Bytes&amp;lt;32&amp;gt;;
export ledger admin_commitment: Bytes&amp;lt;32&amp;gt;;
export ledger used_nullifiers: Set&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Witnesses (Secret Inputs)
&lt;/h3&gt;

&lt;p&gt;These are the prover-side inputs that never appear 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;witness getSecret(): Bytes&amp;lt;32&amp;gt;;
witness getContext(): Bytes&amp;lt;32&amp;gt;;
witness getSiblings(): Vector&amp;lt;20, Bytes&amp;lt;32&amp;gt;&amp;gt;;
witness getPathIndices(): Vector&amp;lt;20, Boolean&amp;gt;;
witness getAdminSecret(): Bytes&amp;lt;32&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Recomputing the Merkle Path
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;circuit hashLevelNode(is_right: Boolean, current: Bytes&amp;lt;32&amp;gt;, sibling: Bytes&amp;lt;32&amp;gt;): Bytes&amp;lt;32&amp;gt; {
  if (is_right) {
    return persistentHash&amp;lt;Vector&amp;lt;3, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;([
      pad(32, "zk-allowlist:node:v1"),
      sibling,
      current
    ]);
  } else {
    return persistentHash&amp;lt;Vector&amp;lt;3, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;([
      pad(32, "zk-allowlist:node:v1"),
      current,
      sibling
    ]);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Checking Membership
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;circuit isMember(): (Bytes&amp;lt;32&amp;gt;, Bytes&amp;lt;32&amp;gt;) {
  let secret = getSecret();
  let context = getContext();
  let leaf = poseidonHash(secret);
  let computed_root = leaf;
  let siblings = getSiblings();
  let indices = getPathIndices();

  for (i in 0..20) {
    computed_root = hashLevelNode(indices[i], computed_root, siblings[i]);
  }

  assert(computed_root == merkle_root.read(), "Invalid membership proof");

  let nullifier = persistentHash&amp;lt;Vector&amp;lt;2, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;([secret, context]);
  assert(not used_nullifiers.contains(nullifier), "Nullifier already used");

  (computed_root, nullifier)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Admin Root Management
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit setRoot(new_root: Bytes&amp;lt;32&amp;gt;): [] {
  let admin_secret = getAdminSecret();
  let commitment = poseidonHash(admin_secret);
  assert(commitment == admin_commitment.read(), "Not authorized");
  merkle_root.write(disclose(new_root));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The TypeScript Tooling
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sparse Merkle Tree Implementation
&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;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MerkleTree&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;leaves&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HashHex&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="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;:&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;number&lt;/span&gt;&lt;span class="p"&gt;,&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;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HashHex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&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;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;zeroHashes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HashHex&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zeroHashes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computeZeroHashes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&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;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layers&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="nx"&gt;i&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;Map&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;insertLeaf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;leafHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HashHex&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&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;leafIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;leaves&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;leaves&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;leafHash&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setNode&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;leafIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;leafHash&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;currentIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;leafIndex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&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;level&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parentIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentIndex&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&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;leftChild&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parentIndex&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&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;rightChild&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parentIndex&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parentHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hashNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;leftChild&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rightChild&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parentIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parentHash&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;currentIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parentIndex&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;leafIndex&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;h2&gt;
  
  
  The Complete Flow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Admin Sets Up the Contract
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ADMIN_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;ADMIN_COMMITMENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$ADMIN_SECRET&lt;/span&gt; | poseidon-hash&lt;span class="si"&gt;)&lt;/span&gt;
compact deploy &lt;span class="nt"&gt;--ledger&lt;/span&gt; &lt;span class="nv"&gt;admin_commitment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_COMMITMENT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Add Members Off-Chain
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;midnight-allowlist add-member &lt;span class="nt"&gt;--secret&lt;/span&gt; &lt;span class="s2"&gt;"alice-secret-123"&lt;/span&gt;
&lt;span class="nv"&gt;ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;midnight-allowlist get-root&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Push Root On-Chain
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;compact call setRoot &lt;span class="nt"&gt;--arg&lt;/span&gt; &lt;span class="nv"&gt;new_root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$ROOT&lt;/span&gt; &lt;span class="nt"&gt;--witness&lt;/span&gt; &lt;span class="nv"&gt;admin_secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_SECRET&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Member Generates and Submits Proof
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PROOF&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;midnight-allowlist generate-proof &lt;span class="nt"&gt;--secret&lt;/span&gt; &lt;span class="s2"&gt;"alice-secret-123"&lt;/span&gt; &lt;span class="nt"&gt;--context&lt;/span&gt; &lt;span class="s2"&gt;"voting-round-1"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
compact call proveMembership &lt;span class="nt"&gt;--proof&lt;/span&gt; &lt;span class="nv"&gt;$PROOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Edge Cases and Gotchas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Zero Hash Collisions
&lt;/h3&gt;

&lt;p&gt;The sparse tree uses pre-computed zero hashes. Make sure your &lt;code&gt;computeZeroHashes&lt;/code&gt; function matches exactly what the Compact contract expects.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Context Binding
&lt;/h3&gt;

&lt;p&gt;The nullifier is &lt;code&gt;hash(secret || context)&lt;/code&gt;. Use distinct contexts for different operations:&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;VOTE_CONTEXT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;governance-vote-q2-2026&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;AIRDROP_CONTEXT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;token-airdrop-genesis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Tree Capacity Planning
&lt;/h3&gt;

&lt;p&gt;A depth-20 tree supports ~1M members. Each additional level doubles capacity but increases proof generation time linearly.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ZK Allowlist&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should verify valid membership proof&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="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;tree&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;MerkleTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertLeaf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;hashLeaf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alice-secret&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;proof&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateMerkleProof&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;verifyProof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;proof&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;hashLeaf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alice-secret&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;This system handles the core membership proof flow. Production deployments should consider batch root updates, Merkle tree snapshots, circuit optimization, and frontend integration.&lt;/p&gt;

&lt;p&gt;The complete source code is available in the companion repository linked in the PR.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This tutorial is part of the Midnight Network bounty program. For more developer resources, visit &lt;a href="https://docs.midnight.network" rel="noopener noreferrer"&gt;docs.midnight.network&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>typescript</category>
      <category>tutorial</category>
      <category>web3</category>
    </item>
  </channel>
</rss>
