Skip to main content

実践・白紙練習:ゼロから手打ち

Counter 白紙練習 第二弾:CLI からローカル Midnight にデプロイする

第一弾でコンパイルした Counter 契約を、TypeScript CLI からローカル Midnight(node・indexer・proof-server)へ実際にデプロイするまでの実験ノート。ハマったエラーと確認ステップを順番に。到達点は「契約 OK・コンパイル OK・デプロイ OK・呼び出しは次回」。


Counter 白紙練習(第一弾) のつづきです。第一弾では、白紙から counter.compact を書いてコンパイル成功し、contract/output/contract/index.js が生成されるところまで進みました。

今回は 第二弾。その生成物を TypeScript の CLI 側から読み込み、ローカル Midnight に 実際にデプロイします。

このページは「最短の正解手順」ではなく、実験ノートです。実際にハマったエラー・確認した内容・直し方・「いまどこまで成功しているか」を、順番に残します。エラーが出ても大丈夫。エラーは、次に足りない部品を教えてくれる先生です。

今日の合言葉:

今回の到達点は、このうちここまでです。

1. 第二弾のゴール

今回のゴールは、第一弾で作った Counter 契約を、ローカル Midnight に実際にデプロイすること。まだ increment を呼んで状態を変えるところまでは行きません(それは第三弾)。

今回たどる道のり:

最後に見たいのは、この成功ログです(アドレスは例)。

このアドレスが「契約のチェーン上の住所」。第三弾でこれを使って呼び出します。

2. CLI ディレクトリを作る

counter-01 直下に cli/ を作ります。

CLI とは Command Line Interface(コマンドで操作する入口)のこと。ここでは「ターミナルから Midnight 契約を操作するためのスクリプト置き場」として使います。

3. 最初の deploy.ts

最初は、本当にただの動作確認でOKです。

実行:

最終的には、counter-01 直下から pnpm tsx cli/src/deploy.ts で動かす形に寄せます(理由は次の章)。

4. process.cwd() の罠

ここ、必ず覚えてください。process.cwd()deploy.ts が置いてある場所ではありません

counter-01 直下から実行するなら、process.cwd()counter-01 を指します。だから、生成物への正しいパスはこう書きます。

間違いやすい例:

だから「実行する場所」を counter-01 直下に固定し、process.cwd() を基準にすると、パスがブレません。

5. index.js が見えるか確認する

いきなり import せず、まず「ファイルが見えているか」だけ確認します。

成功例:

まだデプロイではありません。でも、第一弾の output と第二弾の cli が、ちゃんと手をつなげたことの確認です。

6. 生成された index.js を import する

次に、本当に読み込めるか試します。await をいきなりトップレベルで使うと、こんなエラーが出ることがあります。

その場合は main() 関数で包みます(さっきの形と同じ理由)。

すると今度は、別のエラー:

これは壊れたのではありません。次に足りない部品を教えてくれているエラーです。生成された index.js の中身を見ると、こういう行があります。

しかも、ランタイムのバージョンまでチェックしています。

なので、counter-01 直下にそれを入れます。

第一弾の contract-info.json"runtime-version": "0.16.0" と書いてありましたね。あれが、ここで効いてきます。

7. Node.js の node_modules 探索のしくみ

なぜ cli/ ではなく counter-01 直下に入れたのか。ここも大事です。

今 import しているのは contract/output/contract/index.js。だから Node.js はこの近くから上へ探します。

counter-01/node_modules に依存が入っていれば、ちゃんと見つかります。一方、

兄弟ディレクトリなので、contract/... から上にさかのぼっても通り道に入らず、見つからないことがあります。

だから今回は、依存管理を counter-01 直下に一本化する方針にします。cli/package.json を作ってしまっていたら、最終的には消して counter-01/package.json にまとめるのが望ましいです。

8. Contract instance を作ってみる

依存を入れて再実行すると、import に成功します。

さらに、Contract のインスタンスを作って中身を覗きます。

成功例:

ここで初めて、TypeScript 側から Compact の生成物が見えました。まだデプロイではありませんが、「契約が部品になって、TS の世界に戻ってきた」決定的な瞬間です。

9. Docker の3点セットを確認する

ローカル Midnight でデプロイするには、3つの裏方が動いている必要があります。

確認:

成功例:

今回つなぐ先(endpoint):

⚠️ 古い endpoint を手書きで残さないこと。たとえば下のような「確認用メモ」を残したままにすると、後で混乱します。あとで削除し、以後は network.ts が返す networkConfig を使います。

10. Midnight.js 依存を追加する

既存プロジェクト(kodomo01.app)の package.json を参考に、必要な依存を counter-01 直下に追加します。バージョンは固定して、噛み合わせを安定させます。

開発用:

インストール時に、こんな警告が出ました。

11. ESM / CommonJS 問題

wallet 系を読み込もうとして、こんなエラーが出ました。

見立て:

対策:

cli/package.json も残っているなら、そちらも一旦 ESM にします(ただし最終的には消して一本化)。

今後の初期セットは、最初からこうしておくと楽です。

12. network-id の使い方を確認する

最初、こう書きました。

でも NetworkId実行時の値ではなく、型でした。モジュールの中身を実際に覗いて確認します。

結果:

正しい import はこちら。

README ではこう使うと書いてありました。

今回はローカルなので:

13. network.ts / wallet.ts / wallet-state.ts を移植する

既存プロジェクトの deploy.ts は、いくつかの補助ファイルに依存していました。

なので cli/src に移植します。最初は network.tswallet.ts だけコピーしたら、こうなりました。

足りない子を探して、連れてきます。

最終的にこの4つになりました。

14. network.ts が動くか確認する

部品を一気に全部つながず、1つずつ動かして確かめます。まずは network。

main() の上の方に:

成功ログ:

15. wallet を作れるか確認する

次は wallet。まだ同期(sync)まではせず、作れるかどうかだけ確認します。

Node.js には WebSocket のグローバルが無いので、ws で穴埋め(ポリフィル)します。

確認コード:

成功ログ:

この段階では、wallet を作れただけで、まだネットワークと同期はしていません。

16. wallet sync する

次に、wallet をネットワークに同期します。

成功ログ:

途中で、こんなログが出ることがあります。

17. DUST を準備する

デプロイ前には DUST が必要です。DUST は、トランザクションを作る・証明する・送るための燃料(手数料)です。

wallet sync 後、wallet.stop()に追加します。

成功ログ:

18. deployContract に進む

ここから本番です。大事な順番:

import:

DUST ready 後、wallet.stop() より前に追加します。

成功ログ:

第一弾で「今は読まなくていい」と言った output/contract/index.jsoutput/keys output/zkir が、ここで全部主役になりました。zkConfigProvider がそれらを読み、proofProvider が proof-server に渡して証明を作ります。

19. CostModel エラーと依存バージョン問題

実は、最初の deploy ではこのエラーが出ました。

見立て:JavaScript では、同じ名前の CostModel でも、別々の場所・別バージョンから読み込まれていると、instanceof 的に別物になることがあります。「同じはずのクラスが別物として扱われている」状態です。

調べるコマンド:

ledger-v88.0.3 に揃っていました。でも、別のズレが見つかりました。

つまり compact-runtime0.15.0 と 0.16.0 の二重状態。一方、生成された index.jscheckRuntimeVersion("0.16.0") を要求しています。そこで pnpm.overrides0.16.0 に寄せます。

確認:

修正後:

これで再実行すると、deploy に成功しました。

バージョンの噛み合わせは Support matrix(外部リンク・別タブで開きます) が正本です。「最新どうし」が必ず合うとは限らない、を思い出してください。

20. デプロイアドレスを保存する

deploy に成功しただけでは、あとで呼び出すときにアドレスを忘れてしまいます。network.tsrecordDeployment(...) で保存します。

deploy 成功直後:

21. もう不要になった確認コードを消す

デプロイが通ったら、序盤の「確認用コード」はもう役目を終えています。

これは消してOKです。理由:

ただし注意。generated を使うコードを消すなら、generated の import もセットで消すこと。import だけコメントアウトして使う側を残すと、こうなります。

最後に残すのは、これだけ。

22. 最終的な成功まとめ

ここまでの到達点を、チェックで振り返ります。

一歩ずつ確認してきたので、もしどこかでエラーが出ても、すぐ前のステップに戻れます。それが実験ノート方式の強みです。

✍️ 白紙練習(何度でも反復)

答えを開く前に、いったん自分の手から出してみましょう。デプロイは登場人物が多いので、順番を言えることがいちばんの力になります。

白紙練習

ローカル Midnight に Counter をデプロイするまでの大きな流れを、順番に書いてみよう(契約 → … → Contract Address)。

クイズ

process.cwd() は何を指す? なぜ deploy.ts の場所と混同してはいけない?

クイズ

依存を cli/ ではなく counter-01 直下に入れたのはなぜ?

クイズ

Midnight TS プロジェクトで package.json に必ず入れる1行は? 入れないと何が起きる?

クイズ

NetworkId と setNetworkId / getNetworkId のちがいは?

クイズ

deployContract の前に必要な準備を、思い出せるだけ挙げてみよう。

クイズ

CostModel エラー(expected instance of CostModel)の正体は? どう直した?

今日の呪文

今日の勝利条件

今日のまとめ

  • 今回は 契約 → コンパイル → デプロイ まで。increment の呼び出しは次回(第三弾)。
  • process.cwd() は「実行した場所」。パスは実行場所を基準に組む。
  • 依存は counter-01 直下に一本化。Node は import するファイルの近くから上へ node_modules を探すから。
  • Midnight TS は ESM 前提。"type": "module" を最初から入れる。
  • デプロイ前の準備= wallet sync → tNight → DUST ready → providers
  • CostModel エラーは契約ミスではなく依存の混在。pnpm.overrides でバージョンを揃えて解決。
  • エラーは先生。「壊れた」ではなく「次に足りない部品を教えてくれている」

次回予告(第三弾)

ここまでで、Counter 契約はローカル Midnight に置かれました。ただし、まだ increment は呼んでいません。第三弾では、保存した Contract Address を使って callTxincrement を呼び、ledgerround が変わるところまで進みます。

➡️ 第三弾はこちら:Counter 白紙練習 第三弾:callTx で increment を呼び出す

つぎに読むページ