A Midnight smart contract example demonstrating a simple on-chain counter. The counter uses public ledger state and serves as a starting point for building Midnight DApps.
Supports three network targets:
| Network | Description | Command |
|---|---|---|
| Preprod | Public testnet (recommended for getting started) | npm run preprod-ps |
| Preview | Public preview testnet | npm run preview-ps |
| Standalone | Fully local (node + indexer + proof server via Docker) | npm run standalone |
example-counter/
├── contract/ # Smart contract (Compact language)
│ ├── src/counter.compact # The counter smart contract
│ └── src/test/ # Contract unit tests
└── counter-cli/ # Command-line interface
├── src/ # CLI implementation
├── proof-server.yml # Proof server Docker config (preprod/preview)
├── standalone.yml # Full local stack Docker config
└── standalone.env.example # Default env vars for standalone mode
- Node.js v22.15+ —
node --versionto check - Docker with
docker compose— used for the local proof server
The Compact devtools manage and invoke the Compact toolchain (compiler, formatter, fixup tool, etc.).
Install the devtools and toolchain:
# Install the Compact devtools
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/midnightntwrk/compact/releases/latest/download/compact-installer.sh | sh
# Add to PATH
source $HOME/.local/bin/env
# Install the toolchain version used by this project
compact update 0.28.0
# Verify
compact compile --versionIf you already have the devtools installed, run
compact self updateto get the latest version. If you encounter issues,compact cleanwill reset your.compactdirectory.
npm installcd contract
npm run compact
npm run build
npm run testExpected output from npm run compact:
Compiling 1 circuits:
circuit "increment" (k=10, rows=29)
The first run may download zero-knowledge parameters (~500MB). This is a one-time download.
Option A — auto-start proof server (recommended):
cd counter-cli
npm run preprod-psThis pulls the proof server Docker image, starts it, and launches the CLI.
Mac ARM (Apple Silicon) users: If the proof server hangs, enable Docker VMM in Docker Desktop: Settings → General → "Virtual Machine Options" → select Docker VMM. Restart Docker after changing.
Option B — manual proof server (if you prefer to manage it yourself):
Start the proof server in a separate terminal:
cd counter-cli
docker compose -f proof-server.yml upWait for it to start — you should see:
INFO actix_server::server: starting service: "actix-web-service-0.0.0.0:6300", workers: 24, listening on: 0.0.0.0:6300
Then in another terminal:
cd counter-cli
npm run preprodThe CLI uses a headless wallet (separate from browser wallets like Lace).
- Choose option [1] to create a new wallet
- The system generates a wallet seed and displays your addresses:
──────────────────────────────────────────────────────────────
Wallet Overview Network: preprod
──────────────────────────────────────────────────────────────
Seed: <64-character hex string>
Unshielded Address (send tNight here):
mn_addr_preprod1...
──────────────────────────────────────────────────────────────
Save the seed — you'll need it to restore the wallet later.
- Copy your unshielded address (
mn_addr_preprod1...) from the output - Visit the Preprod faucet
- Paste your address and request tNight tokens
- The CLI will detect incoming funds automatically
After receiving tNight, the CLI automatically registers your NIGHT UTXOs for dust generation. DUST is the non-transferable fee resource required for all transactions on Midnight.
The CLI shows progress:
✓ Registering 1 NIGHT UTXO(s) for dust generation
✓ Waiting for dust to generate
✓ Configuring providers
Once DUST is available, the contract menu appears with your balance:
──────────────────────────────────────────────────────────────
Contract Actions DUST: 405,083,000,000,000
──────────────────────────────────────────────────────────────
[1] Deploy a new counter contract
[2] Join an existing counter contract
[3] Monitor DUST balance
[4] Exit
- Choose option [1] to deploy
- Wait for proving, balancing, and submission
- The contract address is displayed on success:
✓ Deploying counter contract
Contract deployed at: <contract address>
Save the contract address to rejoin the contract in future sessions.
After deployment, the counter menu appears:
- [1] Increment counter — submits a transaction to increment the on-chain counter
- [2] Display current counter value — queries the blockchain for the current value
- [3] Exit
Each increment creates a real transaction on Midnight Preprod.
Next time you run the DApp:
- Choose option [2] to restore wallet from seed
- Enter your saved seed
- Wait for sync and DUST generation
- Choose option [2] to join existing contract
- Enter your saved contract address
The contract menu includes a DUST monitor (option [3]) that shows a live-updating display:
[10:20:03 PM] DUST: 471,219,000,000,000 (1 coins, 0 pending) | NIGHT: 1 UTXOs, 1 registered | ✓ ready to deploy
This is useful for:
- Checking if you have enough DUST before deploying
- Monitoring DUST generation after registering NIGHT
- Diagnosing issues where DUST appears locked (pending coins from failed transactions)
| Issue | Solution |
|---|---|
compact: command not found |
Run source $HOME/.local/bin/env to add it to your PATH. |
connect ECONNREFUSED 127.0.0.1:6300 |
Start the proof server: cd counter-cli && docker compose -f proof-server.yml up |
| Proof server hangs on Mac ARM (Apple Silicon) | In Docker Desktop: Settings → General → "Virtual Machine Options" → select Docker VMM. Restart Docker after changing. |
Failed to clone intent during deploy |
Wallet SDK signing bug — already worked around in this codebase. If you see this, ensure you're running the latest code. See MIGRATION_GUIDE.md Section 4. |
| DUST balance drops to 0 after failed deploy | Known wallet SDK issue. Restart the DApp to release locked DUST coins. |
| Wallet shows 0 balance after faucet | Wait for sync to complete. If still 0, check that you sent to the correct unshielded address. |
| Could not find a working container runtime strategy | Docker isn't running. Start Docker and try again. |
| Tests fail with "Cannot find module" | Build the contract first: cd contract && npm run compact && npm run build |
| Node.js warnings about experimental features | Normal — these don't affect functionality. |
- Preprod Faucet — Get preprod tNight tokens
- Midnight Documentation — Developer guide
- Compact Language Guide — Smart contract language reference
- Migration Guide — Detailed guide for migrating to Preprod with midnight-js 3.0.0 and wallet-sdk-facade 1.0.0