DEV Community

Giovanni
Giovanni

Posted on

Building a Midnight App with ViteJS

Midnight is a privacy‑first L1 where you write Compact smart contracts that compile to ZK circuits and then interact with them from a TypeScript app using generated artifacts and a local proof server. Below is a blog‑style tutorial showing how to scaffold a Midnight DApp frontend with Vite, connect it to a simple Compact contract, and get new users from zero to “first private tx” on Testnet‑02.


What is Midnight and why Vite

Midnight is a blockchain focused on selective disclosure: users can prove facts about their data (compliance, thresholds, membership) without revealing the underlying sensitive information. This is achieved via Compact, a domain‑specific language that compiles to zero‑knowledge circuits and enforces privacy by default.

For onboarding web developers, Vite offers a fast dev server, first‑class TypeScript support, and a familiar React/Vue/Svelte ecosystem, making it a natural fit for Midnight’s TypeScript‑first tooling and generated APIs.


Prerequisites and environment

Before writing any code, new users need both a Midnight wallet and a local dev setup. The basic checklist is:

  • Google Chrome for the Lace Midnight Preview wallet.
  • Node.js 20.x or higher (NVM is recommended for managing versions).
  • Docker Desktop for running the local proof server used to generate ZK proofs.
  • A code editor like VS Code, ideally with the Compact extension installed.

To get a wallet and test funds:

  1. Install the Lace Midnight Preview Chrome extension from the Web Store and pin it.
  2. Create a new wallet, set a strong password, and safely back up the seed phrase offline.
  3. Copy your address and request tDUST from the official testnet faucet; the wallet should soon show a non‑zero tDUST balance.

Installing Compact and the proof server

Compact is the smart contract language for Midnight; you will compile .compact files into ZK circuits and TypeScript bindings. To install the CLI, run the official installer script:

curl --proto '=https' --tlsv1.2 -LsSf \
  https://github.com/midnightntwrk/compact/releases/download/compact-v0.2.0/compact-installer.sh \
  | sh
Enter fullscreen mode Exit fullscreen mode

After installation, ensure the binary is on your PATH and verify it works:

compact --version   # prints Compact version
which compact       # prints installation path
Enter fullscreen mode Exit fullscreen mode

Both commands should succeed to confirm your environment is ready to compile contracts.

For local proof generation, Midnight uses a proof server that runs inside Docker. Start it with:

docker run -p 6300:6300 midnightnetwork/proof-server -- \
  'midnight-proof-server --network testnet'
Enter fullscreen mode Exit fullscreen mode

The container logs should indicate the server is listening on http://localhost:6300; in Lace’s settings under Midnight network, you can optionally point it to the local server (Local (http://localhost:6300)) so wallet‑side proofs also run against your machine.


Writing a minimal Compact contract

To keep the first tutorial approachable, define a simple “Hello Midnight” contract that stores a single string on‑chain. In a fresh folder (the contract side, separate from the Vite UI for clarity), create the required structure:

mkdir my-mn-app
cd my-mn-app
npm init -y

mkdir src contracts
touch contracts/hello-world.compact
Enter fullscreen mode Exit fullscreen mode

At the top of hello-world.compact, pin the Compact language version to guard against breaking changes in future minor releases:

pragma language_version 0.17;
Enter fullscreen mode Exit fullscreen mode

Next, define the ledger section, which declares the on‑chain state schema. Use Opaque<"string"> to store a variable‑length string while keeping it privacy‑aware:

export ledger message: Opaque<"string">;
Enter fullscreen mode Exit fullscreen mode

Finally, add a circuit called storeMessage that accepts a private string input and writes it into the ledger, explicitly disclosing it so it becomes visible on the public state:

export circuit storeMessage(customMessage: Opaque<"string">): [] {
  message = disclose(customMessage);
}
Enter fullscreen mode Exit fullscreen mode

Putting it all together, the full hello-world.compact file is:

pragma language_version 0.17;

export ledger message: Opaque<"string">;

export circuit storeMessage(customMessage: Opaque<"string">): [] {
  message = disclose(customMessage);
}
Enter fullscreen mode Exit fullscreen mode

disclose is required because Compact treats user inputs as private by default; only explicitly disclosed values leak to the public ledger, embodying Midnight’s “privacy‑first” design.


Compiling to ZK circuits and artifacts

From the project root, compile the contract with Compact:

compact compile contracts/hello-world.compact contracts/managed/hello-world
Enter fullscreen mode Exit fullscreen mode

This generates a contracts/managed/hello-world folder containing:

  • contract/ – compiled contract JSON artifacts for deployment and frontend integration.
  • keys/ – proving and verification keys used for generating and validating ZK proofs.
  • zkir/ – the Zero‑Knowledge Intermediate Representation bridging Compact to the ZK backend.
  • compiler/ – intermediate artifacts created during compilation.

These outputs are what your Vite app will ultimately use (directly or via Midnight’s SDK) to construct transactions, generate proofs via the proof server, and talk to the Midnight testnet.


Scaffolding a Vite + TypeScript frontend

With the contract compiled, you can now spin up a Vite app that will serve as the DApp’s UI layer. From a suitable directory (sibling to or inside your repo, depending on your preferred layout), run:

npm create vite@latest midnight-hello-vite -- --template react-ts
cd midnight-hello-vite
npm install
Enter fullscreen mode Exit fullscreen mode

This creates a React + TypeScript project with a hot‑reloading dev server. For other ecosystems, you can swap react-ts for vue-ts, svelte-ts, or vanilla-ts while keeping the Midnight integration story the same.

To connect the Vite app to your contract artifacts, either copy the contracts/managed/hello-world folder into the frontend repo (for example under src/contracts) or expose it via a workspace/monorepo setup if you want a more production‑ready layout. The important part is that your UI code can import the compiled contract metadata and keys.


Wiring Midnight logic into the Vite app

At a high level, the Vite app will need to:

  • Detect and connect to the user’s Lace Midnight Preview wallet in Chrome.
  • Load the compiled contract JSON from contracts/managed/hello-world/contract.
  • Use the Midnight client APIs to create a transaction that calls storeMessage, feeding in the user’s string input.
  • Send the transaction to the testnet, letting the proof server handle ZK proof generation.

A tutorial‑friendly React flow in App.tsx might:

  1. Render a “Connect Lace” button that triggers wallet API discovery in the browser.
  2. Show an input field for the message and a “Store message” button.
  3. On submit, construct a Midnight transaction invoking the storeMessage circuit, sign it via the wallet, and broadcast it.
  4. Optionally query and display the latest message from the contract ledger to prove the write succeeded.

Although the exact JavaScript client bindings are documented elsewhere in the Midnight API reference, the key idea for onboarding is to present this as “just another TypeScript SDK” backed by artifacts generated from Compact, not a black‑box cryptography stack.


Onboarding tips for new Midnight users

To make the Vite tutorial an effective onboarding piece into the Midnight ecosystem, emphasize a few narrative points:

  • Midnight treats privacy as the default; disclosure is explicit, as seen in the disclose(customMessage) call.
  • The same tooling flow scales from a toy hello-world contract to real selective‑disclosure apps (compliance attestations, private credentials, etc.).
  • Lace + proof server + Compact + Vite is a complete vertical stack: wallet, proofs, contracts, and UI all working together on Testnet‑02.

Top comments (0)