midnight-compact — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited midnight-compact (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Complete guide to writing privacy-preserving smart contracts with Compact.
Compact is a purpose-built programming language for zero-knowledge smart contracts. Unlike adapting existing languages, Compact was designed from scratch to make privacy-preserving programming natural and secure.
Key Design Goals:
Key Features:
disclose() wrapperspragma language_version >= 0.20;
import CompactStandardLibrary;
// Ledger state (individual declarations)
export ledger counter: Counter;
export ledger owner: Bytes<32>;
// Witness for private data
witness local_secret_key(): Bytes<32>;
// Circuit (returns [] not Void)
export circuit increment(): [] {
counter.increment(1);
}pragma language_version >= 0.20;
import CompactStandardLibrary;
export ledger count: Counter;
export circuit increment(): [] {
count.increment(1);
}
export circuit decrement(): [] {
count.decrement(1);
}
export circuit getCount(): Uint<64> {
return count.read();
}
export circuit setCount(newValue: Uint<64>): [] {
count.write(newValue);
}Each Compact contract has three components:
A Compact program can contain:
module / import for namespace managementstruct / enum / type for program-defined typesexport ledger for public state storagewitness for callback functions (private state)circuit for the operational coreconstructor for initializationPragma:
✅ pragma language_version >= 0.20;
❌ pragma language_version >= 0.16.0; // Outdated version
❌ pragma version "0.20"; // Wrong formatLedger Declarations:
✅ export ledger counter: Counter;
✅ export ledger owner: Bytes<32>;
✅ export ledger balances: Map<Address, Uint<64>>;
❌ ledger { counter: Counter; } // Block syntax deprecated
❌ ledger counter = Counter; // Missing colonCircuit Return Types:
✅ export circuit increment(): [] { }
✅ export circuit getBalance(): Uint<64> { return 0; }
✅ export circuit getData(): [Uint<32>, Boolean] { return [0, true]; }
❌ export circuit increment(): Void { } // Void doesn't exist
❌ export circuit noReturn() { } // Missing return typeEnum Access:
✅ if (choice == Choice.rock) { }
❌ if (choice == Choice::rock) { } // Rust-style doesn't work
❌ if (choice == "rock") { } // String not enum variantWitness Declarations:
✅ witness local_secret_key(): Bytes<32>;
✅ witness verify_signature(msg: Bytes<32>, sig: Bytes<64>): Boolean;
❌ witness get_key(): Bytes<32> { return ...; } // No body allowed
❌ witness calculate(): Uint<64> = 42; // No body or defaultCounter Operations:
✅ const val = counter.read();
✅ counter.increment(1);
✅ counter.decrement(1);
✅ counter.write(100);
❌ const val = counter.value(); // Method doesn't exist
❌ counter.add(1); // Wrong method namePure Functions:
✅ pure circuit helper(x: Field): Field { return x + 1; }
✅ circuit helper(x: Field): Field { return x + 1; }
❌ pure function helper(x: Field): Field { ... } // 'function' keyword doesn't exist
❌ pure fn helper(x: Field): Field { ... } // 'fn' keyword doesn't existDisclosure in Conditionals:
✅ if (disclose(witness_val == x)) { ... }
❌ if (witness_val == x) { ... } // Implicit disclosure error
❌ disclose(witness_val == x); // Statement form without ifVector/Tuple Operations:
✅ const first = vec[0];
✅ const len = vec.len();
✅ const [a, b] = tuple;
✅ for (i: Uint<64>, item: vec) { ... }
❌ const last = vec[-1]; // No negative indexing
❌ vec.push(item); // No push - vectors are immutable| ❌ Wrong | ✅ Correct | Explanation |
|---|---|---|
ledger { field: Type; } | export ledger field: Type; | Individual declarations |
circuit fn(): Void | circuit fn(): [] | Empty return uses [] |
enum State { ... } | export enum State { ... } | Enums need export |
counter.value() | counter.read() | Use read() method |
Choice::rock | Choice.rock | Dot, not :: |
Cell<T> | Field | Cell type deprecated |
Vector.push(x) | Create new vector | Vectors immutable |
disclose(a == b) alone | if (disclose(a == b)) | Needs conditional |
Uint without size | Uint<64> | Size required |
Fn or function | circuit | Use circuit keyword |
| Type | Description | Example Values |
|---|---|---|
Boolean | Boolean values | true, false |
Field | Prime field integers | 0 to field max |
Uint<N> | N-bit unsigned | Uint<8>, Uint<32>, Uint<64> |
Uint<M..N> | Bounded unsigned | Uint<0..100> |
Bytes<N> | N-byte vector | Bytes<32> for hashes |
Vector<N, T> | N-element homogeneous | Vector<10, Uint<32>> |
[T1, T2, ...] | Heterogeneous tuple | [Uint<32>, Boolean] |
Opaque<"string"> | Opaque string | Debug strings |
Opaque<"Uint8Array"> | Opaque bytes | Binary data |
Struct:
struct Point {
x: Field,
y: Field,
}
struct Token<T> {
amount: Uint<64>,
data: T,
}Enum:
export enum Direction { up, down, left, right }
export enum Status { pending, active, completed }Type Alias:
type Address = Bytes<32>;
new type UserId = Uint<64>; // Nominal (distinct)
type Vec3<T> = Vector<3, T>; // GenericUint<0..N> subtype of Uint<0..M> if N ≤ MUint<0..N> subtype of Field if N ≤ field maxmodule M<T, #N> {
export circuit foo<A>(x: T, v: Vector<N, A>): Vector<N, [A, T]> {
return map((y) => [y, x], v);
}
}
import M<Boolean, 3>;
export circuit bar(): Vector<3, [Uint<8>, Boolean]> {
return foo<Uint<8>>(true, [101, 103, 107]);
}Seven ledger state types for public data storage:
export ledger myCounter: Counter;
counter.increment(n);
counter.decrement(n);
counter.read();
counter.write(value);// Old way (avoid)
export ledger data: Cell<Uint<32>>;
// New way
export ledger data: Field;export ledger members: Set<Address>;
members.insert(addr);
members.contains(addr);
members.remove(addr);export ledger balances: Map<Address, Uint<64>>;
balances.get(addr);
balances.set(addr, value);
balances.contains(addr);
balances.remove(addr);export ledger items: List<Uint<32>>;
items.push(value);
items.get(index);
items.len();export ledger tree: MerkleTree<Depth>;
tree.insert(value);
tree.getRoot();
tree.getPath(index);
tree.verifyPath(path, root);export ledger history: HistoricMerkleTree<Depth>;
history.insert(value);
history.getRoot();
history.getHistoricRoot(blockHeight);
history.getPath(index);Maybe<T> // Optional values (some/none)
Either<L, R> // Result types (left/right)
NativePoint // BLS12-381 points
MerkleTreeDigest // Tree hash digest
MerkleTreePath // Proof path
ContractAddress // Contract addressing
ZswapCoinPublicKey // Shielded keys
UserAddress // User addressingtokenType() -> TokenType
nativeToken() -> TokenType
ownPublicKey() -> CoinPublicKey
createZswapInput(coin: ShieldedCoinInfo) -> ZswapInput
createZswapOutput(recipient: Recipient, amount: Uint<64>) -> ZswapOutput
mintShieldedToken(amount: Uint<64>, recipient: Recipient): [] { }
sendShielded(amount: Uint<64>, recipient: Recipient): [] { }
receiveShielded(input: ZswapInput): [] { }
mintUnshieldedToken(amount: Uint<64>): [] { }
sendUnshielded(amount: Uint<64>, recipient: Address): [] { }
unshieldedBalance(): Uint<64>transientHash(data: Field): Field
transientCommit(data: Field, randomness: Field): Field
persistentHash(key: Field, value: Field): Field
persistentCommit(key: Field, value: Field): Field
degradeToTransient(value: Field): FieldecAdd(p1: Point, p2: Point): Point
ecMul(point: Point, scalar: Field): Point
ecMulGenerator(scalar: Field): Point
hashToCurve(data: Field): Point
upgradeFromTransient(value: Field): FieldmerkleTreePathRoot(path: MerkleTreePath): Field
merkleTreePathRootNoLeafHash(path: MerkleTreePath): FieldblockTimeLt(time: Uint<64>): Boolean
blockTimeGte(time: Uint<64>): Boolean
blockTimeGt(time: Uint<64>): Boolean
blockTimeLte(time: Uint<64>): Booleanif (condition) {
// then branch
} else {
// else branch
}// For each element
for (i: Uint<64>, item: vector) {
process(item);
}
// With index
for (i: Uint<64>, item: vector) {
log(i);
log(item);
}match direction {
Direction.up => 1,
Direction.down => 2,
Direction.left => 3,
Direction.right => 4,
}pure circuit addOne(x: Uint<32>): Uint<32> {
return x + 1;
}Private data must be explicitly disclosed:
witness secret(): Uint<32>;
export circuit checkEqual(x: Uint<32>): Boolean {
// Must disclose private comparison
return disclose(secret() == x);
}
// Valid patterns:
if (disclose(secret() > 100)) { ... }
const revealed = disclose(secret() + publicValue);# Using official CLI
npx create-midnight-app my-project
cd my-project
# Or manual setup
mkdir my-contract && cd my-contract
npm init -y
npm install @midnight-ntwrk/compact-toolsCreate contract.compact:
pragma language_version >= 0.20;
import CompactStandardLibrary;
export ledger counter: Counter;
export circuit increment(): [] {
counter.increment(1);
}
export circuit getCount(): Uint<64> {
return counter.read();
}npx compact build contract.compact# Output in src/ directory# Using deployment script
npx midnight-deploy --network testnetpragma language_version >= 0.20;
import CompactStandardLibrary;
export ledger totalSupply: Uint<64>;
export ledger balances: Map<Address, Uint<64>>;
export circuit mint(to: Address, amount: Uint<64>): [] {
const current = balances.getOrDefault(to, 0);
balances.set(to, current + amount);
totalSupply.set(totalSupply.read() + amount);
}
export circuit burn(from: Address, amount: Uint<64>): [] {
const current = balances.getOrDefault(from, 0);
assert(current >= amount, "Insufficient balance");
balances.set(from, current - amount);
totalSupply.set(totalSupply.read() - amount);
}
export circuit transfer(from: Address, to: Address, amount: Uint<64>): [] {
const fromBal = balances.getOrDefault(from, 0);
assert(fromBal >= amount, "Insufficient balance");
const toBal = balances.getOrDefault(to, 0);
balances.set(from, fromBal - amount);
balances.set(to, toBal + amount);
}
export circuit balanceOf(owner: Address): Uint<64> {
return balances.getOrDefault(owner, 0);
}pragma language_version >= 0.20;
import CompactStandardLibrary;
export ledger votes: Map<Bytes<32>, Uint<64>>;
export ledger voted: Set<Address>;
export circuit castVote(voter: Address, candidate: Bytes<32>): [] {
assert(!voted.contains(voter), "Already voted");
voted.insert(voter);
const current = votes.getOrDefault(candidate, 0);
votes.set(candidate, current + 1);
}
export circuit getVoteCount(candidate: Bytes<32>): Uint<64> {
return votes.getOrDefault(candidate, 0);
}pragma language_version >= 0.20;
import CompactStandardLibrary;
export ledger owner: Address;
export ledger admins: Set<Address>;
export circuit onlyOwner(): [] {
assert(msgSender() == owner, "Not owner");
}
export circuit onlyOwnerOrAdmin(): [] {
const sender = msgSender();
assert(sender == owner || admins.contains(sender), "Not authorized");
}
witness msgSender(): Address;
// Note: msgSender is typically provided by runtimeStandard implementations from @OpenZeppelin/compact-contracts:
// Using OpenZeppelin
import OpenZeppelin.FungibleToken;
export ledger token: FungibleToken;
export circuit mint(to: Address, amount: Uint<64>): [] {
token.mint(to, amount);
}
export circuit transfer(to: Address, amount: Uint<64>): [] {
token.transfer(to, amount);
}Real-world implementations for learning:
Midnight MCP is an MCP server (Model Context Protocol) purpose-built for Midnight development. It gives AI assistants real compiler validation and semantic search across 102 Midnight repositories.
AI assistants hallucinate Compact syntax. Without MCP:
Int instead of Uint<32>Void instead of [] for empty returnsstate instead of ledgerWith MCP, AI generates verified working code.
Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"midnight": {
"command": "npx",
"args": ["-y", "midnight-mcp@latest"]
}
}
}Cursor (.cursor/mcp.json):
{
"mcpServers": {
"midnight": {
"command": "npx",
"args": ["-y", "midnight-mcp@latest"]
}
}
}| Category | Tools |
|---|---|
| Search | midnight-search-compact, midnight-search-docs, midnight-search-typescript, midnight-fetch-docs |
| Validation | midnight-compile-contract, midnight-analyze-contract |
| Analysis | midnight-review-contract, midnight-extract-contract-structure |
| Generation | midnight-generate-contract, midnight-document-contract |
| Repository | midnight-get-file, midnight-get-latest-syntax, midnight-list-examples |
| Version | midnight-check-breaking-changes, midnight-get-migration-guide |
Use exact versions (e.g., 0.30.0) for reproducibility. Latest versions available on GitHub releases.
Official GitHub Action for installing and caching the Compact compiler: midnightntwrk/setup-compact-action
Features:
0.30.0) or use latest- name: Setup Compact Compiler
uses: midnightntwrk/setup-compact-action@v1
with:
compact-version: '0.30.0'
- name: Compile Compact code
run: compact compile src/*.compact| Input | Description | Required | Default |
|---|---|---|---|
compact-version | Version of Compact compiler | No | latest |
cache-enabled | Enable caching | No | true |
Outputs: compact-version, cache-hit
This skill covers Compact language and contracts. For supporting areas see these related skills.
disclose() in conditionals~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.