Prove you belong on the list — without ever revealing the list or which entry is yours.
Ghostlist is a privacy-preserving allowlist mint gate built on the Midnight Network. It uses zero-knowledge proofs so that allowlist members can mint a token without exposing their wallet address, the index of their entry, or any other identifying information.
Connect Lace, click Mint Ghost, and the entire proof generation + transaction submission happens in your browser. Your secret never leaves your device.
| Network | Contract Address |
|---|---|
| Preprod | 0x64261388057269dee716e43acec16fb20d55a804ce1865b77922cb0513db4e6e |
View on Midnight Explorer (Preprod) or query the contract address in any Midnight indexer that supports Preprod.
Every web3 drop starts with an allowlist — a list of wallets eligible to mint early. Publishing that list on a public blockchain means every participant is doxxed before the drop even begins. Phishers scrape the list and swarm each wallet with DMs, fake mint sites, and targeted scams.
Ghostlist solves this. The contract stores only a cryptographic commitment to the allowlist (a Merkle root). When a user mints, their browser generates a zero-knowledge proof that they are on the list — without revealing which entry is theirs or any identifying information.
- Real Lace wallet integration — no mocks, no placeholder wallet
- End-to-end ZK pipeline — proof generated in browser, verified on-chain
- 50-wallet cohort test — validated at scale on Preprod
- Production frontend — TanStack Start (React 19 + Vite 8 + Nitro SSR), deployed on Vercel
- Full test coverage — 4/4 passing on the core contract, plus e2e and cohort suites
- CI/CD — GitHub Actions pipeline on every push and PR
merkleRoot— the Merkle root committing to the full allowlistusedNullifiers— which nullifiers have been consumed (prevents double-minting)totalMinted— public counter of successful mints
- Which allowlist entry minted — nullifier is a one-way hash
- The user's secret — witness is private to the user's device
- The allowlist itself — only the root is on-chain
- Whether two mints came from the same user — nullifiers are unlinkable
- The user's wallet address — not correlated with the nullifier
This is the property that makes allowlists safe to publish.
| Layer | Technology |
|---|---|
| ZK contract | Compact (@midnight-ntwrk/compact-js v2.5) |
| Runtime | Midnight.js SDK v4.1, Compact.js runtime v0.16 |
| Wallet | Lace Midnight (real dApp connector) |
| Frontend | TanStack Start + React 19 + Vite 8 + Nitro SSR |
| Routing | TanStack Router v1.170 |
| Styling | Tailwind CSS v4 + shadcn/ui primitives |
| Animation | GSAP 3.15 + Anime.js 4.5 |
| Data | TanStack Query v5 |
| Proof server | Docker (midnightnetwork/proof-server) |
| Indexer | Midnight public indexer (Preprod) |
| State | Midnight level private state provider |
| CI | GitHub Actions (Node 22) |
| Deploy | Vercel (frontend) |
ghostlist/
├── contracts/
│ ├── allowlist_stub.compact # The ZK contract (single file)
│ └── managed/ # Compiled circuits + keys
├── managed/ # Mirror for the frontend
├── src/
│ ├── deploy.ts # Contract deployment
│ ├── cli.ts # CLI mint tool
│ ├── setup.ts # One-shot onboarding
│ └── wallet.ts # Wallet creation + key derivation
├── scripts/
│ ├── precompute-tree.ts # Multi-entry Merkle tree generator
│ ├── derive-address.ts # Address derivation from seed
│ └── cohort/ # 50-wallet integration tooling
├── tests/
│ └── allowlist_stub.test.ts # Core test suite (4/4 passing)
├── frontend/
│ ├── src/hooks/
│ │ ├── useMidnight.ts # WalletProvider (real Lace integration)
│ │ ├── useMint.ts # Mint hook (proof server + wallet submit)
│ │ └── useWallet.ts # Wallet state wrapper
│ ├── src/routes/
│ │ ├── index.tsx # Landing page
│ │ ├── mint.tsx # Mint page UI
│ │ └── faq.tsx # Privacy FAQ
│ ├── src/components/site/
│ │ ├── GhostCard.tsx # Animated mint card
│ │ ├── ProofPanel.tsx # ZK proof preview (user feedback)
│ │ └── ... # Navbar, Hero, Footer, etc.
│ ├── src/lib/contract/ # Browser Midnight.js provider stack
│ └── public/ # Static ZK artifacts + compiled contract + tree
├── docs/
│ ├── SETUP.md # Local development setup (in depth)
│ ├── USAGE.md # End-user walkthrough
│ ├── COHORT_TESTING.md # 50-wallet integration testing
│ └── FEEDBACK.md # User feedback & iteration log
├── .github/workflows/
│ └── ci.yml # CI/CD pipeline
├── docker-compose.yml # Local proof server
├── PROPOSAL.md # Product proposal (Level 3)
└── README.md # You are here
git clone https://github.com/MayurK-cmd/GhostList.git
cd GhostList
npm install
cd frontend && npm install && cd ..
# Start the proof server
docker compose up -d
# Generate a fresh Merkle tree
npx tsx scripts/precompute-tree.ts > frontend/public/tree.json
# Deploy to Preprod (requires MIDNIGHT_WALLET_SEED)
export MIDNIGHT_WALLET_SEED=your_64_char_hex_seed
npx tsx src/deploy.ts --network preprod
# Launch the frontend
cd frontend && npm run devOpen http://localhost:8080, connect Lace, and mint.
For the complete local setup walkthrough — environment variables, Compact compilation, troubleshooting, and CI parity — see docs/SETUP.md.
- Lace Midnight wallet — install from GitHub, funded on Preprod
- Node.js v22 or later
- Docker Desktop (for the local proof server)
- Compact compiler (optional — managed artifacts are committed)
┌──────────────┐ 1. Generate witness ┌──────────────────────┐
│ Browser │ ─────────────────────────► │ Local: precompute │
│ (Lace + ZK) │ │ Merkle path + secret │
└──────┬───────┘ └──────────┬───────────┘
│ │
│ 2. Request proof (HTTP) │
▼ ▼
┌──────────────┐ ┌──────────────────────┐
│ Proof Server │ ◄────── ZK circuit ────────│ contracts/managed/ │
│ (Docker) │ inputs + witness │ (proving key) │
└──────┬───────┘ └──────────────────────┘
│
│ 3. Return ZK proof
▼
┌──────────────┐ 4. Sign + submit ┌──────────────────────┐
│ Lace │ ─────────────────────────► │ Midnight Preprod │
│ (wallet) │ tx with proof attached │ contract verifies + │
└──────────────┘ │ nullifier consumed │
└──────────────────────┘
The proof attests:
- I know a secret that hashes to one of the leaves of the committed Merkle tree.
- I have not used this nullifier before.
Nothing else is revealed.
npm testCovers:
- A valid allowlist member can mint successfully
- A non-member (invalid Merkle path) is rejected
- The same secret cannot mint twice (nullifier reuse blocked)
- The private secret and Merkle path never appear in test output
End-to-end and cohort suites:
npm run test:e2e
npm run test:cohortThe project uses GitHub Actions (.github/workflows/ci.yml):
- Triggers: push to
main/master, plus every pull request - Steps: checkout, install Node 22, install root + frontend deps, optional Compact compile via Docker (best-effort), full test suite
- Badge: the green CI badge at the top of this README reflects the most recent run on
master
Frontend improvements based on real user feedback (anonymized):
- "Explain zero-knowledge in simpler terms" → Hero section now explains privacy in plain English
- "I want to see the proof before submitting" → New
ProofPanelcomponent shows ZK proof details before submission - "Show me the transaction on-chain" → Post-mint, display Midnight Explorer link with transaction hash
See docs/FEEDBACK.md for the full feedback loop and all changes implemented.
-
docs/SETUP.md — Complete local development setup
-
docs/USAGE.md — End-user walkthrough
-
docs/FEEDBACK.md — User feedback & iteration log
-
PROPOSAL.md — Product proposal and Level 3 specification
-
CONTRIBUTING.md — Contribution guidelines
See /demo-vid/ for the demo video showing full MVP functionality.
- The
MIDNIGHT_WALLET_SEEDin.envis never committed —.gitignoreexcludes it - Production deploys use a freshly funded throwaway seed
- The proof server runs locally in Docker and never persists witnesses
- ZK artifacts served from
frontend/public/are content-hashed by the build pipeline
If you find a security issue, please open a private issue or contact the maintainers directly — do not disclose in public issues.
We welcome PRs. See CONTRIBUTING.md for the workflow, branch naming, and commit conventions. For substantial changes, please open an issue first to discuss the design.
MIT — see the license file for details.
Built for the Midnight Builder Challenge