Inspiration Every time you place a trade on a public blockchain DEX, the whole world sees it before it settles. Bots watch the mempool, spot your incoming swap, and jump ahead of you - buying first, selling into your order, and pocketing the difference. This is MEV (Maximal Extractable Value), and it quietly taxes ordinary traders hundreds of millions of dollars a year.

The usual "solutions" ask you to trust a private relay or a centralized sequencer - you just move the surveillance somewhere else. We wanted something better: a DEX where the trade is mathematically invisible, not just hidden behind a promise. When we saw that Midnight lets you write smart contracts whose inputs are zero-knowledge witnesses, it clicked - you could make swap amounts provably private while keeping the market honest. That's DarkPool DEX.

What it does DarkPool DEX is a shielded constant-product AMM (the $x \cdot y = k$ model that powers Uniswap), rebuilt so that:

Swap amounts are private. Your input amount is a zero-knowledge witness, it never appears on-chain. A front-running bot sees that a swap happened, but not its size or direction impact, so there's nothing to exploit. Reserves stay public. Pool liquidity is public state, so anyone can verify pricing and the constant-product invariant. Privacy for the trader, transparency for the market. It earns revenue. A 0.25% fee on every swap accrues to an on-chain protocol treasury - a privacy-native DEX with a real business model baked in. Crucially, privacy doesn't mean "trust me." The contract proves the swap math is correct with a real ZK proof. A malicious trader who lies about their output amount is rejected on-chain - we tested exactly this.

How we built it The invariant we had to preserve is the constant-product rule. For a swap of $\Delta x$ of token A into a pool with reserves $(x, y)$, honoring the fee $f = 0.0025$:

$$\Delta y = \frac{y \cdot \Delta x (1 - f)}{x + \Delta x (1 - f)}$$

The contract must enforce this without ever learning $\Delta x$ in the clear, that's the whole trick, and it's where zero-knowledge does the work.

Contract (Compact / ZK): We wrote the core circuits, addLiquidity and swapAforB, in Compact, Midnight's ZK circuit language. Private amounts enter as witnesses; the circuit verifies the AMM math and updates public reserves and the treasury. It compiles down to prover/verifier keys and ZKIR. Local proving stack (Docker): We stood up a full local Midnight network, node, indexer, and proof server, via Docker Compose, and funded accounts with the wallet SDK. Backend bridge (Node.js + Express): A REST server deploys the contract on boot, seeds a 5M / 5M liquidity pool, keeps the deployed contract loaded, and exposes clean /api/state, /quote, /swap, and /liquidity endpoints, so the frontend never has to touch ZK plumbing. Frontend (React + react-three-fiber): A live dashboard with a 3D glass "pool orb" that reacts to trades, real-time reserves and treasury readouts, and a gamification layer, XP, privacy-themed ranks (Watcher → Midnight Legend), achievements, and combo streaks, to make privacy fun. The payoff moment: a user clicks Swap in the UI → a real ZK proof generates in ~16s → it settles on-chain → the dashboard shows updated reserves and treasury. On-chain results matched our simulator exactly: 100k A → 90,702 B, with 250 routed to the treasury.

What we learned Zero-knowledge changes how you architect state. You constantly ask "who is allowed to know this?", private witness vs. public ledger, and get that boundary right at design time, not as an afterthought. Proofs are heavy, and that shapes the product. Proof generation is CPU-bound WASM that blocks the Node event loop. We learned to design UX around ~16s proof latency (progress states, optimistic feedback) rather than pretending it's instant. "Trustless privacy" is a real, testable property. Writing the adversarial test, a prover lying about amountOut and getting rejected, was the moment the concept became concrete.

Challenges we ran into The ESM/CJS toolchain maze. ts-node threw an opaque ESM-loader error when importing the compiled contract. We fixed it by deploying with a native ESM script instead- and by symlinking node_modules so the contract's ESM imports and our CJS simulator both resolved. A deadlocking chain. Two backend instances share the genesis wallet and issue conflicting transactions, deadlocking the local network. We learned to enforce a single server instance and reset the network cleanly when needed. Event-loop stalls during proving. Because WASM proof generation blocks Node, the server goes briefly unresponsive mid-proof, we had to account for this in how we sequenced deploys, quotes, and swaps. Getting the funded, DUST-registered genesis account right so that every transaction could actually generate and submit a real proof end-to-end.

What's next A selective-disclosure circuit so a trader can prove a trade to an auditor (regulatory "audit receipt") without revealing it publicly. Multi-hop routing and additional pools. Deploying to Midnight's public testnet beyond our local network.

Built With

Share this project:

Updates

Submission history