<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ragrin Armstrong</title>
    <description>The latest articles on DEV Community by Ragrin Armstrong (@ragrin_armstrong_a78a0748).</description>
    <link>https://dev.to/ragrin_armstrong_a78a0748</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2225341%2Ff2ff9b06-f20f-4fa3-a068-cdcc7d424d57.jpg</url>
      <title>DEV Community: Ragrin Armstrong</title>
      <link>https://dev.to/ragrin_armstrong_a78a0748</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ragrin_armstrong_a78a0748"/>
    <language>en</language>
    <item>
      <title>Building a Shielded Token dApp on Midnight: From Compact Contract to React UI</title>
      <dc:creator>Ragrin Armstrong</dc:creator>
      <pubDate>Wed, 17 Jun 2026 10:24:38 +0000</pubDate>
      <link>https://dev.to/ragrin_armstrong_a78a0748/building-a-shielded-token-dapp-on-midnight-from-compact-contract-to-react-ui-2ja0</link>
      <guid>https://dev.to/ragrin_armstrong_a78a0748/building-a-shielded-token-dapp-on-midnight-from-compact-contract-to-react-ui-2ja0</guid>
      <description>&lt;p&gt;When I first looked at Midnight's privacy model, I kept tripping over the same question. If the chain hides balances inside zero-knowledge proofs, how do you actually write a token contract? Where does the contract end and the wallet begin?&lt;/p&gt;

&lt;p&gt;This tutorial answers that by building one. We write a Compact contract that mints, transfers, and burns shielded tokens, wire it through TypeScript, and drive it from a React frontend. By the end you will have a working dApp you can run locally, plus the mental model to take it to testnet.&lt;/p&gt;

&lt;p&gt;The full source lives at &lt;a href="https://github.com/kwanter/midnight-shielded-dapp" rel="noopener noreferrer"&gt;the companion repo&lt;/a&gt;. Clone it, follow along, or just read the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Midnight actually gives you
&lt;/h2&gt;

&lt;p&gt;Midnight is a privacy first blockchain from the team behind Cardano. The important part for us is how it splits responsibility.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ledger layer&lt;/strong&gt; holds the shielded coins as UTXO commitments. The wallet SDK (&lt;code&gt;@midnight-ntwrk/wallet-sdk-shielded&lt;/code&gt;) spends them. This is where the privacy actually lives. Coins are committed on chain, but their value, recipient, and linkability are hidden behind ZK proofs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contract layer&lt;/strong&gt; is your Compact code. It runs as a circuit. The contract's job is authorization and bookkeeping, not hiding values. Privacy is the ledger's problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This split feels strange if you come from Solidity. In Ethereum the contract is the whole story. On Midnight the contract authorizes a value movement and the ledger hides it.&lt;/p&gt;

&lt;p&gt;That means our shielded token contract does two things. It tracks a public total supply so anyone can audit the cap, and it gates who can mint or burn. The actual private coins are out of scope for the circuit. That is the wallet's job.&lt;/p&gt;

&lt;h2&gt;
  
  
  The keypair admin pattern
&lt;/h2&gt;

&lt;p&gt;Before any tokens exist we need someone authorized to mint them. We could store the deployer's address on chain, but that ties the contract to a specific wallet identity and leaks who deployed it.&lt;/p&gt;

&lt;p&gt;Midnight's official NFT example uses a better pattern. The deployer generates a private key locally, derives a public key from it, and stores only the public key on chain. Every mint call re-derives the public key from the local private key and checks it matches the committed value. The deployer's wallet identity never appears on chain.&lt;/p&gt;

&lt;p&gt;Here is the contract entrypoint that sets this up.&lt;br&gt;
&lt;/p&gt;

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

import CompactStandardLibrary;
import "./modules/ShieldedToken";

export { totalSupply, balanceOf, mint, burn };

struct AdminSecretKey { bytes: Bytes&amp;lt;32&amp;gt;; }
struct AdminPublicKey { bytes: Bytes&amp;lt;32&amp;gt;; }

export ledger contractAdmin: AdminPublicKey;

witness localSecretKey(): AdminSecretKey;

constructor() {
  contractAdmin = disclose(deriveAdminPublicKey(localSecretKey()));
}

export circuit deriveAdminPublicKey(sk: AdminSecretKey): AdminPublicKey {
  return AdminPublicKey {
    bytes: persistentHash&amp;lt;Vector&amp;lt;2, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;([pad(32, "shielded:admin:pk:v1"), sk.bytes])
  };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to notice if this is your first Compact code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ledger&lt;/code&gt; declares on-chain state. It is public and readable by anyone querying the contract.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;witness&lt;/code&gt; declares a function the wallet provides. The contract calls it, but the wallet decides what it returns. This is how private data enters a circuit without leaking.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;disclose&lt;/code&gt; marks a value as safe to reveal on chain. The constructor stores the derived public key, not the private key.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;persistentHash&lt;/code&gt; is a domain-separated hash. We pad a namespace string (&lt;code&gt;shielded:admin:pk:v1&lt;/code&gt;) so this key cannot collide with one derived for some other purpose. Domain separation is a small habit that prevents a whole class of cross-protocol attacks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Minting and burning
&lt;/h2&gt;

&lt;p&gt;With admin authority in place, the mint and burn circuits are short.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit mintShieldedToken(to: ZswapCoinPublicKey, amount: Uint&amp;lt;64&amp;gt;): [] {
  assert(contractAdmin == deriveAdminPublicKey(localSecretKey()), "Not authorized to mint.");
  assert(disclose(amount) &amp;gt; 0, "Amount must be positive.");
  mint(to, amount);
}

export circuit burnShieldedToken(amount: Uint&amp;lt;64&amp;gt;): [] {
  assert(disclose(amount) &amp;gt; 0, "Amount must be positive.");
  const holder = ownPublicKey();
  burn(holder, amount);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ZswapCoinPublicKey&lt;/code&gt; is the 32-byte public key a wallet derives for receiving shielded coins. It is not the same as an account address. When you ask a wallet for its shielded address, you get back this coin public key plus an encryption key.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ownPublicKey()&lt;/code&gt; is a built-in that returns the coin public key of whoever signed the current transaction. For burn, that is the holder proving they own the tokens being destroyed.&lt;/p&gt;

&lt;p&gt;The actual balance arithmetic lives in a module, which keeps the entrypoint readable and matches how the official examples are structured.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module ShieldedToken {

  import CompactStandardLibrary;

  export ledger totalSupply: Uint&amp;lt;64&amp;gt;;
  export ledger balances: Map&amp;lt;ZswapCoinPublicKey, Uint&amp;lt;64&amp;gt;&amp;gt;;

  export circuit balanceOf(holder: ZswapCoinPublicKey): Uint&amp;lt;64&amp;gt; {
    assert(holder != default&amp;lt;ZswapCoinPublicKey&amp;gt;, "Holder cannot be empty.");
    if (balances.member(disclose(holder))) {
      return balances.lookup(disclose(holder));
    } else {
      return 0;
    }
  }

  export circuit mint(to: ZswapCoinPublicKey, amount: Uint&amp;lt;64&amp;gt;): [] {
    assert(to != default&amp;lt;ZswapCoinPublicKey&amp;gt;, "Recipient cannot be empty.");
    assert(disclose(amount) &amp;gt; 0, "Amount must be positive.");
    if (!balances.member(disclose(to))) {
      balances.insert(disclose(to), 0);
    }
    const prev: Uint&amp;lt;64&amp;gt; = balances.lookup(disclose(to));
    const next: Uint&amp;lt;64&amp;gt; = (prev + disclose(amount)) as Uint&amp;lt;64&amp;gt;;
    balances.insert(disclose(to), next);
    totalSupply = (totalSupply + disclose(amount)) as Uint&amp;lt;64&amp;gt;;
  }

  export circuit burn(holder: ZswapCoinPublicKey, amount: Uint&amp;lt;64&amp;gt;): [] {
    assert(holder != default&amp;lt;ZswapCoinPublicKey&amp;gt;, "Holder cannot be empty.");
    assert(balances.member(disclose(holder)), "Holder has no balance.");
    assert(balances.lookup(disclose(holder)) &amp;gt;= disclose(amount), "Insufficient balance.");
    const prev: Uint&amp;lt;64&amp;gt; = balances.lookup(disclose(holder));
    const next: Uint&amp;lt;64&amp;gt; = (prev - disclose(amount)) as Uint&amp;lt;64&amp;gt;;
    balances.insert(disclose(holder), next);
    totalSupply = (totalSupply - disclose(amount)) as Uint&amp;lt;64&amp;gt;;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the part where Compact differs from Solidity the most, so it is worth slowing down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why all the &lt;code&gt;disclose()&lt;/code&gt; calls
&lt;/h3&gt;

&lt;p&gt;In a ZK circuit, values that depend on private inputs stay private by default. The compiler refuses to write them to the public ledger unless you explicitly opt in. When you write &lt;code&gt;amount &amp;gt; 0&lt;/code&gt;, the result of that comparison is fine to leak (it is just a boolean), but &lt;code&gt;amount&lt;/code&gt; itself is a private input until you disclose it.&lt;/p&gt;

&lt;p&gt;When you then do &lt;code&gt;prev + amount&lt;/code&gt; and want to store the result on chain, the compiler says wait, that arithmetic result depends on a private value, and writing it to the ledger would disclose information derived from it. So you call &lt;code&gt;disclose(amount)&lt;/code&gt; to tell the compiler yes, I know this reveals the amount, and that is the point. Mint amounts are public in this design.&lt;/p&gt;

&lt;p&gt;If you wanted the amounts to stay private, you would keep the bookkeeping off chain entirely and let the wallet's shielded coin layer handle it. That is a more advanced pattern and out of scope here, but it is worth knowing the trade-off exists.&lt;/p&gt;

&lt;p&gt;This default-private behavior is the single biggest difference between Compact and Solidity. Solidity has no concept of private inputs to a function. Every parameter is visible to anyone reading the transaction calldata. You can hash values before storing them, but the inputs themselves are public by construction. Compact inverts this. Privacy is the default, and disclosure is an explicit, auditable decision you make at each ledger write. When you read a Compact contract, the &lt;code&gt;disclose()&lt;/code&gt; calls are the points where data crosses from private to public. Scan for them and you have a map of exactly what the contract leaks.&lt;/p&gt;

&lt;p&gt;This also changes how you reason about a contract audit. In Solidity you ask, what can an attacker learn by watching the chain? In Compact you ask, did the author disclose more than they needed to? The answer for this tutorial contract is yes, intentionally, because we want the supply and balances to be auditable. A production privacy coin would disclose almost nothing from the contract layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the explicit &lt;code&gt;as Uint&amp;lt;64&amp;gt;&lt;/code&gt; casts
&lt;/h3&gt;

&lt;p&gt;Compact's type system widens arithmetic results to the full field range, roughly 254 bits. Adding two &lt;code&gt;Uint&amp;lt;64&amp;gt;&lt;/code&gt; values does not give you another &lt;code&gt;Uint&amp;lt;64&amp;gt;&lt;/code&gt;, it gives you a field element that could be larger. The compiler will not silently narrow it back, because doing so could hide an overflow.&lt;/p&gt;

&lt;p&gt;So after every arithmetic expression we cast explicitly. &lt;code&gt;(prev + disclose(amount)) as Uint&amp;lt;64&amp;gt;&lt;/code&gt; tells the compiler we have considered overflow and accept the narrowing. For a tutorial contract this is fine. For production you would add an explicit overflow check first.&lt;/p&gt;

&lt;p&gt;I hit this the hard way while building this. My first version used &lt;code&gt;Counter&lt;/code&gt;, the standard library type from the official counter example. Counter has &lt;code&gt;.increment()&lt;/code&gt; and &lt;code&gt;.decrement()&lt;/code&gt; methods that take &lt;code&gt;Uint&amp;lt;16&amp;gt;&lt;/code&gt; deltas, capped at 65,535. Fine for a click counter, useless for token amounts. The lesson is that Counter is for counting, Uint is for amounts, and the two do not mix.&lt;/p&gt;

&lt;h2&gt;
  
  
  The witness provider
&lt;/h2&gt;

&lt;p&gt;The contract declares &lt;code&gt;witness localSecretKey(): AdminSecretKey&lt;/code&gt; but does not implement it. The implementation lives in TypeScript, because the witness runs in the wallet, not on chain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Ledger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../managed/shielded-token/contract/index.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WitnessContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@midnight-ntwrk/compact-runtime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ShieldedTokenPrivateState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="na"&gt;adminSecretKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createShieldedTokenPrivateState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;adminSecretKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ShieldedTokenPrivateState&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;adminSecretKey&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generateAdminSecretKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&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;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRandomValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;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="nx"&gt;WitnessContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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;ShieldedTokenPrivateState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;ShieldedTokenPrivateState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;privateState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;adminSecretKey&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 witness receives the current private state and returns a tuple. The first element is the updated private state (unchanged here, we just read the key). The second is the value the contract sees as the witness result.&lt;/p&gt;

&lt;p&gt;The private state is a plain object the wallet holds locally. It never goes on chain. The admin key lives in it, generated once at deploy time with &lt;code&gt;crypto.getRandomValues&lt;/code&gt; and persisted in the wallet from then on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compiling and testing
&lt;/h2&gt;

&lt;p&gt;The Compact compiler is not an npm package. Midnight ships it through their own installer.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;That puts a &lt;code&gt;compact&lt;/code&gt; binary at &lt;code&gt;~/.local/bin&lt;/code&gt;. The binary is a version manager. To get the specific compiler version the official examples target, run &lt;code&gt;compact update 0.30.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Compiling the contract generates TypeScript under &lt;code&gt;src/managed/&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;contract
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run compact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated &lt;code&gt;index.js&lt;/code&gt; exports a &lt;code&gt;Contract&lt;/code&gt; class, a &lt;code&gt;ledger&lt;/code&gt; function for reading state, and typed wrappers for every circuit. This is what the rest of your TypeScript code imports.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing without a node
&lt;/h3&gt;

&lt;p&gt;You do not need a running Midnight node to test contract logic. The &lt;code&gt;compact-runtime&lt;/code&gt; package ships an in-memory simulator that runs your circuits directly.&lt;/p&gt;

&lt;p&gt;The pattern, lifted from the official counter example, is a small wrapper class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CircuitContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;sampleContractAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createConstructorContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createCircuitContext&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@midnight-ntwrk/compact-runtime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Ledger&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="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../managed/shielded-token/contract/index.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ShieldedTokenPrivateState&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;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../witnesses.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Recipient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShieldedTokenSimulator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Contract&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ShieldedTokenPrivateState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;circuitContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CircuitContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ShieldedTokenPrivateState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;adminSecretKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Contract&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ShieldedTokenPrivateState&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;witnesses&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;currentPrivateState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;currentContractState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;currentZswapLocalState&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;createConstructorContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;adminSecretKey&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeat&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="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;circuitContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createCircuitContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;sampleContractAddress&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nx"&gt;currentZswapLocalState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;currentContractState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;currentPrivateState&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;mintShieldedToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Recipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;circuitContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;impureCircuits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mintShieldedToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;circuitContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;balanceOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;holder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Recipient&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;bigint&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;this&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="nx"&gt;impureCircuits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balanceOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;circuitContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;holder&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;circuitContext&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;context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="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;Each circuit call returns a new context. You thread that context into the next call. The simulator is pure in the sense that it does not touch the network, but it mutates its own context as circuits run, so you treat it as stateful.&lt;/p&gt;

&lt;p&gt;With that wrapper, tests read like specs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rejects mint from a caller without the admin key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sim&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;ShieldedTokenSimulator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;freshAdminKey&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;wrongKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;freshAdminKey&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;sim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replacePrivateState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;createShieldedTokenPrivateState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wrongKey&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mintShieldedToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sampleRecipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/Not authorized/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rejects burn larger than the holder's balance&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sim&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;ShieldedTokenSimulator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;freshAdminKey&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="nx"&gt;sim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mintShieldedToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sampleRecipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;sim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setCaller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sampleRecipient&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;burnShieldedToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/Insufficient balance/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run them with &lt;code&gt;npm test&lt;/code&gt;. Ten tests cover the happy path plus the authorization and bounds checks. If a test fails, you see the exact assert message from the contract.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;setCaller&lt;/code&gt; method on the simulator is worth a note. The contract calls &lt;code&gt;ownPublicKey()&lt;/code&gt; to find out who is transacting. In the simulator, that reads from the circuit context's Zswap local state. &lt;code&gt;setCaller&lt;/code&gt; swaps the coin public key there, which is how a test pretends to be a different wallet signing the transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring a React frontend
&lt;/h2&gt;

&lt;p&gt;The contract is the hard part. The frontend is plumbing. We use Vite with React and TypeScript, and a thin API layer that talks to the simulator. On a real deploy you would swap the simulator for the wallet SDK, but the function signatures stay the same.&lt;/p&gt;

&lt;p&gt;The API layer is one file.&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;function&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;adminKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;DappState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;contract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Contract&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ShieldedTokenPrivateState&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;witnesses&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;privateState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createShieldedTokenPrivateState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;adminKey&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;constructorCtx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createConstructorContext&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeat&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;init&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;constructorCtx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createCircuitContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;sampleContractAddress&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentZswapLocalState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentContractState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentPrivateState&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;makeState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ready&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;simulator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[deployContract]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&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;makeState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mintTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;DappState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;requireContract&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="nx"&gt;contract&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;impureCircuits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mintShieldedToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recipient&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&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;context&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;makeState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ready&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;totalSupply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;readTotalSupply&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[mintTokens]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&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;makeState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;totalSupply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;safeTotalSupply&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things here matter more than they look.&lt;/p&gt;

&lt;p&gt;First, errors get logged before they get caught. A common mistake in this kind of wrapper is to swallow every exception and return a default. I did that in the first draft, and &lt;code&gt;balanceOfHolder&lt;/code&gt; silently returned &lt;code&gt;0n&lt;/code&gt; on any failure. A corrupted context looked identical to an empty wallet. Now errors throw, callers catch at the boundary, and the console shows what actually broke.&lt;/p&gt;

&lt;p&gt;Second, &lt;code&gt;mintTokens&lt;/code&gt; preserves the known supply on error instead of zeroing it. If a mint fails after the contract already holds tokens, the UI should still show the real supply, not flash to zero. The &lt;code&gt;safeTotalSupply&lt;/code&gt; helper reads the supply if it can and falls back to zero only as a last resort.&lt;/p&gt;

&lt;p&gt;The React side is straightforward. State holds the contract status, total supply, and the recipient balance. Buttons call the API layer. One detail worth flagging is input handling.&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;handleMint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&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;mintAmount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setState&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="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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid amount. Enter a whole number.&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="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;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;mintAmount&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;BigInt("")&lt;/code&gt; throws. So does &lt;code&gt;BigInt("1.5")&lt;/code&gt;. An unhandled throw inside a React event handler leaves the UI in a stale state with no feedback. Wrapping the conversion and showing a friendly error is the fix. It is a small thing, but it is the kind of thing a reviewer notices.&lt;/p&gt;

&lt;h2&gt;
  
  
  One build wrinkle: WebAssembly
&lt;/h2&gt;

&lt;p&gt;Midnight's runtime ships a WebAssembly binary. Vite's production bundler cannot import &lt;code&gt;.wasm&lt;/code&gt; files directly without a plugin. The dev server handles it natively, but &lt;code&gt;vite build&lt;/code&gt; fails with a confusing error about ESM integration for Wasm.&lt;/p&gt;

&lt;p&gt;The fix is two plugins.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; vite-plugin-wasm vite-plugin-top-level-await
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;wasm&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vite-plugin-wasm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;topLevelAwait&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vite-plugin-top-level-await&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;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;react&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;wasm&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;topLevelAwait&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;esnext&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, &lt;code&gt;vite build&lt;/code&gt; bundles the runtime into a single output. The WASM file lands at around 1.4 MB uncompressed, 412 KB gzipped, which is the bulk of the bundle. For a tutorial that is fine. For production you would lazy-load it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting a real wallet
&lt;/h2&gt;

&lt;p&gt;The frontend above runs against the simulator. To connect a real wallet, Midnight defines a DApp connector API. A wallet extension injects itself at &lt;code&gt;window.midnight&lt;/code&gt; keyed by a UUID. Your code detects it, calls &lt;code&gt;connect&lt;/code&gt;, and gets back an API for balances, addresses, and transaction signing.&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="kr"&gt;declare&lt;/span&gt; &lt;span class="nb"&gt;global&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Window&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;midnight&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;InitialAPI&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;detectWallets&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;DetectedWallet&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="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="nx"&gt;midnight&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="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;midnight&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;rdns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rdns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&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;connectWallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;networkId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&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="nx"&gt;midnight&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Wallet not found&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="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;midnight&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;networkId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The connected API gives you shielded and unshielded balances, transaction history, and addresses. To submit a transaction that calls your contract, you build the circuit invocation, hand it to the wallet's &lt;code&gt;balanceTransaction&lt;/code&gt; method, and the wallet handles coin selection, fee payment, and proof generation. Your code stays focused on what the transaction should do, not how it gets balanced.&lt;/p&gt;

&lt;p&gt;This is the piece I left as future work in the companion repo. The simulator proves the contract is correct. The wallet integration is mechanical once you have a deployed contract address on testnet.&lt;/p&gt;

&lt;p&gt;The reason the integration is mechanical is the API contract between your code and the wallet is narrow. Your TypeScript calls the same &lt;code&gt;impureCircuits&lt;/code&gt; methods on the same &lt;code&gt;Contract&lt;/code&gt; object. The difference is where the circuit context comes from. In the simulator you build it yourself with &lt;code&gt;createCircuitContext&lt;/code&gt;. On a real deploy, the wallet SDK hands you a context backed by the actual ledger state, real coin selection, and a live proof server. The &lt;code&gt;Contract&lt;/code&gt; class does not know or care which one it is running against.&lt;/p&gt;

&lt;p&gt;That means the work of going from simulator to testnet is mostly infrastructure. You need a wallet connected, a funded account for fees, and a proof server endpoint. None of that changes your circuit logic or your witness implementations. If the contract is correct in the simulator, it is correct on chain. The simulator is not a toy, it runs the same compiled bytecode the chain would run. It just skips the consensus and networking layers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mental model, restated
&lt;/h2&gt;

&lt;p&gt;If you take one thing from this, take the split.&lt;/p&gt;

&lt;p&gt;The contract authorizes. The ledger hides.&lt;/p&gt;

&lt;p&gt;When you write a shielded token contract on Midnight, you are not reimplementing privacy. You are writing the rules for who can move value, and the ledger enforces those rules behind a ZK proof. Your circuit runs, the wallet generates a proof that it ran correctly, and the chain verifies the proof without ever seeing the private inputs.&lt;/p&gt;

&lt;p&gt;That is why the contract is short. There is no Merkle tree management, no nullifier set, no commitment scheme. Those exist, but they live in the ledger, maintained by the wallet SDK. Your job is the policy layer on top.&lt;/p&gt;

&lt;p&gt;This is also why the testing story is so clean. Because the contract does not own the privacy primitives, you can test every circuit path in memory with no node, no wallet, and no network. The simulator catches logic bugs. Privacy correctness is guaranteed by the ledger, which is battle-tested and shared across every contract on the chain. You inherit its security for free.&lt;/p&gt;

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

&lt;p&gt;A few directions worth exploring once you have this working.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deploy to testnet.&lt;/strong&gt; Get test NIGHT tokens from the faucet, deploy the contract through the wallet SDK, and mint real shielded coins. The simulator code path and the deploy code path share the same contract and witnesses, so the only new code is the wallet connection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a transfer circuit.&lt;/strong&gt; The current contract tracks balances but does not move them between holders. A transfer circuit would debit the sender and credit the recipient in one atomic call, which is the pattern the bounty description calls &lt;code&gt;mint_and_send&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep amounts private.&lt;/strong&gt; Right now mint amounts are public because we &lt;code&gt;disclose()&lt;/code&gt; them. To hide them, drop the on-chain balance tracking entirely and let the wallet's shielded coin layer own the accounting. The contract becomes a pure authorization gate. This is closer to how Midnight's own native tokens work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Compact docs at &lt;a href="https://docs.midnight.network" rel="noopener noreferrer"&gt;docs.midnight.network&lt;/a&gt; go deeper on the ledger primitives. The &lt;code&gt;example-nft-contracts&lt;/code&gt; and &lt;code&gt;example-counter&lt;/code&gt; repos on GitHub are the best reference for real patterns, since the generated TypeScript types they produce are what you actually import.&lt;/p&gt;

&lt;p&gt;Thanks for following along. If you build something with this, tag it &lt;code&gt;#MidnightforDevs&lt;/code&gt; so the team sees it.&lt;/p&gt;

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