Inspiration

Payroll, grants, and business disbursements are broken in Web3. Every on-chain payment leaks: who got paid, how much, and the full payment graph. Tesseract fixes this at the protocol level — a ZK-powered batch payment rail where a payer can pay hundreds of recipients and each recipient claims their funds with a cryptographic proof, while observers see nothing but a Merkle root.

We built on Midnight Network because it's the only L1 that treats privacy as a first-class primitive rather than a bolt-on. Zero-knowledge proofs aren't an afterthought — they're baked into every transaction.

## What It Does

Tesseract is a private business payments rail with six ZK circuits:

  1. Submit Batch Root — Payer commits a Merkle tree of (recipient, amount) pairs on-chain. Only the root is revealed. Recipients, amounts, and count stay private.

  2. Deposit Recipient Coin — Payer deposits a shielded coin per recipient. Each coin is locked to a compound key hash(batchId, leafHash) — no on-chain link between sender and recipient.

  3. Claim Payment — Recipient proves Merkle membership (leaf index + path), proves ownership of the claim secret, and receives the shielded coin. A nullifier is burned to prevent double-spend.

  4. Reclaim — Payer can reclaim unclaimed coins after the batch deadline passes, using a ZK proof of payer identity.

  5. Create Payment Request — Requester commits a ZK proof of identity and creates an on-chain payment request with deadline, amount, and commitment hash.

  6. Mark Request Paid — Payer marks request as paid, anchoring payment proof to the request ID.

All state changes are ZK-proved. No raw keys ever touch the ledger.

## How We Built It

  • Compact (Midnight's ZK smart contract language): 6 circuits, 9 flat ledger maps, Pedersen-hashed Merkle tree with 16-depth paths, persistent commitment hashing, nullifier-based double-spend protection.
  • TypeScript SDK (midnight-js): Custom TesseractClient wrapping findDeployedContract, flow modules for each circuit, Merkle tree construction with SHA-256 leaf hashing.
  • React + Vite frontend: Wallet connect via 1AM browser extension, live contract state subscriptions via indexer GraphQL, batch builder UI.
  • Local devnet: Full Midnight undeployed network (node, indexer, proof server) via Docker Compose.

## Challenges We Faced

ZK proof routing across browser security boundaries: The 1AM wallet extension runs its proving provider in a service worker (chrome-extension:// origin). The local proof server's CORS policy only allows http://localhost:5173. Getting proving to work required routing circuit proofs through httpClientProofProvider in the page context while separately registering the zkConfigProvider with the wallet for DUST fee proving.

Merkle tree construction: Midnight's on-chain Merkle tree uses persistentHash with MerkleTreePath<16, Bytes<32>> — building compatible off-chain proofs required matching the exact leaf encoding (compound hash(batchId, leaf)) and producing 16-deep paths with correct padding.

ZSwap coin lifecycle: Each circuit that touches a shielded coin must pass QualifiedShieldedCoinInfo as a witness, not a raw coin. Coordinating the witness pipeline (indexer query → coin lookup → witness injection → proof) across sequential transactions with 25s ZSwap propagation delays was the core infrastructure challenge.

## What We Learned

  • Midnight's separation of public ledger state from private ZK witnesses is elegant and forces good security design — you can't accidentally leak private data because the type system prevents it.
  • persistentHash vs transientHash in Compact is a critical distinction: only persistent hashes appear in the same location across blocks.
  • Building on a pre-mainnet L1 means debugging blind — no block explorers, no decoded transactions, no community answers. All signal comes from proof server logs and raw indexer GraphQL.

## What's Next

  • Multi-batch streaming payroll (recurring payments with deadline rollover)
  • Sharable claim links (base64-encoded claim packages for off-chain distribution)
  • Web2 → Web3 bridge: upload a CSV of wallet addresses and amounts, get a private batch in one click
  • Port to other privacy-aware chains (Aleo, Aztec) via the same Merkle + nullifier primitive pattern

Built With

Share this project:

Updates