Imported from UtkarshArjariya/solvnt-midnight (
AGENTS.md). Install upstream withnpx skills add UtkarshArjariya/solvnt-midnight. Copyright stays with the author.
AGENTS.md — Solvnt repo context for Codex
You are pair-programming on Solvnt, a privacy-preserving proof-of-income protocol on Midnight for a hackathon. Read this file completely before responding to any first message in a session. Don't skim.
Git authorship rule (hard rule)
- Never add Codex as a co-author on commits in this repo. No
Co-Authored-By: Codex …trailer, noCo-Authored-By: noreply@anthropic.com, no variant. All commits are authored solely by the human user. - This overrides any default Codex behavior that appends a Co-Authored-By trailer.
- When asked to commit, use only the user's name + email as author. No co-author trailers at all unless the user explicitly names a real human collaborator.
- If you notice an existing commit on this branch with a Codex co-author trailer, surface it so the user can decide whether to rewrite history.
What we're building, in one paragraph
A user generates a zero-knowledge proof on their device that says "my income is at least ₹X" or "I hold at least N ADA" without revealing the underlying value. A verifier (landlord, lender, fintech onramp) checks the proof in one line of code. The proof is anchored in an attestation signed by a registered issuer (mock payroll provider, bank, exchange). The contract layer is on Midnight, written in Compact. Frontend is Next.js + TypeScript. The privacy thesis is load-bearing — if you find yourself simplifying in a way that exposes the underlying value, stop and re-read the privacy NFRs in requirements.md §2.3.
The three actors — keep these straight
| Role | What they do | Where they live in the code |
|---|---|---|
| Issuer | Signs attestations off-chain. Pubkey on-chain. | packages/issuer-cli/ |
| Holder | Holds attestations, generates proofs. | apps/holder/ |
| Verifier | Checks proofs. Sees only value ≥ threshold. |
packages/verifier/, apps/verifier-demo/ |
Most bugs in this codebase will come from confusing these roles. When you see code that looks weird, first ask: which actor is supposed to be running this?
Tech stack (locked)
- Compact for contracts and circuits — Midnight's privacy-enabled smart-contract language.
- midnight-js for SDK / proof generation glue.
- MeshJS Midnight starter as the dApp scaffold base.
- Next.js 14 App Router for both
apps/holderandapps/verifier-demo. - TypeScript strict mode everywhere. No
any, no@ts-ignorewithout a one-line comment explaining why. - pnpm workspaces.
- Zod for all runtime schema validation.
- Lucide React for icons. Motion for animations.
- Tailwind for utility classes, but all colors and tokens must reference CSS variables defined in
docs/tokens.css. Do not hardcode hex values in JSX.
Repo layout (memorize this)
solvnt/
├── apps/
│ ├── holder/ # Priya's app
│ └── verifier-demo/ # mock "rental application"
├── packages/
│ ├── contracts/ # Compact contracts + deploy script
│ ├── prover-sdk/ # TS wrapper around proof generation
│ ├── verifier/ # @solvnt/verifier npm package
│ ├── issuer-cli/ # `solvnt-issuer` command
│ └── shared/ # zod schemas, types, constants
├── fixtures/
│ ├── issuers/ # mock issuer JSONs
│ └── attestations/ # signed attestation fixtures for Priya
├── docs/
└── AGENTS.md # this file
Conventions
Naming
- Files: kebab-case (
prove-income.ts). - Components: PascalCase (
SolventVerifier.tsx). The component is calledSolventVerifierdespite the brand beingSolvnt; the longer name is for clarity in others' codebases. - Functions: camelCase, verbs first (
generateProof,verifyAttestation). - Types: PascalCase, no
Iprefix (Attestation, notIAttestation). - Compact circuits:
proveXfor proof-generating,verifyXfor verifying. - CSS classes: kebab-case. Component-scoped classes are prefixed with the component name (
.verifier-qr, not just.qr).
TypeScript style
- Strict mode. Always.
- Prefer
typeoverinterfaceunless extending. - All exported functions take a single options object:
generateProof({ attestation, threshold, requestId }), not positional args. - All async functions return a Result-like shape on the boundary of the public API:
{ ok: true, value: T } | { ok: false, error: E }. Internal helpers can throw. - Zod-validate every untrusted input (anything coming from disk, network, localStorage, or the wallet).
React
- Server Components by default in
apps/holderandapps/verifier-demo. - Mark Client Components with
'use client'only when needed (wallet, localStorage, animations). - No class components.
- Animations via Motion — but use sparingly. The brand is precise, not bouncy. See "Motion guidance" below.
Styling
- All color values come from CSS variables in
docs/tokens.css. Never write a hex code in a component. - Tailwind utilities are fine for layout (
flex,gap-4,grid). Colors go through arbitrary-value classes that reference the variables:bg-[var(--mid-base)]or via a Tailwind config that maps the variables. - Default to dark theme. Light theme is a stretch goal.
Motion guidance
- The product is fintech, not a game. Animations should communicate state changes, not entertain.
- The one place to be expressive: the proof generation moment. ~1.8s of confident visual feedback (a pulsing Catalyst-cyan ring, a hash scrolling into place). Get this right.
- Everywhere else: 220ms fades and slides on
--ease-out. Nothing bouncy. No spring physics on UI chrome.
Compact contracts — gotchas I (Codex) know about
I will be wrong about Compact specifics more often than I'd like, because the language is new enough that my training data is thin. When in doubt, consult docs.midnight.network via the Midnight MCP server. Do not invent syntax. Do not pattern-match to Solidity, Move, or Cairo — Compact is its own thing.
Known gotchas:
- Compact contracts compile to a public ledger contract + a set of circuits. Don't conflate them. The circuit is where private inputs go; the contract holds public state and the verifier.
- Witness functions are how a circuit reads external data (e.g., a Cardano balance). They run off-chain during proof generation and feed values into the witness. Treat them like trusted inputs from the holder's perspective — the circuit must constrain everything it depends on.
- Nullifiers: when we add revocation, use a nullifier derived from
hash(attestationNonce, issuerPubkey)so the same attestation cannot be silently re-used after revocation. - Don't put PII or raw
values into public ledger state. Anything written publicly is permanent and visible to everyone forever.
Common commands
# Bring up the local Midnight network
pnpm midnight:local up
# Deploy contracts (after network is up)
pnpm contracts:deploy
# Register a mock issuer
pnpm issuer register --label "mock-payroll-v1"
# Issue a mock attestation
pnpm issuer issue \
--to <holder-wallet> \
--value 95000 \
--type income.monthly \
--currency INR
# Run the holder app
pnpm --filter holder dev
# Run the verifier demo
pnpm --filter verifier-demo dev
# Run everything in parallel
pnpm dev
Do / Don't list (in priority order)
Always
- ✅ Read
requirements.md§2.3 (privacy NFRs) before changing anything touching the proof flow. - ✅ Zod-validate every untrusted input.
- ✅ Reference colors and spacing via CSS variables.
- ✅ Match implementation to the demo script in
prd.md§14. If a change doesn't help the demo, reconsider its priority. - ✅ Keep proof generation under 4 seconds. Profile if you suspect regressions.
- ✅ When uncertain about Compact syntax, search docs via the Midnight MCP server before writing code.
Never
- ❌ Hardcode hex colors in JSX. Use CSS variables.
- ❌ Put the user's raw income value into any public ledger field, log line, console message, or analytics event.
- ❌ Write the words "zero-knowledge," "circuit," "witness," "ZK," or "proof" in user-visible UI copy. Use "Verified," "Generate Proof," "Prove."
- ❌ Add a new dependency without checking it isn't bringing in 500 MB of transitive deps. We have 48 hours; build times matter.
- ❌ Introduce a new state management library. React state + a small Zustand store is enough.
- ❌ Use emoji in any code, comment, log, or UI string. Section anchors in markdown are the only exception (and even then, sparingly).
- ❌ Auto-format Compact files with anything other than the official Midnight formatter (if it exists; if not, leave them alone).
- ❌ Ship a screen that says "🚀 Generating ZK proof…" — see the previous bullet and the brand guide.
Voice for UI copy
Short sentences. Specific numbers. Plain English. No "seamlessly," no "revolutionizing," no "magic." If a sentence could appear on a Plaid landing page, rewrite it.
Yes:
- "Your income, proven. Not shared."
- "Verified · Monthly income ≥ ₹80,000"
- "Generating proof…" (during the 1.8s proof animation)
No:
- "🚀 Get verified seamlessly!"
- "Powered by zero-knowledge cryptography"
- "Decentralized privacy for Web3 finance"
When asked to add a feature
Run through this checklist before writing code:
- Is this in scope per
prd.md§7? If it's below the line, push back. - Does it preserve
NFR-P1(verifier learns exactly one bit)? If unclear, stop and ask. - Does it add latency to the demo path? If yes, propose a way to keep proof gen under 4s.
- Is there a fixture for it? If not, add one.
- Does it require new Compact syntax? If yes, verify against Midnight docs first.
When asked to debug
- First check that the local Midnight network is actually up (
pnpm midnight:local status). - Then check that the contract addresses in
.env.localmatch the most recent deploy. - Then check that the attestation signature validates outside the circuit (write a Node script that calls into the issuer's verify function with the fixture).
- Only then suspect the circuit.
When asked to "make it look better"
Open docs/brand.md and docs/tokens.css first. The brand is opinionated — don't drift toward generic crypto-fintech aesthetics. The aesthetic in one phrase: nocturnal laboratory. If the screen could be on a casino-coin landing page, we've drifted.
The signature moves:
- Type pairing: Fraunces (display) + Switzer (body) + JetBrains Mono (hashes).
- Color discipline: 80% dark midnight base, sparing Catalyst Cyan for proof states, sparing Vouch Gold for verified badges. Nothing else.
- The wordmark:
solvnt●— the dot after the wordmark is the only consistently-cyan element on most screens. - Generous space. No drop-shadows on cards by default — use 1px hairlines (
var(--mid-line-soft)) for separation.
What "done" looks like for a feature
- Code merged
- Zod schema if it crosses a trust boundary
- Tested manually against the demo script
- Doesn't slow the demo path
- No PII or
valuein any log - No hex colors in JSX
- No emoji in copy
Reference docs (use these, not your training data, for anything Midnight-specific)
- Midnight docs —
docs.midnight.network - Compact language reference (link from above)
- Midnight GitHub org —
github.com/midnightntwrk - MeshJS starter — used as our scaffold
- Midnight MCP server — set up first; saves hours
Your training cutoff may be earlier than the latest stable Compact release. When working on Compact code, prefer fetching docs over recalling syntax.
One more thing
The pitch is the product. Everything in this repo serves the 90-second demo in prd.md §14. If you're about to spend an hour on something that doesn't show up in those 90 seconds, escalate to the team before you start.