Hello Compact — write your first contract¶
Outcome: you have a 6-line Compact contract that compiles, deploys
on a Midnight localnet, and exposes a single circuit that bumps a
shared counter by one — the universal "Hello World" of smart contracts.
You can read every line of the source and the compile output, you know
which compactc + language + runtime versions go together, and you
have a clear next stop for everything beyond "make the counter go up."
What this recipe is, and what it isn't
This is the SDK's minimum-viable Compact intro — enough to read
the counter contract that ships with kuira-starter-android,
understand the toolchain pinning that makes it work, and compile +
deploy it.
For deeper Compact learning — witnesses, ZK proof construction,
ledger types beyond Counter, multi-party patterns, on-chain
verification of off-chain compute — go to the official Midnight
contract examples.
Calculator, election, battleship-simple, private-guest-list,
private-reserve-auction, token-transfers. Each is a focused tutorial
on one Compact pattern.
The Kuira SDK consumes compiled Compact artifacts; it does not teach Compact authoring beyond what's in this recipe.
The counter contract¶
pragma language_version 0.23.0;
import CompactStandardLibrary;
export ledger count: Counter;
export circuit increment(): [] {
count.increment(1);
}
Six lines. What each one does:
pragma language_version 0.23.0;— pins the Compact language grammar version. The Compact language is versioned independently fromcompactc(the compiler) and from@midnight-ntwrk/compact-runtime(the JS runtime that compiled contracts call into). Mismatched versions produce alanguage version X.Y.Z mismatcherror at compile time.import CompactStandardLibrary;— brings in the standard library types and helpers, includingCounter.export ledger count: Counter;— declares a single on-chain ledger field, namedcount, of typeCounter. The Standard Library'sCounterwraps aUint<64>with built-in.increment()and.read()helpers.export ledgermakes the field readable by off-chain clients (the Kuira SDK reads it viacontract.ledger().getUint64("count")).export circuit increment(): [] { count.increment(1); }— a single circuit that takes no arguments and bumps the counter by 1. Circuits are the on-chain entrypoints; calling one produces a ZK proof + a transaction that updates ledger state.
No witnesses, no privacy controls, no access checks. Anyone with Dust can increment. The point is the SDK-integration story, not contract design — for non-trivial patterns, jump to the Midnight examples.
Toolchain pinning¶
Three versions move independently. Mismatched values surface as compile errors with the generated language version, not the version you typed:
| Layer | Pinned value | Where it lives |
|---|---|---|
compactc binary |
0.31.0 | ~/.compact/versions/0.31.0/aarch64-darwin/compactc |
| Compact language pragma | 0.23.0 | pragma language_version <v>; in your .compact source |
@midnight-ntwrk/compact-runtime |
0.16.0 | contract/package.json deps |
Each compactc binary self-introspects:
compactc --version # → 0.31.0
compactc --language-version # → 0.23.0
compactc --runtime-version # → 0.16.0
When upgrading, run the three flags on the new binary first to
discover the matching triple before editing any pragma. The starter's
contract/README.md
documents the upgrade recipe step by step.
Compile + verify¶
From your project root:
mkdir -p contract/src/managed
~/.compact/versions/0.31.0/aarch64-darwin/compactc \
contract/src/counter.compact \
contract/src/managed/counter
Output: a contract/src/managed/counter/ directory containing
contract/index.js (the runnable contract), keys/increment.verifier
+ keys/increment.prover (proving + verifying keys), and zkir/ (the
intermediate representation). After syncing into your app's assets as
runtime/<alias>-contract.js, this is what the Kuira SDK consumes via
MidnightContract.create(sdk.config) { contractJs = context.assets.open(...) }.
Quick sanity check with the mn CLI:
mn contract inspect --managed contract/src/managed/counter
Output should show:
Compiler: 0.31.0
Language: 0.23.0
Runtime: 0.16.0
Circuits
increment() — impure, proof
Witnesses
(none)
If those three versions don't match what you have on disk, you've got
a mismatch between the compactc you ran and the @midnight-ntwrk/compact-runtime
your project pins. Re-align using the toolchain table above.
For a full localnet verify loop (deploy → call → read state), follow
the starter's
contract/README.md § Verify against a localnet.
Wire it into an Android app¶
Once the contract is compiled, the Kuira SDK consumes it as Android
assets. See Deploy and call a Compact contract
for the integration walkthrough — the io.github.kuiralabs.contract
Gradle plugin (or a hand-rolled syncContractAssets Copy task), then
MidnightContract.create(sdk.config) { … } and .deploy() / .call().
Where to go from here¶
| Topic | Source |
|---|---|
| Working reference implementation | kuiralabs/kuira-starter-android — this counter contract end-to-end with Android UI |
| Witnesses + on-chain verification of off-chain compute | Calculator example (Midnight docs) |
| Multi-party patterns + commit-reveal | Battleship Simple example (Midnight docs) |
| Selective-disclosure + private state | Private Guest List + Private Reserve Auction (Midnight docs) |
| Token + asset transfer patterns | Token Transfers example (Midnight docs) |
| Voting / quorum patterns | Election example (Midnight docs) |
| Full Compact language reference | Midnight docs root (Midnight project) |
The Kuira SDK is happy to consume any Compact contract that compactc
produces — once you've moved beyond the counter, the Android-side
integration story doesn't change. Compile, drop the artifacts under
contract/src/managed/<name>/, point MidnightContract.create at
them, deploy, call.