<?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: Elliot lucky</title>
    <description>The latest articles on DEV Community by Elliot lucky (@codebigint_01).</description>
    <link>https://dev.to/codebigint_01</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%2F1706891%2Feeaef3a0-80c9-4d44-9af3-3ae6d6d6f6a9.jpg</url>
      <title>DEV Community: Elliot lucky</title>
      <link>https://dev.to/codebigint_01</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/codebigint_01"/>
    <language>en</language>
    <item>
      <title>Build a Private Vault DApp Smart Contract on Midnight with Compact</title>
      <dc:creator>Elliot lucky</dc:creator>
      <pubDate>Mon, 27 Apr 2026 01:25:37 +0000</pubDate>
      <link>https://dev.to/codebigint_01/build-a-private-vault-dapp-smart-contract-on-midnight-with-compact-53od</link>
      <guid>https://dev.to/codebigint_01/build-a-private-vault-dapp-smart-contract-on-midnight-with-compact-53od</guid>
      <description>&lt;p&gt;This article is the written companion to my Midnight Simplified video:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=MCumZemoX5c" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=MCumZemoX5c&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the video, we build a vault dApp smart contract from scratch using Compact on Midnight. The goal is to help developers who already know the basics of Compact start writing a complete smart contract that handles real vault behavior: creating positions, receiving shielded tokens, tracking balances, and withdrawing funds.&lt;/p&gt;

&lt;p&gt;We will focus on the contract side of the dApp. There is no frontend in this guide. The point is to understand the smart contract mechanics clearly before adding UI complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Are Building
&lt;/h2&gt;

&lt;p&gt;We are building a shielded vault contract where a user can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create a vault position&lt;/li&gt;
&lt;li&gt;choose the coin color that vault accepts&lt;/li&gt;
&lt;li&gt;deposit a shielded coin into the vault&lt;/li&gt;
&lt;li&gt;reject deposits with the wrong coin color&lt;/li&gt;
&lt;li&gt;withdraw from the vault&lt;/li&gt;
&lt;li&gt;keep the contract's shielded UTXO balance updated manually&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final project has this shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vault-dapp/
|-- package.json
|-- src/
|   |-- vault.compact
|   |-- witness.ts
|   |-- managed/
|   `-- test/
|       |-- vault.test.ts
|       |-- vault-setup.ts
|       `-- utils.ts
`-- tsconfig.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core files are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/vault.compact&lt;/code&gt;: the Compact smart contract&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/witness.ts&lt;/code&gt;: the TypeScript witness implementation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/test/vault-setup.ts&lt;/code&gt;: the simulator setup&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/test/vault.test.ts&lt;/code&gt;: the behavior tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Github link: &lt;a href="https://github.com/codeBigInt/midnight-simplified-tutorial-dapps/tree/main/vault-dapp" rel="noopener noreferrer"&gt;https://github.com/codeBigInt/midnight-simplified-tutorial-dapps/tree/main/vault-dapp&lt;/a&gt; &lt;/p&gt;

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

&lt;p&gt;To follow along, you should already have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your Midnight developer environment set up&lt;/li&gt;
&lt;li&gt;the Compact toolchain installed&lt;/li&gt;
&lt;li&gt;basic Compact knowledge&lt;/li&gt;
&lt;li&gt;Bun installed&lt;/li&gt;
&lt;li&gt;a code editor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the video description, I also mention linking to the updated setup resources because the Midnight toolchain has had upgrades. Make sure your Compact compiler version matches the language version used in this project.&lt;/p&gt;

&lt;p&gt;This contract uses:&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;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That goes with the newer Compact compiler version used in the tutorial.&lt;/p&gt;

&lt;p&gt;You will also want the Compact standard library documentation open because we use shielded token helpers such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;receiveShielded&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sendShielded&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mergeCoinImmediate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;insertCoin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ownPublicKey&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kernel.self&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Create the Project
&lt;/h2&gt;

&lt;p&gt;In the video, we start from a blank project.&lt;/p&gt;

&lt;p&gt;Create the folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;vault-dapp
&lt;span class="nb"&gt;cd &lt;/span&gt;vault-dapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize the project with Bun:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose the blank template. Bun will create the initial &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then create the source folder and the Compact file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;src
&lt;span class="nb"&gt;touch &lt;/span&gt;src/vault.compact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Add Compile Scripts
&lt;/h2&gt;

&lt;p&gt;Before writing the full contract, add scripts so you do not have to keep typing the Compact compiler command manually.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;package.json&lt;/code&gt;, add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test-compile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"compact compile --skip-zk src/vault.compact ./src/managed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"compile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"compact compile src/vault.compact ./src/managed"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;test-compile&lt;/code&gt; script compiles the Compact code without generating the full zero-knowledge materials. That makes it useful while you are actively developing.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;compile&lt;/code&gt; script performs the full compile.&lt;/p&gt;

&lt;p&gt;Both scripts write generated artifacts into:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;If that folder does not exist, the compiler creates it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Start the Compact Contract
&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;src/vault.compact&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The first line is the language pragma:&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;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then import the standard library:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The standard library gives us access to the built-in circuits and types we need for token transfer, ledger helpers, hashing, and shielded coin management.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Add the Main Circuit Skeleton
&lt;/h2&gt;

&lt;p&gt;Before defining all the state, it is useful to outline the main behavior of the vault.&lt;/p&gt;

&lt;p&gt;The vault needs three exported circuits:&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 createVault(
    _color: Bytes&amp;lt;32&amp;gt;
): []{
}

export circuit deposit(
    _coin: ShieldedCoinInfo
): []{
}

export circuit withdraw(_amount: Uint&amp;lt;128&amp;gt;): []{
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are the three user-facing actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;createVault&lt;/code&gt;: create a vault position&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;deposit&lt;/code&gt;: deposit shielded tokens into the contract&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;withdraw&lt;/code&gt;: withdraw shielded tokens from the contract&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point in the video, we compile early to confirm the setup works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun run test-compile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the setup is correct, Compact generates the managed TypeScript artifacts in &lt;code&gt;src/managed&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Define the Vault Struct
&lt;/h2&gt;

&lt;p&gt;Now we define the shape of a vault position:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export struct Vault {
    balance: Uint&amp;lt;128&amp;gt;;
    createdAt: Uint&amp;lt;64&amp;gt;;
    coinColor: Bytes&amp;lt;32&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The vault stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;balance&lt;/code&gt;: the amount deposited into the vault&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;createdAt&lt;/code&gt;: when the vault was created&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;coinColor&lt;/code&gt;: the token color this vault accepts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On Midnight, token types are identified by a byte string called a color. Different assets have different colors, so we use &lt;code&gt;coinColor&lt;/code&gt; to make sure a user deposits the same asset type they selected when creating the vault.&lt;/p&gt;

&lt;p&gt;The final code uses &lt;code&gt;Uint&amp;lt;128&amp;gt;&lt;/code&gt; for &lt;code&gt;balance&lt;/code&gt; because &lt;code&gt;ShieldedCoinInfo.value&lt;/code&gt; is also &lt;code&gt;Uint&amp;lt;128&amp;gt;&lt;/code&gt;. If you use &lt;code&gt;Uint&amp;lt;64&amp;gt;&lt;/code&gt; for &lt;code&gt;balance&lt;/code&gt;, the compiler will complain when you add a coin value to the vault balance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Add Ledger State
&lt;/h2&gt;

&lt;p&gt;We need one ledger map for vault positions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export ledger vaults: Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Vault&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This maps a generated user ID to a vault.&lt;/p&gt;

&lt;p&gt;We also need another map to manage shielded coin balances:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export ledger balances: Map&amp;lt;Bytes&amp;lt;32&amp;gt;, QualifiedShieldedCoinInfo&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This map stores contract-held shielded coins by coin color.&lt;/p&gt;

&lt;p&gt;This part is important: when you deal with shielded tokens, the contract does not automatically maintain an easy balance table for you. You receive the shielded coin, but you still need to manage the resulting UTXO state yourself. That is why the &lt;code&gt;balances&lt;/code&gt; map exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  ShieldedCoinInfo vs QualifiedShieldedCoinInfo
&lt;/h2&gt;

&lt;p&gt;The video spends time on this because it matters for token transfers.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ShieldedCoinInfo&lt;/code&gt; represents a newly created shielded coin used when receiving or spending a coin in a transaction. It has fields such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;nonce&lt;/li&gt;
&lt;li&gt;color&lt;/li&gt;
&lt;li&gt;value&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;QualifiedShieldedCoinInfo&lt;/code&gt; represents a shielded coin that already exists on the ledger and is ready to be spent. It includes the extra information needed to locate it, such as the Merkle tree index.&lt;/p&gt;

&lt;p&gt;That is why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deposits accept &lt;code&gt;ShieldedCoinInfo&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;contract balances are stored as &lt;code&gt;QualifiedShieldedCoinInfo&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;insertCoin&lt;/code&gt; converts the received shielded coin into the qualified form stored in the ledger&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 7: Add Witness Functions
&lt;/h2&gt;

&lt;p&gt;We do not want to store the user's public wallet address as the vault owner. That would defeat the purpose of building a shielded contract.&lt;/p&gt;

&lt;p&gt;Instead, we derive a user ID from a secret key stored in the user's private state.&lt;/p&gt;

&lt;p&gt;Declare the witnesses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;witness getSecretKey(): Bytes&amp;lt;32&amp;gt;;
witness getCurrentTime(): Uint&amp;lt;64&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;getSecretKey()&lt;/code&gt; lets the contract receive the user's secret key from private state.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;getCurrentTime()&lt;/code&gt; lets the contract set the vault creation timestamp.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: Generate a User ID
&lt;/h2&gt;

&lt;p&gt;Now add a helper circuit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;circuit generateUserId(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, "vaul:user"),
        sk
    ]);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;persistentHash&lt;/code&gt; gives us a deterministic ID from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a fixed domain separator&lt;/li&gt;
&lt;li&gt;the user's secret key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The current tutorial code uses &lt;code&gt;"vaul:user"&lt;/code&gt; as the domain separator. For a real deployed contract, choose this string carefully before deployment because changing it later changes all derived user IDs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 9: Implement createVault
&lt;/h2&gt;

&lt;p&gt;Inside &lt;code&gt;createVault&lt;/code&gt;, derive the user ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const newUserId = generateUserId(disclose(getSecretKey()));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the secret key comes from a witness, we disclose it before using it in the circuit.&lt;/p&gt;

&lt;p&gt;Next, make sure this user does not already have a vault:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assert (!vaults.member(newUserId), "Already exist");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create the empty vault:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const emptyVault = Vault {
    balance: 0,
    coinColor: disclose(_color),
    createdAt: disclose(getCurrentTime())
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, insert it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vaults.insert(newUserId, emptyVault);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complete circuit:&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 createVault(
    _color: Bytes&amp;lt;32&amp;gt;
): []{
    const newUserId = generateUserId(disclose(getSecretKey()));

    assert (!vaults.member(newUserId), "Already exist");

    const emptyVault = Vault {
        balance: 0,
        coinColor: disclose(_color),
        createdAt: disclose(getCurrentTime())
    };

    vaults.insert(newUserId, emptyVault);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run a quick compile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun run test-compile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During the video, one common issue appears here: forgetting semicolons. Compact expects semicolons at the end of statements, and the compiler will point you to the line that needs fixing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 10: Implement deposit
&lt;/h2&gt;

&lt;p&gt;The user deposits a shielded coin:&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 deposit(
    _coin: ShieldedCoinInfo
): []{
    const coin = disclose(_coin);
    const userId = generateUserId(disclose(getSecretKey()));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before receiving the token, check that the user has a vault:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assert (vaults.member(userId), "You have no vault position");
const vault = vaults.lookup(userId);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then make sure the deposited coin is the right type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assert (coin.color == vault.coinColor, "Invalid coin type deposited");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now receive the shielded coin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;receiveShielded(coin);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;receiveShielded&lt;/code&gt; validates that the coin is being received by the contract in this transaction.&lt;/p&gt;

&lt;p&gt;But receiving is not enough. The contract still needs to store or update the shielded balance manually.&lt;/p&gt;

&lt;p&gt;If there is already a balance for this coin color, merge the previous ledger coin with the new coin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if(balances.member(coin.color)){
    const prevBalance = balances.lookup(coin.color);
    const newBalance = mergeCoinImmediate(prevBalance, coin);

    balances.insertCoin(
        coin.color,
        newBalance,
        right&amp;lt;ZswapCoinPublicKey, ContractAddress&amp;gt;(kernel.self())
    );
}else{
    balances.insertCoin(
       coin.color,
       coin,
       right&amp;lt;ZswapCoinPublicKey, ContractAddress&amp;gt;(kernel.self())
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why &lt;code&gt;mergeCoinImmediate&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mergeCoin&lt;/code&gt; is for merging two coins that already exist on the ledger. In this case, we are merging an existing &lt;code&gt;QualifiedShieldedCoinInfo&lt;/code&gt; with a new incoming &lt;code&gt;ShieldedCoinInfo&lt;/code&gt;, so we use &lt;code&gt;mergeCoinImmediate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Why &lt;code&gt;right&amp;lt;ZswapCoinPublicKey, ContractAddress&amp;gt;(kernel.self())&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;The recipient type can be either a user public key or a contract address. Since the contract is storing this balance for itself, we pass the contract address using the &lt;code&gt;right&lt;/code&gt; constructor.&lt;/p&gt;

&lt;p&gt;Finally, update the vault accounting balance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const updatedVault = Vault {
    ...vault,
    balance: (vault.balance + coin.value) as Uint&amp;lt;128&amp;gt;
};

vaults.insert(userId, updatedVault);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cast to &lt;code&gt;Uint&amp;lt;128&amp;gt;&lt;/code&gt; matters because &lt;code&gt;coin.value&lt;/code&gt; is &lt;code&gt;Uint&amp;lt;128&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The complete deposit circuit:&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 deposit(
    _coin: ShieldedCoinInfo
): []{
    const coin = disclose(_coin);
    const userId = generateUserId(disclose(getSecretKey()));

    assert (vaults.member(userId), "You have no vault position");
    const vault = vaults.lookup(userId);

    assert (coin.color == vault.coinColor, "Invalid coin type deposited");

    receiveShielded(coin);

    if(balances.member(coin.color)){
        const prevBalance = balances.lookup(coin.color);
        const newBalance = mergeCoinImmediate(prevBalance, coin);

        balances.insertCoin(
            coin.color,
            newBalance,
            right&amp;lt;ZswapCoinPublicKey, ContractAddress&amp;gt;(kernel.self())
        );
    }else{
        balances.insertCoin(
           coin.color,
           coin,
           right&amp;lt;ZswapCoinPublicKey, ContractAddress&amp;gt;(kernel.self())
        );
    }

    const updatedVault = Vault {
        ...vault,
        balance: (vault.balance + coin.value) as Uint&amp;lt;128&amp;gt;
    };

    vaults.insert(userId, updatedVault);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compile again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun run test-compile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 11: Implement withdraw
&lt;/h2&gt;

&lt;p&gt;The withdrawal circuit accepts an amount:&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 withdraw(_amount: Uint&amp;lt;128&amp;gt;): []{
    const amount = disclose(_amount);
    const userId = generateUserId(disclose(getSecretKey()));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check that the user has a vault:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assert (vaults.member(userId), "You have no vault position");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Load the vault and check the user has enough balance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const vault = vaults.lookup(userId);
assert (vault.balance &amp;gt;= amount, "Insufficient vault balance");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Load the contract-held shielded coin for the vault's coin color:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const balanceToSendFrom = balances.lookup(vault.coinColor);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now call &lt;code&gt;sendShielded&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const sendResult = sendShielded(
    balanceToSendFrom,
    left&amp;lt;ZswapCoinPublicKey, ContractAddress&amp;gt;(ownPublicKey()),
    amount
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;sendShielded&lt;/code&gt; sends a value from a shielded coin owned by the contract to a recipient.&lt;/p&gt;

&lt;p&gt;Here the recipient is the user calling the circuit, so we use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;left&amp;lt;ZswapCoinPublicKey, ContractAddress&amp;gt;(ownPublicKey())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ownPublicKey()&lt;/code&gt; returns the &lt;code&gt;ZswapCoinPublicKey&lt;/code&gt; of the end user creating the transaction.&lt;/p&gt;

&lt;p&gt;After sending, Compact returns a send result. The important part for this contract is the change. If there is change left, we must store it. If there is no change, we remove the balance entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if(sendResult.change.is_some){
    balances.insertCoin(
        vault.coinColor,
        sendResult.change.value,
        right&amp;lt;ZswapCoinPublicKey, ContractAddress&amp;gt;(kernel.self())
    );
}else{
    balances.remove(vault.coinColor);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The auto-generated subtitle says &lt;code&gt;is_sum&lt;/code&gt; in this part, but the contract code uses &lt;code&gt;is_some&lt;/code&gt;. Follow the generated Compact type in your local compiler output if this API changes.&lt;/p&gt;

&lt;p&gt;Then reduce the user's vault balance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const updatedVault = Vault {
    ...vault,
    balance: (vault.balance - amount) as Uint&amp;lt;128&amp;gt;
};

vaults.insert(userId, updatedVault);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complete withdraw circuit:&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 withdraw(_amount: Uint&amp;lt;128&amp;gt;): []{
    const amount = disclose(_amount);
    const userId = generateUserId(disclose(getSecretKey()));
    assert (vaults.member(userId), "You have no vault position");

    const vault = vaults.lookup(userId);
    assert (vault.balance &amp;gt;= amount, "Insufficient vault balance");

    const balanceToSendFrom = balances.lookup(vault.coinColor);

    const sendResult = sendShielded(
        balanceToSendFrom,
        left&amp;lt;ZswapCoinPublicKey, ContractAddress&amp;gt;(ownPublicKey()),
        amount
    );

    if(sendResult.change.is_some){
        balances.insertCoin(
            vault.coinColor,
            sendResult.change.value,
            right&amp;lt;ZswapCoinPublicKey, ContractAddress&amp;gt;(kernel.self())
        );
    }else{
        balances.remove(vault.coinColor);
    }

    const updatedVault = Vault {
        ...vault,
        balance: (vault.balance - amount) as Uint&amp;lt;128&amp;gt;
    };

    vaults.insert(userId, updatedVault);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the compile script again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun run test-compile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see an error around &lt;code&gt;ownPublicKey&lt;/code&gt;, check the capitalization. It should be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ownPublicKey()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 12: Implement the TypeScript Witnesses
&lt;/h2&gt;

&lt;p&gt;The Compact file declares witnesses. TypeScript provides their runtime implementation.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;src/witness.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;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;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;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/contract&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;VaultPrivateState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;secreteKey&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;function&lt;/span&gt; &lt;span class="nf"&gt;createVaultPrivateState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secreteKey&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;VaultPrivateState&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;secreteKey&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;getSecretKey&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;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;VaultPrivateState&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;VaultPrivateState&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="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;secreteKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;getCurrentTime&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;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;VaultPrivateState&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;VaultPrivateState&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="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="nc"&gt;BigInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The witness returns a tuple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[privateState, witnessValue]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this project, the private state does not change, so each witness returns the same private state it received.&lt;/p&gt;

&lt;p&gt;One cleanup you can make later is renaming &lt;code&gt;secreteKey&lt;/code&gt; to &lt;code&gt;secretKey&lt;/code&gt;. The current project uses &lt;code&gt;secreteKey&lt;/code&gt;, so the article keeps that spelling where it reflects the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 13: Test the Contract
&lt;/h2&gt;

&lt;p&gt;The final project includes Vitest tests around the generated contract bindings.&lt;/p&gt;

&lt;p&gt;The simulator in &lt;code&gt;src/test/vault-setup.ts&lt;/code&gt; deploys the generated &lt;code&gt;Contract&lt;/code&gt;, initializes private state, and exposes helper methods:&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;createVault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TEST_COIN_COLOR&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Ledger&lt;/span&gt;
&lt;span class="nf"&gt;deposit&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;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TEST_COIN_COLOR&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Ledger&lt;/span&gt;
&lt;span class="nf"&gt;withdraw&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;Ledger&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tests cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deploying with empty ledgers&lt;/li&gt;
&lt;li&gt;creating a vault&lt;/li&gt;
&lt;li&gt;rejecting deposits before vault creation&lt;/li&gt;
&lt;li&gt;accepting valid deposits&lt;/li&gt;
&lt;li&gt;rejecting invalid coin colors&lt;/li&gt;
&lt;li&gt;partial withdrawal&lt;/li&gt;
&lt;li&gt;full withdrawal and balance removal&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun run &lt;span class="nb"&gt;test&lt;/span&gt;:run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Test Files  1 passed (1)
Tests       7 passed (7)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Full Contract
&lt;/h2&gt;

&lt;p&gt;Here is the complete &lt;code&gt;src/vault.compact&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

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

export struct Vault {
    balance: Uint&amp;lt;128&amp;gt;;
    createdAt: Uint&amp;lt;64&amp;gt;;
    coinColor: Bytes&amp;lt;32&amp;gt;;
}

export ledger vaults: Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Vault&amp;gt;;
export ledger balances: Map&amp;lt;Bytes&amp;lt;32&amp;gt;, QualifiedShieldedCoinInfo&amp;gt;;

witness getSecretKey(): Bytes&amp;lt;32&amp;gt;;
witness getCurrentTime(): Uint&amp;lt;64&amp;gt;;

export circuit createVault(
    _color: Bytes&amp;lt;32&amp;gt;
): []{
    const newUserId = generateUserId(disclose(getSecretKey()));

    assert (!vaults.member(newUserId), "Already exist");

    const emptyVault = Vault {
        balance: 0,
        coinColor: disclose(_color),
        createdAt: disclose(getCurrentTime())
    };

    vaults.insert(newUserId, emptyVault);
}

export circuit deposit(
    _coin: ShieldedCoinInfo
): []{
    const coin = disclose(_coin);
    const userId = generateUserId(disclose(getSecretKey()));

    assert (vaults.member(userId), "You have no vault position");
    const vault = vaults.lookup(userId);

    assert (coin.color == vault.coinColor, "Invalid coin type deposited");

    receiveShielded(coin);

    if(balances.member(coin.color)){
        const prevBalance = balances.lookup(coin.color);
        const newBalance = mergeCoinImmediate(prevBalance, coin);

        balances.insertCoin(
            coin.color,
            newBalance,
            right&amp;lt;ZswapCoinPublicKey, ContractAddress&amp;gt;(kernel.self())
        );
    }else{
        balances.insertCoin(
           coin.color,
           coin,
           right&amp;lt;ZswapCoinPublicKey, ContractAddress&amp;gt;(kernel.self())
        );
    }

    const updatedVault = Vault {
        ...vault,
        balance: (vault.balance + coin.value) as Uint&amp;lt;128&amp;gt;
    };

    vaults.insert(userId, updatedVault);
}

export circuit withdraw(_amount: Uint&amp;lt;128&amp;gt;): []{
    const amount = disclose(_amount);
    const userId = generateUserId(disclose(getSecretKey()));
    assert (vaults.member(userId), "You have no vault position");

    const vault = vaults.lookup(userId);
    assert (vault.balance &amp;gt;= amount, "Insufficient vault balance");

    const balanceToSendFrom = balances.lookup(vault.coinColor);

    const sendResult = sendShielded(
        balanceToSendFrom,
        left&amp;lt;ZswapCoinPublicKey, ContractAddress&amp;gt;(ownPublicKey()),
        amount
    );

    if(sendResult.change.is_some){
        balances.insertCoin(
            vault.coinColor,
            sendResult.change.value,
            right&amp;lt;ZswapCoinPublicKey, ContractAddress&amp;gt;(kernel.self())
        );
    }else{
        balances.remove(vault.coinColor);
    }

    const updatedVault = Vault {
        ...vault,
        balance: (vault.balance - amount) as Uint&amp;lt;128&amp;gt;
    };

    vaults.insert(userId, updatedVault);
}

circuit generateUserId(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, "vaul:user"),
        sk
    ]);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What You Learned
&lt;/h2&gt;

&lt;p&gt;In this tutorial, you learned how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;start a Compact project with Bun&lt;/li&gt;
&lt;li&gt;add fast and full compile scripts&lt;/li&gt;
&lt;li&gt;write exported Compact circuits&lt;/li&gt;
&lt;li&gt;define ledger maps&lt;/li&gt;
&lt;li&gt;model a vault position with a struct&lt;/li&gt;
&lt;li&gt;use witness functions for private state&lt;/li&gt;
&lt;li&gt;generate a private user ID&lt;/li&gt;
&lt;li&gt;receive shielded tokens with &lt;code&gt;receiveShielded&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;store shielded coin balances with &lt;code&gt;insertCoin&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;merge new deposits with &lt;code&gt;mergeCoinImmediate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;send withdrawals with &lt;code&gt;sendShielded&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;manage returned change manually&lt;/li&gt;
&lt;li&gt;test the contract with generated TypeScript bindings&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Improvements
&lt;/h2&gt;

&lt;p&gt;This vault is intentionally minimal. If you want to extend it, try:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;adding zero-amount checks&lt;/li&gt;
&lt;li&gt;supporting multiple vaults per user&lt;/li&gt;
&lt;li&gt;extracting repeated balance update logic into helper circuits&lt;/li&gt;
&lt;li&gt;adding events or richer metadata&lt;/li&gt;
&lt;li&gt;building a frontend for create, deposit, and withdraw actions&lt;/li&gt;
&lt;li&gt;supporting only one approved asset instead of multiple coin colors&lt;/li&gt;
&lt;li&gt;improving naming before production use&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;This vault dApp is a compact example of how Midnight smart contracts can combine private state, shielded assets, and public ledger updates.&lt;/p&gt;

&lt;p&gt;If you are learning Compact, this is a good project to study because it touches the pieces you need for many real dApps: identity derivation, token transfer, state management, and testing.&lt;/p&gt;

&lt;p&gt;Watch the full tutorial video here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=MCumZemoX5c" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=MCumZemoX5c&lt;/a&gt;&lt;br&gt;
Github link: &lt;a href="https://github.com/codeBigInt/midnight-simplified-tutorial-dapps/tree/main/vault-dapp" rel="noopener noreferrer"&gt;https://github.com/codeBigInt/midnight-simplified-tutorial-dapps/tree/main/vault-dapp&lt;/a&gt; &lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>privacy</category>
      <category>tutorial</category>
      <category>web3</category>
    </item>
    <item>
      <title>JOURNEY INTO BLOCKCHAIN DEVELOPMENT THE EASY WAY</title>
      <dc:creator>Elliot lucky</dc:creator>
      <pubDate>Sat, 01 Nov 2025 15:28:53 +0000</pubDate>
      <link>https://dev.to/codebigint_01/journey-into-blockchain-development-the-easy-way-40jj</link>
      <guid>https://dev.to/codebigint_01/journey-into-blockchain-development-the-easy-way-40jj</guid>
      <description>&lt;p&gt;Hmmm, thank goodness you found this blog post. I’m sure by now you’ve toured half the internet searching for an easier way to get started in the blockchain industry as a developer. I get it looks complicated, sounds technical, and can feel overwhelming trying to piece everything together.&lt;/p&gt;

&lt;p&gt;That’s exactly why I decided to share my experience to give you a simpler, clearer path into blockchain development and hopefully inspire your own journey.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Like Isaac Newton once said, “If I have seen further, it is by standing on the shoulders of giants.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So before we dive into my personal story, let’s first understand what blockchain really is, and why it was invented in plain, beginner-friendly terms.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;🔗 WHAT AND WHY BLOCKCHAIN?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Let’s take a scenario.&lt;br&gt;
When you think of a bank, you think about monetary records of people who own accounts i.e., a ledger or journal, although a bit advanced these days with the invention of computers. These ledgers are owned and controlled by these banks. What happens if there’s a fire outbreak and all the financial records become historical ashes (not the cricket trophy, LOL 🤭)? I mean actual ashes.&lt;/p&gt;

&lt;p&gt;Of course, we would not know who owns what. This is one of the reasons blockchain was invented a “distributed/decentralized ledger,” not owned or controlled by a single entity or enterprise.&lt;/p&gt;

&lt;p&gt;These ledger records are distributed in the sense that they are stored on several computers around the globe called “nodes,” each having the same copy of the ledger.&lt;/p&gt;

&lt;p&gt;In today’s world where ledgers are no longer used, it can be likened to a distributed or decentralized database.&lt;/p&gt;

&lt;p&gt;But you may ask, why then is it called a blockchain and not just a decentralized database? Well, instead of storing user transactions or data in rows and columns as banks would do, transactions or data are bundled into small units called “blocks.” And unlike pages of a ledger linked together by glue and referencing each other in the head of the documents, each block is linked together by “hashes”; a fixed-length string that represents the data contained in the block and the next block combined together.&lt;/p&gt;

&lt;p&gt;Enough of the jargon. Back to my experience diving into blockchain development. There are different blockchains, each solving a particular problem e.g., Bitcoin (removes the need for financial intermediaries like banks), Ethereum (eliminating the need for third parties in agreements through smart contracts), Cardano, Midnight (a privacy-centered blockchain), Avalanche, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤯 MY EXPERIENCE DEVELOPING ON ETHEREUM
&lt;/h2&gt;

&lt;p&gt;For me, I started with Ethereum not because it was easy to learn, but because it was one of the most popular at the time and still is. Development on Ethereum introduced the concept of smart contracts, which is now an integral part of every blockchain.&lt;/p&gt;

&lt;p&gt;In fact, smart contracts are what make modern blockchains usable. They are “self-executing” programs or code you write and deploy to the blockchain just like you would on AWS or Vercel with an application you just finished building. They execute themselves when the conditions you define within the contract are met. Like any other application (web or mobile), they have to be coded using a programming language. For Ethereum, I had to learn “Solidity.”&lt;/p&gt;

&lt;p&gt;Solidity was difficult for me to catch up with, as it had a syntax (language writing pattern) that looked like C++ and JavaScript combined. Being a newbie in programming who only had knowledge of web development-centered languages like JavaScript, a bit of TypeScript (a subset of JavaScript), and frontend frameworks like React as at the time, I became overwhelmed.&lt;/p&gt;

&lt;p&gt;I had to learn how to manage memory usage while writing Solidity code, structs, maps, etc. After a while, I knew I was not just ready for it and had to go back to where I came from 😄, web development. Still with the intent of learning blockchain, but with a failed attempt.&lt;/p&gt;

&lt;h2&gt;
  
  
  💡WHAT CHANGED?
&lt;/h2&gt;

&lt;p&gt;Eventually, I came across a sidechain built on the Cardano blockchain called Midnight. Unlike every other blockchain, its language is so easy to learn, and it solves a key pain point in blockchain, decentralization without compromising users’ privacy. On most blockchains, everything is public, including user assets, details, and confidential information.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://midnight.network/" rel="noopener noreferrer"&gt;&lt;br&gt;
Midnight blockchain&lt;/a&gt; solves this by ensuring users’ private data is secure and allowing them to choose when to share their info, while still maintaining the decentralized nature expected from every blockchain.&lt;/p&gt;

&lt;p&gt;Most importantly, its smart contract language is really easy to learn. It’s just like TypeScript. That’s to say, if you know TypeScript, you can dive straight in and start developing on Midnight. The language is called Minokawa (formerly Compact). It uses data types similar to arrays, objects, functions, and strings in TypeScript. Except that it differs slightly in how you write them.&lt;/p&gt;

&lt;p&gt;This was when I knew I had found the right spot for me to master and experience what developing on the blockchain feels like without having to learn an entirely strange programming language. Believe me, in less than 3 months I had learned how to write complex smart contracts just because I knew TypeScript. Its ecosystem is enriched with great minds building and collaborating on both individual projects and hackathons (two of which I have won) all in the quest to improve the ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHAT I RECOMMEND ⭐⭐⭐⭐⭐
&lt;/h2&gt;

&lt;p&gt;While choosing a blockchain to start developing on depends on the programming languages and concepts one is already familiar with, I would recommend &lt;a href="https://midnight.network/" rel="noopener noreferrer"&gt;Midnight Blockchain&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Why? Because if you’re someone like me coming from a web or mobile development background, you already know JavaScript. This means you can easily learn TypeScript in less than a month and transition into blockchain development on &lt;a href="https://docs.midnight.network/develop/reference/compact/writing" rel="noopener noreferrer"&gt;Midnight&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Everyone’s blockchain journey is different, but the key is to start where you are and build from what you already know.&lt;/p&gt;

&lt;p&gt;That’s what helped me find my path and hopefully, this helps you find yours too.&lt;/p&gt;

&lt;p&gt;Want to checkout Midnight? Here’s a glimpse of what it feels like writing smart contracts!&lt;br&gt;
&lt;a href="https://docs.midnight.network/develop/reference/compact/writing" rel="noopener noreferrer"&gt;https://docs.midnight.network/develop/reference/compact/writing&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stay curious. Keep building. The future is decentralized. ✨&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>web3</category>
      <category>midnightchallenge</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
