example-leaderboard-dapp について
Midnight Network 向けに、Compact コントラクトと Next.js UI を備えたプライバシー保護型ランキング DApp を生成するスキルです。匿名またはカスタム名でスコアを表示でき、ゼロ知識証明でエントリーの所有権を検証します。
主な機能
- 01ランキングコントラクトと Next.js UI の生成
- 02匿名・公開・カスタム名に対応
- 03スコア送信とオンチェーン状態の読み取り
- 04ゼロ知識証明による所有権検証
- 05ウォレットなしでランキングを閲覧
活用シーン
- 01プライバシー保護型ゲームランキングの構築
- 02表示名を選んでスコアを送信
- 03エントリーの所有権をプレイヤーが証明
- 04訪問者にランキング結果を公開
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ kali-decoder/ midnight-skills --skill example-leaderboard-dappインストール後、Claude Code で/example-leaderboard-dappを入力して読み込まれたか確認します。
詳細
Midnight Network リーダーボード DApp
verifyOwnership(秘密を明かさずに、ownerCommitment(secretKey) が保存された ownerHash と一致することを確認する ZK 回路)を使って、プレイヤーがオンチェーンでスコアを登録し、エントリーの所有権を証明できる プライバシー保護型リーダーボードです。表示モードは 3 種類あります。
実行可能なテンプレート: templates/leaderboard-dapp/ をコピーして、Next.js プロジェクト一式(コントラクト + UI)を用意してください。1AM ウォレットをインストールした後、npm install && npm run compact && npm run sync:assets && npm run dev を実行します。
このスキルで生成されるもの:
contract/— Compact リーダーボード + TypeScript の witness + コンパイルスクリプトapp/leaderboard/— Next.js のクライアント UI(クリックゲーム、プライバシーモード選択、リーダーボードテーブル)lib/midnight.ts— ウォレットセッション + パッチ適用済みインデクサープロバイダー(references/midnight-session.mdまたはtemplates/leaderboard-dapp/lib/midnight.tsからコピーしてください)lib/leaderboard.ts— デプロイ、submitScore、verifyOwnership、台帳のデコードlib/display-name.ts— 匿名ハッシュのバイト列を生成された名前(「Crimson Tiger」など)にデコードpublic/zk/leaderboard/— コントラクトのビルドから同期された ZK 証明アセット
共通リファレンス(正式なプロバイダー + トラブルシューティング — プロンプト内で重複させないでください):
references/midnight-session.md—createConnectedSession、インデクサーのパッチ、デプロイ/呼び出しヘルパーreferences/gotchas.md— preprod でのデプロイ停止、GraphQL のoffset: null、ZK アセットのパスreferences/versions.json— 固定された@midnight-ntwrk/*のバージョン
主なリファレンス:
example-locker-dapp//templates/locker-dapp/— Next.js + 1AM のパターン、低レベルのデプロイ/呼び出しexample-payment-dapp/— プロバイダーの接続、インデクサーのポーリングcompact/—disclose()、witness、persistentHash、Map、Counter、assertsecurity/— オンチェーンで公開される情報と witness データ内のプライベート情報indexer/— ウォレット接続なしでコントラクトの状態を読み取る方法
主なアーキテクチャ上の注意点:
scoresは公開Map<Uint<64>, ScoreEntry>であり、誰でもインデクサーから読み取れますlocalSecretKeywitness はプライベート状態から 32 バイトの秘密鍵を返します。オンチェーンで開示してはいけませんownerCommitment(sk)は、ドメイン区切り文字列"leaderboard:owner:"を使ってpersistentHashを呼び出しますuseCustomNameブール値で、witness から渡された名前とハッシュベースの匿名表示名を切り替えますverifyOwnershipは証明専用の回路で、台帳への書き込みはありません。「これが自分のスコアだ」と証明するために使います- ページを更新した後も所有権の証明ができるよう、秘密鍵を
localStorageに保存します createUnprovenDeployTx+submitTxAsyncを使います。deployContract()は使わないでください(preprod では停止します)indexerPublicDataProviderは、パッチを適用したqueryContractStateでラップします(GraphQL のoffset: nullのバグ)- リーダーボードテーブルはウォレットなしでインデクサーから読み込めます。接続が必要なのはスコアの登録/検証のみです
ワークフロー
ユーザーを支援する際は、次の順序に従ってください。
- コントラクト —
leaderboard.compactのコンパイル + witness(localSecretKey、getCustomName) - プロバイダー —
createConnectedSession(references/midnight-session.mdから取得) - デプロイ — 低レベルのデプロイ、プライベート状態 + コントラクトアドレスの保存
- 状態の読み取り — インデクサー GraphQL →
ContractState.deserialize→ledger()→ エントリーのデコード - スコアの登録 —
submitScore(score, useCustomName)。必要に応じて事前にsetCustomNameを呼び出します - 所有権の検証 — 賞品の請求や「自分のもの」バッジに
verifyOwnership(entryId)を使います - UI — クリックゲーム、プライバシーモード選択、証明ボタン付きリーダーボードテーブル
1) プロジェクト構成
leaderboard-dapp/
├── package.json
├── next.config.mjs
├── lib/
│ ├── isomorphic-ws-fix.mjs
│ ├── midnight.ts # session, patched provider, hex helpers
│ ├── leaderboard.ts # deploy, submitScore, verifyOwnership, decode
│ └── display-name.ts # anonymous name generator
├── app/
│ ├── layout.tsx
│ └── leaderboard/
│ └── LeaderboardClient.tsx # game + wallet + leaderboard UI
├── contract/
│ ├── package.json
│ └── src/
│ ├── leaderboard.compact
│ ├── witnesses.ts
│ ├── index.ts
│ └── managed/leaderboard/ # compiler output (gitignored)
├── scripts/
│ └── sync-zk-assets.mjs # → public/zk/leaderboard/
└── public/zk/leaderboard/ # keys + zkir (gitignored until sync)
2) Compact コントラクト
contract/src/leaderboard.compact:
struct ScoreEntry {
score: Uint<64>,
displayName: Bytes<32>,
ownerHash: Bytes<32>
}
export ledger scores: Map<Uint<64>, ScoreEntry>;
export ledger nextId: Counter;
witness localSecretKey(): Bytes<32>;
witness getCustomName(): Bytes<32>;
export circuit ownerCommitment(sk: Bytes<32>): Bytes<32> {
return persistentHash<Vector<2, Bytes<32>>>([pad(32, "leaderboard:owner:"), sk]);
}
export circuit submitScore(score: Uint<64>, useCustomName: Boolean): [] { /* ... */ }
export circuit verifyOwnership(targetEntryId: Uint<64>): [] { /* ... */ }
プライバシー設計:
- 匿名モード(
useCustomName = false):displayName = persistentHash(sk)— UI が生成された名前にデコードします - 公開/カスタムモード(
useCustomName = true):displayName = getCustomName()witness — アプリがアドレスまたはユーザー名を渡します - 所有権:すべてのエントリーに
ownerHash = ownerCommitment(sk)を保存します。verifyOwnershipで再導出して照合します
コンパイル:
cd contract && npm run compact && cd ..
npm run sync:assets
3) 証人
contract/src/witnesses.ts:
export type LeaderboardPrivateState = { secretKey: Uint8Array };
export const witnesses = {
localSecretKey: (ctx) => [ctx.privateState, ctx.privateState.secretKey] as const,
getCustomName: (ctx) => [ctx.privateState, customName] as const,
};
export const setCustomName = (name: string) => { /* encode to Bytes<32> */ };
パブリック表示またはカスタム表示モードを使用する場合は、submitScore を呼び出す前に TypeScript で setCustomName(name) を呼び出してください。
4) TypeScript との連携
lib/leaderboard.ts は locker-dapp のパターンを踏襲しています:
| 関数 | 目的 |
|---|---|
getOrCreateSecretKey() | 32 バイトのシークレットを localStorage に保存します |
deployLeaderboard(session) | createUnprovenDeployTx + submitTxAsync |
submitScore(session, addr, score, customName?) | submitCallTxAsync → submitScore 回路 |
verifyOwnership(session, addr, entryId) | submitCallTxAsync → verifyOwnership 回路 |
fetchLeaderboardState(queryUrl, addr) | インデクサーへのポーリング + decodeLeaderboardState |
ZK アセットのパス:/zk/leaderboard(public/zk/leaderboard/ に同期されます)。
5) ブラウザー UI
LeaderboardClient.tsx のフロー:
- 接続 —
detectWallet()→wallet.connect('preprod')→createConnectedSession(api, ZK_PATH) - デプロイ/参加 — 新しいコントラクトをデプロイするか、既存の 64 文字の 16 進アドレスを貼り付けます
- プレイ — 10 秒間のクリックチャレンジ
- 送信 — Anonymous/Public/Custom を選択し、
submitScoreを呼び出します - リーダーボード — 15 秒ごとにインデクサーから自動更新します(読み取りにウォレットは不要です)
- 証明 —
verifyOwnership(entryId)を実行すると、UI でエントリーが自分のものとしてマークされます
インデクサーのみを使用した読み取りにより、観戦者はウォレットに接続せずにリーダーボードを確認できます。
6) 前提条件
| コンポーネント | バージョン |
|---|---|
| Compact コンパイラー | 0.31.0 |
| Compact ランタイム | 0.16.0 |
| Ledger | 8.0.3 |
| midnight-js | 4.0.4 |
| proof server | 8.0.3 |
docker run -d -p 6300:6300 midnightntwrk/proof-server:8.0.3 -- \
midnight-proof-server --network preprod
ウォレット:Preprod の 1AM 拡張機能、proof server http://localhost:6300、フォーセットから取得した tNIGHT + tDUST。
7) 互換性マトリックス
references/versions.json のバージョンを固定してください。アップグレード前に docs.midnight.network のサポートマトリックス と照合してください。
8) 本番環境に関する注意事項
- フロントエンドを Vercel/Netlify にデプロイし、ビルド手順で
public/zk/leaderboardアセットを設定します(npm run sync:assets) - 再訪プレイヤー向けに、デフォルトのコントラクトアドレスを env または
localStorageに保存します verifyOwnershipは台帳を変更しないため、「自分のものだと証明」する UX で繰り返し安全に使用できます- mainnet では、ウォレットの
getConfiguration()を使ってネットワーク ID、インデクサー URL、proof server の設定を更新します
クイックコマンド
cd templates/leaderboard-dapp
npm install
npm run compact
npm run sync:assets
npm run dev
http://localhost:3000 を開く → 1AM に接続 → Deploy New → プレイ → 送信 → リーダーボードを更新。