mcpField
E
example-leaderboard-dapp

example-leaderboard-dapp

github.com/kali-decoder開発18121 回インストール1 日前に更新
リポジトリ

example-leaderboard-dapp について

Midnight Network 向けに、Compact コントラクトと Next.js UI を備えたプライバシー保護型ランキング DApp を生成するスキルです。匿名またはカスタム名でスコアを表示でき、ゼロ知識証明でエントリーの所有権を検証します。

主な機能

  1. 01ランキングコントラクトと Next.js UI の生成
  2. 02匿名・公開・カスタム名に対応
  3. 03スコア送信とオンチェーン状態の読み取り
  4. 04ゼロ知識証明による所有権検証
  5. 05ウォレットなしでランキングを閲覧

活用シーン

  1. 01プライバシー保護型ゲームランキングの構築
  2. 02表示名を選んでスコアを送信
  3. 03エントリーの所有権をプレイヤーが証明
  4. 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、assert
  • security/ — オンチェーンで公開される情報と witness データ内のプライベート情報
  • indexer/ — ウォレット接続なしでコントラクトの状態を読み取る方法

主なアーキテクチャ上の注意点:

  • scores は公開 Map<Uint<64>, ScoreEntry> であり、誰でもインデクサーから読み取れます
  • localSecretKey witness はプライベート状態から 32 バイトの秘密鍵を返します。オンチェーンで開示してはいけません
  • ownerCommitment(sk) は、ドメイン区切り文字列 "leaderboard:owner:" を使って persistentHash を呼び出します
  • useCustomName ブール値で、witness から渡された名前とハッシュベースの匿名表示名を切り替えます
  • verifyOwnership は証明専用の回路で、台帳への書き込みはありません。「これが自分のスコアだ」と証明するために使います
  • ページを更新した後も所有権の証明ができるよう、秘密鍵を localStorage に保存します
  • createUnprovenDeployTx + submitTxAsync を使います。deployContract() は使わないでください(preprod では停止します)
  • indexerPublicDataProvider は、パッチを適用した queryContractState でラップします(GraphQL の offset: null のバグ)
  • リーダーボードテーブルはウォレットなしでインデクサーから読み込めます。接続が必要なのはスコアの登録/検証のみです

ワークフロー

ユーザーを支援する際は、次の順序に従ってください。

  1. コントラクト — leaderboard.compact のコンパイル + witness(localSecretKey、getCustomName)
  2. プロバイダー — createConnectedSession(references/midnight-session.md から取得)
  3. デプロイ — 低レベルのデプロイ、プライベート状態 + コントラクトアドレスの保存
  4. 状態の読み取り — インデクサー GraphQL → ContractState.deserialize → ledger() → エントリーのデコード
  5. スコアの登録 — submitScore(score, useCustomName)。必要に応じて事前に setCustomName を呼び出します
  6. 所有権の検証 — 賞品の請求や「自分のもの」バッジに verifyOwnership(entryId) を使います
  7. 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 のフロー:

  1. 接続 — detectWallet() → wallet.connect('preprod') → createConnectedSession(api, ZK_PATH)
  2. デプロイ/参加 — 新しいコントラクトをデプロイするか、既存の 64 文字の 16 進アドレスを貼り付けます
  3. プレイ — 10 秒間のクリックチャレンジ
  4. 送信 — Anonymous/Public/Custom を選択し、submitScore を呼び出します
  5. リーダーボード — 15 秒ごとにインデクサーから自動更新します(読み取りにウォレットは不要です)
  6. 証明 — verifyOwnership(entryId) を実行すると、UI でエントリーが自分のものとしてマークされます

インデクサーのみを使用した読み取りにより、観戦者はウォレットに接続せずにリーダーボードを確認できます。


6) 前提条件

コンポーネントバージョン
Compact コンパイラー0.31.0
Compact ランタイム0.16.0
Ledger8.0.3
midnight-js4.0.4
proof server8.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 → プレイ → 送信 → リーダーボードを更新。

コメント