For the complete documentation index, see llms.txt
ZK Loan DApp
The ZK Loan example shows how a loan application can be evaluated against private credit data without that data ever leaving the user's machine. A bank-style attestation provider signs the credit profile off-chain; the smart contract verifies the signature inside a zero-knowledge circuit and writes only the loan status and authorized amount to the ledger.
The credit score, monthly income, employment tenure, attestation signature, and user PIN all stay private. The blockchain learns whether the user qualifies for which tier, never why.
What this DApp demonstrates
This example is intentionally larger than a minimal DApp. It stitches together several Midnight building blocks in one place:
- Witness-provided private inputs: TypeScript code feeds credit data into the zero-knowledge prover at proving time without exposing it on-chain.
- Schnorr-on-Jubjub signature verification inside a circuit: a hand-rolled Compact module (to be replaced by a standard-library verifier once one ships) proves the credit data came from a registered attestation provider.
- PIN-derived on-chain identity: a witness-supplied user secret is hashed with a secret PIN to produce a per-user public key. The key reveals neither the secret nor the PIN, but it is a pseudonym, not an unlinkable identity. All of a user's loans sit under that one key in
loans, the attestation provider signs a hash of it with the credit data, andchangePinnames the old and new keys in the same transaction. - Two-phase approval flow: requests above the user's tier cap are stored as
Proposedand require a follow-uprespondToLoancall to accept or decline. - Witness-derived admin and identity: caller identity (both admin and per-user) is derived from a 32-byte witness secret.
ownPublicKey()is not used by the contract. Only the holder of the admin's secret can register providers, blacklist users, or rotate the admin role; only the holder of a user's secret can request, accept, or decline that user's loans.
Project structure
The example lives in examples/zk-loan in the midnight-examples monorepo:
examples/zk-loan/
├── contract/
│ ├── zk-loan.compact # Loan logic, eligibility tiers, ledger
│ ├── schnorr.compact # In-circuit signature verification
│ ├── witnesses.ts # Private state and witnesses (TypeScript)
│ └── index.ts # The compiled contract, with witnesses attached
├── attestation-api/ # REST server signing credit data
│ └── {signing,server,index,types}.ts
├── src/
│ ├── test/zk-loan.test.ts # End-to-end suite against a Midnight network
│ ├── test/zk-loan.sim.test.ts # Contract unit tests, in-memory simulator
│ └── {config,providers,wallet}.ts # Shared test harness
├── ui/ # React + Vite UI (Lace wallet, Preprod by default)
└── compose.yml # Local node + indexer + proof server
Prerequisites
- Node.js 22: the SDK uses Iterator helpers, so Node 20 crashes at the first sync
- Corepack, for Yarn 4:
corepack enable - Docker and Docker Compose, for the local network
- Compact toolchain: install with the
compactdevtool, then runcompact update 0.31.1and check withcompact compile --version - Midnight Lace wallet, for the UI only. See the installation guide
The example is verified against Compact language 0.23, compiler 0.31.1, @midnight-ntwrk/midnight-js-* 4.1.1 and wallet SDK 1.2.0. The local network in compose.yml runs midnightntwrk/midnight-node:1.0.0, midnightntwrk/indexer-standalone:4.3.3 and midnightntwrk/proof-server:8.1.0.
Set it up
1. Install and compile
git clone https://github.com/midnightntwrk/midnight-examples.git
cd midnight-examples
yarn install && yarn compile
cd examples/zk-loan
yarn compile generates contract/managed/zk-loan/: the JavaScript bindings, the prover and verifier keys, and the ZK IR.
2. Run the full flow on a local network
yarn env:up # node, indexer and proof server from compose.yml
yarn wait:dust # wait until the test wallet can pay fees
yarn test:local
The suite starts the attestation API in-process, then deploys the contract, registers the API's key as admin, requests loans for two borrowers across the tiers, and checks that tampered credit data, non-admin calls, blacklisted borrowers and an ex-admin are rejected. Every successful call generates a real proof, so it takes several minutes. Stop the network with yarn env:down.
The contract's simulator tests need no network:
yarn test:sim
3. Run the attestation API
The attestation API signs credit data with a Schnorr signature on Jubjub; the contract verifies the signature inside the ZK circuit.
PROVIDER_SECRET_KEY="$(node -e 'console.log(require("crypto").randomBytes(32).toString("hex"))')" \
yarn attestation:start
It listens on port 4000 (set PORT to change it) and prints the Provider ID (default 1) and the public key's x and y: the values an admin registers on-chain with registerProvider. Keep the PROVIDER_SECRET_KEY. Without it, every restart generates a new key and invalidates the on-chain registration.
4. Run the UI
The UI in ui/ connects through the Midnight Lace wallet, on Preprod by default. Lace can't balance or sign transactions for the local undeployed chain, so use the test suite for local iteration.
cd ui
yarn dev # http://localhost:5173
The UI can deploy or join a contract, request loans (fetching the signature from the attestation API in step 3) and accept or decline proposals. It can't register the attestation provider. On a contract you deploy from the UI, nothing else can either: the admin role comes from a secret that lives only in that browser tab's memory, and the UI can't export it. So every loan request on a UI-deployed contract fails with Attestation provider not registered. To see a loan go through end to end, use the test suite in step 2. A page refresh drops the secret too, and with it your loans and the admin role. See the UI README for the settings and the browser-specific details.
Learn how it works
The three-part tutorial walks through this example's code: the witness/disclose split, the Schnorr verification inside the circuit, the PIN-derived identity, and the on-chain test suite.
- Part 1: ZK Loan contract: the Compact contract, the Schnorr verification module and the witnesses.
- Part 2: Attestation API: the off-chain Schnorr signer, the REST endpoints and the attestation flow.
- Part 3: ZK Loan tests: the full flow on a local network, including the attacks the contract rejects.