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 Compact and Midnight themselves — the language, witnesses, ZK proof
construction, ledger types beyond Counter, the on-chain model — Kuira is
not where you learn them. Start at the Midnight documentation,
then its contract examples
(calculator, election, battleship, private-guest-list, …) for focused
tutorials on each Compact pattern. We assume that familiarity here.
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 by name viacontract.ledger().getUint64("count"), or as a typed field on the generated contract facade).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 | installed under ~/.compact/versions/0.31.0/<os-arch>/, driven via the compact wrapper |
| 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 |
On-chain runtime ships with the node
The Compact runtime is also embedded in the Midnight node.
midnight-node:0.22.5 — the mn localnet default — ships on-chain
Compact runtime 0.16.0, the runtime this
contract targets. Older nodes (≤ 0.22.3) run runtime 0.15.0 and
reject a 0.16.0 contract with a
runtime-version mismatch. Stick with the default node and you're
aligned.
The toolchain self-introspects:
compact list # installed compilers + the default
compact compile +0.31.0 --language-version # → 0.23.0
compact compile +0.31.0 --runtime-version # → 0.16.0
The language and runtime flags hang off compact compile (the
subcommand that owns them) — there is no bare compactc --runtime-version.
When upgrading, run the three commands on the new toolchain first to
discover the matching triple before editing any pragma. The starter's
contract/README.md
documents the upgrade recipe step by step.
Set up the contract project¶
The compile + runtime live in a small npm project alongside your app.
The fastest path is to copy the starter's
contract/
directory — it already pins the toolchain — and install it:
cd contract
npm install
This pulls in @midnight-ntwrk/compact-runtime (0.16.0,
the JS runtime compiled contracts call into) and makes the
compact / compactc devtools available, so the compile step below
has everything it needs. The starter's contract/package.json also
wires up npm run compile / npm run inspect shortcuts for the
commands you'll run by hand here.
Compile + verify¶
From your project root:
mkdir -p contract/src/managed
compact compile +0.31.0 contract/src/counter.compact contract/src/managed/counter
Pin the compiler version with +0.31.0
compact auto-selects the right binary for your platform, but it runs the
toolchain's default compiler — which may be older than your
pragma language_version. Compiling without the + pin fails with
language version X.Y.Z mismatch; +0.31.0 forces the
compiler that matches the pragma. (The engines.compactc field in
package.json is informational — it is not enforced, and npm run compile
must carry the same + pin.) Run compact list to see installed versions
and the default.
The binary directory varies by OS/arch (aarch64-darwin, x86_64-darwin,
x86_64-linux, …) — never hardcode a path to it. On Windows, run under WSL2.
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
The compile also writes the machine-readable
contract/src/managed/counter/compiler/contract-info.json, which
records the exact triple the artifacts were built with plus the
circuit shapes. For the counter it looks like this (trimmed):
{
"compiler-version": "0.31.0",
"language-version": "0.23.0",
"runtime-version": "0.16.0",
"circuits": [
{ "name": "increment", "pure": false, "proof": true }
],
"witnesses": [],
"ledger": [
{ "name": "count", "exported": true, "storage": "Counter" }
]
}
The exact field set evolves with the compiler, so treat the block
above as illustrative, not a byte-for-byte match — read your own
file rather than diffing against this. What matters: the three
*-version values are the triple your artifacts were built with. If
they don't line up with the @midnight-ntwrk/compact-runtime and
pragma language_version 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.