📘 Academy原文準拠 | Phase 3 · Unit 3 · Lesson 3.3 Bulletin Board DApp — The CLI and Deployment 内容に忠実な日本語版です。原文(英語)・図・動画は公式 Academy(外部リンク・別タブで開きます)を正本に。
レッスン 3.1 では契約(contract)そのものを、レッスン 3.2 ではそれを包む API レイヤーを学びました。
このレッスンでは最後のピース、CLI を見ていきます。CLI はすべてを配線してウォレットを作り、プロバイダ(providers)を組み立て、コマンドラインからけいじ板(bulletin board)を操作できるようにするものです。
bboard-cli/ パッケージは、レッスン 1.3〜2.3 で学んだインフラの考え方が、いよいよ本物になる場所です。ウォレット・DUST 生成・プロバイダ構築、そして契約をデプロイしてサーキットを呼ぶ対話ループが、ここで現実になります。
CLI のファイル構成
エントリーポイント
各 launcher(起動ファイル)は薄い1枚で、config・logger を作って run() を呼ぶだけです。
Preprod の launcher:
standalone の launcher も同じ形で、ちがいは StandaloneConfig(ローカルの Docker コンテナを立ち上げる)を使う点だけ。preview の launcher は PreviewRemoteConfig を使います。CLI は npm スクリプトで起動します。
これらのスクリプトは、対応する launcher を ts-node 経由で走らせます。
設定(configuration)
どの config クラスも Config インターフェースを実装します。
各フィールドの意味は次のとおりです。
privateStateStoreName:暗号化された秘密鍵を保存する LevelDB データベースの名前。どの config でも'bboard-private-state'に設定されています。zkConfigPath:コンパイル済みの ZK サーキット資産(assets)へのパス。contract/src/managed/bboardを指します。requestFaucetTokens:faucet から tNight を自動で要求するかどうか。リモートのネットワークではfalse(faucet API が 500 エラーを返すため)にしてあり、ユーザーは手動で資金を入れます。generateDust:DUST 登録フローを走らせるかどうか。リモートではtrue、standalone ではfalse(standalone のネットワークは DUST が要らない)。
PreprodRemoteConfig はネットワーク ID を設定し、Preprod のエンドポイントで構成したテスト環境を返します。
環境設定(environment configuration)が、各ネットワークの URL を提供します。
standalone モードでは、これらの URL は compose.yml で定義した Docker コンテナを指します。
3つのコンテナ ── proof server、standalone の indexer、dev モードの Midnight ノード ── が立ち上がります。standalone の config は、このローカルネットワーク全体を自動でそろえてくれます。
ウォレットプロバイダ(wallet provider)
このクラスは、SDK が要求する WalletProvider と MidnightProvider の両インターフェースを実装します。
ここで大事なメソッドは2つ、balanceTx と submitTx です。これらは、レッスン 25 で学んだトランザクションの流れの中で SDK が呼びます。
balanceTxは、unbound なトランザクション(手数料がまだ付いていない、サーキットの出力)を受け取り、wallet.balanceUnboundTransaction()を呼んで shielded 鍵と dust 鍵で DUST 手数料を足し、最後に recipe を finalize します。ttl(time-to-live、有効期限)は既定で1時間です。submitTxは、finalize 済みのトランザクションをウォレット経由でネットワークに送ります。
build という static メソッドは、testkit の FluentWalletBuilder を使ってウォレットを構築します。引数の seed は任意で、与えれば同じウォレットを決定的に作り直せます。与えなければランダムな seed を生成します。
builder は seed から3種類の鍵を導出します ── master seed、shielded 鍵(ZSwap の操作用)、dust 鍵(手数料の管理用)です。
メインドライバ(main driver)
これはいちばん大きなファイルです。リポジトリにある順番のまま、節ごとに見ていきましょう。
グローバル WebSocket の準備とヘルパー関数
indexer は、リアルタイムの状態更新に WebSocket 接続を使います。Node.js にはネイティブの WebSocket グローバルが無いので、CLI は ws パッケージでポリフィル(穴埋め)します。
台帳(ledger)状態の問い合わせ
これは特定の契約について、現在のオンチェーン状態を indexer に問い合わせ、型付きの Ledger オブジェクトに変換します。メニューの「Display ledger state」で使われます。
デプロイか参加か(Deploy or join)
これはウォレットの準備が終わった後、ユーザーが最初に目にする画面です。
- 選択肢 1 は
BBoardAPI.deploy()を呼びます ── レッスン 29 の完全なデプロイフローです。 - 選択肢 2 は契約アドレスを尋ねてから
BBoardAPI.join()を呼びます。
どちらも BBoardAPI のインスタンスを返し、それをメインループが使います。
表示用の関数
CLI には、契約の状態を別々の見方で表示する関数が3つあります。
displayLedgerState は、ネットワーク上の誰もが見られるものを表示します ── 板の状態、メッセージ、sequence、そして owner の公開鍵(hex 表記)。これは完全に透明な(fully transparent)ビューです。
displayPrivateState は、この DApp インスタンスだけが知っているもの ── 秘密鍵 ── を表示します。これは完全に非公開な(fully private)ビューです。
displayDerivedState は、state$ という observable(レッスン 29)から来る合わさったビューを表示します。
生の公開鍵のかわりに、ここでは 'you'(あなた)か 'not you'(あなたではない)を表示します。これは isOwner フィールドで、導出した公開鍵をオンチェーンの owner と比べて計算したものです。
メインループ(main loop)
メインループは bboardApi.state$ を購読(subscribe)し、最新の状態をローカル変数に保持します。
台帳がオンチェーンで変わるたびに、observer の next コールバックが currentState を更新します。メニューの各選択肢は、API と表示用関数にそのまま対応しています。
finally ブロックは、終了するときに必ず購読を後始末する(クリーンアップする)ことを保証します。
ウォレットの構築(Wallet building)
standalone モードでは、ウォレットはハードコードされた genesis seed を使います。これはローカルの dev ノードの genesis ブロックで mint されたトークンにアクセスできます。リモートのネットワークでは、ユーザーは新しいウォレット(ランダムな32バイトの seed)を作るか、保存しておいた seed から復元できます。
run 関数
これが起動のフル手順です。順にたどってみましょう。
- 環境を起動:
testEnv.start()が、Docker コンテナを起動する(standalone)か、リモートのエンドポイントに接続する(preprod / preview)。 - ウォレットを構築:seed を尋ねるか生成する。
- ウォレットプロバイダを構築:
MidnightWalletProvider.build()が、FluentWalletBuilderを使って seed から3つのウォレットシステム(shielded・unshielded・dust)を作る。 - ウォレットを開始:
walletProvider.start()がネットワークとの同期を始める。 - 資金を待つ:
waitForUnshieldedFunds()が tNight 残高を確認する。ゼロなら、ウォレットの同期を待つ(リモートではユーザーが faucet で外から入金する必要がある)。 - DUST を生成:リモートでは
generateDust()が unshielded UTXO を dust 生成のために登録し、登録トランザクションを送信し、DUST がたまるのを待つ。 - プロバイダを組み立てる:ここで
BBoardProvidersが要求する6つのプロバイダを組み立てます。privateStateProvider:LevelDB を裏に持つ暗号化ストレージ(テスト用パスワード付き)publicDataProvider:indexer。オンチェーン状態の問い合わせと観測にzkConfigProvider:コンパイル済みの ZK サーキットファイルを指すproofProvider:proof server の HTTP クライアント(ZK config を使う)walletProvider:MidnightWalletProvider(balanceTxとsubmitTx用)midnightProvider:これも同じウォレットプロバイダ(coin 公開鍵と暗号化公開鍵用)
- メインループに入る:
mainLoop()に処理を渡し、デプロイ/参加と対話メニューを扱わせる。
入れ子になった finally ブロックは、プロセスがどう終わっても確実にきれいに後始末することを保証します ── readline インターフェースを閉じ、ウォレットを止め、テスト環境をシャットダウンします。
実際の起動フロー
npm run preprod-remote を実行すると、ステップごとに次のことが起こります。
- Preprod の launcher が config・logger・テスト環境を作る
run()が環境を起動する(Preprod のエンドポイントに接続)- 新しいウォレットを作るか、seed から復元するかを選ぶ
- ウォレットがネットワークと同期し、shielded アドレスと seed が表示される
- Preprod の faucet ウェブサイトから unshielded アドレスに入金する
- CLI が入ってきた tNight を検知し、DUST 生成のために UTXO を登録する
- DUST がたまり、契約メニューが表示される
- 新しい板をデプロイするか、既存の板に参加する
- メッセージを投稿し、取り下げ、状態を確認する
各ステップには、ウォレット同期・faucet トランザクション・DUST 登録・契約デプロイ・ZK 証明の生成を伴うサーキット呼び出しといった、本物のネットワークとのやり取りが含まれます。
次に向けて(原文の “What’s next”)
これで example-bboard リポジトリのすべてのファイルを理解できました。レッスン 4.1 では、アプリ全体をエンドツーエンドで動かし、デプロイ → 投稿 → 取り下げ、そして ledger・private・derived の各ビューで状態が変わっていくようすを、各段階で見ていきます。
開発者として押さえる点
- CLI の構造は launcher(薄い起動ファイル)→
run()→mainLoop()という流れ。launcher は config・logger・テスト環境を作ってrun()を呼ぶだけ - 動作モードは config で切り替わる:
StandaloneConfig(ローカル Docker、DUST 不要、genesis seed)/PreviewRemoteConfig/PreprodRemoteConfig(リモート、手動入金、generateDust = true) MidnightWalletProviderの核はbalanceTx(DUST 手数料を足して finalize)とsubmitTx(送信)。buildは seed から shielded・dust の鍵を導出するrun()は6つのプロバイダ(privateStateProvider/publicDataProvider/zkConfigProvider/proofProvider/walletProvider/midnightProvider)を組み立ててBBoardProvidersを完成させる- 状態の見方は3種:
displayLedgerState(誰でも見える)/displayPrivateState(このDAppだけ・秘密鍵)/displayDerivedState(state$の合成ビュー・isOwnerでyou/not you) - 入れ子の
finallyで readline・ウォレット・テスト環境を必ず後始末する。Node には WebSocket が無いのでwsでポリフィルする
やさしい版・公式へ
- やさしい版:けいじ板チュートリアル
- 公式:Academy Courses(外部リンク・別タブで開きます)(Phase 3 / Unit 3 / 3.3)
- 関連 docs:Midnight Developer Docs(外部リンク・別タブで開きます) / example-bboard とチュートリアル(外部リンク・別タブで開きます)
つぎに読むページ
➡️ 原文準拠コースの入口へ戻る。このコースについて(次のレッスンは順次追加します)