Midnight Network: 6 Bugs, 8 Workarounds, and 4 Working Compact Contracts
We built 4 Compact contracts with 18 passing tests and a working local deploy. But getting here was full of obstacles. This article documents every bug, workaround, and friction point encountered during a day of development on Midnight Network.
Context
The DPO2U Self-Funding Protocol implements an autonomous fee distribution pipeline for compliance agents on Midnight Network:
- PaymentGateway — Treasury deposits and $NIGHT staking
- FeeDistributor — 40/60 split between expert and auditor
- ComplianceRegistry — ZK privacy-preserving attestations
- AgentRegistry — Agent lifecycle (registration, deactivation, tasks)
Stack: Compact 0.29.0, Runtime 0.14.0, Node.js 20.18.1, TypeScript 5.9.3
Repo: github.com/fredericosanntana/dpo2u-midnight
Bug #1: Compact Compiler (compactc) Not Available
Severity: Blocking
The compactc compiler is not available as an installable npm package or standalone binary. The documentation mentions the compiler but doesn't provide clear installation instructions outside the Midnight IDE (VS Code extension).
Impact: Impossible to compile new .compact contracts to generate build artifacts (contract/index.js, zkir/, keys/).
Workaround: Reverse-engineered the already-compiled contracts (PaymentGateway as reference) and hand-wrote the contract/index.js. The AgentRegistry (400+ lines of JS) was entirely hand-written following the runtime patterns. Works for local tests but doesn't generate the ZK artifacts needed for real deployment.
Hand-written:
compact/build/agent-registry/contract/index.js (hand-written)
compact/build/agent-registry/contract/index.d.ts
compact/build/agent-registry/compiler/contract-info.json
NOT generated (require compactc):
compact/build/agent-registry/zkir/*.zkir
compact/build/agent-registry/keys/*.prover / *.verifier
Recommendation: Publish compactc as an npm package (@midnight-ntwrk/compactc) or downloadable binary.
Bug #2: signRecipe with Hardcoded Proof Marker
Severity: Critical (blocks deploy)
The wallet-sdk signRecipe method uses a hardcoded 'pre-proof' marker when cloning intents, but proved transactions (UnboundTransaction) have data with 'proof' marker. The signing fails silently or produces an invalid TX.
Implemented workaround (in scripts/lib/wallet-setup.ts):
function signTransactionIntents(
tx: { intents?: Map<number, any> },
signFn: (payload: Uint8Array) => any,
proofMarker: 'proof' | 'pre-proof',
): void {
for (const segment of tx.intents.keys()) {
const intent = tx.intents.get(segment);
const cloned = ledger.Intent.deserialize(
'signature', proofMarker, 'pre-binding', intent.serialize()
);
const sigData = cloned.signatureData(segment);
const signature = signFn(sigData);
// ... apply signatures to offers
}
}
// Correct usage with distinct markers:
signTransactionIntents(recipe.baseTransaction, signFn, 'proof');
signTransactionIntents(recipe.balancingTransaction, signFn, 'pre-proof');
Bug #3: wallet.getBalance() Doesn't Exist
Severity: Medium
The WalletFacade API doesn't expose a getBalance() method despite it being the most basic wallet operation expected:
Failed: ctx.wallet.getBalance is not a function
There's no way to programmatically check whether the wallet has enough tDUST before attempting a deploy. The developer needs to open Lace Wallet manually.
Bug #4: PreProd Deploy Hangs Indefinitely
Severity: Critical
deployContract() hangs indefinitely during the ZK proof generation phase. The wallet syncs successfully, the proof server responds health OK, but the call never returns:
=== DPO2U Self-Funding Protocol — Deploy All Contracts ===
Network: preprod
[1/3] Setting up wallet...
Wallet synced.
[2/3] Deploying contracts sequentially...
Deploying ComplianceRegistry...
<--- HANGS HERE FOR 15+ MINUTES, NO OUTPUT --->
Diagnostics:
- Proof server (
localhost:6300) returns{"status":"ok"} - Wallet syncs normally with the PreProd indexer
- No errors, no timeouts, no debug messages
- The process stays alive consuming CPU
Possible causes: Slow proof generation without feedback, silent tDUST insufficiency, no configurable timeout in deployContract().
Bug #5: Kittie DApp Doesn't Work
Severity: High
The Midnight example app "Kittie" (Midnight Kitties) doesn't work. As documented in our previous post, the tutorial asks for a 32-byte hex seed, but Lace wallet uses a 24-word mnemonic with completely different HD derivation.
Two incompatible SDKs:
- Lace Wallet (new):
HDWallet+mnemonicToSeedSync+Roles - Kitties (old): direct hex
seed, no HD derivation
The official example referenced in the Academy is broken for any developer using Lace Wallet.
Bug #6: Compact Types That Don't Work at Runtime
Severity: Medium
The Compact language documentation and examples mention types that don't work in the actual runtime:
| Documented Type | Status | Workaround |
|---|---|---|
sealed ledger | Doesn't work | Use regular export ledger |
Boolean in ledger | Doesn't work as slot type | Use Uint<64> with 0/1 |
Counter | Doesn't work | Use Uint<64> with manual read + increment |
This cost hours of trial-and-error until discovering that the runtime only supports Uint<64> and Bytes<32> as ledger slot types.
Consolidated Workaround Table
| Problem | Workaround |
|---|---|
No compactc compiler | Hand-write contract/index.js following pre-compiled contract patterns |
Buggy signRecipe | Manual signing with correct proof markers |
Missing getBalance() | Check balance via Lace Wallet manually |
| Deploy hangs on PreProd | Local deploy works; PreProd awaiting resolution |
Boolean doesn't work in ledger | Use Uint<64> with 0/1 |
Counter doesn't work | Use Uint<64> with manual read + increment |
sealed ledger doesn't work | Use regular export ledger |
| Kittie incompatible with Lace | Build from scratch based on counter example |
What Works Well
- Compact language — Elegant syntax.
disclose(),assert(), and the privacy model are well-designed - compact-runtime for testing — Testing contracts locally without a network is excellent. 18 tests in under 500ms
- Privacy model — Opaque
Bytes<32>for sensitive data, superior to any other chain - Local devnet deploy — Docker compose works. Deploy of 3 compiled contracts completed successfully
- ZK circuits — Separation between
pureandimpure, with automatic proofs
Results
Compact contracts: 4
ZK circuits: 12 total
Passing tests: 18/18
Local devnet deploy: 3/4 contracts (AgentRegistry without ZK keys)
PreProd deploy: Blocked (proof generation timeout)
Frontend: React 19 + Vite 6, 3 pages
Time on workarounds: ~60% of total time
Recommendations for the Midnight Team
- Publish
compactcas an npm package — Blocking for any developer - Fix
signRecipe— Critical bug that prevents deploys - Add
getBalance()to WalletFacade — Basic API missing - Timeout + logs in
deployContract()— Can't hang forever without feedback - Keep examples working — Kittie referenced in Academy but broken
- Create
create-midnight-app— Template with pinned compatible versions - Document hardware requirements — How much RAM/CPU does the proof server need?
Links:
- Repo
- Wallet:
mn_dust_preprod1wd4mw37k4x26sfmtm5mmwhcgck5w43kqkp29f4qcjwtlaw9upwfywv50p24 - Stack: Compact 0.29.0 | Runtime 0.14.0 | Node 20.18.1
