Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ef9abfa
chore(deps): bump Midnight SDK dependencies to Ledger v8
aagargoura Mar 25, 2026
3f26999
ci: update compact compiler to 0.30.0 for Ledger v8
aagargoura Mar 25, 2026
f6344b5
refactor: update ledger-v7 imports to ledger-v8 and onchain-runtime-v…
aagargoura Mar 25, 2026
1eac141
fix: adapt to DustWalletState API changes in Ledger v8
aagargoura Mar 25, 2026
c3f44be
fix: adapt to wallet SDK type changes in Ledger v8
aagargoura Mar 25, 2026
ff42d1e
fix: add required accountId to levelPrivateStateProvider
aagargoura Mar 25, 2026
b01c4c8
fix: implement new PrivateStateProvider interface methods for Ledger v8
aagargoura Mar 25, 2026
27da728
chore(deps): update Vite from 7.3.1 to 8.0.2
aagargoura Mar 25, 2026
167ed33
docs: update Compact compiler version to 0.30.0 in README
aagargoura Mar 25, 2026
678a513
chore: update proof server Docker image to 8.0.3
aagargoura Mar 25, 2026
3fb69d7
fix: call setContractAddress before accessing private state
aagargoura Mar 25, 2026
0bb1bec
fix: wait for funds after wallet sync completes
aagargoura Mar 25, 2026
afbf87c
fix: replace deprecated Node.js loader flags with tsx
aagargoura Mar 25, 2026
30d2fb3
fix: handle contract errors gracefully in CLI main loop and update pa…
aagargoura Mar 25, 2026
7866dfa
feat: add elapsed time logging for contract operations in CLI
aagargoura Mar 25, 2026
cc93c90
refactor: simplify Vite config for Vite 8 compatibility
aagargoura Mar 25, 2026
decb099
fix: resolve isomorphic-ws WebSocket import warnings in Vite build
aagargoura Mar 25, 2026
6d5a17c
docs: update README for Ledger v8 migration
aagargoura Mar 25, 2026
dfc2581
fix: resolve CI lint errors in bboard-ui
aagargoura Mar 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Setup Compact Compiler
uses: midnightntwrk/setup-compact-action@836895c8fffbbea6bd986af2b17e8941ff29d1f8
with:
compact-version: '0.29.0'
compact-version: '0.30.0'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Expand Down
29 changes: 11 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

This project is built on the [Midnight Network](https://midnight.network/).

[![Generic badge](https://img.shields.io/badge/Compact%20Compiler-0.29.0-1abc9c.svg)](https://shields.io/)
[![Generic badge](https://img.shields.io/badge/TypeScript-5.8.3-blue.svg)](https://shields.io/)
[![Generic badge](https://img.shields.io/badge/Compact%20Compiler-0.30.0-1abc9c.svg)](https://shields.io/)
[![Generic badge](https://img.shields.io/badge/TypeScript-5.9.3-blue.svg)](https://shields.io/)

A Midnight smart contract example demonstrating a simple one-item bulletin board with zero-knowledge proofs on testnet. Users can post a single message at a time, and only the message author can remove it.

Expand All @@ -30,7 +30,7 @@ You need Node.js (tested with current LTS):
node --version
```

Expected output: `v24.11.1` or higher.
Expected output: `v24.14.0` or higher.

If you get a lower version: [Install Node.js LTS](https://nodejs.org/).

Expand All @@ -42,45 +42,40 @@ The [proof server](https://docs.midnight.network/develop/tutorial/using/proof-se
docker --version
```

Expected output: `Docker version X.X.X`.
Expected output: `Docker version 29.3.0` or higher.

If Docker is not found: [Install Docker Desktop](https://docs.docker.com/desktop/). Make sure Docker Desktop is running.

### 3. Lace Wallet Extension (UI Only)

For the web interface, install the official Cardano Lace wallet extension on [Chrome Store](https://chromewebstore.google.com/detail/lace/gafhhkghbfjjkeiendhlofajokpaflmk) or the [Edge Store](https://microsoftedge.microsoft.com/addons/detail/lace/efeiemlfnahiidnjglmehaihacglceia) (tested with version 1.36.0).
For the web interface, install the official Cardano Lace wallet extension on [Chrome Store](https://chromewebstore.google.com/detail/lace/gafhhkghbfjjkeiendhlofajokpaflmk) or the [Edge Store](https://microsoftedge.microsoft.com/addons/detail/lace/efeiemlfnahiidnjglmehaihacglceia) (tested with version 1.36.0 or higher).

After installing, set up the Midnight wallet:

1. Open the Lace wallet extension and go to **Settings**
2. Enable the **Beta Program** to unlock Midnight network support
3. Create a **new wallet** — Midnight will appear as a network option
4. Go to **Settings > Midnight** and set **Network** to **Preprod**
4. Go to **Settings > Midnight** and set **Network** to **Preprod** or **Preview**
5. Set **Proof server** to **Local (http://localhost:6300)** — this must point to your local proof server started via Docker
6. Click **Save configuration**
7. Fund your wallet with tNIGHT tokens from the [Preprod Faucet](https://faucet.preprod.midnight.network/)
7. Fund your wallet with tNIGHT tokens from the [Preprod Faucet](https://faucet.preprod.midnight.network/) or [Preview Faucet](https://faucet.preview.midnight.network/)
8. Go to **Tokens** in the wallet, click **Generate tDUST**, and confirm the transaction — tDUST tokens are required to pay transaction fees on preprod

## Setup Instructions

### Install Project Dependencies

```bash
# Install root dependencies
# Install all workspace dependencies from the root
npm install

# Install API dependencies
cd api && npm install && cd ..

# Install contract dependencies and compile
cd contract && npm install
```

### Compile the Smart Contract

The Compact compiler (v0.29.0) generates TypeScript bindings and zero-knowledge circuits from the smart contract source code:
The Compact compiler (v0.30.0) generates TypeScript bindings and zero-knowledge circuits from the smart contract source code:

```bash
cd contract
npm run compact # Compiles the Compact contract
npm run build # Copies compiled files to dist/
cd ..
Expand All @@ -105,7 +100,6 @@ Compiling 2 circuits:

```bash
cd bboard-cli
npm install
npm run build
cd ..
```
Expand All @@ -116,7 +110,6 @@ Only needed if you want to use the web interface:

```bash
cd bboard-ui
npm install
npm run build
cd ..
```
Expand Down Expand Up @@ -153,7 +146,7 @@ Using unshielded address: mn_addr_preprod1hdvtst70zfgd8wvh7l8ppp7mcrxnjn56wc5hlx
Before deploying contracts, you need testnet tokens.

1. Copy your wallet address from the output above
2. Visit the [faucet](https://faucet.preprod.midnight.network/)
2. Visit the [Preprod faucet](https://faucet.preprod.midnight.network/) or [Preview faucet](https://faucet.preview.midnight.network/)
3. Paste your address and request funds
4. Wait for the CLI to detect the funds (takes 2-3 minutes)

Expand Down
3 changes: 2 additions & 1 deletion api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export class BBoardAPI implements DeployedBBoardAPI {
const deployedBBoardContract = await deployContract(providers, {
compiledContract: CompiledBBoardContractContract,
privateStateId: bboardPrivateStateKey,
initialPrivateState: await BBoardAPI.getPrivateState(providers),
initialPrivateState: createBBoardPrivateState(utils.randomBytes(32)),
});

logger?.trace({
Expand Down Expand Up @@ -212,6 +212,7 @@ export class BBoardAPI implements DeployedBBoardAPI {
},
});

providers.privateStateProvider.setContractAddress(contractAddress);
const deployedBBoardContract = await findDeployedContract<BBoardContract>(providers, {
contractAddress,
compiledContract: CompiledBBoardContractContract,
Expand Down
6 changes: 3 additions & 3 deletions bboard-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
"ci": "npm run typecheck && npm run lint && npm run build",
"lint": "eslint src",
"prepack": "npm run build",
"standalone": "node --experimental-specifier-resolution=node --loader ts-node/esm src/launcher/standalone.ts",
"preview-remote": "node --experimental-specifier-resolution=node --loader ts-node/esm src/launcher/preview.ts",
"preprod-remote": "node --experimental-specifier-resolution=node --loader ts-node/esm src/launcher/preprod.ts",
"standalone": "node --import tsx src/launcher/standalone.ts",
"preview-remote": "node --import tsx src/launcher/preview.ts",
"preprod-remote": "node --import tsx src/launcher/preprod.ts",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion bboard-cli/proof-server-local.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
services:
proof-server:
image: midnightntwrk/proof-server:8.0.2
image: midnightntwrk/proof-server:8.0.3
command: ['midnight-proof-server', '-v']
container_name: "proof-server-local"
ports:
Expand Down
2 changes: 1 addition & 1 deletion bboard-cli/proof-server.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
services:
proof-server:
image: midnightntwrk/proof-server:8.0.2
image: midnightntwrk/proof-server:8.0.3
command: ['midnight-proof-server', '-v']
container_name: "proof-server_$TESTCONTAINERS_UID"
ports:
Expand Down
15 changes: 9 additions & 6 deletions bboard-cli/src/generate-dust.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
// import { webcrypto } from 'crypto';

import { type WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade';
import { UtxoWithMeta as UtxoWithMetaDust } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
import { createKeystore, UnshieldedWalletState } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
import { Logger } from 'pino';
import { HDWallet, Roles } from '@midnight-ntwrk/wallet-sdk-hd';
Expand Down Expand Up @@ -51,9 +50,13 @@ export const generateDust = async (
const dustState = await walletFacade.dust.waitForSyncedState();
const networkId = getNetworkId();
const unshieldedKeystore = createKeystore(getUnshieldedSeed(walletSeed), networkId);
const utxos: UtxoWithMetaDust[] = unshieldedState.availableCoins
const utxos = unshieldedState.availableCoins
.filter((coin) => !coin.meta.registeredForDustGeneration)
.map((utxo) => ({ ...utxo.utxo, ctime: new Date(utxo.meta.ctime) }));
.map((coin) => ({
...coin.utxo,
ctime: new Date(coin.meta.ctime),
registeredForDustGeneration: coin.meta.registeredForDustGeneration,
}));

if (utxos.length === 0) {
logger.info('No unregistered UTXOs found for dust generation.');
Expand All @@ -67,7 +70,7 @@ export const generateDust = async (
ttlIn10min,
utxos,
unshieldedKeystore.getPublicKey(),
dustState.dustAddress,
dustState.address,
);

const intent = registerForDustTransaction.intents?.get(1);
Expand All @@ -80,8 +83,8 @@ export const generateDust = async (

const dustBalance = await rx.firstValueFrom(
walletFacade.state().pipe(
rx.filter((s) => s.dust.walletBalance(new Date()) > 0n),
rx.map((s) => s.dust.walletBalance(new Date())),
rx.filter((s) => s.dust.balance(new Date()) > 0n),
rx.map((s) => s.dust.balance(new Date())),
),
);
logger.info(`Dust generation transaction submitted with txId: ${txId}`);
Expand Down
45 changes: 36 additions & 9 deletions bboard-cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
type BBoardProviders,
type DeployedBBoardContract,
type PrivateStateId,
} from '../../api/src/index';
} from '../../api/src/index.js';
import { type WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade';
import { ledger, type Ledger, State } from '../../contract/src/managed/bboard/contract/index.js';
import { NodeZkConfigProvider } from '@midnight-ntwrk/midnight-js-node-zk-config-provider';
Expand All @@ -44,15 +44,31 @@ import { type ContractAddress } from '@midnight-ntwrk/compact-runtime';
import { assertIsContractAddress, toHex } from '@midnight-ntwrk/midnight-js-utils';
import { TestEnvironment } from '@midnight-ntwrk/testkit-js';
import { MidnightWalletProvider } from './midnight-wallet-provider';
import { randomBytes } from '../../api/src/utils';
import { unshieldedToken } from '@midnight-ntwrk/ledger-v7';
import { randomBytes } from '../../api/src/utils/index.js';
import { unshieldedToken } from '@midnight-ntwrk/ledger-v8';
import { syncWallet, waitForUnshieldedFunds } from './wallet-utils';
import { generateDust } from './generate-dust';
import { BBoardPrivateState } from '@midnight-ntwrk/bboard-contract';

// @ts-expect-error: It's needed to enable WebSocket usage through apollo
globalThis.WebSocket = WebSocket;

const timed = async <T>(logger: Logger, label: string, fn: () => Promise<T>): Promise<T> => {
const start = Date.now();
const timer = setInterval(() => {
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
logger.info(`${label}... ${elapsed}s`);
}, 5000);
try {
const result = await fn();
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
logger.info(`${label} completed in ${elapsed}s`);
return result;
} finally {
clearInterval(timer);
}
};

/* **********************************************************************
* getBBoardLedgerState: a helper that queries the current state of
* the data on the ledger, for a specific bulletin board contract.
Expand Down Expand Up @@ -94,13 +110,15 @@ const deployOrJoin = async (providers: BBoardProviders, rli: Interface, logger:
const choice = await rli.question(DEPLOY_OR_JOIN_QUESTION);
switch (choice) {
case '1':
api = await BBoardAPI.deploy(providers, logger);
api = await timed(logger, 'Deploying contract', () => BBoardAPI.deploy(providers, logger));
logger.info(`Deployed contract at address: ${api.deployedContractAddress}`);
return api;
case '2':
api = await BBoardAPI.join(providers, await rli.question('What is the contract address (in hex)? '), logger);
case '2': {
const addr = await rli.question('What is the contract address (in hex)? ');
api = await timed(logger, 'Joining contract', () => BBoardAPI.join(providers, addr, logger));
logger.info(`Joined contract at address: ${api.deployedContractAddress}`);
return api;
}
case '3':
logger.info('Exiting...');
return null;
Expand Down Expand Up @@ -199,11 +217,19 @@ const mainLoop = async (providers: BBoardProviders, rli: Interface, logger: Logg
switch (choice) {
case '1': {
const message = await rli.question(`What message do you want to post? `);
await bboardApi.post(message);
try {
await timed(logger, 'Posting message', () => bboardApi.post(message));
} catch (e) {
logError(logger, e);
}
break;
}
case '2':
await bboardApi.takeDown();
try {
await timed(logger, 'Taking down message', () => bboardApi.takeDown());
} catch (e) {
logError(logger, e);
}
break;
case '3':
await displayLedgerState(providers, bboardApi.deployedContract, logger);
Expand Down Expand Up @@ -318,8 +344,9 @@ export const run = async (config: Config, testEnv: TestEnvironment, logger: Logg
privateStateStoreName: config.privateStateStoreName,
signingKeyStoreName: `${config.privateStateStoreName}-signing-keys`,
privateStoragePasswordProvider: () => {
return 'key-just-for-testing-here!';
return 'Key-just-for-testing-here!1';
},
accountId: 'bboard-default',
}),
publicDataProvider: indexerPublicDataProvider(envConfiguration.indexer, envConfiguration.indexerWS),
zkConfigProvider: zkConfigProvider,
Expand Down
4 changes: 2 additions & 2 deletions bboard-cli/src/midnight-wallet-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
type FinalizedTransaction,
LedgerParameters,
ZswapSecretKeys,
} from '@midnight-ntwrk/ledger-v7';
} from '@midnight-ntwrk/ledger-v8';
import { type MidnightProvider, UnboundTransaction, type WalletProvider } from '@midnight-ntwrk/midnight-js-types';
import { ttlOneHour } from '@midnight-ntwrk/midnight-js-utils';
import { type WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade';
Expand Down Expand Up @@ -109,7 +109,7 @@ export class MidnightWalletProvider implements MidnightProvider, WalletProvider

const initialState = await getInitialShieldedState(logger, wallet.shielded);
logger.info(
`Your wallet seed is: ${seeds.masterSeed} and your address is: ${initialState.address.coinPublicKeyString()}`,
`Your wallet seed is: ${seeds.masterSeed} and your address is: ${initialState.address.coinPublicKey.toHexString()}`,
);

return new MidnightWalletProvider(
Expand Down
Loading