<?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: Tosh</title>
    <description>The latest articles on DEV Community by Tosh (@tosh2308).</description>
    <link>https://dev.to/tosh2308</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%2F3882963%2Ff98fa978-b1f1-431a-8433-c5817c02279f.png</url>
      <title>DEV Community: Tosh</title>
      <link>https://dev.to/tosh2308</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tosh2308"/>
    <language>en</language>
    <item>
      <title>I Replaced My $8k/Month Freelance Stack with 3 Claude Prompts</title>
      <dc:creator>Tosh</dc:creator>
      <pubDate>Thu, 16 Apr 2026 23:06:43 +0000</pubDate>
      <link>https://dev.to/tosh2308/i-replaced-my-8kmonth-freelance-stack-with-3-claude-prompts-3kme</link>
      <guid>https://dev.to/tosh2308/i-replaced-my-8kmonth-freelance-stack-with-3-claude-prompts-3kme</guid>
      <description>&lt;p&gt;Last year I was charging $150/hour for consulting work that took me 6 hours.&lt;/p&gt;

&lt;p&gt;Same work now takes 45 minutes. I haven't lowered my rates.&lt;/p&gt;

&lt;p&gt;Here's exactly what changed — and the three prompts that did it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem I Had (That You Probably Have Too)
&lt;/h2&gt;

&lt;p&gt;I was doing B2B content strategy for SaaS companies. Each engagement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3 hours of research&lt;/li&gt;
&lt;li&gt;1 hour writing the strategy doc&lt;/li&gt;
&lt;li&gt;1 hour of revisions&lt;/li&gt;
&lt;li&gt;1 hour of client calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;$900 for a 6-hour day. Before expenses.&lt;/p&gt;

&lt;p&gt;It was good money. But it wasn't &lt;em&gt;leveraged&lt;/em&gt; money.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompt 1: The Research Accelerator
&lt;/h2&gt;

&lt;p&gt;Before I discovered this, research meant reading 20+ competitor pages, taking notes, then synthesizing everything into a coherent picture. 3 hours minimum.&lt;/p&gt;

&lt;p&gt;Now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are a B2B content strategist. I need competitive landscape research for [COMPANY].

They sell [PRODUCT] to [ICP].

Analyze their top 5 competitors: [LIST]. For each, identify:
1. Core messaging angle (what problem do they claim to solve)
2. Content pillars (what topics they consistently publish)
3. SEO gaps (topic clusters competitors haven't covered well)
4. Tone and positioning (how they talk to buyers)

Then synthesize: what's the white space? What angles aren't being covered well?
Format as a strategic brief with executive summary + detailed breakdown.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This doesn't replace thinking. It replaces the &lt;em&gt;mechanical&lt;/em&gt; part of thinking — the reading, noting, and initial pattern-matching. I spend 30 minutes reviewing and refining instead of 3 hours doing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompt 2: The Strategy Writer
&lt;/h2&gt;

&lt;p&gt;Once I have research, I used to spend 60-90 minutes writing the actual strategy document. Now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Based on this competitive analysis: [PASTE RESEARCH]

Write a 6-month content strategy for [COMPANY] that:
- Targets [ICP] at each stage of the funnel
- Differentiates from [MAIN COMPETITORS] by focusing on [WHITE SPACE ANGLE]
- Prioritizes [METRIC] as the primary success measure

Structure: Executive Summary → Strategic Pillars (3-4) → Topic Clusters per Pillar → Distribution Strategy → Month-by-Month Roadmap

Tone: [COMPANY TONE]. Be specific, not generic. Every recommendation should be actionable.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output isn't perfect. It's 70% there. But 70% is infinitely better than 0%, and editing a draft is 3x faster than writing from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompt 3: The Client Brief Translator
&lt;/h2&gt;

&lt;p&gt;This is the one most people miss.&lt;/p&gt;

&lt;p&gt;Clients don't know what they want. They say "we need more leads" when they mean "our sales team is complaining and we need to show we're doing something."&lt;/p&gt;

&lt;p&gt;Before every engagement, I run their brief through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Here's a client brief: [PASTE BRIEF]

Translate this into:
1. What they literally asked for
2. What they actually want (the underlying business goal)
3. What success looks like to THEM (not to us)
4. What the hidden risks are (what could make this fail)
5. What questions I should ask in the discovery call to clarify scope

Be direct. If the brief is vague, say so.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This saves me from building the wrong thing and having to redo it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Math
&lt;/h2&gt;

&lt;p&gt;Old workflow: 6 hours × $150 = $900 project&lt;br&gt;
New workflow: 1.5 hours × $150 = $225 billable (for same output)&lt;/p&gt;

&lt;p&gt;But here's the thing: I didn't cut my rates. I took on more projects.&lt;/p&gt;

&lt;p&gt;4 projects/week at $900 = $3,600/week&lt;br&gt;
Now I do 8 projects/week at same quality = $7,200/week&lt;/p&gt;

&lt;p&gt;I also got better at identifying which projects weren't worth taking.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Isn't
&lt;/h2&gt;

&lt;p&gt;This isn't "AI does my job." The thinking, the judgment, the client relationships — all still human. What AI eliminated is the &lt;em&gt;mechanical&lt;/em&gt; parts: reading, structuring, first drafts.&lt;/p&gt;

&lt;p&gt;If your skill is mechanical (raw research, raw writing, raw coding) you need to adapt. If your skill is judgment, strategy, and relationships — AI just made you faster.&lt;/p&gt;




&lt;p&gt;If you're building an AI services practice, I put together an agency starter kit with the full stack: cold email templates, service packages with pricing, Claude prompt libraries, and delivery SOPs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://toshleonard.gumroad.com/l/mtgqp" rel="noopener noreferrer"&gt;AI Agency Starter Kit →&lt;/a&gt;&lt;/strong&gt; — $97&lt;/p&gt;

&lt;p&gt;The templates alone save 20+ hours. One client engagement pays for it 10x over.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What prompts are you using to accelerate your work? Drop them below.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>freelancing</category>
      <category>productivity</category>
      <category>career</category>
    </item>
    <item>
      <title>7 Ways I'm Using AI to Make Money Right Now (With Exact Tools and Prompts)</title>
      <dc:creator>Tosh</dc:creator>
      <pubDate>Thu, 16 Apr 2026 22:36:22 +0000</pubDate>
      <link>https://dev.to/tosh2308/7-ways-im-using-ai-to-make-money-right-now-with-exact-tools-and-prompts-289p</link>
      <guid>https://dev.to/tosh2308/7-ways-im-using-ai-to-make-money-right-now-with-exact-tools-and-prompts-289p</guid>
      <description>&lt;p&gt;I've been running an experiment: can AI tools generate real income in 2026?&lt;/p&gt;

&lt;p&gt;Short answer: yes — but only if you know &lt;em&gt;which&lt;/em&gt; tasks to focus on.&lt;/p&gt;

&lt;p&gt;Here's what's actually working, with specifics.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. AI Copywriting as a Service ($50–$500/project)
&lt;/h2&gt;

&lt;p&gt;The highest-leverage move I've found is selling AI-assisted copywriting on Upwork and Fiverr. Not "AI wrote it" but "I used AI to write 10x faster."&lt;/p&gt;

&lt;p&gt;What sells:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product descriptions for e-commerce stores&lt;/li&gt;
&lt;li&gt;Sales page copy for SaaS products&lt;/li&gt;
&lt;li&gt;Email sequences for coaches and consultants&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key: AI drafts, you edit for voice and accuracy. Clients don't care how it was made — they care if it converts.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Building Claude Code Tools for Bounties ($50–$500/bounty)
&lt;/h2&gt;

&lt;p&gt;There's a growing ecosystem of bounty boards paying for Claude Code utilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CHANGELOG generators&lt;/li&gt;
&lt;li&gt;PR review bots&lt;/li&gt;
&lt;li&gt;Workflow automation tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://github.com/claude-builders-bounty/claude-builders-bounty" rel="noopener noreferrer"&gt;claude-builders-bounty&lt;/a&gt; repo has active bounties. These pay $50–$200 each and you can build them in an afternoon with Claude.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Technical Tutorial Writing for Web3 Projects ($300–$1,000/article)
&lt;/h2&gt;

&lt;p&gt;Privacy blockchain projects like &lt;a href="https://midnight.network" rel="noopener noreferrer"&gt;Midnight Network&lt;/a&gt; actively pay for high-quality tutorials. Their bounty board: &lt;a href="https://github.com/midnightntwrk/contributor-hub" rel="noopener noreferrer"&gt;midnightntwrk/contributor-hub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The secret: they want articles that explain complex ZK/privacy concepts to developers. AI is great at structuring this — you just need to verify technical accuracy.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Selling Prompt Packs on Gumroad ($19–$47)
&lt;/h2&gt;

&lt;p&gt;This one is passive. I've published three prompt packs that sell without any ongoing work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://toshleonard.gumroad.com/l/rzenot" rel="noopener noreferrer"&gt;500+ AI Business Prompts&lt;/a&gt;&lt;/strong&gt; — $27 — covers content, sales, strategy, email, social&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://toshleonard.gumroad.com/l/ijhbmk" rel="noopener noreferrer"&gt;AI Freelancer Income Blueprint&lt;/a&gt;&lt;/strong&gt; — $47 — full playbook for monetizing AI skills&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://toshleonard.gumroad.com/l/ekrbqu" rel="noopener noreferrer"&gt;200 Developer Prompts&lt;/a&gt;&lt;/strong&gt; — $19 — code review, testing, debugging, architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The math: 10 sales/day at $27 = $270/day = $8,100/month. Even 2 sales/day is meaningful passive income.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. n8n + Claude Automations for Local Businesses ($200–$1,000/setup)
&lt;/h2&gt;

&lt;p&gt;Local businesses will pay for "set it and forget it" automations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Weekly report generation from their Google Sheets&lt;/li&gt;
&lt;li&gt;Customer review response drafts&lt;/li&gt;
&lt;li&gt;Invoice follow-up emails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Build once with n8n + Claude API. Sell to 10 businesses. Ongoing maintenance is 2 hours/month.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. GitHub Bounties via Algora and IssueHunt
&lt;/h2&gt;

&lt;p&gt;Algora lists funded GitHub issues across real open source projects. Filter by &lt;code&gt;min_reward=$100&lt;/code&gt; and you'll find issues you can solve with AI assistance in a few hours.&lt;/p&gt;

&lt;p&gt;Key insight: AI doesn't just write code — it reads codebases, understands patterns, and suggests fixes. The human's job is to validate and submit.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. AI-Assisted Content for Medium Partner Program
&lt;/h2&gt;

&lt;p&gt;Medium pays per read. AI-assisted articles on AI topics perform well because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The audience is interested in AI&lt;/li&gt;
&lt;li&gt;You can produce high-quality content faster&lt;/li&gt;
&lt;li&gt;Evergreen topics (how to use Claude, prompt engineering) keep earning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apply to the Partner Program on day one. It takes 2–4 weeks to get approved but then every article you've published starts earning.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Stack I'm Using
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Sonnet&lt;/strong&gt; — writing, code generation, research synthesis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;n8n&lt;/strong&gt; — automation workflows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gumroad&lt;/strong&gt; — digital product sales&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;dev.to + Hashnode&lt;/strong&gt; — content distribution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt; — bounty hunting and PR submissions&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What Doesn't Work (Honest Assessment)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pure AI content farms&lt;/strong&gt;: Too crowded, Google filters it, ad revenue is terrible&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trading bots&lt;/strong&gt;: The edge evaporates faster than you can build it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"AI will do everything" freelance promises&lt;/strong&gt;: Clients want accountability, not automation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The winners are people who use AI as a 10x multiplier on &lt;em&gt;skilled&lt;/em&gt; work — not a replacement for judgment.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://toshleonard.gumroad.com/l/rzenot" rel="noopener noreferrer"&gt;500+ AI Business Prompts Pack&lt;/a&gt; — $27&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://toshleonard.gumroad.com/l/ijhbmk" rel="noopener noreferrer"&gt;AI Freelancer Income Blueprint&lt;/a&gt; — $47
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://toshleonard.gumroad.com/l/ekrbqu" rel="noopener noreferrer"&gt;200 Developer Prompts&lt;/a&gt; — $19&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;What's working for you? Drop it in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>career</category>
      <category>freelancing</category>
    </item>
    <item>
      <title>Handling Midnight SDK Breaking Changes: A Developer's Survival Guide</title>
      <dc:creator>Tosh</dc:creator>
      <pubDate>Thu, 16 Apr 2026 18:24:25 +0000</pubDate>
      <link>https://dev.to/tosh2308/handling-midnight-sdk-breaking-changes-a-developers-survival-guide-56kb</link>
      <guid>https://dev.to/tosh2308/handling-midnight-sdk-breaking-changes-a-developers-survival-guide-56kb</guid>
      <description>&lt;h1&gt;
  
  
  Handling Midnight SDK Breaking Changes: A Developer's Survival Guide
&lt;/h1&gt;

&lt;p&gt;Every developer building on early-stage blockchain SDKs has lived through this: you run &lt;code&gt;npm install&lt;/code&gt; after a team member updates the lockfile, and suddenly nothing compiles. Midnight's SDK is actively evolving, and breaking changes are frequent enough that you need a systematic approach to handling them.&lt;/p&gt;

&lt;p&gt;This guide covers practical strategies for managing Midnight SDK upgrades — from detecting breaking changes before they break production, to a step-by-step migration workflow, to maintaining backward compatibility when you can't upgrade immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Midnight SDK Changes Break More Than Normal Libraries
&lt;/h2&gt;

&lt;p&gt;Midnight SDK upgrades are more disruptive than typical library updates for a structural reason: &lt;strong&gt;proving keys are cryptographically bound to specific circuit versions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When you upgrade the SDK, even a minor version bump can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Change the circuit constraint system&lt;/li&gt;
&lt;li&gt;Invalidate your existing proving keys&lt;/li&gt;
&lt;li&gt;Require regeneration of proving and verification keys&lt;/li&gt;
&lt;li&gt;Break existing proofs generated against the old keys&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Unlike a typical API change where you update a function call, a circuit change means any proofs generated before the upgrade are incompatible with contracts deployed after it.&lt;/p&gt;

&lt;p&gt;This creates a hard migration surface on two dimensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Your development environment&lt;/strong&gt;: compilation and proof generation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your deployment state&lt;/strong&gt;: on-chain contracts vs. client-generated proofs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Track Changes Before Upgrading
&lt;/h2&gt;

&lt;p&gt;Never upgrade blindly. Before changing any &lt;code&gt;@midnight-ntwrk/*&lt;/code&gt; version:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check the changelog:&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;# In your repo, view the current versions&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;package.json | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"@midnight-ntwrk"&lt;/span&gt;

&lt;span class="c"&gt;# On npm, check what changed between versions&lt;/span&gt;
npx npm-check-updates &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="s2"&gt;"@midnight-ntwrk/*"&lt;/span&gt; &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Read the release notes directly:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Midnight SDK publishes changelogs at: &lt;code&gt;https://github.com/midnight-ntwrk/midnight-js/releases&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Look specifically for entries labeled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;BREAKING CHANGE&lt;/code&gt; — requires code changes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Circuit change&lt;/code&gt; — requires key regeneration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;API rename&lt;/code&gt; — function/interface names changed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Type change&lt;/code&gt; — TypeScript types modified (common source of silent breakage)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Check the migration guide:&lt;/strong&gt;&lt;br&gt;
If a migration guide exists, read it in full before starting. The 15 minutes of reading saves hours of debugging.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 2: Pin Exact Versions (Never Use &lt;code&gt;^&lt;/code&gt; or &lt;code&gt;~&lt;/code&gt;)
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;DANGEROUS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;—&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;floating&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;versions&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@midnight-ntwrk/compact-runtime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^0.14.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@midnight-ntwrk/midnight-js-types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"~0.14.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;CORRECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;—&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;pinned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;exact&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;versions&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@midnight-ntwrk/compact-runtime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.14.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@midnight-ntwrk/midnight-js-types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.14.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@midnight-ntwrk/midnight-js-contracts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.14.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@midnight-ntwrk/midnight-js-network-id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.14.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Lockfile discipline:&lt;/strong&gt;&lt;br&gt;
Commit &lt;code&gt;package-lock.json&lt;/code&gt; (or &lt;code&gt;yarn.lock&lt;/code&gt;). Never &lt;code&gt;.gitignore&lt;/code&gt; it. Your CI should install with &lt;code&gt;npm ci&lt;/code&gt; (not &lt;code&gt;npm install&lt;/code&gt;) to use the exact lockfile versions.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 3: Upgrade in an Isolated Branch
&lt;/h2&gt;

&lt;p&gt;Never upgrade the Midnight SDK directly on &lt;code&gt;main&lt;/code&gt;. Create an upgrade branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; upgrade/midnight-sdk-0.15.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run the full migration without disrupting other work&lt;/li&gt;
&lt;li&gt;Revert cleanly if something breaks badly&lt;/li&gt;
&lt;li&gt;Get a code review of the upgrade diff before merging&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 4: Update Versions and Recompile Contracts
&lt;/h2&gt;

&lt;p&gt;Update &lt;code&gt;package.json&lt;/code&gt; manually (don't use &lt;code&gt;npm update&lt;/code&gt; with floating versions):&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;# Update to specific new version&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; @midnight-ntwrk/compact-runtime@0.15.0 &lt;span class="se"&gt;\&lt;/span&gt;
  @midnight-ntwrk/midnight-js-types@0.15.0 &lt;span class="se"&gt;\&lt;/span&gt;
  @midnight-ntwrk/midnight-js-contracts@0.15.0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--save-exact&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then recompile all Compact contracts:&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;# Recompile all .compact files&lt;/span&gt;
find ./contracts &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.compact"&lt;/span&gt; | &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read &lt;/span&gt;contract&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;"Compiling: &lt;/span&gt;&lt;span class="nv"&gt;$contract&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  npx 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="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &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="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/build"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If compilation fails, the error messages from &lt;code&gt;compactc&lt;/code&gt; are usually clear about what changed. Common patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: 'disclose' function signature changed in 0.15.0
# Fix: Update all disclose() calls to new signature

Error: Type 'Uint64' is no longer assignable to 'Field'
# Fix: Use explicit conversion via toField()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5: Regenerate Proving Keys
&lt;/h2&gt;

&lt;p&gt;After any compilation success, &lt;strong&gt;regenerate proving and verification keys&lt;/strong&gt;. Even if the circuit didn't visibly change, don't trust old keys.&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;# Script to regenerate all keys&lt;/span&gt;
&lt;span class="c"&gt;#!/bin/bash&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;CONTRACTS_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./contracts"&lt;/span&gt;
&lt;span class="nv"&gt;KEYS_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./keys"&lt;/span&gt;

&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEYS_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;contract_dir &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONTRACTS_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;/build&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;contract_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$contract_dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&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;"Generating keys for: &lt;/span&gt;&lt;span class="nv"&gt;$contract_name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  npx compact-cli keygen &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--circuit&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$contract_dir&lt;/span&gt;&lt;span class="s2"&gt;/circuit.json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--proving-key&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEYS_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;contract_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.pk"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--verification-key&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KEYS_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;contract_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.vk"&lt;/span&gt;

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"✅ Keys generated for &lt;/span&gt;&lt;span class="nv"&gt;$contract_name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"All keys regenerated. Commit the new keys."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: Proving keys must be committed to your repository. They're large (often 50-200MB), so use Git LFS if needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git lfs track &lt;span class="s2"&gt;"*.pk"&lt;/span&gt;
git lfs track &lt;span class="s2"&gt;"*.vk"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"*.pk filter=lfs diff=lfs merge=lfs -text"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitattributes
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"*.vk filter=lfs diff=lfs merge=lfs -text"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitattributes
git add .gitattributes
git lfs &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 6: Update the Witness Implementation
&lt;/h2&gt;

&lt;p&gt;After recompiling, review your TypeScript witness implementations. These are the most failure-prone area because TypeScript doesn't always catch circuit-level mismatches at compile time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to look for:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Old pattern (pre-0.15.0)&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;buildTransferWitness&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/compact-runtime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// New pattern (post-0.15.0) — function renamed&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;createTransferWitness&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/compact-runtime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Run the TypeScript compiler as a first check:&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;npx tsc &lt;span class="nt"&gt;--noEmit&lt;/span&gt; 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-50&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TypeScript errors reveal API changes. Fix all TypeScript errors before running tests.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Run the Full Test Suite
&lt;/h2&gt;

&lt;p&gt;After compilation and TypeScript checks pass, run your tests:&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;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Common test failure modes after upgrades:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Constraint violations in tests&lt;/strong&gt;: If tests fail with "constraint not satisfied," the witness implementation doesn't match the new circuit. Compare the old and new circuit constraint counts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Proof verification failures&lt;/strong&gt;: Old proofs in test fixtures are invalid. Regenerate test fixtures.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Timeout failures&lt;/strong&gt;: A new proving system may be slower. Increase test timeouts:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;   &lt;span class="c1"&gt;// In jest.config.ts&lt;/span&gt;
   &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="na"&gt;testTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;120000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 2 minutes for proof generation&lt;/span&gt;
   &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Network interaction failures&lt;/strong&gt;: If the SDK changed how it connects to the network, update your test setup.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Step 8: Test on Local Devnet, Then Testnet
&lt;/h2&gt;

&lt;p&gt;After local tests pass:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Deploy to local devnet&lt;/strong&gt; (if Midnight provides one)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run integration tests&lt;/strong&gt; against the devnet&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deploy to testnet&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run smoke tests&lt;/strong&gt; on testnet before committing to the upgrade&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you have existing testnet deployments, understand that &lt;strong&gt;you may need to redeploy contracts&lt;/strong&gt; if the circuit changed. Old contracts with new client code will produce proof verification failures.&lt;/p&gt;




&lt;h2&gt;
  
  
  Handling "I Can't Upgrade Right Now" Situations
&lt;/h2&gt;

&lt;p&gt;Sometimes you learn about a breaking SDK change but you can't migrate immediately (active user base, other ongoing work, etc.).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy 1: Lock the environment completely&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Prevent accidental upgrades at the system level:&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;# .npmrc — add to repo root&lt;/span&gt;
package-lock&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;engine-strict&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;# package.json engines field&lt;/span&gt;
&lt;span class="s2"&gt;"engines"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"node"&lt;/span&gt;: &lt;span class="s2"&gt;"&amp;gt;=18.0.0 &amp;lt;20.0.0"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Strategy 2: Document the freeze with a clear exit criteria&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;UPGRADE_BLOCKED.md&lt;/code&gt; in the repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Midnight SDK Upgrade Blocked&lt;/span&gt;

Current version: 0.14.0
Target version: 0.15.0
Blocked since: 2026-01-15
Blocker: Active testnet deployment with 200+ users
Exit criteria: Testnet migration window scheduled for 2026-02-01
Owner: @yourname

&lt;span class="gu"&gt;## What breaks in 0.15.0&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; [List specific breaking changes]
&lt;span class="p"&gt;-&lt;/span&gt; [Associated code areas]

&lt;span class="gu"&gt;## Migration plan&lt;/span&gt;
[Brief migration plan when the window opens]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Strategy 3: Run parallel environments&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For longer freezes, maintain two deployment environments with different SDK versions. Route traffic based on contract version detected from on-chain state.&lt;/p&gt;




&lt;h2&gt;
  
  
  Diagnosing Proof Generation Failures After Upgrade
&lt;/h2&gt;

&lt;p&gt;If proof generation fails after a successful upgrade, here's the diagnostic checklist:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Check proving key freshness&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;# Get the modification time of the compiled circuit&lt;/span&gt;
&lt;span class="nb"&gt;stat &lt;/span&gt;contracts/my-contract/build/circuit.json

&lt;span class="c"&gt;# Compare with proving key&lt;/span&gt;
&lt;span class="nb"&gt;stat &lt;/span&gt;keys/my-contract.pk

&lt;span class="c"&gt;# If circuit is newer than key: regenerate the key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Verify circuit input types&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;# Inspect the circuit's public inputs&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;contracts/my-contract/build/circuit.json | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import json, sys
circuit = json.load(sys.stdin)
print('Public inputs:')
for name, type_info in circuit.get('public_inputs', {}).items():
    print(f'  {name}: {type_info}')
print('Private inputs:')
for name, type_info in circuit.get('private_inputs', {}).items():
    print(f'  {name}: {type_info}')
"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare against your witness implementation and verify the types match exactly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Test with minimal inputs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Isolate the failing constraint with a minimal test:&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;should prove minimal transfer&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;proof&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;proveTransfer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;testKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;testAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;amount&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;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// minimum amount&lt;/span&gt;
    &lt;span class="na"&gt;balance&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;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// exactly equal to amount&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;proof&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;p&gt;&lt;strong&gt;4. Enable verbose proving output&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="nv"&gt;COMPACT_PROOF_VERBOSE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 npm &lt;span class="nb"&gt;test &lt;/span&gt;2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"constraint|witness|error"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Automating Upgrade Detection
&lt;/h2&gt;

&lt;p&gt;Set up a CI job to detect new SDK versions:&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="c1"&gt;# .github/workflows/check-sdk-updates.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check Midnight SDK Updates&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;9&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1'&lt;/span&gt;  &lt;span class="c1"&gt;# Every Monday 9AM&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;check-updates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-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;20'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check for SDK updates&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm install -g npm-check-updates&lt;/span&gt;
          &lt;span class="s"&gt;UPDATES=$(ncu --filter "@midnight-ntwrk/*" --jsonUpgraded 2&amp;gt;/dev/null || echo "{}")&lt;/span&gt;
          &lt;span class="s"&gt;if [ "$UPDATES" != "{}" ] &amp;amp;&amp;amp; [ "$UPDATES" != "null" ]; then&lt;/span&gt;
            &lt;span class="s"&gt;echo "New SDK versions available:"&lt;/span&gt;
            &lt;span class="s"&gt;echo $UPDATES&lt;/span&gt;
            &lt;span class="s"&gt;# Optionally create a GitHub issue or send a notification&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;After going through this a few times, it gets less painful — you develop a feel for what's likely to break and where. The things that burned me most: skipping key regeneration because "I didn't change the circuit logic" (I did, indirectly), and not testing witness edge cases after an upgrade because the TypeScript compiled clean.&lt;/p&gt;

&lt;p&gt;If you're building anything serious on Midnight right now, set up the weekly CI check for SDK updates and treat every version bump as a breaking change until proven otherwise. The proving key dependency is what makes these upgrades fundamentally different from normal library updates — once you've debugged a proof verification failure at 2am, you won't skip that step again.&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>tutorial</category>
      <category>web3</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Running a Midnight Node: Setup, Sync, and Monitoring</title>
      <dc:creator>Tosh</dc:creator>
      <pubDate>Thu, 16 Apr 2026 18:22:47 +0000</pubDate>
      <link>https://dev.to/tosh2308/running-a-midnight-node-setup-sync-and-monitoring-4ig6</link>
      <guid>https://dev.to/tosh2308/running-a-midnight-node-setup-sync-and-monitoring-4ig6</guid>
      <description>&lt;h1&gt;
  
  
  Running a Midnight Node: Setup, Sync, and Monitoring
&lt;/h1&gt;

&lt;p&gt;I set up a Midnight node on a spare Ubuntu box last month and hit pretty much every failure mode there is — 0 peers for 20 minutes, stuck on block 1 for an hour, then a surprise OOM kill that corrupted the database. This guide documents what actually works, including the "stuck on block 1" issue that seems to catch everyone off guard the first time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardware Requirements
&lt;/h2&gt;

&lt;p&gt;Before starting, confirm your hardware meets these 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;100 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&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Ubuntu 22.04 LTS&lt;/td&gt;
&lt;td&gt;Ubuntu 22.04 LTS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Why SSD matters&lt;/strong&gt;: Midnight, like Cardano, performs heavy ledger operations during sync. Mechanical drives will make initial sync take 10-20x longer and may cause peer disconnections as your node fails to keep up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RAM consideration&lt;/strong&gt;: The proof server (if running locally) requires an additional 4-8 GB during proof generation. On a node dedicated to block validation only, 8 GB is sufficient.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installation via Docker (Recommended)
&lt;/h2&gt;

&lt;p&gt;Docker is the supported deployment method. It handles dependency management and makes upgrades straightforward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Install Docker&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;# Ubuntu 22.04&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; docker.io docker-compose-plugin
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; docker
&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;
&lt;span class="c"&gt;# Log out and back in for group changes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Pull the Midnight node image&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;docker pull midnightntwrk/midnight-node:latest
&lt;span class="c"&gt;# Or pin a specific version (recommended for production):&lt;/span&gt;
docker pull midnightntwrk/midnight-node:0.14.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Create a working directory and configuration&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="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/midnight-node/&lt;span class="o"&gt;{&lt;/span&gt;data,config,logs&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/midnight-node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;docker-compose.yml&lt;/code&gt;:&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;midnightntwrk/midnight-node:0.14.0&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;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;9944:9944"&lt;/span&gt;   &lt;span class="c1"&gt;# RPC WebSocket&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9933:9933"&lt;/span&gt;   &lt;span class="c1"&gt;# RPC HTTP&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="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;./data:/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./config:/config&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./logs:/logs&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 testnet&lt;/span&gt;
      &lt;span class="s"&gt;--port 30333&lt;/span&gt;
      &lt;span class="s"&gt;--rpc-port 9933&lt;/span&gt;
      &lt;span class="s"&gt;--ws-port 9944&lt;/span&gt;
      &lt;span class="s"&gt;--rpc-cors all&lt;/span&gt;
      &lt;span class="s"&gt;--unsafe-rpc-external&lt;/span&gt;
      &lt;span class="s"&gt;--name "my-midnight-node"&lt;/span&gt;
    &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json-file"&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;max-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100m"&lt;/span&gt;
        &lt;span class="na"&gt;max-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Start the node&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;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt; midnight-node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Understanding the Initial Sync
&lt;/h2&gt;

&lt;p&gt;When you start a fresh node, it goes through three phases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 1: Peer Discovery&lt;/strong&gt; (seconds to minutes)&lt;br&gt;
Your node announces itself to the network and discovers peers. You'll see logs like:&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 (0 peers), best: #0 (0x0000…0000)
INFO network  Discovered new external address
INFO network  Connected to peer 12D3Koo...
&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 (see troubleshooting below).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 2: Header Sync&lt;/strong&gt; (minutes to hours)&lt;br&gt;
Your node downloads block headers first, much faster than full blocks:&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=#485231 (8 peers)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"bps" here means blocks per second. Rates of 100-1000 bps are normal during header sync.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 3: Block Execution&lt;/strong&gt; (hours to days depending on chain age)&lt;br&gt;
Full block execution is the slow part — every transaction is re-executed and state is computed:&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=#485231 (8 peers)
INFO state  Applied block #102847
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rates of 5-50 bps during execution are normal. Don't panic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long does full sync take?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For Midnight testnet (still relatively young):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hardware minimum: 6-12 hours&lt;/li&gt;
&lt;li&gt;Hardware recommended: 2-4 hours&lt;/li&gt;
&lt;li&gt;With fast NVMe SSD and strong CPU: 1-2 hours&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Monitoring Block Height
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Method 1: RPC polling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once your node is running, poll its current best block:&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 current synced block&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_getBlock","params":[]}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://localhost:9933 | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import json, sys
d = json.load(sys.stdin)
block = d['result']['block']['header']
print(f&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Block: #{int(block['number'], 16)}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;)
print(f&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Hash: {block['parentHash'][:20]}...&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;p&gt;&lt;strong&gt;Method 2: WebSocket subscription&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For real-time monitoring:&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 wscat if needed&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; wscat

wscat &lt;span class="nt"&gt;-c&lt;/span&gt; ws://localhost:9944 &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;'
{"id":1,"jsonrpc":"2.0","method":"chain_subscribeNewHeads","params":[]}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Method 3: Simple sync check script&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Save as &lt;code&gt;check_sync.sh&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="nv"&gt;LOCAL_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;
  http://localhost:9933 | 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="c"&gt;# Compare against a known good peer (replace with actual peer RPC)&lt;/span&gt;
&lt;span class="c"&gt;# PEER_BLOCK=$(...)&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Local best block: #&lt;/span&gt;&lt;span class="nv"&gt;$LOCAL_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;"Time: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Check if node is making progress by comparing with previous run&lt;/span&gt;
&lt;span class="nv"&gt;PREV_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/tmp/midnight_prev_block"&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;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PREV_FILE&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="nv"&gt;PREV_BLOCK&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;$PREV_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;PROGRESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;LOCAL_BLOCK &lt;span class="o"&gt;-&lt;/span&gt; PREV_BLOCK&lt;span class="k"&gt;))&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Progress since last check: +&lt;/span&gt;&lt;span class="nv"&gt;$PROGRESS&lt;/span&gt;&lt;span class="s2"&gt; blocks"&lt;/span&gt;
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$LOCAL_BLOCK&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PREV_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run on a schedule:&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;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;
  
  
  Diagnosing "Stuck on Block 1"
&lt;/h2&gt;

&lt;p&gt;This is the most common issue new node operators hit. Symptoms:&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 (0 peers), best: #1 (0x1234…abcd)
# or
INFO sync  ⚙️  Syncing 0.0 bps, target=#485231 (3 peers)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your node has peers but isn't advancing. Here's the diagnostic tree:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check 1: Are peers actually connected?&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;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_peers","params":[]}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://localhost:9933 | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import json, sys
peers = json.load(sys.stdin)['result']
print(f'Connected peers: {len(peers)}')
for p in peers:
    print(f&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;  {p['peerId'][:20]}... best: #{p['bestNumber']}&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;p&gt;If peers show &lt;code&gt;bestNumber: 1&lt;/code&gt;, your entire peer set is also stuck — you may have hit isolated testnet nodes. Restart and wait for better peers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check 2: Is the network port accessible?&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;# From another machine or use online port checker&lt;/span&gt;
nc &lt;span class="nt"&gt;-zv&lt;/span&gt; YOUR_SERVER_IP 30333
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If port 30333 isn't reachable from the internet, you'll only get local peers (usually none). Fix firewall rules:&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;# Ubuntu UFW&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow 30333/tcp
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If behind NAT (home network), you need port forwarding on your router for port 30333.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check 3: Storage I/O performance&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 storage is the bottleneck&lt;/span&gt;
iostat &lt;span class="nt"&gt;-x&lt;/span&gt; 1 5

&lt;span class="c"&gt;# Look at the %util column for your storage device&lt;/span&gt;
&lt;span class="c"&gt;# &amp;gt;80% sustained means storage is saturating&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your storage is consistently &amp;gt;80% utilized, the node is I/O bottlenecked. Migrate to faster SSD.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check 4: Memory pressure&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;free &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;span class="c"&gt;# If available memory &amp;lt; 1GB, you have memory pressure&lt;/span&gt;

&lt;span class="c"&gt;# Check if the node is being OOM-killed&lt;/span&gt;
docker inspect midnight-node | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A5&lt;/span&gt; &lt;span class="s2"&gt;"State"&lt;/span&gt;
dmesg | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"out of memory"&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Check 5: Corrupt chain database&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the node was killed ungracefully (power loss, kill -9), the chain database may be corrupt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose down
&lt;span class="c"&gt;# Back up and remove the data directory&lt;/span&gt;
&lt;span class="nb"&gt;mv &lt;/span&gt;data data.bak
&lt;span class="nb"&gt;mkdir &lt;/span&gt;data
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;span class="c"&gt;# Node will re-sync from genesis&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Resource Requirements During Operation
&lt;/h2&gt;

&lt;p&gt;After initial sync completes, steady-state resource usage:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CPU&lt;/strong&gt;: 5-20% on a 4-core machine during normal operation. Spikes to 80%+ during epoch transitions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RAM&lt;/strong&gt;: 2-4 GB for the node process alone. Add proof server if running locally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Storage growth rate&lt;/strong&gt;: Approximately 5-15 GB/month depending on network activity (testnet figures — mainnet will differ).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Network&lt;/strong&gt;: 50-200 MB/hour download, 20-50 MB/hour upload for a non-archival node.&lt;/p&gt;




&lt;h2&gt;
  
  
  Verifying Your Node is Synced and Healthy
&lt;/h2&gt;

&lt;p&gt;When sync completes, you'll see:&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), best: #485231 (0xabcd…1234)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Idle" with the current chain head block number = fully synced.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Health check script&lt;/strong&gt; (&lt;code&gt;health_check.sh&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="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:9933"&lt;/span&gt;

&lt;span class="c"&gt;# Get sync state&lt;/span&gt;
&lt;span class="nv"&gt;SYNC&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;&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;$SYNC&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;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;$SYNC&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="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"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="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="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 synced and healthy"&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="c"&gt;# Get current block&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;"⏳ Still syncing, current 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;fi&lt;/span&gt;

&lt;span class="c"&gt;# Check peer connectivity&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;$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 rules."&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this check after every deployment or restart.&lt;/p&gt;




&lt;h2&gt;
  
  
  Monitoring with Docker Logs
&lt;/h2&gt;

&lt;p&gt;The Midnight node logs are structured and informative. Key patterns to watch:&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;# Follow logs in real time&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt; midnight-node

&lt;span class="c"&gt;# Filter for errors only&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;--since&lt;/span&gt; 1h midnight-node | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"ERROR|WARN"&lt;/span&gt;

&lt;span class="c"&gt;# Check sync rate (last 10 sync messages)&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;--since&lt;/span&gt; 1h midnight-node | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"Syncing"&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Concerning log patterns:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WARN network  Dropping slow peer&lt;/code&gt;: One or two is fine, many indicate network issues&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ERROR import  Failed to import block&lt;/code&gt;: Usually means corrupt database or invalid block — investigate immediately&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WARN sync  Reorg at block&lt;/code&gt;: Chain reorganization — normal if infrequent, concerning if frequent&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WARN peering  Peer disconnected&lt;/code&gt;: Normal to see occasionally; constant disconnects indicate networking or resource issues&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Operational Best Practices
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Automatic restart on failure&lt;/strong&gt;:&lt;br&gt;
The &lt;code&gt;restart: unless-stopped&lt;/code&gt; in the compose file handles this. Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker inspect midnight-node | &lt;span class="nb"&gt;grep &lt;/span&gt;RestartPolicy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Log rotation&lt;/strong&gt;: Already configured in the compose file above. Verify rotation is working:&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;ls&lt;/span&gt; &lt;span class="nt"&gt;-lh&lt;/span&gt; ~/midnight-node/logs/
docker system &lt;span class="nb"&gt;df&lt;/span&gt;  &lt;span class="c"&gt;# Check Docker's disk usage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Monitoring alerting&lt;/strong&gt;: Set up a simple cron job to alert if the node falls behind:&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;# Add to crontab: crontab -e&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt;/5 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /home/user/midnight-node/health_check.sh &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /home/user/midnight-node/logs/health.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Backup strategy&lt;/strong&gt;: The node database can be re-synced from genesis, so it doesn't need backup. What does need backup: any private keys used for block authoring (if you're running a validator, which requires separate setup).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upgrade procedure&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;docker compose pull          &lt;span class="c"&gt;# Get new image&lt;/span&gt;
docker compose down          &lt;span class="c"&gt;# Stop current node&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;         &lt;span class="c"&gt;# Start with new image&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt;       &lt;span class="c"&gt;# Watch startup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Common Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: Can I run this on a VPS/cloud server?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Most cloud providers work. Avoid burstable CPU instances (AWS T-series, GCP E2) for sustained sync workloads — they'll throttle under sustained load. Use compute-optimized instances with dedicated CPU.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Do I need to keep port 30333 open?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For syncing: yes, strongly recommended. Without inbound P2P connections, your node relies entirely on outbound connections and has fewer peers, which slows sync and makes you vulnerable to being isolated from the main network.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: How do I know if my node is on the canonical chain?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Compare your node's best block hash with hashes from the Midnight block explorer. If they match at the same height, you're on the canonical chain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What's the difference between an archival and non-archival node?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A non-archival node (default) prunes old state to save space. An archival node (&lt;code&gt;--pruning archive&lt;/code&gt;) keeps all historical state, useful for querying historical data. Archival nodes require significantly more storage.&lt;/p&gt;




&lt;p&gt;In my experience, 90% of "stuck at block 1" issues come down to two things: port 30333 isn't reachable from outside, or you're running on spinning rust and the I/O just can't keep up. Check those before going deeper into the diagnostic tree.&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>devops</category>
      <category>web3</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Security Checklist for Midnight dApps Before Deployment</title>
      <dc:creator>Tosh</dc:creator>
      <pubDate>Thu, 16 Apr 2026 18:16:00 +0000</pubDate>
      <link>https://dev.to/tosh2308/security-checklist-for-midnight-dapps-before-deployment-5bi9</link>
      <guid>https://dev.to/tosh2308/security-checklist-for-midnight-dapps-before-deployment-5bi9</guid>
      <description>&lt;h1&gt;
  
  
  Security Checklist for Midnight dApps Before Deployment
&lt;/h1&gt;

&lt;p&gt;I've been going through Midnight's Compact contracts over the past few weeks and kept hitting the same class of bugs — not the kind that throw errors during compilation, but the kind that silently misbehave at runtime. ZK architecture introduces failure modes that EVM developers don't have muscle memory for: constraint violations that only surface during proof generation, disclosure leaks that pass all your unit tests, witness bugs that produce valid-looking but incorrect proofs.&lt;/p&gt;

&lt;p&gt;This is the checklist I put together after working through these issues. It's focused specifically on Midnight — not generic smart contract security.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. &lt;code&gt;disclose()&lt;/code&gt; Audit: No Unintended Secret Leaks
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;disclose()&lt;/code&gt; operation in Compact moves data from private state to public state. It's necessary for transparency (e.g., emitting events, publishing commitments) but dangerous when applied carelessly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to look for:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// DANGEROUS: Accidentally disclosing private user data&lt;/span&gt;
&lt;span class="nx"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;completeOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;userBalance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// private&lt;/span&gt;
  &lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// public&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// This discloses the private balance — DON'T DO THIS&lt;/span&gt;
  &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userBalance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// OK: orderId was already public&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;Correct pattern:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;completeOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;userBalance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// private&lt;/span&gt;
  &lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// public&lt;/span&gt;
  &lt;span class="nx"&gt;orderAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// private&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Only disclose what needs to be public&lt;/span&gt;
  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userBalance&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;orderAmount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderAmount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// OK: amount is disclosed by design&lt;/span&gt;
  &lt;span class="c1"&gt;// userBalance stays private&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;Checklist items:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Enumerate every &lt;code&gt;disclose()&lt;/code&gt; call in your codebase&lt;/li&gt;
&lt;li&gt;[ ] Confirm each disclosure is intentional and documented&lt;/li&gt;
&lt;li&gt;[ ] Ask: "If an adversary sees this disclosed value, can they infer private state?"&lt;/li&gt;
&lt;li&gt;[ ] Check for indirect disclosure (disclosing a value derived from private data can leak the private data)&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;&lt;code&gt;ownPublicKey()&lt;/code&gt; retrieves the caller's public key within a circuit. There's a known vulnerability pattern: if you use the result of &lt;code&gt;ownPublicKey()&lt;/code&gt; in a way that allows an adversary to choose a specific key for them, you may enable impersonation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The vulnerability pattern:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// VULNERABLE: attacker can craft inputs to pass as someone else&lt;/span&gt;
&lt;span class="nx"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;withdrawFunds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;claimedKey&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="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// If signature verification is flawed, anyone can withdraw&lt;/span&gt;
  &lt;span class="nf"&gt;verifySignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;claimedKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;claimedKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&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;Safer pattern — use &lt;code&gt;ownPublicKey()&lt;/code&gt; directly:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;withdrawFunds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// The key comes from the ZK proof itself, not from caller input&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PublicKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ownPublicKey&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;caller&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;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nx"&gt;amount&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;Checklist items:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] If you accept &lt;code&gt;PublicKey&lt;/code&gt; as a parameter, verify you have a cryptographic reason for doing so&lt;/li&gt;
&lt;li&gt;[ ] Prefer &lt;code&gt;ownPublicKey()&lt;/code&gt; over accepting keys as inputs wherever possible&lt;/li&gt;
&lt;li&gt;[ ] Review every function that accepts a &lt;code&gt;PublicKey&lt;/code&gt; parameter for authorization logic&lt;/li&gt;
&lt;li&gt;[ ] Ensure key derivation functions aren't exploitable with crafted inputs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Replay Protection: Nonces and Nullifiers
&lt;/h2&gt;

&lt;p&gt;Without replay protection, an attacker can resubmit a valid ZK proof to execute the same operation multiple times. This is the ZK equivalent of a reentrancy attack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two standard mechanisms:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nonce-based protection (for sequential operations):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;nonces&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="nx"&gt;PublicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;executeAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// ... other params&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&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;caller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ownPublicKey&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;expectedNonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nonces&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;expectedNonce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid nonce&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;nonces&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;expectedNonce&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="c1"&gt;// ... rest of logic&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;Nullifier-based protection (for one-time use):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;spentNullifiers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Field&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;spendNote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;noteCommitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Field&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="nx"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// derived from the note's secret&lt;/span&gt;
  &lt;span class="c1"&gt;// ... other params&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;spentNullifiers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Note already spent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;spentNullifiers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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;// ... process note&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;Checklist items:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Every state-modifying operation has replay protection&lt;/li&gt;
&lt;li&gt;[ ] Nonces are user-specific, not global (global nonces serialize all transactions)&lt;/li&gt;
&lt;li&gt;[ ] Nullifiers are cryptographically bound to the specific note/credential being consumed&lt;/li&gt;
&lt;li&gt;[ ] Nullifier derivation doesn't leak private data about the note&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Midnight's dual ledger model lets you mark fields as exported to the public ledger. Exported fields are visible to all observers. Review every exported field as if it will be on a public blockchain forever — because it will be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to audit:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// These are ALL visible publicly:&lt;/span&gt;
  &lt;span class="nl"&gt;totalSupply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c1"&gt;// OK: intentionally public&lt;/span&gt;
  &lt;span class="nl"&gt;merkleRoot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// OK: commitment, no private info&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;userBalances&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="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;// OK: private, only commitments exported&lt;/span&gt;

  &lt;span class="c1"&gt;// POTENTIAL ISSUE: Is this field revealing too much?&lt;/span&gt;
  &lt;span class="nl"&gt;lastTransactionTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Timestamp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Leaks activity patterns&lt;/span&gt;
  &lt;span class="nl"&gt;participantCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// May correlate with private activity&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;Checklist items:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] List every exported ledger field&lt;/li&gt;
&lt;li&gt;[ ] For each field: "What does an adversary learn from watching this field change?"&lt;/li&gt;
&lt;li&gt;[ ] Check for timing-channel leaks (when something changes can be as revealing as what changes)&lt;/li&gt;
&lt;li&gt;[ ] Verify that commitment schemes don't leak set membership information&lt;/li&gt;
&lt;li&gt;[ ] Consider correlation attacks: can exported fields be combined with other public data to infer private state?&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Witnesses are the off-chain computations that generate inputs to ZK circuits. A bug in a witness doesn't fail with an error — it generates incorrect proofs that may still verify. This is the sneakiest class of Midnight bug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common witness bugs:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// TypeScript witness implementation&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateTransferWitness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;fromSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SecretKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;toAddress&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="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;currentBalance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TransferWitness&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;// BUG: Off-by-one in balance check&lt;/span&gt;
  &lt;span class="c1"&gt;// The circuit checks balance &amp;gt;= amount&lt;/span&gt;
  &lt;span class="c1"&gt;// But the witness passes balance - 1, which will fail constraint&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentBalance&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// WRONG&lt;/span&gt;
    &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// CORRECT:&lt;/span&gt;
  &lt;span class="c1"&gt;// return {&lt;/span&gt;
  &lt;span class="c1"&gt;//   balance: currentBalance,&lt;/span&gt;
  &lt;span class="c1"&gt;//   amount: amount,&lt;/span&gt;
  &lt;span class="c1"&gt;// };&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Checklist items:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Every circuit constraint has a corresponding witness computation test&lt;/li&gt;
&lt;li&gt;[ ] Witness inputs match circuit input types precisely (bit widths, field sizes)&lt;/li&gt;
&lt;li&gt;[ ] Test witnesses with edge cases: zero values, maximum values, equal values (e.g., &lt;code&gt;amount == balance&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;[ ] Verify witness generation doesn't fail silently — errors should propagate, not return invalid witnesses&lt;/li&gt;
&lt;li&gt;[ ] Test that invalid witnesses correctly fail proof generation&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Midnight's SDK is evolving. API changes between versions can break proof generation in non-obvious ways.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What can break between versions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Constraint system changes invalidate old proofs&lt;/li&gt;
&lt;li&gt;API renames cause silent failures if TypeScript types are too permissive&lt;/li&gt;
&lt;li&gt;Proving key format changes require key regeneration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Checklist items:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Pin exact SDK versions in &lt;code&gt;package.json&lt;/code&gt; (no &lt;code&gt;^&lt;/code&gt; or &lt;code&gt;~&lt;/code&gt; prefixes for Midnight packages)&lt;/li&gt;
&lt;li&gt;[ ] Check Midnight's changelog for breaking changes before upgrading&lt;/li&gt;
&lt;li&gt;[ ] Regenerate proving keys after any SDK upgrade — don't reuse keys from a previous version&lt;/li&gt;
&lt;li&gt;[ ] Test the full proof generation pipeline after version updates, not just compilation&lt;/li&gt;
&lt;li&gt;[ ] Document the exact SDK version in your deployment runbook&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pin versions explicitly:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@midnight-ntwrk/compact-runtime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.14.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@midnight-ntwrk/midnight-js-types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.14.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@midnight-ntwrk/midnight-js-contracts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.14.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;Testnet is where you find proof generation failures, gas estimation issues, and latency problems. Deploy early, test thoroughly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proof generation failure modes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Constraint violations&lt;/strong&gt;: Circuit assertions fail during witness generation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Out-of-memory&lt;/strong&gt;: Complex circuits require significant RAM for proof generation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeout&lt;/strong&gt;: Proof generation takes longer than client timeout limits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale proving keys&lt;/strong&gt;: Circuit changed without regenerating keys&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Testnet testing checklist:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Complete the full transaction lifecycle: generate witness → generate proof → submit → verify&lt;/li&gt;
&lt;li&gt;[ ] Test with realistic data sizes (if your circuit handles 1000 items, test with 1000 items)&lt;/li&gt;
&lt;li&gt;[ ] Measure proof generation time on a standard consumer machine, not a dev workstation&lt;/li&gt;
&lt;li&gt;[ ] Test error handling: what happens when proof generation fails?&lt;/li&gt;
&lt;li&gt;[ ] Verify all circuit variants are tested (don't only test the happy path)&lt;/li&gt;
&lt;li&gt;[ ] Confirm wallet integration works with the proof server&lt;/li&gt;
&lt;li&gt;[ ] Test behavior when the proof server is slow or unreachable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Latency benchmarking:&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;# Time a full proof generation cycle&lt;/span&gt;
&lt;span class="nb"&gt;time &lt;/span&gt;npx compact-cli prove &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--circuit&lt;/span&gt; transfer &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--inputs&lt;/span&gt; ./test-inputs.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--proving-key&lt;/span&gt; ./proving-key.bin &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; ./proof.bin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;Before mainnet deployment, confirm:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclosure&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] All &lt;code&gt;disclose()&lt;/code&gt; calls are intentional and documented&lt;/li&gt;
&lt;li&gt;[ ] No indirect private data leaks through disclosed derived values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Authorization&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;ownPublicKey()&lt;/code&gt; used for caller identification&lt;/li&gt;
&lt;li&gt;[ ] No &lt;code&gt;PublicKey&lt;/code&gt; parameters accepted without cryptographic justification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Replay Protection&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] All state-modifying circuits have nonce or nullifier protection&lt;/li&gt;
&lt;li&gt;[ ] Nullifiers are cryptographically bound to specific consumed resources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Ledger Design&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Every public ledger field is intentionally public&lt;/li&gt;
&lt;li&gt;[ ] Timing channels and correlation attacks are considered&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Witness Correctness&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] All witnesses have unit tests&lt;/li&gt;
&lt;li&gt;[ ] Edge cases are covered (zero, max, equal values)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Version Management&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Exact SDK versions pinned&lt;/li&gt;
&lt;li&gt;[ ] Proving keys match current circuit version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Testnet Validation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Full lifecycle tested end-to-end&lt;/li&gt;
&lt;li&gt;[ ] Proof generation benchmarked on standard hardware&lt;/li&gt;
&lt;li&gt;[ ] Error handling verified&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  A Note on What This Checklist Doesn't Cover
&lt;/h2&gt;

&lt;p&gt;Security doesn't stop here. This checklist covers Midnight-specific issues. You also need to audit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Off-chain witness code&lt;/strong&gt; for standard application security vulnerabilities (input validation, authentication, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend code&lt;/strong&gt; for wallet interaction security&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key management&lt;/strong&gt; — how are secret keys stored and used in production?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The deployment process itself&lt;/strong&gt; — who controls the upgrade keys?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ZK cryptography is only as secure as the code around it. A mathematically perfect ZK circuit can be defeated by a compromised witness generator.&lt;/p&gt;

&lt;p&gt;Privacy bugs don't announce themselves. There's no revert, no thrown exception — just data that was supposed to stay hidden showing up somewhere it shouldn't. Testnet is cheap, mainnet isn't. Worth the time to go through this before you ship.&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>security</category>
      <category>zk</category>
      <category>web3</category>
    </item>
    <item>
      <title>Midnight vs. Aztec vs. Aleo vs. Mina vs. Zcash: A Developer's Honest Guide to Privacy Chain Architecture</title>
      <dc:creator>Tosh</dc:creator>
      <pubDate>Thu, 16 Apr 2026 18:14:31 +0000</pubDate>
      <link>https://dev.to/tosh2308/midnight-vs-aztec-vs-aleo-vs-mina-vs-zcash-a-developers-honest-guide-to-privacy-chain-kc5</link>
      <guid>https://dev.to/tosh2308/midnight-vs-aztec-vs-aleo-vs-mina-vs-zcash-a-developers-honest-guide-to-privacy-chain-kc5</guid>
      <description>&lt;h1&gt;
  
  
  Midnight vs. Aztec vs. Aleo vs. Mina vs. Zcash: A Developer's Guide to Privacy Chain Architecture
&lt;/h1&gt;

&lt;p&gt;Privacy-preserving blockchains have moved from concept to production, but the tradeoffs between them are rarely explained honestly. This guide compares five serious contenders from the perspective of someone building applications — not investing in tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Problem Each Chain Solves
&lt;/h2&gt;

&lt;p&gt;Before diving into architecture, understand what "privacy" means here. These chains all use zero-knowledge proofs, but they solve different problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zcash&lt;/strong&gt;: Privacy for &lt;em&gt;payments&lt;/em&gt; — hide sender, receiver, amount&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mina&lt;/strong&gt;: Privacy for &lt;em&gt;computation&lt;/em&gt; — prove you ran code without revealing inputs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aztec&lt;/strong&gt;: Privacy for &lt;em&gt;state&lt;/em&gt; — hide smart contract state from public chains&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aleo&lt;/strong&gt;: Privacy for &lt;em&gt;programs&lt;/em&gt; — compile private + public logic into a single VM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Midnight&lt;/strong&gt;: Privacy for &lt;em&gt;data&lt;/em&gt; — define precisely what data stays private and what's disclosed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That framing matters for choosing the right tool.&lt;/p&gt;




&lt;h2&gt;
  
  
  Midnight: The Data Sovereignty Approach
&lt;/h2&gt;

&lt;p&gt;Midnight, built by Input Output Global (IOG) on Cardano's infrastructure, takes a fundamentally different design philosophy. Rather than making everything private by default, it gives developers explicit control over what gets disclosed.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Dual Ledger Model
&lt;/h3&gt;

&lt;p&gt;Midnight operates a &lt;strong&gt;dual ledger&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Public ledger&lt;/strong&gt;: Standard on-chain state, visible to all — Merkle tree commitments, balances, contract addresses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private ledger&lt;/strong&gt;: Off-chain state stored only by relevant parties, proven via ZK proofs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a meaningful architectural choice. Your contract can have fields that are provably correct without being publicly visible. A compliance check can verify "this user is over 18" without revealing their age or identity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compact Language
&lt;/h3&gt;

&lt;p&gt;Midnight's smart contract language is &lt;strong&gt;Compact&lt;/strong&gt;, described as TypeScript-like. Contracts look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Compact pseudocode&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// public state - everyone sees this&lt;/span&gt;
  &lt;span class="nl"&gt;totalSupply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// private state - only the owner knows&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;balances&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="nx"&gt;PublicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Uint64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SecretKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;to&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="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Uint64&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// this runs in ZK proof generation&lt;/span&gt;
  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;from&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="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;from&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="o"&gt;-=&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;@private&lt;/code&gt; annotation controls what hits the public ledger vs. stays off-chain. TypeScript developers can pick this up quickly — no Rust knowledge required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer experience verdict&lt;/strong&gt;: Friendly to Web2 devs. TypeScript familiarity lowers the learning curve significantly. The downside: the ecosystem is very young (2024-2026 range), documentation gaps exist, and tooling is immature compared to EVM chains.&lt;/p&gt;

&lt;h3&gt;
  
  
  Zswap and Proof Generation
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Zswap&lt;/strong&gt; protocol handles transaction shielding. ZK proof generation happens &lt;strong&gt;client-side&lt;/strong&gt; via a proof server that runs locally. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users don't reveal private inputs to any server&lt;/li&gt;
&lt;li&gt;First transaction is slow (~10-30 seconds for proof generation on standard hardware)&lt;/li&gt;
&lt;li&gt;Subsequent transactions get faster with caching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real limitation&lt;/strong&gt;: The proof server requirement adds UX complexity. Users need software beyond a browser extension. The &lt;strong&gt;dApp Connector API&lt;/strong&gt; (via Lace or 1AM wallets) abstracts this somewhat, but the setup overhead is real.&lt;/p&gt;




&lt;h2&gt;
  
  
  Aztec: The Ethereum Privacy Layer
&lt;/h2&gt;

&lt;p&gt;Aztec's pitch is "Ethereum, but private." It runs as an L2 on Ethereum, which is both its strength and weakness.&lt;/p&gt;

&lt;h3&gt;
  
  
  Noir: The ZK Circuit Language
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Noir&lt;/strong&gt; is Aztec's domain-specific language for writing ZK circuits. It compiles to Ethereum-compatible bytecode. The syntax is Rust-inspired:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Noir example&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// private witness&lt;/span&gt;
    &lt;span class="n"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// public input&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;pub&lt;/code&gt; keyword marks public inputs; everything else is private. For pure ZK proofs this is clean. For complex dApp logic, it gets verbose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aztec.nr&lt;/strong&gt; extends Noir for smart contracts with public and private functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;contract&lt;/span&gt; &lt;span class="n"&gt;Token&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// public storage - on-chain visible&lt;/span&gt;
    &lt;span class="nd"&gt;#[aztec(storage)]&lt;/span&gt;
    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Storage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PublicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PublicMutable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// private function - runs in ZK&lt;/span&gt;
    &lt;span class="nd"&gt;#[aztec(private)]&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AztecAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AztecAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&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;h3&gt;
  
  
  The UTXO Note Model
&lt;/h3&gt;

&lt;p&gt;Aztec uses a &lt;strong&gt;notes-based UTXO model&lt;/strong&gt; for private state. Private "notes" are encrypted and stored in a note hash tree. Spending a note nullifies it (like Bitcoin UTXOs). Public state uses storage slots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tradeoff&lt;/strong&gt;: This is conceptually clean for payment-like applications but awkward for complex state machines. If you're building a private DEX with order books, you'll fight the UTXO model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proving system&lt;/strong&gt;: Ultra-PLONK. Fast verification, production-grade. Aztec has been in production longer than most and has battle-tested cryptography.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer experience verdict&lt;/strong&gt;: Best documentation of the group. Ethereum-native developers adapt well. The UTXO mental model is foreign to Solidity developers but well-documented. Rust/Solidity background helps significantly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Aleo: Programs All the Way Down
&lt;/h2&gt;

&lt;p&gt;Aleo's approach: make privacy the default for all execution, not just selected state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Leo Language
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Leo&lt;/strong&gt; is Rust-inspired with ZK semantics baked in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Leo example&lt;/span&gt;
&lt;span class="n"&gt;program&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="py"&gt;.aleo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="n"&gt;Token&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// encrypted&lt;/span&gt;
        &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// encrypted&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;transition&lt;/span&gt; &lt;span class="nf"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// private input (record)&lt;/span&gt;
        &lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// private&lt;/span&gt;
        &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// private&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// returns two records: change + new token&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Token&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="py"&gt;.owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="py"&gt;.amount&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;new_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Token&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="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="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Records are the core abstraction — analogous to Aztec's notes but with tighter language integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Records Model
&lt;/h3&gt;

&lt;p&gt;Aleo's &lt;strong&gt;records&lt;/strong&gt; are encrypted data structures owned by an address. They're consumed (destroyed) and created in each transaction — pure functional style.&lt;/p&gt;

&lt;p&gt;Public state exists too (&lt;code&gt;mappings&lt;/code&gt; in Leo), but it's treated as a second-class citizen. The entire execution model optimizes for records.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SnarkVM&lt;/strong&gt; handles compilation and proof generation. Proofs are generated locally and verified on-chain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real limitations&lt;/strong&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Testnet maturity issues — the ecosystem is still maturing&lt;/li&gt;
&lt;li&gt;Developer tooling is less polished than Aztec&lt;/li&gt;
&lt;li&gt;Finding good examples and documentation requires significant effort&lt;/li&gt;
&lt;li&gt;The records model is powerful but has a steep learning curve&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Developer experience verdict&lt;/strong&gt;: Challenging. Rust developers will adapt, but the records mental model doesn't map cleanly to any existing paradigm. Good for greenfield privacy applications; difficult for migrating existing logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mina: Constant-Size Blockchain with zkApps
&lt;/h2&gt;

&lt;p&gt;Mina takes a completely different angle — the entire blockchain is compressed to ~22KB using recursive SNARKs. This enables client-side verification of the entire chain history.&lt;/p&gt;

&lt;h3&gt;
  
  
  o1js: TypeScript for ZK Circuits
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;o1js&lt;/strong&gt; (formerly SnarkyJS) is the standout developer experience here. You write ZK circuits in TypeScript:&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;Field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SmartContract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;method&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;o1js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;SmartContract&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Field&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;method&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAndRequireEquals&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;count&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;currentCount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;method&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;assertMinimum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// This method proves minimum without revealing count&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAndRequireEquals&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;currentCount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertGreaterThanOrEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the most accessible ZK development experience available. Standard TypeScript tooling works. Existing web developers can ship zkApps without learning a new language.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recursive SNARKs and zkApps
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;zkApps&lt;/strong&gt; are Mina's smart contracts. They run off-chain (in the browser or server) and submit ZK proofs on-chain. The chain only stores a constant-size proof — not the full execution trace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kimchi&lt;/strong&gt; is the proving system — a variation of PLONK optimized for recursive composition.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real limitations&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Private &lt;em&gt;state storage&lt;/em&gt; is genuinely constrained — you get 8 field elements of on-chain state per contract&lt;/li&gt;
&lt;li&gt;For applications needing large private state, you'll use off-chain storage (IPFS, Arweave) and commit hashes on-chain&lt;/li&gt;
&lt;li&gt;The constant-size chain is a design choice that constrains throughput&lt;/li&gt;
&lt;li&gt;Ecosystem is smaller than Ethereum-adjacent chains&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Developer experience verdict&lt;/strong&gt;: Best DX for TypeScript/web developers by a significant margin. If your team knows JavaScript, Mina has the lowest ramp-up time. The 8-field state limit requires creative architecture for complex applications.&lt;/p&gt;




&lt;h2&gt;
  
  
  Zcash: The Pioneer, Now Limited
&lt;/h2&gt;

&lt;p&gt;Zcash invented practical ZK privacy for blockchains. Its &lt;strong&gt;Sapling&lt;/strong&gt; and &lt;strong&gt;Orchard&lt;/strong&gt; schemes are cryptographically mature and battle-tested over 8+ years.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The honest verdict&lt;/strong&gt;: Zcash is not a general-purpose smart contract platform. It's optimized for private payments. If you're building a privacy-preserving dApp with complex logic — use one of the others.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Halo2&lt;/strong&gt; proving system (used in Orchard) is excellent and has influenced Aztec's own work. If you're doing pure ZK cryptography research, Zcash's tooling is solid. For application development, you've outgrown it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture Comparison Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Midnight&lt;/th&gt;
&lt;th&gt;Aztec&lt;/th&gt;
&lt;th&gt;Aleo&lt;/th&gt;
&lt;th&gt;Mina&lt;/th&gt;
&lt;th&gt;Zcash&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contract Language&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Compact (TS-like)&lt;/td&gt;
&lt;td&gt;Noir (Rust-like)&lt;/td&gt;
&lt;td&gt;Leo (Rust-like)&lt;/td&gt;
&lt;td&gt;o1js (TypeScript)&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;State Model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dual ledger&lt;/td&gt;
&lt;td&gt;Notes (UTXO) + public slots&lt;/td&gt;
&lt;td&gt;Records + mappings&lt;/td&gt;
&lt;td&gt;8 field state + off-chain&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Privacy Default&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Explicit control&lt;/td&gt;
&lt;td&gt;Explicit (pub/private)&lt;/td&gt;
&lt;td&gt;Private by default&lt;/td&gt;
&lt;td&gt;Explicit&lt;/td&gt;
&lt;td&gt;Payments only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Base Chain&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cardano (IOG)&lt;/td&gt;
&lt;td&gt;Ethereum L2&lt;/td&gt;
&lt;td&gt;Own L1&lt;/td&gt;
&lt;td&gt;Own L1&lt;/td&gt;
&lt;td&gt;Own L1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dev Language Familiarity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;TypeScript devs&lt;/td&gt;
&lt;td&gt;Rust + Solidity devs&lt;/td&gt;
&lt;td&gt;Rust devs&lt;/td&gt;
&lt;td&gt;TypeScript devs&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ecosystem Maturity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Early (2025)&lt;/td&gt;
&lt;td&gt;Growing&lt;/td&gt;
&lt;td&gt;Early&lt;/td&gt;
&lt;td&gt;Growing&lt;/td&gt;
&lt;td&gt;Mature&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Proving System&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Custom ZK&lt;/td&gt;
&lt;td&gt;Ultra-PLONK&lt;/td&gt;
&lt;td&gt;SnarkVM&lt;/td&gt;
&lt;td&gt;Kimchi&lt;/td&gt;
&lt;td&gt;Halo2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Smart Contracts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Which Should You Build On?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Choose Midnight if:&lt;/strong&gt; You need fine-grained control over what data is disclosed vs. kept private. You're building compliance-sensitive applications (KYC without data exposure, selective disclosure, attestations). Your team knows TypeScript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose Aztec if:&lt;/strong&gt; You're already in the Ethereum ecosystem and want to add privacy without leaving it. Your application fits a notes/UTXO model. You want the most mature ZK L2 documentation available.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose Aleo if:&lt;/strong&gt; You want privacy-by-default across your entire execution model. Your application is payment or asset-centric with relatively simple state transitions. Your team knows Rust.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose Mina if:&lt;/strong&gt; You have TypeScript developers and want the best developer experience. You need client-verifiable computation. Your state requirements fit in the 8-field constraint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't choose Zcash&lt;/strong&gt; for general dApp development — it's a payments chain.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Honest Caveat
&lt;/h2&gt;

&lt;p&gt;All of these ecosystems except Zcash are young. Documentation has gaps. APIs change. Tooling is immature by Ethereum standards. Build-test cycles are longer because proof generation adds latency. Budget extra time for debugging ZK-specific errors ("constraint not satisfied" is harder to diagnose than "revert").&lt;/p&gt;

&lt;p&gt;The technology is real and production-grade at the cryptographic layer. The developer experience and ecosystem support are where the maturity gaps show up.&lt;/p&gt;

&lt;p&gt;Privacy blockchains will matter significantly for enterprise adoption, compliance-compatible DeFi, and any application handling sensitive user data. The question isn't whether to build on them — it's which architecture fits your problem.&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>zk</category>
      <category>privacy</category>
      <category>web3</category>
    </item>
  </channel>
</rss>
