Midnight Compact Smart Contract Development
You are an expert Midnight smart contract developer. Compact is a TypeScript-like domain-specific language that compiles to zero-knowledge circuits, enabling privacy-preserving computation on the Midnight blockchain.
Core Principles
- Privacy by default — all computation is private unless explicitly disclosed with
disclose(). - Dual-state model — contracts have public ledger state (on-chain) and private state (off-chain, per-user).
- Circuits, not functions — exported
circuitdeclarations compile to ZK proofs. There are nofunctionkeywords. - Witnesses bridge private data —
witnessdeclarations in Compact are implemented in TypeScript, providing off-chain private inputs. - Correctness is enforced — all circuit computation is verified by ZK proofs. Only witness code runs unverified.
- Test everything — use the Compact simulator first, then standalone network, then testnet.
Decision Tree
When asked to write a smart contract:
- Specify the contract — before writing code, define:
- What state is public vs private?
- What operations (circuits) does it expose?
- What invariants must hold? (e.g., "total supply is conserved", "only owner can withdraw")
- What are the trust boundaries? (what can witnesses lie about?)
- What are the failure modes?
- Identify the privacy requirements: what must be shielded vs public?
- Design ledger state —
export ledgerfor public, plainledgerfor contract-private - Design witnesses — what private data do users provide off-chain?
- Write circuits — exported for external calls, plain for internal
- Add
disclose()calls — required for any witness-derived value written to ledger or used in conditionals - Write TypeScript witnesses — implement witness bodies returning
[newPrivateState, returnValue] - Write tests — progressive approach:
- Unit test witnesses in isolation (correct types, immutable state, edge cases)
- Simulator tests for every circuit (happy path + error conditions)
- Invariant tests with
fast-check(conservation laws, state machine validity) - Privacy leak tests (verify secrets don't appear in public state)
- Adversarial tests (replay attacks, privilege escalation, malicious witnesses)
- Review for privacy leaks — check the security patterns in security.md
- Check circuit complexity — verify k-values are acceptable (k <= 14 fast, k >= 17 needs optimization)
- Compile and deploy —
compact compile, test with proof server, deploy to preprod before mainnet
When asked to audit or review a contract:
- Follow the auditing methodology in auditing.md
- Phase 1: Map ledger state, circuits, witnesses, and trust boundaries
- Phase 2: Privacy leak scan — check all
disclose()calls, witness interactions, and indirect leakage - Phase 3: Circuit complexity analysis — check
kvalues, ledger operation costs - Phase 4: SDK integration review — version alignment, provider configuration
- Phase 5: Test coverage assessment
- Report findings with severity, privacy impact, and fix
Compact Language — Essential Syntax
Pragma (REQUIRED at top of every file)
pragma language_version >= 0.20;
Imports
import CompactStandardLibrary; // ALWAYS required
import "./path/to/Module" prefix Module_; // OZ composition pattern
Ledger Declarations
CRITICAL: Use individual statements. Block syntax ledger { } is DEPRECATED and causes parse errors.
export ledger counter: Counter; // public, readable by anyone
export ledger owner: Bytes<32>; // public
export sealed ledger name: Opaque<"string">; // set once in constructor, immutable
ledger privateData: Field; // NOT exported = private to contract
Types
Primitives:
| Type | Description |
|---|---|
Field |
Finite field element (basic numeric type for ZK circuits) |
Boolean |
true/false |
Bytes<N> |
Fixed-size byte array (N=32 most common) |
Uint<N> |
Unsigned integer (N = 8, 16, 32, 64, 128). NOTE: Uint<256> NOT supported |
Uint<MIN..MAX> |
Bounded unsigned integer |
Opaque<"string"> |
External type bridged from TypeScript |
Collections:
| Type | Description |
|---|---|
Counter |
Incrementable/decrementable counter (ledger-backed) |
Map<K, V> |
Key-value mapping (ledger-backed, expensive) |
Set<T> |
Unique value collection (ledger-backed, expensive) |
Vector<N, T> |
Fixed-size array (circuit-friendly) |
Maybe<T> |
Optional value — some<T>(val) / none<T>() |
Either<L, R> |
Union type — left<L, R>(val) / right<L, R>(val) |
Midnight-specific:
| Type | Description |
|---|---|
ZswapCoinPublicKey |
Wallet public key for coin operations |
ContractAddress |
On-chain contract address |
CoinInfo |
Coin descriptor for shielded tokens |
Custom types:
export enum GameState { waiting, playing, finished }
export struct PlayerConfig { name: Opaque<"string">, score: Uint<32> }
NOTE: Enum access uses dot notation: GameState.waiting, NOT GameState::waiting.
Circuits
// Exported circuit — callable from TypeScript, generates ZK proof
export circuit increment(): [] {
counter.increment(1);
}
// Circuit with parameters and return value
export circuit getBalance(addr: Bytes<32>): Uint<64> {
return balances.lookup(addr);
}
// Internal circuit — not exported, callable only from other circuits
circuit validateOwner(caller: Bytes<32>): Boolean {
return caller == owner;
}
// Pure circuit — no state access, no side effects
export pure circuit hash(data: Bytes<32>): Bytes<32> {
return persistentHash<Vector<1, Bytes<32>>>([data]);
}
CRITICAL: Return type is [] (empty tuple) for void circuits, NOT Void. The keyword function does NOT exist — use pure circuit for stateless computation.
Witnesses
Declared in Compact (no body), implemented in TypeScript:
// Compact — declaration only, ends with semicolon
witness localSecretKey(): Bytes<32>;
witness getAmount(max: Uint<64>): Uint<64>;
// TypeScript — implementation returns [newPrivateState, returnValue]
export const witnesses = {
localSecretKey: ({ privateState }: WitnessContext<Ledger, PrivateState>):
[PrivateState, Uint8Array] => [privateState, privateState.secretKey],
getAmount: ({ privateState }: WitnessContext<Ledger, PrivateState>, max: bigint):
[PrivateState, bigint] => [privateState, privateState.amount],
};
Disclosure — The Core Privacy Primitive
// MUST wrap witness-derived values for ledger writes or conditionals
owner = disclose(publicKey(localSecretKey()));
// Assertions on private values
assert(disclose(caller == storedOwner), "Not authorized");
// Branching on private values
if (disclose(guess == secret)) { /* ... */ }
CRITICAL: From Compact 0.16+, disclose() is MANDATORY for all witness-derived values written to ledger state or used in boolean expressions affecting control flow. Omitting it causes compilation errors.
Constructor
constructor() {
counter.increment(1);
owner = disclose(publicKey(localSecretKey()));
}
Common Operations
// Counter
counter.increment(1); counter.decrement(1);
counter.read(); counter.lessThan(100);
// Map
balances.insert(key, value); balances.remove(key);
balances.lookup(key); balances.member(key);
// Maybe
const opt = some<Field>(42); const empty = none<Field>();
if (opt.is_some) { const val = opt.value; }
// Either (used for wallet-or-contract addresses)
const wallet = left<ZswapCoinPublicKey, ContractAddress>(ownPublicKey());
const contract = right<ZswapCoinPublicKey, ContractAddress>(kernel.self());
// NB the OpenZeppelin Compact library no longer uses this shape for party identity —
// it moved to account ids, Either<Bytes<32>, ContractAddress>. See below.
// Hashing
persistentHash<Vector<2, Bytes<32>>>([data1, data2]); // SHA-256
transientHash<Vector<2, Bytes<32>>>([data1, data2]); // Poseidon (10x cheaper in-circuit)
// Type casting
const bytes: Bytes<32> = myField as Bytes<32>;
const num: Uint<64> = myField as Uint<64>;
// Assertions
assert(condition, "Error message");
Coin Operations (Shielded Tokens)
receive(coin); // accept incoming coin
sendImmediate(coin, recipient, amount); // send coin out
mintShieldedToken(domainSeparator, amount, nonce, recipient); // create new token
tokenType(pad(32, "myToken"), kernel.self()); // get token type ID
CLI Workflow
# Install / update Compact toolchain
curl --proto '=https' --tlsv1.2 -LsSf \
https://github.com/midnightntwrk/compact/releases/latest/download/compact-installer.sh | sh
compact self update # update dev tools FIRST
compact update 0.31.1 # then update toolchain — see the 0.31.0 warning below
# Scaffold, compile, test
npx create-mn-app my-project # scaffold new project
compact compile src/contract.compact src/managed/contract # compile
compact fmt src/contract.compact # format (compiler 0.25.0+)
npm test # run tests (Vitest/Jest)
⚠ SECURITY: do not compile with Compact 0.31.0
Compact 0.31.0 has a soundness bug. It can silently drop a range constraint from a circuit. Fixed in 0.31.1 — use that or later for anything you will deploy.
If you deployed anything compiled with 0.31.0, Midnight's guidance is:
- Recompile the same source with 0.31.1.
- Diff the resulting verifier key against the one deployed on-chain.
- Keys identical → the bug did not fire; that deployment is fine.
- Keys differ → the bug may have dropped a range constraint. Assess exposure: does any
Uint<N>cast feed a balance, quorum, counter, index, or other security-relevant sink? - Exposed → redeploy with 0.31.1. The on-chain verifier key cannot be patched in place; it can only be replaced by redeploying, or via maintenance authority if you have a reachable committee.
⚠ Do not try to find this by reading your source. Midnight's own analysis: "the trigger has too many non-obvious origins for pattern review to be reliable. The VK diff is the authoritative check."
This matters most for the patterns in examples/ that touch value or authority —
fungible-token, lending, multi-sig (quorum), crowdfunding, sealed-bid-auction.
Current versions (verified 2026-08-17)
Authoritative matrix: https://docs.midnight.network/relnotes/support-matrix
| component | version |
|---|---|
| Compact compiler | 0.31.1 |
@midnight-ntwrk/compact-runtime |
0.16.0 |
Midnight.js (midnight-js-*) |
4.1.1 |
| DApp Connector API | 4.0.1 |
| Node | 1.0.2 (mainnet + preprod) · 1.0.1 (preview) |
| Ledger | 8.1.0 |
| Indexer | 4.3.3-hotfix (mainnet + preprod) · 4.3.5 (preview) |
| Proof Server | 8.1.0 |
⚠ Node version now differs per network — preview trails mainnet/preprod rather than
leading it. Verified 2026-08-30 by asking each network directly:
curl -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"system_version","params":[]}' https://rpc.mainnet.midnight.network
returns 1.0.2-eb71e64e; preview returns 1.0.1-5edf8ddd. The live endpoint is the
authority, not the release page — see the artifact-availability warning below.
⚠ Keep every @midnight-ntwrk/midnight-js-* package on the same major line. Mixing 3.x
and 4.x produces Cannot read properties of undefined (reading 'ctor') — an error that looks
like a contract problem and is not. Ledger v7 is no longer supported.
The simulator API used throughout this skill (createConstructorContext,
createCircuitContext, sampleContractAddress) was verified present in compact-runtime
0.16.0, re-confirmed 2026-08-30 by running 10 contract suites (69 tests) against it.
⚠ Never npm install @midnight-ntwrk/compact-runtime@latest
npm's latest tag is AHEAD of every released compiler. As of 2026-08-30 npm serves
compact-runtime@0.19.0, but the current compiler (compactc 0.31.1) emits code targeting
0.16.0. Install latest and every contract fails the moment it loads:
CompactError: Version mismatch: compiled code expects 0.16.0, runtime is 0.19.0
The compiler decides the runtime version, not npm. Pin compact-runtime to the version in
the compatibility matrix for your compiler and let nothing bump it — a caret range is fine
within a 0.x minor, but @latest or a blind npm update will break the whole project.
Measured 2026-08-30 across all 10 example contracts, which is how this was found:
| configuration | result |
|---|---|
| compiled 0.14.0 + runtime 0.14.0 (as-was) | 10/10 pass |
compiled 0.14.0 + runtime 0.19.0 (@latest) |
10/10 fail — expects 0.14.0, runtime is 0.19.0 |
| recompiled 0.31.1 + runtime 0.19.0 | 10/10 fail — expects 0.16.0, runtime is 0.19.0 |
| recompiled 0.31.1 + runtime 0.16.0 | 10/10 pass ✓ |
⚠ Changing the runtime version means RECOMPILING. The version is baked into the generated
src/managed/<name>/contract/index.js at compile time. Bumping the npm package alone always
fails — you must re-run compactc and keep the runtime pin matched to it.
The failure is at least a good one: the runtime version-gates on load and names both versions, so a mismatch is loud and immediate rather than silently wrong.
Verified compatible pairing (2026-08-30):
"@midnight-ntwrk/compact-runtime": "^0.16.0",
"@midnight-ntwrk/midnight-js-network-id": "^4.1.1"
with compactc 0.31.1. The midnight-js 3.x → 4.x major bump caused no breakage in any of
the 10 suites. Contracts written six months ago compiled unchanged on 0.31.1 — no Compact
language regressions in that window.
⚠ OpenZeppelin Compact: identity moved from coin public keys to account IDs
If you have contracts written against OpenZeppelin/compact-contracts before ~mid-2026, they
will not compile against the current library. The identity model changed:
| before | now | |
|---|---|---|
| party identity | Either<ZswapCoinPublicKey, ContractAddress> |
Either<Bytes<32>, ContractAddress> |
the Bytes<32> is |
— | persistentHash(secretKey) — an account id |
| authentication | implicit from ownPublicKey() |
caller proves knowledge of the secret key via a witness |
ZswapCoinPublicKey no longer appears in FungibleToken at all (verified 2026-08-30). Both
Ownable and FungibleToken now identify parties by account id, and each declares its own
witness:
witness wit_OwnableSK(): Bytes<32>; // Ownable.compact
witness wit_FungibleTokenSK(): Bytes<32>; // FungibleToken.compact
Ownable.assertOnlyOwner() compares persistentHash(wit_OwnableSK()) against the stored owner,
so authorisation is a zero-knowledge proof of key possession rather than an address comparison.
Three things that will catch you migrating:
- Every call site is a compile error, not a silent bug. The compiler names both the supplied and declared types. That is the good case — fix them mechanically.
- Supply EVERY module's witness. Composing modules means composing witnesses. Missing one
does not fail at load; it fails at every circuit call, which looks like broken test logic
rather than missing wiring. Both factories read
privateState.secretKey, so one secret key serves both:const witnesses = { ...OwnableWitnesses(), ...FungibleTokenWitnesses() }; // and pass the key in private state: createConstructorContext({ secretKey: OWNER_SK }, coinKeyEither.left) - Build the account id the same way the contract does, or
assertOnlyOwnerwill reject a caller that looks correct:const buildAccountIdHash = (sk: Uint8Array) => persistentHash(new CompactTypeVector(1, new CompactTypeBytes(32)), [sk]); const ownerEither = { is_left: true, left: buildAccountIdHash(sk), right: { bytes: zeroBytes } };
A complete worked example composing FungibleToken + Ownable + Pausable — contract and passing
test suite — is in examples/composition/. Verified 2026-08-30 against
compiler 0.31.1 / compact-runtime 0.16.0 / ledger-v8 8.1.0.
Testing
Always test with the simulator before deploying. See testing.md for full details including invariant testing, witness validation, adversarial testing, performance baselines, and CI/CD integration.
import { Contract } from "../managed/counter/contract/index.js";
import { createConstructorContext, createCircuitContext, sampleContractAddress } from "@midnight-ntwrk/compact-runtime";
import { setNetworkId } from "@midnight-ntwrk/midnight-js-network-id";
setNetworkId("undeployed");
// Create contract instance with witnesses
const contract = new Contract<PrivateState>(witnesses);
// Initialize — sampleContractAddress() is a FUNCTION, call it
const addr = sampleContractAddress();
const initial = contract.initialState(createConstructorContext(initialPrivateState, addr));
// Create first circuit context — requires 4 params: (address, zswapState, contractState, privateState)
const ctx = createCircuitContext(addr, initial.currentZswapLocalState, initial.currentContractState, initial.currentPrivateState);
// Execute circuit
const result = contract.impureCircuits.increment(ctx);
// Chain calls: pass result.context directly — it IS the continuation
const readResult = contract.impureCircuits.read(result.context);
console.log(readResult.result); // 1n
DUST Fee Economics
DUST is Midnight's fee token — a high-precision micro-token generated continuously from tNight holdings. All transaction fees are paid in DUST.
Generation Model
| Parameter | Value | Meaning |
|---|---|---|
nightDustRatio |
5,000,000,000 | Peak DUST generated per tNight per second |
timeToCapSeconds |
604,815 | ~7 days to reach generation cap |
generationDecayRate |
8,267 | Decay factor for generation curve |
dustGracePeriodSeconds |
10,800 | 3-hour grace period before generation starts decaying |
DUST has no decimal places — values are in the smallest indivisible unit. The numbers are intentionally large to provide high precision for fee calculation.
Deployment Costs (preprod, protocol v21000)
Measured from 13 contract deployments on preprod (March 2026):
| Complexity | Fee Range | Examples |
|---|---|---|
| Simple (3 circuits) | 331B–367B DUST | Counter, RPS, Upgrade-V1, Token Minting |
| Medium (5 circuits) | 479B–564B DUST | Credential, DID, Prescription, Market, Staking, Crowdfunding |
| Complex (6-7 circuits) | 629B–721B DUST | NFT, DAO, Lending, Upgrade-V2 |
- Fees are deterministic —
paidFeesmatchesestimatedFeesexactly - Deploy time: 16–22 seconds (includes ZK proof generation + on-chain confirmation)
- With 2,000 tNight, DUST generation outpaces deployment fees — all 13 contracts deployed with DUST to spare
Practical Guidance
- DUST accrues passively from tNight — no explicit conversion needed
- The
DustWalletSDK handles fee calculation and payment automatically - Set
additionalFeeOverheadinDustWalletconfig for fee buffer (default examples use 300T DUST) - On preprod, request tNight from the faucet (below) — 1,000 tNight per request is sufficient for dozens of deployments
⚠ DUST ordering matters. Create wallet → request tNight → designate a DUST address. If you funded the wallet before designating, send tNight to yourself to create a new UTXO that will generate DUST. Getting this order wrong leaves the send option greyed out with no explanation.
⚠ You cannot download the version the network is running (verified 2026-08-30)
Node 1.0.2 runs on mainnet and preprod, but there is no public 1.0.2 artifact. Every documented channel disagrees, and each one looks authoritative on its own:
| source | says |
|---|---|
Live RPC system_version |
1.0.2-eb71e64e (mainnet, preprod) |
| Docs compatibility matrix | 1.0.2 (mainnet, preprod) |
| GitHub releases | latest stable is 1.0.1; 1.0.2 exists only as alpha.1/2/3 |
Docker Hub midnightnetwork/midnight-node |
stops at 0.12.1 (June 2025) |
| Docs "set up a full node" guide | install 0.22.5 (April 2026) |
midnight-node-docker presets |
qanet, testnet-02 — both retired networks |
Practical consequences:
- Do not build on
midnight-node-dockerfor a current network. Its only presets target networks that no longer exist, and it has had no functional commit since March 2026. The compose scaffold cannot reach preview/preprod/mainnet without being rewritten. - The full-node guide installs a version four generations behind the network. Following it literally gives you 0.22.5 against a 1.0.2 chain.
- The newest tag is not the newest network.
node-2.1.0-beta.1(21 Aug 2026) is ahead of what any network runs. Newest-tag-wins picks a beta that matches nothing. - If you need to run a node, take the latest stable release (
node-1.0.1) and verify it against the target chain, or ask in the service desk for the 1.0.2 artifact. Do not assume the version in the announcement is downloadable.
Rule: read the version off the chain (system_version), never off a release page. For
anything that only talks to a node — DApps, indexers, monitoring — use the public RPC
endpoints below and skip local node operation entirely.
Networks (verified 2026-08-17)
Mainnet is live — Node 1.0.0 from 20 Jul 2026, 1.0.1 from 29 Jul, 1.0.2 from 22 Aug. It runs in federated mode: block production is operated by the foundation, and third-party validation has not opened. An Incentivised Testnet is expected to precede it.
Measured 2026-08-30, not inferred. sidechain_getAriadneParameters across Cardano epochs
640-653 returns, on both mainnet and preprod, 13 permissioned candidates and 0 registered
(SPO) candidates every epoch, with dParameter = {numPermissionedCandidates: 0, numRegisteredCandidates: 0}. So third-party validator registration is not merely
undocumented — there is nothing registered on chain. Anyone planning to run a Midnight
validator should treat that as the current state of the world.
curl -s -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"sidechain_getAriadneParameters","params":[652]}' \
https://rpc.mainnet.midnight.network
| endpoint | |
|---|---|
| mainnet RPC | https://rpc.mainnet.midnight.network/ |
| mainnet indexer | https://indexer.mainnet.midnight.network/api/v4/graphql |
| preprod faucet | https://midnight-tmnight-preprod.nethermind.dev/ |
| preview faucet | https://midnight-tmnight-preview.nethermind.dev/ |
| status | https://status.shielded.tools/preprod · /preview |
| service desk | https://midnightntwrk.github.io/servicedesk/ |
⚠ Check network health before a deploy session, not during one. The /api/health endpoint
on either Nethermind faucet returns the real state — testnets are reset and go out of service
with some regularity. Measured 2026-08-17: preprod {"status":"ok"} while preview returned
{"status":"NOT_SERVING","reason":"SYNC_STUCK_RECOVERY"}. Community advice about which network
to prefer goes stale within days; the health endpoint does not.
⚠ Lace Midnight Preview (the standalone extension) is deprecated. Midnight support is in
the main Lace wallet now; 1AM is the Midnight-native alternative.
Report infrastructure problems through the service desk rather than chat — it is the monitored triage route.
Common errors (observed in the wild)
Errors whose message points somewhere other than the cause. Each of these cost a real developer material time in the Midnight dev channels.
Cannot read properties of undefined (reading 'ctor') — usually thrown from
findDeployedContract, and it looks like the contract is missing or the address is wrong. It
is neither. Two causes, in order of likelihood:
- Mixed SDK majors.
@midnight-ntwrk/midnight-js-contractsandmidnight-js-protocol(and the rest ofmidnight-js-*) must be on the same 4.x line. Check every one of them. - Passing the raw compiled contract object where a
CompiledContractis required. Wrap it:CompiledContract.make(tag, ctor).
Wallet connects but never syncs / send greyed out — see the DUST ordering note above. Also check you are on the current Lace, not the deprecated standalone Midnight Preview extension.
Deploy or sync hangs with no error — check the network health endpoint before debugging your code. Testnet sync outages are common and present as your application being broken.
1014 reject (dust contention) — reported as potentially leaving a wallet's dust note
marked spent indefinitely. If a wallet becomes stuck after a reject, a fresh wallet is the
known workaround; report it to the service desk.
Reference Material
For detailed information, consult:
- Language reference — types, syntax, modules, casting, operators
- Privacy model — shielded vs unshielded, disclose(), witness pattern, ZK fundamentals
- Security patterns — ZK-specific attack vectors, privacy leaks, common mistakes
- Testing guide — simulator, invariant testing, witness validation, adversarial testing, CI/CD, performance baselines
- Design patterns — circuit optimization, off-chain computation, module composition
- Standard library — CompactStandardLibrary built-in functions and types
- Gotchas — 52 compiler bugs, SDK pitfalls, design traps (Discord + real compilation)
- Off-chain integration — TypeScript SDK, wallet, deployment, contract monitoring, error handling
- Auditing methodology — ZK contract audit process, privacy leak detection
Examples
30 examples (27 validated + 2 network-only + 1 compiled-only). 151 circuits compiled, 182/182 tests passing on the original March run; re-verified 2026-08-17 on Compact 0.31.1 (29/29 compile) with 10/10 simulator suites / 69 tests passing. 30 contracts deployed on preprod:
Core Patterns:
- Counter — 3 circuits, 5/5 tests. Simplest contract, increment/decrement with ledger state.
- Bulletin Board — 3 circuits, 8/8 tests. Witness authentication, ownership, CRUD.
- Fungible Token — 7 circuits, 6/6 tests. ERC20-equivalent with OZ module composition.
- NFT — 7 circuits, 6/6 tests. Commitment-based ownership, mint/burn/transfer/approve.
- Rock-Paper-Scissors — 3 circuits, 6/6 tests. Minimal commit-reveal 2-player game.
Privacy Patterns:
- Shielded Voting — 6 circuits, 9/9 tests. Commit-reveal private ballot.
- Sealed-Bid Auction — 6 circuits, 8/8 tests. Commit-reveal with ZK verification.
- Identity Proof — 4 circuits, 6/6 tests. Selective disclosure, parameterized witnesses.
- Credential Registry — 5 circuits, 6/6 tests. Nullifier-based double-use prevention.
- Prescription — 5 circuits, 6/6 tests. Batch registration with Vector, nullifier for double-fill.
- Privacy Mixer — 3 circuits, 7/7 tests. Commitment deposits, nullifier withdrawals.
DeFi & Escrow:
- Escrow — 5 circuits, 8/8 tests. Two-party conditional exchange with deadline.
- Time Lock — 3 circuits, 7/7 tests. LOK/RELEASE pattern for timed asset release.
- Multi-Sig — 6 circuits, 6/6 tests. M-of-N authorization, composite keys.
- Staking — 5 circuits, 6/6 tests. Lock period, ZK-friendly reward calculation.
- Crowdfunding — 5 circuits, 6/6 tests. Anonymous backing with ZK refund proofs.
- Lending — 6 circuits, 6/6 tests. Collateral, health factor, liquidation.
- Prediction Market — 5 circuits, 7/7 tests. Commitment-based bets with ZK payout.
- Vesting — 4 circuits, 8/8 tests. Time-based tranche release schedule.
- Revenue Sharing — 3 circuits, 7/7 tests. Private share allocations, ZK withdrawal.
- Lottery — 4 circuits, 8/8 tests. Commit-reveal multi-party randomness.
Advanced:
- Oracle Feed — 5 circuits, 6/6 tests. External data, freshness checks.
- Token Swap — 6 circuits. Atomic swap with
receiveShielded/sendImmediateShielded, preprod deployed. - Access Control — 8 circuits, 6/6 tests. Role hierarchy, internal guards.
- DID Registry — 5 circuits, 6/6 tests. Document lifecycle (create/update/deactivate).
- Micro-DAO — 7 circuits, 7/7 tests. Token-gated voting, treasury, governance.
- Contract Upgradability — V1: 3 + V2: 7 circuits, 8/8 tests. Migration pattern.
- Token Minting — 3 circuits. Zswap coin creation (
mintShieldedToken), preprod deployed. - Native Shielded Token — 2 circuits. Contract-issued shielded token via OZ
NativeShieldedTokenCore. ⚠ Documents the coin-info hazard: contract-minted coins create NO ciphertext, so the returnedShieldedCoinInfois the recipient's only copy — drop it and the value is stranded permanently. - Supply Chain — 4 circuits, 7/7 tests. Selective disclosure provenance tracking.
Production References
Open-source Midnight contracts and tools for studying real implementations:
OpenZeppelin Compact Contracts (Canonical Reference):
- OpenZeppelin/compact-contracts — Ownable, Pausable, AccessControl, FungibleToken, Capped, Nonces. Module composition pattern. Production-grade.
Brick Towers (Most Active Community Builder):
- midnight-seabattle — Full-stack dApp (game). Multi-user, shielded state, E2E tests. Best reference for real dApp architecture.
- midnight-local-network — Docker Compose for local development (node + indexer + proof server). Community standard.
- midnight-proof-server — Pre-baked proof server with circuit parameters. Eliminates download timeouts.
- midnight-rwa — Real-world asset tokenization.
Official Midnight Examples:
- example-counter — Official counter (simplest contract). Template for
create-mn-app. - example-bboard — Official bulletin board. Canonical witness + auth pattern.
- midnight-awesome-dapps — Curated list of community dApps.
Community Projects:
- midnight-kitties — CryptoKitties-style NFT dApp.
- compact-by-example — Learn Compact through practical examples.
- pulse-finance/midnight-dex-contract — AMM DEX in Compact.
Developer Tools:
- midnight-mcp — MCP server for Midnight (Idris, Midnight team).
- compact-vscode — VSCode syntax highlighting.
- compact.vim — Vim/Neovim tree-sitter plugin.
- Midnight docs (open source) — Official documentation source.
Key Community Experts:
- Sergey | Brick Towers — de facto community expert. 836+ Discord messages. Maintains midnight-seabattle, midnight-local-network, midnight-proof-server. Most practical SDK knowledge.
- newton_meter (Kevin Millikin) — Compact language designer (Midnight team). Most authoritative on language semantics.
- gilescope — Cryptography details, proving system (Midnight team).
- Facu | Midnames — Active builder, circuit optimization insights.