Install
$ agentstack add skill-uvroxx-midnight-agent-skills-midnight-compact-guide ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Midnight Compact Language Reference (v0.19+)
> CRITICAL: This reference is derived from actual compiling contracts in the Midnight ecosystem (MeshJS starter template). Always verify syntax against this reference before generating contracts.
Quick Start Template
Use this as a starting point - it compiles successfully:
pragma language_version >= 0.19;
import CompactStandardLibrary;
// Ledger state (individual declarations, NOT a block)
export ledger counter: Counter;
export ledger owner: Bytes;
// Witness for private/off-chain data (declaration only)
witness local_secret_key(): Bytes;
// Circuit (returns [] not Void)
export circuit increment(): [] {
counter.increment(1);
}
1. Pragma (Version Declaration)
CORRECT - simple minimum version:
pragma language_version >= 0.19;
WRONG - these will cause issues:
pragma language_version >= 0.14.0; // ❌ outdated version
pragma language_version >= 0.16 && = 0.19
2. Imports
Always import the standard library:
import CompactStandardLibrary;
For modular code:
import "path/to/module";
import { SomeType } from "other/module";
3. Ledger Declarations
CORRECT - individual declarations with export ledger:
export ledger counter: Counter;
export ledger owner: Bytes;
export ledger balances: Map, Uint>;
// Private state (off-chain only)
ledger secretValue: Field; // no export = private
WRONG - block syntax is DEPRECATED:
// ❌ This causes parse error: found "{" looking for an identifier
ledger {
counter: Counter;
owner: Bytes;
}
Ledger Modifiers
export ledger publicData: Field; // Public, readable by anyone
export sealed ledger immutableData: Field; // Set once in constructor, cannot change
ledger privateData: Field; // Private, not exported
4. Data Types
Primitive Types
| Type | Description | Example | |------|-------------|---------| | Field | Finite field element (basic numeric) | amount: Field | | Boolean | True or false | isActive: Boolean | | Bytes | Fixed-size byte array | hash: Bytes | | Uint | Unsigned integer (N = 8, 16, 32, 64, 128, 256) | balance: Uint | | Uint | Bounded unsigned integer | score: Uint |
⚠️ Uint Type Equivalence: Uint and Uint are the SAME type family.
Uint=UintUint=UintUint=Uint
Collection Types
| Type | Description | Example | |------|-------------|---------| | Counter | Incrementable/decrementable | count: Counter | | Map | Key-value mapping | Map, Uint> | | Set | Unique value collection | Set> | | Vector | Fixed-size array | Vector | | List | Dynamic list | List> | | Maybe | Optional value | Maybe> | | Either | Union type | Either> | | Opaque | External type from TypeScript | Opaque |
Custom Types
Enums - must use export to access from TypeScript:
export enum GameState { waiting, playing, finished }
export enum Choice { rock, paper, scissors }
Enum Access - use DOT notation (not Rust-style ::):
// ✅ CORRECT - dot notation
if (choice == Choice.rock) { ... }
game_state = GameState.waiting;
// ❌ WRONG - Rust-style double colon
if (choice == Choice::rock) { ... } // Parse error!
Structs:
export struct PlayerConfig {
name: Opaque,
score: Uint,
isActive: Boolean,
}
5. Circuits
Circuits are on-chain functions that generate ZK proofs.
CRITICAL: Return type is [] (empty tuple), NOT Void:
// ✅ CORRECT - returns []
export circuit increment(): [] {
counter.increment(1);
}
// ✅ CORRECT - with parameters
export circuit transfer(to: Bytes, amount: Uint): [] {
assert(amount > 0, "Amount must be positive");
// ... logic
}
// ✅ CORRECT - with return value
export circuit getBalance(addr: Bytes): Uint {
return balances.lookup(addr);
}
// ❌ WRONG - Void does not exist
export circuit broken(): Void { // Parse error!
counter.increment(1);
}
Circuit Modifiers
export circuit publicFn(): [] // Callable externally
circuit internalFn(): [] // Internal only, not exported
export pure circuit hash(x: Field): Bytes // No state access
6. Witnesses
Witnesses provide off-chain/private data to circuits. They run locally, not on-chain.
CRITICAL: Witnesses are declarations only - NO implementation body in Compact! The implementation goes in your TypeScript prover.
// ✅ CORRECT - declaration only, semicolon at end
witness local_secret_key(): Bytes;
witness get_merkle_path(leaf: Bytes): MerkleTreePath>;
witness store_locally(data: Field): [];
witness find_user(id: Bytes): Maybe;
// ❌ WRONG - witnesses cannot have bodies
witness get_caller(): Bytes {
return public_key(local_secret_key()); // ERROR!
}
7. Constructor
Optional - initializes sealed ledger fields at deploy time:
export sealed ledger owner: Bytes;
export sealed ledger nonce: Bytes;
constructor(initNonce: Bytes) {
owner = disclose(public_key(local_secret_key()));
nonce = disclose(initNonce);
}
8. Pure Circuits (Helper Functions)
Use pure circuit for helper functions that don't modify ledger state:
// ✅ CORRECT - use "pure circuit"
pure circuit determine_winner(p1: Choice, p2: Choice): Result {
if (p1 == p2) {
return Result.draw;
}
// ... logic
}
// ❌ WRONG - "function" keyword doesn't exist
pure function determine_winner(p1: Choice, p2: Choice): Result {
// ERROR: unbound identifier "function"
}
9. Common Operations
Counter Operations
counter.increment(1); // Increase by amount (Uint)
counter.decrement(1); // Decrease by amount (Uint)
const val = counter.read(); // Get current value (returns Uint)
const low = counter.lessThan(100); // Compare with threshold (Boolean)
counter.resetToDefault(); // Reset to zero
// ⚠️ WRONG: counter.value() does NOT exist - use counter.read()
Map Operations
// Insert/update operations
balances.insert(address, 100); // insert(key, value): []
balances.insertDefault(address); // insertDefault(key): []
// Query operations (all work in circuits ✅)
const balance = balances.lookup(address); // lookup(key): value_type
const exists = balances.member(address); // member(key): Boolean
const empty = balances.isEmpty(); // isEmpty(): Boolean
const count = balances.size(); // size(): Uint
// Remove operations
balances.remove(address); // remove(key): []
balances.resetToDefault(); // resetToDefault(): []
Set Operations
// Insert/remove operations
members.insert(address); // insert(elem): []
members.remove(address); // remove(elem): []
members.resetToDefault(); // resetToDefault(): []
// Query operations (all work in circuits ✅)
const isMember = members.member(address); // member(elem): Boolean
const empty = members.isEmpty(); // isEmpty(): Boolean
const count = members.size(); // size(): Uint
Maybe Operations
const opt: Maybe = some(42);
const empty: Maybe = none();
if (opt.is_some) {
const val = opt.value;
}
Type Casting
const bytes: Bytes = myField as Bytes; // Field to Bytes
const num: Uint = myField as Uint; // Field to Uint (bounds not checked!)
const field: Field = myUint as Field; // Uint to Field (safe)
Hashing
// Persistent hash (same input = same output across calls)
const hash = persistentHash>>([data1, data2]);
// Persistent commit (hiding commitment)
const commit = persistentCommit(value);
10. Assertions
assert(condition, "Error message");
assert(amount > 0, "Amount must be positive");
assert(disclose(caller == owner), "Not authorized");
11. Common Patterns
Authentication Pattern
witness local_secret_key(): Bytes;
// IMPORTANT: public_key() is NOT a builtin - use this pattern
circuit get_public_key(sk: Bytes): Bytes {
return persistentHash>>([pad(32, "myapp:pk:"), sk]);
}
export circuit authenticated_action(): [] {
const sk = local_secret_key();
const caller = get_public_key(sk);
assert(disclose(caller == owner), "Not authorized");
// ... action
}
Commit-Reveal Pattern
pragma language_version >= 0.19;
import CompactStandardLibrary;
export ledger commitment: Bytes;
export ledger revealed_value: Field;
export ledger is_revealed: Boolean;
witness local_secret_key(): Bytes;
witness store_secret_value(v: Field): [];
witness get_secret_value(): Field;
// Helper: compute commitment hash
circuit compute_commitment(value: Field, salt: Bytes): Bytes {
const value_bytes = value as Bytes;
return persistentHash>>([value_bytes, salt]);
}
// Commit phase
export circuit commit(value: Field): [] {
const salt = local_secret_key();
store_secret_value(value);
commitment = disclose(compute_commitment(value, salt));
is_revealed = false;
}
// Reveal phase
export circuit reveal(): Field {
const salt = local_secret_key();
const value = get_secret_value();
const expected = compute_commitment(value, salt);
assert(disclose(expected == commitment), "Value doesn't match commitment");
assert(disclose(!is_revealed), "Already revealed");
revealed_value = disclose(value);
is_revealed = true;
return disclose(value);
}
Disclosure in Conditionals
When branching on witness values, wrap comparisons in disclose():
// ✅ CORRECT
export circuit check(guess: Field): Boolean {
const secret = get_secret(); // witness
if (disclose(guess == secret)) {
return true;
}
return false;
}
// ❌ WRONG - will not compile
export circuit check_broken(guess: Field): Boolean {
const secret = get_secret();
if (guess == secret) { // implicit disclosure error
return true;
}
return false;
}
12. Common Mistakes to Avoid
| Mistake | Correct | |---------|---------| | ledger { field: Type; } | export ledger field: Type; | | circuit fn(): Void | circuit fn(): [] | | pragma >= 0.16.0 | pragma language_version >= 0.19; | | enum State { ... } | export enum State { ... } | | if (witness_val == x) | if (disclose(witness_val == x)) | | Cell | Field (Cell is deprecated) | | counter.value() | counter.read() | | pure function helper() | pure circuit helper() | | Choice::rock | Choice.rock (use dot, not ::) |
13. Exports for TypeScript
To use types/values in TypeScript, they must be exported:
// These are accessible from TypeScript
export enum GameState { waiting, playing }
export struct Config { value: Field }
export ledger counter: Counter;
export circuit play(): []
// Standard library re-exports (if needed in TS)
export { Maybe, Either, CoinInfo };
Reference Contracts
These contracts compile successfully and demonstrate correct patterns:
- Counter (beginner):
midnightntwrk/example-counter - Bulletin Board (intermediate):
midnightntwrk/example-bboard - Naval Battle Game (advanced):
ErickRomeroDev/naval-battle-game_v2 - Sea Battle (advanced):
bricktowers/midnight-seabattle
When in doubt, reference these repos for working syntax.
Rules
See /rules/ directory for detailed pattern documentation:
privacy-selective-disclosure.md- ZK disclosure patternstokens-shielded-unshielded.md- Token vault patterns
References
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: UvRoxx
- Source: UvRoxx/midnight-agent-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.