<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: ayush singh</title>
    <description>The latest articles on Forem by ayush singh (@ayush_singh_4525768ba4731).</description>
    <link>https://forem.com/ayush_singh_4525768ba4731</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3928960%2F6bc7e2cb-3ede-4246-b8f1-12981a1fbdfc.jpeg</url>
      <title>Forem: ayush singh</title>
      <link>https://forem.com/ayush_singh_4525768ba4731</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ayush_singh_4525768ba4731"/>
    <language>en</language>
    <item>
      <title># [Tutorial] Confidential Asset Management on Midnight</title>
      <dc:creator>ayush singh</dc:creator>
      <pubDate>Wed, 13 May 2026 10:13:54 +0000</pubDate>
      <link>https://forem.com/ayush_singh_4525768ba4731/-tutorial-confidential-asset-management-on-midnight-2hmb</link>
      <guid>https://forem.com/ayush_singh_4525768ba4731/-tutorial-confidential-asset-management-on-midnight-2hmb</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;📁 Source: &lt;a href="https://github.com/ayushsingh82/Midnight-dApps/tree/main/confidential-asset-management" rel="noopener noreferrer"&gt;Midnight-dApps/confidential-asset-management&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The TradFi-to-DeFi pipeline has been stuck on one specific question for half a decade: how do you give LPs the programmability and speed of public chains without doxxing the entire allocator book and giving away the GP's edge?&lt;/p&gt;

&lt;p&gt;Tribute, Enzyme, dHedge, and every other on-chain fund I've looked at has the same gap. They publish the cap table, they publish the holdings, and increasingly they publish the strategy. That's fine for retail trying their luck with $100. It's a deal-breaker for a real $100M fund.&lt;/p&gt;

&lt;p&gt;Midnight is the first chain where this trade-off actually splits the right way. AUM can be public. ROI can be public. Per-LP allocations stay private. Strategy stays off-chain. Auditors get verifiable counter values; LPs get the privacy they need to participate.&lt;/p&gt;

&lt;p&gt;This dApp is a working demonstrator of that model. ~120 lines of Compact, ~600 lines of React, four roles wired in (GP, LP, auditor, observer). I'll walk through the contract piece by piece, then the frontend, then what's missing for production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use cases this could actually serve
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Family-office managed funds&lt;/strong&gt; — single-family offices pool capital across cousins, in-laws, and siblings. Today these are private LP agreements with annual paper statements. Move them on-chain on Midnight and you keep the privacy from outsiders, give every family branch independent verifiability of pool performance, and settle distributions same-day instead of quarterly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Crypto-native hedge funds with TradFi LPs&lt;/strong&gt; — a quant fund that takes capital from both crypto-native LPs and traditional allocators. The crypto LPs want on-chain transparency; the TradFi LPs require a privacy guarantee before they'll allocate. Today the fund has to pick one. With this contract, both can sit in the same vehicle.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tokenised emerging-markets credit funds&lt;/strong&gt; — high-yield credit funds where the LP list itself is sensitive (sanctions exposure, reputation, jurisdictional issues). Aggregate AUM and yield can be public so the protocol stays auditable; per-LP positions are commitments in a Merkle tree.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ul&gt;
&lt;li&gt;Node 20+&lt;/li&gt;
&lt;li&gt;Lace or 1AM, on Midnight Preprod&lt;/li&gt;
&lt;li&gt;tNIGHT + tDUST from the Preprod faucet&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;The Compact compiler
&lt;/li&gt;
&lt;/ul&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; &lt;span class="se"&gt;\&lt;/span&gt;
  https://github.com/midnightntwrk/compact/releases/latest/download/compact-installer.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Privacy/auditability — where the line sits
&lt;/h2&gt;

&lt;p&gt;The interesting design decision in this dApp is &lt;em&gt;which&lt;/em&gt; fields are public versus private. There's no single right answer; it depends on what the regulator needs to see, what the LPs want hidden, and what the GP needs to keep secret.&lt;/p&gt;

&lt;p&gt;Here's how I drew the lines:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Public?&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;manager&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Sealed at deploy; identifies who can act as GP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LP commitments&lt;/td&gt;
&lt;td&gt;Tree only&lt;/td&gt;
&lt;td&gt;Chain sees leaves, never wallets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LP allocation amount&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Public&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Added to &lt;code&gt;aum&lt;/code&gt;; the chain needs to verify solvency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;aum&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Public AUM for compliance and LP comfort&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reportedRoiBp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;LP and regulator verifiability of performance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-LP holdings ratio&lt;/td&gt;
&lt;td&gt;Off-chain&lt;/td&gt;
&lt;td&gt;GP keeps these books&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strategy&lt;/td&gt;
&lt;td&gt;Off-chain&lt;/td&gt;
&lt;td&gt;The GP's edge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payout amounts&lt;/td&gt;
&lt;td&gt;Public&lt;/td&gt;
&lt;td&gt;Solvency requires it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payout recipients&lt;/td&gt;
&lt;td&gt;Private&lt;/td&gt;
&lt;td&gt;Same pattern as the dividend dApp&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last row is the key insight: the chain learns "an LP claimed 60,000 for period 2026-05" without learning which LP. The trade is "amount public, identity private" — the opposite of most on-chain funds today.&lt;/p&gt;

&lt;p&gt;This is the &lt;em&gt;opposite&lt;/em&gt; trade-off of every existing on-chain fund product. Tribute, Enzyme, et al. leak per-wallet holdings on a public chain. Off-chain funds leak nothing on-chain. Midnight finds the third option.&lt;/p&gt;

&lt;h2&gt;
  
  
  The contract
&lt;/h2&gt;

&lt;p&gt;Full source: &lt;a href="//./contracts/Contract.compact"&gt;contracts/Contract.compact&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ledger state
&lt;/h3&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;

export sealed ledger manager: Bytes&amp;lt;32&amp;gt;;

export ledger lpCommitments: HistoricMerkleTree&amp;lt;10, Bytes&amp;lt;32&amp;gt;&amp;gt;;
export ledger payoutNullifiers: Set&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;;

export ledger aum: Uint&amp;lt;64&amp;gt;;
export ledger reportedRoiBp: Uint&amp;lt;64&amp;gt;;

export ledger totalLps: Counter;
export ledger totalPayouts: Counter;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ledger has six pieces of state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;manager&lt;/code&gt;&lt;/strong&gt; is the GP's public key, sealed at deploy. Every privileged circuit checks &lt;code&gt;manager == publicKey(callerSk)&lt;/code&gt;. There is no admin handover circuit — for a production system you'd add a two-step &lt;code&gt;proposeNewManager&lt;/code&gt; + &lt;code&gt;acceptManager&lt;/code&gt; flow with a time-lock.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;lpCommitments&lt;/code&gt;&lt;/strong&gt; is the LP cap table. Each leaf is &lt;code&gt;H(lpSk, fundId)&lt;/code&gt;. Depth 10 supports up to 1024 LPs, which is fine for any real fund (most institutional funds cap out at 200–300 LPs by SEC rules anyway).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;payoutNullifiers&lt;/code&gt;&lt;/strong&gt; is the anti-double-claim set. A nullifier is &lt;code&gt;H(lpSk, fundId, period)&lt;/code&gt;. Two claims with the same triple produce the same nullifier; the second one bounces.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;aum&lt;/code&gt;&lt;/strong&gt; is the public assets-under-management counter. Both &lt;code&gt;admitLp&lt;/code&gt; (which increments) and &lt;code&gt;redeemLp&lt;/code&gt; / &lt;code&gt;claimPayout&lt;/code&gt; (which decrement) touch it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;reportedRoiBp&lt;/code&gt;&lt;/strong&gt; is the per-period ROI in basis points. 1200 = +12.00%. Public so LPs can verify the GP is reporting what they claim, and so external observers can rank the fund's performance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The two counters are public dashboard fodder.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Witnesses
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;witness localSecretKey(): Bytes&amp;lt;32&amp;gt;;
witness findLpPath(commit: Bytes&amp;lt;32&amp;gt;): MerkleTreePath&amp;lt;10, Bytes&amp;lt;32&amp;gt;&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same pattern as the other two dApps in this repo: one witness for the caller's secret, one for the Merkle path. The TypeScript side that resolves them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;witnesses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;localSecretKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;privateState&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;privateState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;privateState&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="na"&gt;findLpPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;privateState&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;commit&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;path&lt;/span&gt; &lt;span class="o"&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;lpCommitments&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;commit&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="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="s1"&gt;LP commitment not found in tree&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;privateState&lt;/span&gt;&lt;span class="p"&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Midnight runtime calls these during proof generation. The path becomes part of the ZK proof; the chain only verifies the resulting root match.&lt;/p&gt;

&lt;h3&gt;
  
  
  Constructor
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;constructor(managerSk: Bytes&amp;lt;32&amp;gt;) {
    manager = disclose(publicKey(managerSk));
}

circuit publicKey(sk: 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;(
        [pad(32, "fund:pk:v1"), sk]
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The deployer passes their secret key as a witness argument. We hash it (domain-separated with &lt;code&gt;"fund:pk:v1"&lt;/code&gt; so it doesn't collide with public keys in other Midnight dApps using the same wallet) and seal the result into &lt;code&gt;manager&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;getLpCommitment(sk, fundId)&lt;/code&gt; — off-chain helper
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;circuit lpCommit(sk: Bytes&amp;lt;32&amp;gt;, fundId: Bytes&amp;lt;32&amp;gt;): Bytes&amp;lt;32&amp;gt; {
    return persistentHash&amp;lt;Vector&amp;lt;3, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;(
        [pad(32, "fund:lp:v1"), fundId, sk]
    );
}

export circuit getLpCommitment(sk: Bytes&amp;lt;32&amp;gt;, fundId: Bytes&amp;lt;32&amp;gt;): Bytes&amp;lt;32&amp;gt; {
    return lpCommit(sk, fundId);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;lpCommit&lt;/code&gt; is the private helper. &lt;code&gt;getLpCommitment&lt;/code&gt; is the public wrapper the frontend can call as a pure circuit — no on-chain tx, just a hash.&lt;/p&gt;

&lt;p&gt;The same wallet always produces the same commitment for a given fund, which is what makes "lose your wallet seed, lose your LP identity" the security model. Same wallet across two funds produces &lt;em&gt;different&lt;/em&gt; commitments because &lt;code&gt;fundId&lt;/code&gt; is part of the hash, which is exactly what you want.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;admitLp(commit, allocation)&lt;/code&gt; — GP only
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit admitLp(holderCommit: Bytes&amp;lt;32&amp;gt;, allocation: Uint&amp;lt;64&amp;gt;): [] {
    const sk = localSecretKey();
    assert(manager == disclose(publicKey(sk)), "Not the fund manager");
    lpCommitments.insert(disclose(holderCommit));
    aum = (aum + allocation) as Uint&amp;lt;64&amp;gt;;
    totalLps.increment(1);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The GP onboards an LP by inserting their commitment and bumping the AUM. Three observations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The allocation is &lt;code&gt;disclose&lt;/code&gt;d&lt;/strong&gt; — it has to be, because we add it to the public &lt;code&gt;aum&lt;/code&gt;. If we tried to keep allocations private, the AUM counter wouldn't be credible (the GP could lie about the sum).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The chain learns the allocation but not who.&lt;/strong&gt; It's "5 million was added by an LP whose commitment is somewhere in the new tree." Nobody can tell which leaf is the 5M vs the 50M.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The off-chain bookkeeping&lt;/strong&gt; is on the GP. They need to remember "commitment X is worth 5M, commitment Y is worth 50M" so they can compute payouts correctly. The contract has no way of enforcing that — it trusts the GP on per-LP weighting.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a deliberate trade-off. Putting per-LP allocations on-chain (even as commitments) would either reveal them or require a full SNARK over the sum, which gets expensive at scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;reportRoi(roiBp)&lt;/code&gt; — GP only
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit reportRoi(roiBp: Uint&amp;lt;64&amp;gt;): [] {
    const sk = localSecretKey();
    assert(manager == disclose(publicKey(sk)), "Not the fund manager");
    reportedRoiBp = roiBp;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sets the public ROI in basis points. 1200 = +12.00%. Anyone can read this off the indexer.&lt;/p&gt;

&lt;p&gt;The contract doesn't verify the ROI matches the AUM movement — that's by design. In a real fund, the GP signs off on a quarterly NAV from an admin (State Street, Northern Trust, et al.), and the on-chain ROI is the externally-attested number. You could build a full audit-chain on top (require a quarterly hash of the admin's report), but that's outside the MVP.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;redeemLp(commit, allocation)&lt;/code&gt; — GP only
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit redeemLp(holderCommit: Bytes&amp;lt;32&amp;gt;, allocation: Uint&amp;lt;64&amp;gt;): [] {
    const sk = localSecretKey();
    assert(manager == disclose(publicKey(sk)), "Not the fund manager");
    assert(aum &amp;gt;= allocation, "Allocation exceeds AUM");
    aum = (aum - allocation) as Uint&amp;lt;64&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The GP can process an LP exit. Note we do &lt;em&gt;not&lt;/em&gt; remove the commitment from the historic Merkle tree — that's because proofs against older roots are still valid in Compact's &lt;code&gt;HistoricMerkleTree&lt;/code&gt;, so removing leaves would break already-issued proofs.&lt;/p&gt;

&lt;p&gt;For a real partial-redemption flow you'd pair this with a per-LP redemption nullifier set, so the same LP can only redeem once per cycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;proveLp(fundId)&lt;/code&gt; — LP, no-claim variant
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit proveLp(fundId: Bytes&amp;lt;32&amp;gt;): Boolean {
    const sk = localSecretKey();
    const commit = lpCommit(sk, fundId);
    const path = findLpPath(commit);
    assert(
        lpCommitments.checkRoot(disclose(merkleTreePathRoot&amp;lt;10, Bytes&amp;lt;32&amp;gt;&amp;gt;(path))),
        "Not an LP of this fund"
    );
    return disclose(true);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A standalone "I'm an LP of this fund" proof, no payout side-effect. Useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LP-only Discord channels that gate access on this proof&lt;/li&gt;
&lt;li&gt;Compliance attestations ("I am a qualified investor in fund X")&lt;/li&gt;
&lt;li&gt;Authentication flows that don't want passwords&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The chain sees one tx; an observer learns "some LP of this fund authenticated." No way to map back to the wallet.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;claimPayout(fundId, period, amount)&lt;/code&gt; — LP
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit claimPayout(fundId: Bytes&amp;lt;32&amp;gt;, period: Bytes&amp;lt;32&amp;gt;, amount: Uint&amp;lt;64&amp;gt;): Boolean {
    const sk = localSecretKey();
    const commit = lpCommit(sk, fundId);
    const path = findLpPath(commit);
    assert(
        lpCommitments.checkRoot(disclose(merkleTreePathRoot&amp;lt;10, Bytes&amp;lt;32&amp;gt;&amp;gt;(path))),
        "Not an LP of this fund"
    );

    const nul = payoutNullifier(sk, fundId, period);
    assert(!payoutNullifiers.member(disclose(nul)), "Payout already claimed for this period");
    assert(aum &amp;gt;= amount, "Payout exceeds AUM");

    payoutNullifiers.insert(disclose(nul));
    aum = (aum - amount) as Uint&amp;lt;64&amp;gt;;
    totalPayouts.increment(1);

    return disclose(true);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the core LP action. Steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Recompute the LP's commitment.&lt;/li&gt;
&lt;li&gt;Ask the witness for the Merkle path.&lt;/li&gt;
&lt;li&gt;Assert the path roots to a tree the contract has historically had.&lt;/li&gt;
&lt;li&gt;Compute the nullifier for &lt;code&gt;(sk, fundId, period)&lt;/code&gt; and assert it hasn't been used.&lt;/li&gt;
&lt;li&gt;Assert solvency — the AUM has to be large enough for the requested amount.&lt;/li&gt;
&lt;li&gt;Side effects: insert the nullifier, deduct from AUM, bump payout counter.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The LP supplies &lt;code&gt;amount&lt;/code&gt; themselves. They compute it off-chain by multiplying their &lt;em&gt;private&lt;/em&gt; allocation by the &lt;em&gt;public&lt;/em&gt; ROI. The contract doesn't enforce that the math is correct — it only checks solvency. In a real system you'd either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have the GP submit per-LP payouts (slower, more centralised)&lt;/li&gt;
&lt;li&gt;Use a more sophisticated SNARK that proves &lt;code&gt;amount == allocation * (1 + roi)&lt;/code&gt; without revealing &lt;code&gt;allocation&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both have engineering cost. The MVP trusts the LP to compute their payout honestly, which works fine for small consortiums and breaks down for adversarial LPs in larger funds.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;payoutNullifier(sk, fundId, period)&lt;/code&gt; — private helper
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;circuit payoutNullifier(sk: Bytes&amp;lt;32&amp;gt;, fundId: Bytes&amp;lt;32&amp;gt;, period: Bytes&amp;lt;32&amp;gt;): Bytes&amp;lt;32&amp;gt; {
    return persistentHash&amp;lt;Vector&amp;lt;4, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;(
        [pad(32, "fund:nul:v1"), fundId, period, sk]
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four inputs in the hash: domain, fundId, period, secret. Different period → different nullifier → fresh claim allowed. Same period → same nullifier → claim rejected. The same LP can claim across funds without any cross-fund linkage because &lt;code&gt;fundId&lt;/code&gt; is in the hash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frontend wiring
&lt;/h2&gt;

&lt;p&gt;Same structural pattern as the other two dApps in this repo. The router has four protected routes:&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Routes&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;       &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HomePage&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/deploy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RequireWallet&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DeployPage&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/RequireWallet&amp;gt;} /&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/admit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;  &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RequireWallet&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AdmitPage&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/RequireWallet&amp;gt;} /&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/report&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RequireWallet&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ReportPage&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/RequireWallet&amp;gt;} /&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/payout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RequireWallet&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PayoutPage&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/RequireWallet&amp;gt;} /&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Routes&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;RequireWallet&lt;/code&gt; redirects to &lt;code&gt;/&lt;/code&gt; if the wallet isn't attached. No deep-linking around the wallet gate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wallet → role key
&lt;/h3&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;APP_SALT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confidential-asset-management-v1&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;deriveRoleKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shieldedCoinPublicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;role&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;master&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;deriveKeyFromPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;APP_SALT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;shieldedCoinPublicKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;deriveKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;master&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`fund:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Roles are &lt;code&gt;'lp'&lt;/code&gt; and &lt;code&gt;'manager'&lt;/code&gt;. The same wallet can act as both a GP of fund A and an LP of fund B — they get different role keys, derived deterministically from the same wallet seed. This is exactly what a family office that runs its own fund &lt;em&gt;and&lt;/em&gt; allocates to others would need.&lt;/p&gt;

&lt;h3&gt;
  
  
  The provider bundle
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;src/lib/midnight.ts&lt;/code&gt; centralises the providers so the page files don't repeat themselves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildProviders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&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;zkConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FetchZkConfigProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CONTRACT_PATH&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/managed/fund/keys`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;privateStateProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;levelPrivateStateProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;privateStateStoreName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;privateStateStoreName&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;publicDataProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="nf"&gt;indexerPublicDataProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;INDEXER_HTTP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;INDEXER_WS&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;zkConfigProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="nx"&gt;zkConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;proofProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="nf"&gt;httpClientProofProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PROOF_SERVER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;zkConfig&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;walletProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* getCoinPublicKey, balanceTx adapter */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;midnightProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* submitTx adapter */&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;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadCompiledContract&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;contractPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CONTRACT_PATH&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/managed/fund&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contractModule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* @vite-ignore */&lt;/span&gt; &lt;span class="nx"&gt;contractPath&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;cc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CompiledContract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fund&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contractModule&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ccW&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CompiledContract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withWitnesses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;witnesses&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;contractModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;finalContract&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CompiledContract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withCompiledFileAssets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ccW&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;CONTRACT_PATH&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/managed/fund`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The string-concat for &lt;code&gt;contractPath&lt;/code&gt; is on purpose — Vite 8's static import-analysis tries to resolve template literals at build time, which fails if the contract isn't compiled yet. String concatenation defeats that analysis and lets the import resolve at runtime. Same approach the upstream &lt;code&gt;midnight-apps/fullstack-dapp&lt;/code&gt; uses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Submitting a circuit call
&lt;/h3&gt;

&lt;p&gt;Every action page (Admit, Report, Payout, Deploy) follows the same shape:&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;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;buildProviders&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;finalContract&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadCompiledContract&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;findDeployedContract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createCircuitCallTxInterface&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@midnight-ntwrk/midnight-js-contracts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;findDeployedContract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;compiledContract&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;finalContract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;privateStateId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PRIVATE_STATE_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;initialPrivateState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createFundPrivateState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;managerSk&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;txInterface&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createCircuitCallTxInterface&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;finalContract&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;PRIVATE_STATE_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;txInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;admitLp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;hexToUint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;holderCommit&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;allocation&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lazy import of &lt;code&gt;@midnight-ntwrk/midnight-js-contracts&lt;/code&gt; is significant — it's a ~1MB chunk and we don't need it until the user hits "Submit." Lazy-loading shaves the initial page-load by a few hundred ms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading aggregate state
&lt;/h2&gt;

&lt;p&gt;The Home page pulls the live AUM, ROI, LP count, and payout count straight from the indexer:&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;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;indexerPublicDataProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;INDEXER_HTTP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;INDEXER_WS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;provider&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contractModule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/src/contracts/managed/fund/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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;contractModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&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="c1"&gt;// ledger.totalLps         → bigint&lt;/span&gt;
&lt;span class="c1"&gt;// ledger.aum              → bigint&lt;/span&gt;
&lt;span class="c1"&gt;// ledger.reportedRoiBp    → bigint&lt;/span&gt;
&lt;span class="c1"&gt;// ledger.totalPayouts     → bigint&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a production fund-admin dashboard you'd want an Express + Postgres polling cache in front of the indexer. The pattern is documented in the &lt;a href="https://github.com/midnight-network/midnight-apps/blob/main/fullstack-dapp/tutorial.md" rel="noopener noreferrer"&gt;upstream fullstack-dapp tutorial&lt;/a&gt;. For an MVP, querying the indexer directly on page load is fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an auditor sees
&lt;/h2&gt;

&lt;p&gt;A regulator or LP auditor with the contract address gets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;manager = 0x...&lt;/code&gt; (sealed GP key)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;totalLps = 14&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;totalPayouts = 13&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;aum = 67_250_000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reportedRoiBp = 1200&lt;/code&gt; (i.e. +12.00%)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From these they can verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every payout consumed exactly the declared amount from AUM&lt;/li&gt;
&lt;li&gt;No nullifier was reused&lt;/li&gt;
&lt;li&gt;Every claim came from a commitment in the tree&lt;/li&gt;
&lt;li&gt;The number of LPs match the number of &lt;code&gt;admitLp&lt;/code&gt; transactions in the indexer history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What they cannot see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which wallet owns which commitment&lt;/li&gt;
&lt;li&gt;Per-LP allocation amounts (those exist only in the GP's books)&lt;/li&gt;
&lt;li&gt;The fund's strategy or holdings&lt;/li&gt;
&lt;li&gt;Any cross-period linkage of an individual LP's claims (different period → different nullifier hash)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the trade-off the dApp embodies in one paragraph. The chain proves process integrity and solvency; identities and strategies stay off the chain entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production extensions worth building
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Per-LP allocation commitments.&lt;/strong&gt; Replace the disclosed &lt;code&gt;allocation&lt;/code&gt; with a Pedersen commitment, and have the AUM accumulate commitments rather than scalars. More private, much more expensive in ZK proving time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Time-locked GP transfer.&lt;/strong&gt; Two-step &lt;code&gt;proposeNewManager&lt;/code&gt; + &lt;code&gt;acceptManager&lt;/code&gt; with a &lt;code&gt;block_height + 100&lt;/code&gt; delay. Catches the "GP key compromised" case.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Performance fees with high-water mark.&lt;/strong&gt; Have the GP commit to a high-water mark, and reject performance-fee claims if the current period's ROI doesn't exceed it. Useful for hedge-fund style 2-and-20 fee structures.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multi-fund routing.&lt;/strong&gt; Replace &lt;code&gt;manager: Bytes&amp;lt;32&amp;gt;&lt;/code&gt; with &lt;code&gt;Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Bytes&amp;lt;32&amp;gt;&amp;gt;&lt;/code&gt; keyed by fundId. One deployed contract hosts many funds, each with its own manager.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Real settlement.&lt;/strong&gt; Pair with a &lt;a href="https://github.com/midnight-network/midnight-apps/tree/main/shielded-token" rel="noopener noreferrer"&gt;shielded token&lt;/a&gt; so payouts mint actual coin commitments to fresh recipient addresses, instead of just decrementing a counter.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Vite throws "Failed to resolve import .../fund/contract/index.js"&lt;/strong&gt; — you haven't compiled. Run &lt;code&gt;npx compact compile contracts/Contract.compact src/contracts/managed/fund&lt;/code&gt;. The path matters: the frontend imports from exactly there.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Not the fund manager&lt;/code&gt; on &lt;code&gt;admitLp&lt;/code&gt; / &lt;code&gt;reportRoi&lt;/code&gt;&lt;/strong&gt; — your wallet is not the one that deployed. Switch wallets or redeploy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Not an LP of this fund&lt;/code&gt; on &lt;code&gt;claimPayout&lt;/code&gt;&lt;/strong&gt; — the GP hasn't called &lt;code&gt;admitLp&lt;/code&gt; for your commitment yet, &lt;em&gt;or&lt;/em&gt; you computed the commitment with a different fundId. Double-check the fundId you're claiming on matches exactly what was used at admit time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Payout already claimed for this period&lt;/code&gt;&lt;/strong&gt; — the nullifier is working. Move to the next period or wait for the GP to declare a new one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Payout exceeds AUM&lt;/code&gt;&lt;/strong&gt; — either the LP is claiming more than the fund can pay, or the GP hasn't admitted enough capital to cover this period's payouts. Recheck the math.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Proof generation hangs&lt;/strong&gt; — your local proof server died. Restart with &lt;code&gt;docker run -p 6300:6300 midnightntwrk/proof-server:8.0.3 midnight-proof-server -v&lt;/code&gt;. First proofs after a server restart can take 30–60 seconds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Wallet not detected&lt;/strong&gt; — Lace and 1AM inject &lt;code&gt;window.midnight&lt;/code&gt; asynchronously. The &lt;code&gt;ConnectButton&lt;/code&gt; polls for up to 3 seconds. If still nothing, the button deep-links to lace.io.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;BalanceCheckOverspend&lt;/code&gt; (error 138)&lt;/strong&gt; — you're out of tDUST. Hit the Preprod faucet.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Repo and next steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Code: &lt;a href="https://github.com/ayushsingh82/Midnight-dApps/tree/main/confidential-asset-management" rel="noopener noreferrer"&gt;https://github.com/ayushsingh82/Midnight-dApps/tree/main/confidential-asset-management&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Compact language reference: &lt;a href="https://docs.midnight.network/" rel="noopener noreferrer"&gt;https://docs.midnight.network/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you fork it and ship something interesting, I'd love to hear about it.&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>privacy</category>
      <category>tutorial</category>
      <category>web3</category>
    </item>
    <item>
      <title># [Tutorial] Confidential Dividend Distribution on Midnight</title>
      <dc:creator>ayush singh</dc:creator>
      <pubDate>Wed, 13 May 2026 10:12:51 +0000</pubDate>
      <link>https://forem.com/ayush_singh_4525768ba4731/-tutorial-confidential-dividend-distribution-on-midnight-15e1</link>
      <guid>https://forem.com/ayush_singh_4525768ba4731/-tutorial-confidential-dividend-distribution-on-midnight-15e1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;📁 Source: &lt;a href="https://github.com/ayushsingh82/Midnight-dApps/tree/main/confidential-dividend" rel="noopener noreferrer"&gt;Midnight-dApps/confidential-dividend&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A public corporate cap table is the unsolved scandal of on-chain finance. Every public company in the world reports aggregate shareholdings in 10-Q filings, but anyone who issues equity on a transparent blockchain effectively publishes positions of every holder, every insider, every family office — addressable by wallet ID forever. Half of corporate America walks away the moment you mention "on-chain."&lt;/p&gt;

&lt;p&gt;Midnight gives you a way out. This dApp shows it.&lt;/p&gt;

&lt;p&gt;A corporate issuer registers shareholders, tops up a dividend pool each quarter, and declares the per-share rate. Holders prove eligibility and claim with zero-knowledge proofs. Nullifiers enforce one-claim-per-cycle. Auditors and regulators see the totals; nobody sees the cap table.&lt;/p&gt;

&lt;p&gt;The contract is ~120 lines of Compact. The frontend is one role-aware React app with three roles wired in (issuer, shareholder, observer). The whole thing runs on Midnight Preprod with the proof server hosted locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three real use cases this unlocks
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Late-stage private equity&lt;/strong&gt; — companies that want to start paying preferred-stock dividends on-chain without revealing their cap table to competitors, journalists, or the SEC EDGAR scrapers. The holders are already known to the company (registered through onboarding), so the only privacy boundary that matters is the &lt;em&gt;public&lt;/em&gt; one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tokenised co-ops and DAOs that act like co-ops&lt;/strong&gt; — a member-owned org that distributes annual surplus to members. You want every member to be able to verify they were paid, and the public to be able to verify the org distributed exactly what it said it would, without doxxing every member.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Token-buyback and revenue-share programmes&lt;/strong&gt; — a protocol that programmatically distributes a share of revenue to token holders, but doesn't want the holder list public (because price-sensitive insiders are on it). Today these programmes are either centralised escrow or fully public; Midnight enables a third option.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ul&gt;
&lt;li&gt;Node 20+&lt;/li&gt;
&lt;li&gt;Lace or 1AM, on Midnight Preprod&lt;/li&gt;
&lt;li&gt;tNIGHT + tDUST from the Preprod faucet&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;Compact compiler:
&lt;/li&gt;
&lt;/ul&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; &lt;span class="se"&gt;\&lt;/span&gt;
  https://github.com/midnightntwrk/compact/releases/latest/download/compact-installer.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The contract walk-through
&lt;/h2&gt;

&lt;p&gt;Full source: &lt;a href="//./contracts/Contract.compact"&gt;contracts/Contract.compact&lt;/a&gt;. I'll walk through it function by function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ledger state
&lt;/h3&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;

export sealed ledger issuer: Bytes&amp;lt;32&amp;gt;;

export ledger shareholderCommitments: HistoricMerkleTree&amp;lt;10, Bytes&amp;lt;32&amp;gt;&amp;gt;;
export ledger dividendNullifiers: Set&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;;

export ledger dividendPool: Uint&amp;lt;64&amp;gt;;
export ledger declaredDividend: Uint&amp;lt;64&amp;gt;;

export ledger totalShareholders: Counter;
export ledger totalDividendsPaid: Counter;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five pieces of state worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;issuer&lt;/code&gt;&lt;/strong&gt; is &lt;code&gt;sealed&lt;/code&gt;. That means the value can only be written once, by the constructor. Every privileged circuit checks &lt;code&gt;assert(issuer == publicKey(callerSk))&lt;/code&gt;. There's no admin transfer, by design — for an MVP this is fine; in production you'd add a multi-step &lt;code&gt;transferIssuer&lt;/code&gt; flow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;shareholderCommitments&lt;/code&gt;&lt;/strong&gt; is a depth-10 historic Merkle tree. "Historic" means proofs against older roots stay valid. So a shareholder who got registered 200 blocks ago can still prove they're a holder today.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;dividendNullifiers&lt;/code&gt;&lt;/strong&gt; is the anti-double-claim set. A nullifier is &lt;code&gt;H(shareholderSk, classId, cycle)&lt;/code&gt;. Two claims with the same triple produce the same nullifier; the second one gets rejected.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;dividendPool&lt;/code&gt;&lt;/strong&gt; is a public scalar. Anyone observing the contract knows exactly how much treasury is available.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;declaredDividend&lt;/code&gt;&lt;/strong&gt; is the per-share rate for the &lt;em&gt;current&lt;/em&gt; cycle. Issuers update this each quarter via &lt;code&gt;declareCycleDividend&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I deliberately kept it to a single share class. Adding multi-class support is straight-forward — you'd replace the Merkle tree and counters with a &lt;code&gt;Map&amp;lt;Bytes&amp;lt;32&amp;gt;, ...&amp;gt;&lt;/code&gt; keyed by classId.&lt;/p&gt;

&lt;h3&gt;
  
  
  Witnesses
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;witness localSecretKey(): Bytes&amp;lt;32&amp;gt;;
witness findShareholderPath(commit: Bytes&amp;lt;32&amp;gt;): MerkleTreePath&amp;lt;10, Bytes&amp;lt;32&amp;gt;&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two witnesses. &lt;code&gt;localSecretKey()&lt;/code&gt; fetches the caller's private key from off-chain state. &lt;code&gt;findShareholderPath()&lt;/code&gt; finds the Merkle path from the caller's commitment to a root the tree has historically had.&lt;/p&gt;

&lt;p&gt;The TypeScript side that resolves these:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;witnesses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;localSecretKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;privateState&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;privateState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;privateState&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="na"&gt;findShareholderPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;privateState&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;commit&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;path&lt;/span&gt; &lt;span class="o"&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;shareholderCommitments&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;commit&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="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="s1"&gt;Shareholder commitment not found in tree&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;privateState&lt;/span&gt;&lt;span class="p"&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Midnight runtime hands these to Compact during proof generation. The path goes into the ZK proof; the chain only ever sees the resulting root assertion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Constructor
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;constructor(issuerSk: Bytes&amp;lt;32&amp;gt;) {
    issuer = disclose(publicKey(issuerSk));
}

circuit publicKey(sk: 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;(
        [pad(32, "dividend:pk:v1"), sk]
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The deployer's secret key is passed in as a witness argument. We compute its public key (domain-separated with &lt;code&gt;"dividend:pk:v1"&lt;/code&gt; so it doesn't collide with public keys in other dApps) and seal it. Nothing else is needed at deploy time.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;getShareholderCommitment(sk, classId)&lt;/code&gt; — off-chain helper
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;circuit shareholderCommit(sk: Bytes&amp;lt;32&amp;gt;, classId: Bytes&amp;lt;32&amp;gt;): Bytes&amp;lt;32&amp;gt; {
    return persistentHash&amp;lt;Vector&amp;lt;3, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;(
        [pad(32, "dividend:sh:v1"), classId, sk]
    );
}

export circuit getShareholderCommitment(sk: Bytes&amp;lt;32&amp;gt;, classId: Bytes&amp;lt;32&amp;gt;): Bytes&amp;lt;32&amp;gt; {
    return shareholderCommit(sk, classId);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;shareholderCommit&lt;/code&gt; is what makes the privacy model work. A shareholder's commitment is &lt;code&gt;H(domain, classId, sk)&lt;/code&gt;. Three properties matter:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic.&lt;/strong&gt; Same wallet + same class → same commitment forever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-way.&lt;/strong&gt; Even if the issuer leaks the entire tree, you can't reverse a commitment back to a wallet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain-separated.&lt;/strong&gt; The &lt;code&gt;"dividend:sh:v1"&lt;/code&gt; padding ensures the same secret in a different dApp produces a different commitment.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;getShareholderCommitment&lt;/code&gt; is the &lt;code&gt;export&lt;/code&gt; wrapper, callable from the frontend as a pure circuit (no on-chain tx, just hash computation in JS).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;registerShareholder(commit)&lt;/code&gt; — issuer only
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit registerShareholder(shareholderCommitArg: Bytes&amp;lt;32&amp;gt;): [] {
    const sk = localSecretKey();
    assert(issuer == disclose(publicKey(sk)), "Not the issuer");
    shareholderCommitments.insert(disclose(shareholderCommitArg));
    totalShareholders.increment(1);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shareholder hands the issuer a commitment off-chain (Slack DM, KYC portal, anywhere). The issuer calls &lt;code&gt;registerShareholder&lt;/code&gt; with it. The chain's only new fact: a leaf was added to the tree. The chain still doesn't know whose.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;disclose(publicKey(sk))&lt;/code&gt; call is interesting — it computes the public key inside the circuit and crosses the public-private boundary. Without &lt;code&gt;disclose&lt;/code&gt;, the compiler rejects the equality check.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;topUpDividendPool(amount)&lt;/code&gt; — issuer only
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit topUpDividendPool(amount: Uint&amp;lt;64&amp;gt;): [] {
    const sk = localSecretKey();
    assert(issuer == disclose(publicKey(sk)), "Not the issuer");
    dividendPool = (dividendPool + amount) as Uint&amp;lt;64&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bumps the public pool counter. The &lt;code&gt;as Uint&amp;lt;64&amp;gt;&lt;/code&gt; cast is needed because Compact requires explicit width annotations on arithmetic that might overflow.&lt;/p&gt;

&lt;p&gt;In a real system you'd probably swap this for a circuit that accepts a shielded coin commitment and deposits actual tNIGHT into the contract. The accountant simplification here is for clarity.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;declareCycleDividend(amount)&lt;/code&gt; — issuer only
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit declareCycleDividend(amount: Uint&amp;lt;64&amp;gt;): [] {
    const sk = localSecretKey();
    assert(issuer == disclose(publicKey(sk)), "Not the issuer");
    declaredDividend = amount;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sets the per-share dividend for the upcoming cycle. Public. Every shareholder watching the contract knows what they can claim next.&lt;/p&gt;

&lt;p&gt;There's no &lt;code&gt;cycle&lt;/code&gt; argument here intentionally — the cycle is derived implicitly from the nullifier on the claim side. If you want a richer model you can declare per-cycle by adding a &lt;code&gt;Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Uint&amp;lt;64&amp;gt;&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;claimDividend(classId, cycle)&lt;/code&gt; — shareholder
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit claimDividend(classId: Bytes&amp;lt;32&amp;gt;, cycle: Bytes&amp;lt;32&amp;gt;): Boolean {
    const sk = localSecretKey();
    const commit = shareholderCommit(sk, classId);
    const path = findShareholderPath(commit);

    assert(
        shareholderCommitments.checkRoot(disclose(merkleTreePathRoot&amp;lt;10, Bytes&amp;lt;32&amp;gt;&amp;gt;(path))),
        "Not a registered shareholder"
    );

    const nul = dividendNullifier(sk, classId, cycle);
    assert(!dividendNullifiers.member(disclose(nul)), "Dividend already claimed this cycle");
    assert(dividendPool &amp;gt;= declaredDividend, "Insufficient dividend pool");

    dividendNullifiers.insert(disclose(nul));
    dividendPool = (dividendPool - declaredDividend) as Uint&amp;lt;64&amp;gt;;
    totalDividendsPaid.increment(1);

    return disclose(true);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the heart of the dApp. Four assertions in sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The shareholder's commitment is in the Merkle tree. (&lt;code&gt;checkRoot&lt;/code&gt; on a historic root.)&lt;/li&gt;
&lt;li&gt;The nullifier for &lt;code&gt;(sk, classId, cycle)&lt;/code&gt; hasn't been seen. This is what blocks the same shareholder from claiming twice in the same cycle.&lt;/li&gt;
&lt;li&gt;The pool can cover the declared per-share amount.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If all three pass, we insert the nullifier, decrement the pool by the declared amount, and bump the public payout counter.&lt;/p&gt;

&lt;p&gt;Notice the cycle is opaque bytes — the contract doesn't care if it's &lt;code&gt;"2026-Q2"&lt;/code&gt;, an ISO date, or a UNIX timestamp. The issuer just needs to keep using fresh values. Reusing a cycle ID is allowed, but it means each shareholder can only claim once across the cycle's lifetime.&lt;/p&gt;

&lt;p&gt;Why does the chain learn the &lt;em&gt;amount&lt;/em&gt; but not the &lt;em&gt;recipient&lt;/em&gt;? Because we deduct from a public pool — the deduction has to be public for solvency. The recipient stays private because the proof verifies "some commitment in the tree" not "this particular commitment."&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;proveEligibility(classId)&lt;/code&gt; — shareholder, no-claim variant
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit proveEligibility(classId: Bytes&amp;lt;32&amp;gt;): Boolean {
    const sk = localSecretKey();
    const commit = shareholderCommit(sk, classId);
    const path = findShareholderPath(commit);
    assert(
        shareholderCommitments.checkRoot(disclose(merkleTreePathRoot&amp;lt;10, Bytes&amp;lt;32&amp;gt;&amp;gt;(path))),
        "Not a registered shareholder"
    );
    return disclose(true);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same inclusion proof, but doesn't claim or touch the nullifier set. Useful for shareholder portals that want to gate features behind "are you a holder?" without burning a cycle.&lt;/p&gt;

&lt;p&gt;You can build authentication flows on top of this — the dApp authenticates "yes, this caller is a holder of class X" without any password, OAuth, or off-chain database.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;dividendNullifier(sk, classId, cycle)&lt;/code&gt; — private helper
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;circuit dividendNullifier(sk: Bytes&amp;lt;32&amp;gt;, classId: Bytes&amp;lt;32&amp;gt;, cycle: Bytes&amp;lt;32&amp;gt;): Bytes&amp;lt;32&amp;gt; {
    return persistentHash&amp;lt;Vector&amp;lt;4, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;(
        [pad(32, "dividend:nul:v1"), classId, cycle, sk]
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four inputs into the hash: domain, classId, cycle, secret. Each new cycle changes the hash; each shareholder produces a different one. The result is the exact byte string the &lt;code&gt;Set&lt;/code&gt; insertion checks against.&lt;/p&gt;

&lt;h2&gt;
  
  
  The frontend, by page
&lt;/h2&gt;

&lt;p&gt;The router has four protected pages and one Home page:&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Routes&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;         &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HomePage&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/deploy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;   &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RequireWallet&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DeployPage&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/RequireWallet&amp;gt;} /&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/register&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RequireWallet&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RegisterPage&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/RequireWallet&amp;gt;} /&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/declare&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;  &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RequireWallet&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DeclarePage&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/RequireWallet&amp;gt;} /&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/claim&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;    &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RequireWallet&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ClaimPage&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/RequireWallet&amp;gt;} /&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Routes&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;RequireWallet&lt;/code&gt; redirects to &lt;code&gt;/&lt;/code&gt; if &lt;code&gt;isConnected&lt;/code&gt; is false on the Zustand store. Means you can't deep-link into Deploy without a wallet attached.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wallet detection edge case
&lt;/h3&gt;

&lt;p&gt;This one bit me. Lace and 1AM inject &lt;code&gt;window.midnight&lt;/code&gt; &lt;em&gt;after&lt;/em&gt; the React app mounts. If you read &lt;code&gt;window.midnight&lt;/code&gt; once on mount, you'll miss late injections.&lt;/p&gt;

&lt;p&gt;The fix is to poll for the first few seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&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="nx"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;found&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getCompatibleWallets&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;setWallets&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prev&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;found&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;prev&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="nx"&gt;found&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;attempts&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;300&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&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;Polls every 300ms for up to 3s. If a wallet shows up, we stop. If not, the "Install wallet" button shows up and deep-links to lace.io.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deriving the role key
&lt;/h3&gt;

&lt;p&gt;A single helper does the wallet → role key derivation:&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;APP_SALT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confidential-dividend-v1&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;deriveRoleKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shieldedCoinPublicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;role&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;master&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;deriveKeyFromPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;APP_SALT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;shieldedCoinPublicKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;deriveKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;master&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`dividend:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Roles are &lt;code&gt;'shareholder'&lt;/code&gt; or &lt;code&gt;'issuer'&lt;/code&gt;. Same wallet = same role key forever, and an issuer using their issuer key can't accidentally act as a shareholder (different domain separator on the same wallet seed).&lt;/p&gt;

&lt;h3&gt;
  
  
  Submitting a circuit call
&lt;/h3&gt;

&lt;p&gt;Every action page follows roughly the same pattern (Deploy/Register/Declare/Claim):&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;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;buildProviders&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;connectedApi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;shieldedCoinPublicKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shieldedCoinPublicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;shieldedEncryptionPublicKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shieldedEncryptionPublicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;privateStateStoreName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dividend&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;finalContract&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadCompiledContract&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;findDeployedContract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createCircuitCallTxInterface&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@midnight-ntwrk/midnight-js-contracts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;findDeployedContract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;compiledContract&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;finalContract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;privateStateId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PRIVATE_STATE_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;initialPrivateState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createDividendPrivateState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;issuerSk&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;txInterface&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createCircuitCallTxInterface&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;finalContract&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;PRIVATE_STATE_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;txInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerShareholder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;hexToUint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;holderCommit&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;lazy import&lt;/code&gt; of &lt;code&gt;@midnight-ntwrk/midnight-js-contracts&lt;/code&gt; keeps the initial bundle small — the contracts library is heavy and not needed until a user actually submits a transaction.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;buildProviders&lt;/code&gt; lives in &lt;code&gt;src/lib/midnight.ts&lt;/code&gt; and wires up six things (private-state level DB, indexer provider, ZK config, proof server, wallet adapter, midnight adapter). Centralising it means the page files stay focused on the UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading aggregate state
&lt;/h2&gt;

&lt;p&gt;The Home page reads stats from the indexer:&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;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;indexerPublicDataProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;INDEXER_HTTP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;INDEXER_WS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;provider&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contractModule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/src/contracts/managed/dividend/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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;contractModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&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="c1"&gt;// ledger.totalShareholders     → bigint&lt;/span&gt;
&lt;span class="c1"&gt;// ledger.totalDividendsPaid    → bigint&lt;/span&gt;
&lt;span class="c1"&gt;// ledger.dividendPool          → bigint&lt;/span&gt;
&lt;span class="c1"&gt;// ledger.declaredDividend      → bigint&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;indexerPublicDataProvider&lt;/code&gt; wraps an Apollo client around &lt;code&gt;indexer.preprod.midnight.network/api/v4/graphql&lt;/code&gt;. Calling &lt;code&gt;queryContractState&lt;/code&gt; gives you raw &lt;code&gt;ContractState&lt;/code&gt;; passing it through &lt;code&gt;contractModule.ledger()&lt;/code&gt; deserialises it into typed fields.&lt;/p&gt;

&lt;p&gt;For a production dashboard you'd usually layer an Express + Postgres cache in front of the indexer (the pattern from the &lt;a href="https://github.com/midnight-network/midnight-apps/blob/main/fullstack-dapp/tutorial.md" rel="noopener noreferrer"&gt;upstream fullstack-dapp tutorial&lt;/a&gt;). Otherwise every page load hits the indexer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auditor view
&lt;/h2&gt;

&lt;p&gt;An auditor or regulator with the contract address can verify, in zero knowledge but with full confidence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;That every payout consumed exactly &lt;code&gt;declaredDividend&lt;/code&gt; from the pool&lt;/li&gt;
&lt;li&gt;That no nullifier appears twice&lt;/li&gt;
&lt;li&gt;That every claim came from a commitment in the tree&lt;/li&gt;
&lt;li&gt;The exact number of shareholders, total payouts, and pool balance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What they cannot see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which wallet maps to which commitment&lt;/li&gt;
&lt;li&gt;Any individual shareholder's claim count over time&lt;/li&gt;
&lt;li&gt;Any link between two different dividend cycles for the same shareholder (because the nullifier hash is different each cycle)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the trade-off in one paragraph. The chain proves process integrity; the cap table stays off-chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd build on top
&lt;/h2&gt;

&lt;p&gt;Three concrete extensions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multi-class&lt;/strong&gt; — replace the single tree with &lt;code&gt;Map&amp;lt;Bytes&amp;lt;32&amp;gt;, HistoricMerkleTree&amp;lt;10, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; keyed by classId. Different rates per class, same nullifier scheme.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Real settlement&lt;/strong&gt; — pair with a &lt;a href="https://github.com/midnight-network/midnight-apps/tree/main/shielded-token" rel="noopener noreferrer"&gt;shielded token&lt;/a&gt;. On &lt;code&gt;claimDividend&lt;/code&gt;, mint a coin commitment to a fresh recipient address.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Time-locked declarations&lt;/strong&gt; — the issuer commits to a future dividend rate cryptographically before the record date, and reveals it on schedule. Useful for compliance frameworks that require advance notice.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Vite throws "Failed to resolve import .../dividend/contract/index.js"&lt;/strong&gt; — compile the contract first. The right command is &lt;code&gt;npx compact compile contracts/Contract.compact src/contracts/managed/dividend&lt;/code&gt;. The path matters — the frontend imports from exactly that location.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Not the issuer&lt;/code&gt; on &lt;code&gt;topUpDividendPool&lt;/code&gt;&lt;/strong&gt; — you're calling from a wallet that wasn't the deployer. Switch wallets or redeploy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Not a registered shareholder&lt;/code&gt; on &lt;code&gt;claimDividend&lt;/code&gt;&lt;/strong&gt; — issuer hasn't called &lt;code&gt;registerShareholder&lt;/code&gt; for your commitment yet. Verify the issuer's logs show the registration tx succeeded, and that you computed the commitment with the same classId you're now claiming on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Dividend already claimed this cycle&lt;/code&gt;&lt;/strong&gt; — pick a fresh cycle ID, or wait for the issuer to declare a new one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Insufficient dividend pool&lt;/code&gt;&lt;/strong&gt; — issuer needs to &lt;code&gt;topUpDividendPool&lt;/code&gt; before the next batch of claims. The contract correctly rejects payouts that would overdraw.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;WebSocket disconnects during proof generation&lt;/strong&gt; — the proof server can take 30+ seconds for first-time circuit proving. Don't refresh the page. If it really dies, the wallet shows "Connection failed" and you can retry.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Transactions stuck "pending"&lt;/strong&gt; — Midnight Preprod blocks are roughly every 6 seconds. If it's been more than 30 seconds, check explorer.1am.xyz for the tx hash. If it's not there, it never got submitted — usually means your wallet was out of tDUST.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Repo and next steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Code: &lt;a href="https://github.com/ayushsingh82/Midnight-dApps/tree/main/confidential-dividend" rel="noopener noreferrer"&gt;https://github.com/ayushsingh82/Midnight-dApps/tree/main/confidential-dividend&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Compact language reference: &lt;a href="https://docs.midnight.network/" rel="noopener noreferrer"&gt;https://docs.midnight.network/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you fork it and ship something interesting, I'd love to hear about it.&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>privacy</category>
      <category>showdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title># [Tutorial] Building Confidential Tokenized Real Estate on Midnight</title>
      <dc:creator>ayush singh</dc:creator>
      <pubDate>Wed, 13 May 2026 10:11:10 +0000</pubDate>
      <link>https://forem.com/ayush_singh_4525768ba4731/-tutorial-building-confidential-tokenized-real-estate-on-midnight-26o9</link>
      <guid>https://forem.com/ayush_singh_4525768ba4731/-tutorial-building-confidential-tokenized-real-estate-on-midnight-26o9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;📁 Source: &lt;a href="https://github.com/ayushsingh82/Midnight-dApps/tree/main/confidential-real-estate" rel="noopener noreferrer"&gt;Midnight-dApps/confidential-real-estate&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I've been watching real-estate tokenisation projects come and go for years. Every one of them has the same blocker: cap-table privacy. The minute you put a property's ownership on a transparent ledger, you're publishing who owns what to the entire world. Family offices won't touch it. RIAs won't touch it. And the institutional money that this market needs to scale just isn't going to show up.&lt;/p&gt;

&lt;p&gt;Midnight changes the math. The chain stores commitments instead of identities, ZK proofs replace public lookups, and suddenly the trade-off between regulatory auditability and shareholder privacy goes away.&lt;/p&gt;

&lt;p&gt;This is a working dApp that demonstrates the pattern. I'll walk through the contract function by function, the React frontend that talks to it, the wallet integration, and the bits that took me longer than they should have.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we're actually building
&lt;/h2&gt;

&lt;p&gt;A property sponsor (think: a building owner or a REIT issuer) deploys a contract. They can issue fractional shares in a property by inserting an investor's &lt;em&gt;commitment&lt;/em&gt; into a Merkle tree. The investor can then later prove ownership of that property in zero knowledge, and claim a slice of the rental pool each cycle.&lt;/p&gt;

&lt;p&gt;The bits that are public:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total number of properties registered&lt;/li&gt;
&lt;li&gt;Total shares issued (count of leaves in the tree)&lt;/li&gt;
&lt;li&gt;Rental pool size&lt;/li&gt;
&lt;li&gt;Total yield claims processed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bits that stay private:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which wallet owns which property&lt;/li&gt;
&lt;li&gt;Each holder's allocation size&lt;/li&gt;
&lt;li&gt;The mapping between commitments and real-world identities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That mapping happens off-chain, between the investor and the sponsor. The sponsor does KYC, decides allocations, and only puts a hash into the tree. Everyone after that point sees only the tree.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this dApp could actually ship
&lt;/h2&gt;

&lt;p&gt;Three concrete use cases I'd build on top of this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Family-office REIT&lt;/strong&gt; — a single private REIT that wants to onboard 40-50 family offices without leaking any of their positions to each other. Today this is done with paper certificates and an SS&amp;amp;C transfer agent. Move it to Midnight and you keep the privacy but settle in seconds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fractional luxury rentals&lt;/strong&gt; — Airbnb-style rentals where the building is fractionally owned by users, and rental income flows back to holders pro-rata. Users want to invest without doxxing themselves as owners of a specific building.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Regulated tokenised mortgages&lt;/strong&gt; — the lender is a public entity but the borrowers / co-investors want their participation hidden from competitors. Aggregate compliance numbers (total loaned, total outstanding) stay public; per-borrower amounts don't.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ul&gt;
&lt;li&gt;Node 20+&lt;/li&gt;
&lt;li&gt;Lace or 1AM wallet, on the Midnight Preprod network&lt;/li&gt;
&lt;li&gt;Some tNIGHT and tDUST from the Preprod faucet&lt;/li&gt;
&lt;li&gt;Docker (for the local proof server)&lt;/li&gt;
&lt;li&gt;The Compact compiler — &lt;code&gt;curl --proto '=https' --tlsv1.2 -LsSf https://github.com/midnightntwrk/compact/releases/latest/download/compact-installer.sh | sh&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The contract, function by function
&lt;/h2&gt;

&lt;p&gt;The whole contract is about 110 lines. Here's the full ledger declaration first:&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;

export sealed ledger sponsor: Bytes&amp;lt;32&amp;gt;;

export ledger ownershipCommitments: HistoricMerkleTree&amp;lt;10, Bytes&amp;lt;32&amp;gt;&amp;gt;;
export ledger yieldClaimNullifiers: Set&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;;

export ledger totalProperties: Counter;
export ledger totalShares: Counter;
export ledger totalYieldClaims: Counter;

export ledger rentalPoolAvailable: Uint&amp;lt;64&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth pointing out before we move on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sealed ledger&lt;/code&gt; for the sponsor means the value can only be set once, in the constructor. After that, no circuit can change it. This is how we enforce "only the sponsor can issue shares."&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HistoricMerkleTree&amp;lt;10, Bytes&amp;lt;32&amp;gt;&amp;gt;&lt;/code&gt; is a tree of depth 10 (so up to 2¹⁰ = 1024 leaves) where each leaf is 32 bytes. The "historic" part is crucial: proofs against past roots stay valid, which means an investor whose commitment was added 50 blocks ago can still prove they're in the tree.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Set&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;&lt;/code&gt; for nullifiers gives us O(log n) double-claim prevention. Once a nullifier goes in, the contract rejects any future tx that tries to add the same one.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Counter&lt;/code&gt; and &lt;code&gt;Uint&amp;lt;64&amp;gt;&lt;/code&gt; are public scalars. Anyone can read them off the indexer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;constructor(sponsorSk: Bytes&amp;lt;32&amp;gt;)&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;constructor(sponsorSk: Bytes&amp;lt;32&amp;gt;) {
    sponsor = disclose(publicKey(sponsorSk));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The constructor takes the sponsor's secret key (32 bytes, witness data — never appears on chain) and seals the corresponding public key into the contract. &lt;code&gt;disclose(...)&lt;/code&gt; tells the compiler this value crosses the public-private boundary on purpose.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;publicKey()&lt;/code&gt; is a private helper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;circuit publicKey(sk: 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;(
        [pad(32, "realestate:pk:v1"), sk]
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;pad(32, "realestate:pk:v1")&lt;/code&gt; is a 32-byte domain separator. Without it, the same secret key could collide with a public key in a different dApp using the same hash function. The &lt;code&gt;:v1&lt;/code&gt; suffix is there in case we ever want to migrate to a different hash without invalidating identities.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;getOwnershipCommitment(sk, propertyId)&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is the only circuit the &lt;em&gt;frontend&lt;/em&gt; calls off-chain. It's marked &lt;code&gt;export&lt;/code&gt; and uses &lt;code&gt;pureCircuits&lt;/code&gt;, which means it runs in JavaScript without producing a transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;circuit ownershipCommit(sk: Bytes&amp;lt;32&amp;gt;, propertyId: Bytes&amp;lt;32&amp;gt;): Bytes&amp;lt;32&amp;gt; {
    return persistentHash&amp;lt;Vector&amp;lt;3, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;(
        [pad(32, "realestate:own:v1"), propertyId, sk]
    );
}

export circuit getOwnershipCommitment(sk: Bytes&amp;lt;32&amp;gt;, propertyId: Bytes&amp;lt;32&amp;gt;): Bytes&amp;lt;32&amp;gt; {
    return ownershipCommit(sk, propertyId);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what produces the 64-character hex string the investor copies on the Home page. It's deterministic: the same wallet + same property always gives the same commitment. That's what makes "lose your password, lose your identity" actually work — there's no extra randomness needed, the wallet is sufficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;issueShare(holderCommit)&lt;/code&gt; — sponsor-only
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit issueShare(holderCommit: Bytes&amp;lt;32&amp;gt;): [] {
    const sk = localSecretKey();
    assert(sponsor == disclose(publicKey(sk)), "Not the sponsor");
    ownershipCommitments.insert(disclose(holderCommit));
    totalShares.increment(1);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things happening here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The witness &lt;code&gt;localSecretKey()&lt;/code&gt; pulls the caller's secret key out of their private state. (We'll see how this is wired up shortly.)&lt;/li&gt;
&lt;li&gt;The assertion &lt;code&gt;sponsor == publicKey(sk)&lt;/code&gt; is what gates this circuit to the sponsor only. If any other wallet tries to call &lt;code&gt;issueShare&lt;/code&gt;, their &lt;code&gt;publicKey(sk)&lt;/code&gt; won't match the sealed &lt;code&gt;sponsor&lt;/code&gt; and the proof generation fails before a transaction is ever submitted.&lt;/li&gt;
&lt;li&gt;The commitment goes into the tree. The chain learns that &lt;em&gt;a&lt;/em&gt; new leaf was added — nothing about who.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;totalShares.increment(1)&lt;/code&gt; is a public counter and useful for dashboards. You could also have the sponsor pass an explicit share count and increment by that amount, but I kept it simple.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;registerProperty()&lt;/code&gt; — sponsor-only
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit registerProperty(): [] {
    const sk = localSecretKey();
    assert(sponsor == disclose(publicKey(sk)), "Not the sponsor");
    totalProperties.increment(1);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a bookkeeping circuit that just bumps a counter. You'd call it once per property the sponsor onboards, so the public dashboard can show "12 properties registered." It doesn't change anything else.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;depositRent(amount)&lt;/code&gt; — sponsor-only
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit depositRent(amount: Uint&amp;lt;64&amp;gt;): [] {
    const sk = localSecretKey();
    assert(sponsor == disclose(publicKey(sk)), "Not the sponsor");
    rentalPoolAvailable = (rentalPoolAvailable + amount) as Uint&amp;lt;64&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adds to the rental pool. Public on-chain because everyone watching the tree wants to know how much money is available for yield claims this cycle.&lt;/p&gt;

&lt;p&gt;In a real system the sponsor wouldn't just add to a counter — they'd send actual tokens to the contract. You can pair this with a &lt;a href="https://github.com/midnight-network/midnight-apps/tree/main/shielded-token" rel="noopener noreferrer"&gt;shielded token&lt;/a&gt; and have &lt;code&gt;depositRent&lt;/code&gt; accept a coin commitment.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;proveOwnership(propertyId)&lt;/code&gt; — investor
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit proveOwnership(propertyId: Bytes&amp;lt;32&amp;gt;): Boolean {
    const sk = localSecretKey();
    const commit = ownershipCommit(sk, propertyId);
    const path = findOwnershipPath(commit);

    assert(
        ownershipCommitments.checkRoot(disclose(merkleTreePathRoot&amp;lt;10, Bytes&amp;lt;32&amp;gt;&amp;gt;(path))),
        "Not an owner of this property"
    );

    return disclose(true);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where the ZK magic happens. The investor:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetches their own secret key via &lt;code&gt;localSecretKey()&lt;/code&gt; (witness).&lt;/li&gt;
&lt;li&gt;Recomputes their commitment locally — &lt;code&gt;ownershipCommit(sk, propertyId)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Asks the witness &lt;code&gt;findOwnershipPath(commit)&lt;/code&gt; to provide the Merkle path from that commitment to a root the tree has seen.&lt;/li&gt;
&lt;li&gt;Asserts the path roots match. If they do, the proof succeeds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The on-chain effect of a successful call: nothing. No state changes. The wallet just records that the tx was submitted. But the chain has now mathematically certified that &lt;em&gt;somebody&lt;/em&gt; who owns property X has proven ownership — without revealing who.&lt;/p&gt;

&lt;p&gt;This is useful for "did you own this property at any point" verifications. A KYC portal, a tenant directory, a benefits programme — anything that needs proof of ownership without leaking identity.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;claimYield(propertyId, cycle, amount)&lt;/code&gt; — investor
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit claimYield(propertyId: Bytes&amp;lt;32&amp;gt;, cycle: Bytes&amp;lt;32&amp;gt;, amount: Uint&amp;lt;64&amp;gt;): Boolean {
    const sk = localSecretKey();
    const commit = ownershipCommit(sk, propertyId);
    const path = findOwnershipPath(commit);

    assert(
        ownershipCommitments.checkRoot(disclose(merkleTreePathRoot&amp;lt;10, Bytes&amp;lt;32&amp;gt;&amp;gt;(path))),
        "Not an owner of this property"
    );

    const nul = yieldNullifier(sk, propertyId, cycle);
    assert(!yieldClaimNullifiers.member(disclose(nul)), "Yield already claimed this cycle");
    assert(rentalPoolAvailable &amp;gt;= amount, "Insufficient rental pool");

    yieldClaimNullifiers.insert(disclose(nul));
    rentalPoolAvailable = (rentalPoolAvailable - amount) as Uint&amp;lt;64&amp;gt;;
    totalYieldClaims.increment(1);

    return disclose(true);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same ownership check as &lt;code&gt;proveOwnership&lt;/code&gt;, plus three more assertions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The nullifier &lt;code&gt;H(sk, propertyId, cycle)&lt;/code&gt; hasn't been seen before. If it has, this investor already claimed for this cycle.&lt;/li&gt;
&lt;li&gt;The rental pool has enough funds for this payout.&lt;/li&gt;
&lt;li&gt;We insert the nullifier and decrement the pool.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;cycle&lt;/code&gt; argument is what makes this work over time. Every quarter the sponsor sets a new cycle ID ("2026-Q2", "2026-Q3"), and each (investor, property, cycle) tuple produces a fresh nullifier. Without the cycle in the nullifier hash, the investor could only ever claim once and never again.&lt;/p&gt;

&lt;p&gt;You'll notice &lt;code&gt;amount&lt;/code&gt; is public — it has to be, so the pool can be decremented correctly. The privacy isn't in the amount, it's in &lt;em&gt;who's claiming&lt;/em&gt;. If you want amounts to be private too, you'd shield the rental pool itself and pay out from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The witnesses
&lt;/h2&gt;

&lt;p&gt;Witnesses are the link between the on-chain circuit and the off-chain private state. Two of them:&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;// confidential-real-estate/src/pages/witnesses.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;witnesses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;localSecretKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;privateState&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;privateState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;privateState&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="na"&gt;findOwnershipPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;privateState&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;commit&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;path&lt;/span&gt; &lt;span class="o"&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;ownershipCommitments&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;commit&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="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="s1"&gt;Ownership commitment not found in tree&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;privateState&lt;/span&gt;&lt;span class="p"&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;localSecretKey&lt;/code&gt; just hands the circuit the secret key from the local private state — never touches the chain.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;findOwnershipPath&lt;/code&gt; is a bit cleverer. It looks at the &lt;em&gt;local&lt;/em&gt; copy of the ledger that the Midnight.js client has been syncing in the background, walks the Merkle tree, and finds the path from the requested commitment to the root. That path is what &lt;code&gt;proveOwnership&lt;/code&gt; and &lt;code&gt;claimYield&lt;/code&gt; verify on-chain.&lt;/p&gt;

&lt;p&gt;If the commitment isn't in the local tree yet — for example, the sponsor only just added it and our client hasn't caught up — &lt;code&gt;findPathForLeaf&lt;/code&gt; returns null and we throw. The wallet displays the error to the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  The deterministic identity
&lt;/h2&gt;

&lt;p&gt;Every action on the dApp is signed with a key derived from your wallet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// confidential-real-estate/src/hooks/useIdentity.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;APP_SALT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confidential-real-estate-v1&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;deriveRoleKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shieldedCoinPublicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;role&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;master&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;deriveKeyFromPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;APP_SALT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;shieldedCoinPublicKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;deriveKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;master&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`realestate:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;deriveKeyFromPassword&lt;/code&gt; is PBKDF2 with 100,000 iterations of SHA-256. The "password" here is a static salt; the per-user entropy comes from &lt;code&gt;shieldedCoinPublicKey&lt;/code&gt;, which is fully determined by the wallet's seed phrase. Same wallet → same key, always.&lt;/p&gt;

&lt;p&gt;Earlier versions of this dApp had a separate user password on top of this. I dropped it because it was just adding a UX step without meaningful security gain — if someone has your wallet seed, they already control your assets; an extra password doesn't help. (If you want belt-and-braces security, add a passphrase wallet-side, not dApp-side.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring up the providers
&lt;/h2&gt;

&lt;p&gt;The frontend talks to Midnight through a bundle of providers, one for each concern:&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;// shape (real wiring is in src/pages/Deploy.tsx)&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="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;privateStateProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;levelPrivateStateProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;privateStateStoreName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;realestate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;publicDataProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="nf"&gt;indexerPublicDataProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;INDEXER_HTTP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;INDEXER_WS&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;zkConfigProvider&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;FetchZkConfigProvider&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;CONTRACT_PATH&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/managed/realestate/keys`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="na"&gt;proofProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="nf"&gt;httpClientProofProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PROOF_SERVER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;zkConfig&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;walletProvider&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;balanceTx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;getCoinPublicKey&lt;/span&gt; &lt;span class="nx"&gt;adapters&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;midnightProvider&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;submitTx&lt;/span&gt; &lt;span class="nx"&gt;adapter&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;In English:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;privateStateProvider&lt;/code&gt; is the local IndexedDB store for your secret key + cached tree state. Clearing browser storage clears this — but since the key is deterministic, it gets rederived on next page load.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;publicDataProvider&lt;/code&gt; reads chain state from the GraphQL indexer at &lt;code&gt;indexer.preprod.midnight.network&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;zkConfigProvider&lt;/code&gt; loads the proving/verifier keys generated by &lt;code&gt;compact compile&lt;/code&gt;. Heavy artefacts (~megabytes each).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;proofProvider&lt;/code&gt; is your local proof server at &lt;code&gt;localhost:6300&lt;/code&gt;. Heavy CPU work happens here.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;walletProvider&lt;/code&gt; adapts the Midnight JS transaction format to whatever your wallet extension expects.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;midnightProvider&lt;/code&gt; does the final submit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deploying the contract
&lt;/h2&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;finalContract&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadCompiledContract&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;deployContract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;compiledContract&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;finalContract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;privateStateId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PRIVATE_STATE_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;initialPrivateState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createRealEstatePrivateState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sponsorSk&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;sponsorSk&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;contractAddress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deployTxData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;args: [sponsorSk]&lt;/code&gt; is what gets passed to the Compact constructor. The sponsor public key gets sealed in, and now only this wallet (or anyone who knows their seed) can ever call &lt;code&gt;issueShare&lt;/code&gt; or &lt;code&gt;depositRent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The contract address is a 64-char hex string. We stash it in &lt;code&gt;localStorage&lt;/code&gt; so the rest of the app knows which contract to talk to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Submitting a circuit call
&lt;/h2&gt;

&lt;p&gt;After the contract is deployed, every circuit call follows the same shape:&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;txInterface&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createCircuitCallTxInterface&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;finalContract&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;PRIVATE_STATE_ID&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;txInterface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;issueShare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;hexToUint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;holderCommit&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Internally that does roughly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Look up the circuit by name on the compiled contract.&lt;/li&gt;
&lt;li&gt;Resolve witnesses against your private state.&lt;/li&gt;
&lt;li&gt;Build an unsigned transaction including the ZK proof.&lt;/li&gt;
&lt;li&gt;Hand it to the wallet provider to balance and sign.&lt;/li&gt;
&lt;li&gt;Submit through the midnight provider.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If anything fails — invalid commitment, mismatched root, proof timeout — the wallet popup shows the error and the chain state stays clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Home dashboard
&lt;/h2&gt;

&lt;p&gt;The investor's Home page shows a property gallery (London / NYC / Singapore mock listings), the live commitment for whichever property is selected, the contract address, and a quick-action grid.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/pages/Home.tsx (simplified)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;investorSk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useIdentity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;investor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;investorSk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&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;64&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;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;investorSk&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;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;padTo32Bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;propertyId&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;hash&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;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SHA-256&lt;/span&gt;&lt;span class="dl"&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="nf"&gt;setCommitHex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;uint8ArrayToHex&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;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hash&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;investorSk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;propertyId&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 a SHA-256 sketch of the commitment — it's not bit-for-bit identical to what &lt;code&gt;getOwnershipCommitment&lt;/code&gt; produces on-chain (which uses Midnight's &lt;code&gt;persistentHash&lt;/code&gt;), but it's good enough to give the user a stable hex string to share off-band. In production you'd want to either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Call the &lt;code&gt;pureCircuit&lt;/code&gt; on-chain helper, which gives you the &lt;em&gt;exact&lt;/em&gt; hash, or&lt;/li&gt;
&lt;li&gt;Reimplement the Midnight &lt;code&gt;persistentHash&lt;/code&gt; in JS and use it directly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The current dApp uses SHA-256 as a placeholder because the &lt;code&gt;pureCircuit&lt;/code&gt; import is heavy and only available after compile. A real production version would do the swap.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's not in the dApp (yet)
&lt;/h2&gt;

&lt;p&gt;I deliberately left these out to keep the demo focused:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Per-share weighting.&lt;/strong&gt; Currently each commitment is "one share." For variable allocations you'd store a &lt;code&gt;Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Uint&amp;lt;64&amp;gt;&amp;gt;&lt;/code&gt; mapping commitment to share count, then weight yield claims by it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Property NFTs.&lt;/strong&gt; No on-chain link between a property and any external identifier. You'd add a &lt;code&gt;Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Bytes&amp;lt;32&amp;gt;&amp;gt;&lt;/code&gt; mapping propertyId to a property-metadata hash.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redemption / share burn.&lt;/strong&gt; Once issued, shares stay in the tree forever. Adding a redemption nullifier set per (investor, property) gives you partial exits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real token settlement.&lt;/strong&gt; The rental pool is a &lt;code&gt;Uint&amp;lt;64&amp;gt;&lt;/code&gt; accumulator, not a real shielded token balance. Plug in a shielded token for real money movement.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Any of these would make a fun PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting from live testing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Failed to resolve import "/src/contracts/managed/realestate/contract/index.js"&lt;/code&gt;&lt;/strong&gt; — you haven't compiled the contract yet. Run &lt;code&gt;npx compact compile contracts/Contract.compact src/contracts/managed/realestate&lt;/code&gt;. The frontend imports the contract at that exact path.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Wallet not detected&lt;/strong&gt; — Lace and 1AM inject &lt;code&gt;window.midnight&lt;/code&gt; asynchronously. The &lt;code&gt;ConnectButton&lt;/code&gt; polls for a couple of seconds after mount to pick that up. If it's still not found, the button deep-links to lace.io.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Not the sponsor&lt;/code&gt; on &lt;code&gt;issueShare&lt;/code&gt;&lt;/strong&gt; — your wallet doesn't match the wallet that deployed. Switch to the deployer wallet or redeploy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Not an owner of this property&lt;/code&gt; on &lt;code&gt;claimYield&lt;/code&gt;&lt;/strong&gt; — sponsor hasn't called &lt;code&gt;issueShare&lt;/code&gt; for your commitment yet, &lt;em&gt;or&lt;/em&gt; you computed the commitment with a different &lt;code&gt;propertyId&lt;/code&gt; than the one you're claiming on. Double-check the spelling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Yield already claimed this cycle&lt;/code&gt;&lt;/strong&gt; — the nullifier is doing its job. Pick a different cycle ID or wait for the sponsor to declare the next one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tx hangs at "Generating proof"&lt;/strong&gt; — your local proof server probably died. Re-run &lt;code&gt;docker run -p 6300:6300 midnightntwrk/proof-server:8.0.3 midnight-proof-server -v&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;BalanceCheckOverspend&lt;/code&gt; (error 138)&lt;/strong&gt; — your wallet doesn't have enough tDUST to pay tx fees. Hit the Preprod faucet.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Clone the repo and try it: &lt;a href="https://github.com/ayushsingh82/Midnight-dApps" rel="noopener noreferrer"&gt;https://github.com/ayushsingh82/Midnight-dApps&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Extend the contract with per-share weighting (the most-requested feature).&lt;/li&gt;
&lt;li&gt;Read the &lt;a href="https://docs.midnight.network/" rel="noopener noreferrer"&gt;Midnight Compact docs&lt;/a&gt; for the full circuit/witness/ledger model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you fork it and ship something interesting, I'd love to hear about it.&lt;/p&gt;

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