Scope
This skill covers the two error layers in Compact (static vs dynamic), how to read compiler messages (especially undeclared disclosure traces), the fast development loop with
, version management across 6 components, and the most common debugging patterns. Official testing docs are "coming soon" — this is battle-tested from real development.
1) Two Error Layers
Compact has two error types, not one:
Static errors — Caught by the compiler before generating any output. You see these in your terminal while developing. Static errors are your friend — the compiler tells you exactly what's wrong and where.
Dynamic errors — Caught at runtime by the generated JavaScript. You see these when a circuit executes. These are harder to debug because they happen inside generated code you didn't write.
Static Errors (Compile Time)
| Error type | What it is | When caught |
|---|
| Syntax | Malformed code | Parser |
| Type mismatch | Wrong type used | Type checker |
| Undeclared disclosure | missing | Witness protection |
| Undefined reference | Unknown identifier | Name resolver |
| Generic not specialized | Generic entity used at top level | Scope checker |
| Recursive struct | Struct refers to itself | Declaration checker |
| Recursive circuit | Circuit calls itself | Declaration checker |
| in | inside loop | Statement checker |
| Sealed ledger write | Write to sealed field in circuit | Declaration checker |
If the compiler produces no output, there's at least one static error. Check the messages.
Dynamic Errors (Runtime)
| Error type | What it is | Example |
|---|
| Type mismatch | Wrong argument type/number | Calling with wrong args |
| Overflow | Cast value too large for target | |
| Underflow | Counter decremented below zero | when at 0 |
| Uninitialized nested value | Nested ledger state not initialized | before insert |
| Merkle tree full | Insert into full tree | when |
Dynamic errors are harder to debug. Read the error message for the line number in your source file.
2) Reading Compiler Error Messages
Type Error
/path/contract.compact line 12 char 5:
type error: expected Uint<64>, got Field
Read:
line:character, expected type, got type. The caret (
) points to the problem token.
Undeclared Disclosure
Exception: /path/contract.compact line 6 char 11:
potential witness-value disclosure must be declared but is not:
witness value potentially disclosed:
the return value of witness getBalance at line 2 char 1
nature of the disclosure:
ledger operation might disclose the witness value
via this path through the program:
the right-hand side of = at line 6 char 11
Read this bottom to top. The path traces how witness data traveled:
- Origin: at line 2
- Path: flows through the right-hand side of the assignment
- Destination: the ledger operation at line 6
Fix: Add
somewhere along that path, as close to the disclosure point as possible.
Missing on Return
Exception: line 5 char 3:
potential witness-value disclosure must be declared but is not:
witness value potentially disclosed:
the return value of witness getBalance at line 2 char 1
nature of the disclosure:
the value returned from exported circuit check might disclose
the result of a comparison involving the witness value
Even a
comparison result counts as disclosure. Wrap the witness call or the return value with
.
Version Mismatch
Error: runtime version mismatch: expected 0.15.0, got 0.14.2
The compiled contract expects a different runtime version. See version management below.
3) The Development Loop
Generating proving keys is slow. During iterative development, skip it:
bash
compact compile --skip-zk contracts/contract.compact contracts/managed/contract
This produces
and
compiler/contract-info.json
, enough to test logic. Re-enable for final builds.
4) Version Management
Midnight has 6 components that must stay in sync. When they're not, you get opaque runtime errors about version mismatches.
| Component | What it is | How to check |
|---|
| CLI tool | binary | |
| Compiler | | compact compile --version
|
| Runtime | @midnight-ntwrk/compact-runtime
| |
| Ledger | @midnight-ntwrk/ledger-v8
| |
| JS libraries | @midnight-ntwrk/midnight-js-*
| |
| Proof server | Docker image | image tag |
Check Current Versions
bash
compact --version
compact compile --version
npm list @midnight-ntwrk/compact-runtime
npm list @midnight-ntwrk/ledger-v8
Consult the Compatibility Matrix
The
official release compatibility matrix is the source of truth. Never mix versions without checking it.
Lock Exact Versions in
json
{
"dependencies": {
"@midnight-ntwrk/compact-runtime": "0.15.0",
"@midnight-ntwrk/ledger-v8": "8.0.3"
}
}
Do not use or — these allow automatic updates that silently break compatibility.
Use for Reproducible Installs
bash
rm -rf node_modules
npm ci
installs exactly what's in
.
fetches the latest matching version.
After Updating Any Component
- Update all related components together
- Recompile contracts
- Restart the proof server with the new Docker image
- Run your test suite
5) Common Mistakes and Fixes
Forgot on Ledger Write
compact
// wrong — compiler error
balance = getBalance();
// correct
balance = disclose(getBalance());
Forgot on Return Value
compact
// wrong — compiler error (comparison of witness data)
export circuit check(n: Uint<64>): Boolean {
return getSecret() > n;
}
// correct — declare the disclosure
export circuit check(n: Uint<64>): Boolean {
return disclose(getSecret()) > n;
}
Inside Loop
compact
// wrong — static error
circuit findFirst(v: Vector<4, Field>, target: Field): Boolean {
for (const x of v) {
if (x == target) return true;
}
return false;
}
// correct — use fold
circuit findFirst(v: Vector<4, Field>, target: Field): Boolean {
return fold((found, x) => found || x == target, false, v);
}
Recursive Circuit
compact
// wrong — static error: recursion not allowed
circuit factorial(n: Uint<64>): Uint<64> {
return n == 0 ? 1 : n * factorial(n - 1);
}
Rewrite using
or explicit unrolling. Compact requires finite circuits.
Narrowing Cast Overflows at Runtime
compact
const x: Uint<64> = 1000;
const y = x as Uint<8>; // dynamic error: 1000 doesn't fit
Always verify the value fits before casting. Use
or bounded types.
Uninitialized Nested Ledger State
compact
ledger fld: Map<Boolean, Map<Field, Counter>>;
// wrong — dynamic error (inner map not initialized)
export circuit increment(b: Boolean, n: Field): [] {
fld.lookup(b).lookup(n) += 1;
}
// correct — initialize first
export circuit init(b: Boolean): [] {
fld.insert(disclose(b), default<Map<Field, Counter>>);
}
Result Used Without
compact
// wrong — compiler error (witness-tainted)
ledger h: Field;
export circuit store(v: Field): [] {
h = transientHash<Field>(v);
}
// correct — declare disclosure
h = disclose(transientHash<Field>(v));
// or use transientCommit (nonce provides hiding, no disclose needed)
h = transientCommit<Field>(v, nonce);
6) Common Environment Issues
| Error | Cause | Fix |
|---|
compact: command not found
| Binary not on PATH | export PATH="$HOME/.compact/bin:$PATH"
|
ERR_UNSUPPORTED_DIR_IMPORT
| Node.js tried to import directory | Open new terminal, clear caches |
| Docker connection errors | Docker Desktop not running | Start Docker Desktop |
| Port 6300 in use | Another container on same port | |
| Version mismatch at runtime | Outdated runtime package | Check compatibility matrix, update |
7) Version Check Script
Run this before filing a bug report:
bash
#!/bin/bash
echo "=== Midnight Version Check ==="
echo "CLI:"; compact --version || echo "not found"
echo "Compiler:"; compact compile --version || echo "not found"
echo "Runtime:"
npm list --depth=0 | grep @midnight-ntwrk || echo "none found"
echo "Node.js:"; node --version
echo "Compare with: docs.midnight.network/relnotes/support-matrix"
8) Getting Help
If stuck after checking these notes:
- Discord — post your error message and version details
- FAQ — docs.midnight.network/troubleshoot/faq
- Forum — forum.midnight.network
Always include:
- Output of the version check script
- The full error message
- The file (or relevant excerpt)
- What you expected vs what happened
9) Quick Recap
- Static errors — compiler catches them before output. Check the messages.
- Dynamic errors — happen at runtime inside generated code. Read the line numbers.
- Undeclared disclosure trace — read bottom to top, it traces the path from origin to disclosure.
- Use during development. Enable for final builds.
- Lock exact versions in . Use .
- Check the compatibility matrix before updating any component.
- After any update: recompile, restart proof server, run tests.