📘 Academy原文準拠 | Phase 3 · Unit 1 · Lesson 1.3 Your First Contract [Hello World] 内容に忠実な日本語版です。原文(英語)・図・動画は公式 Academy(外部リンク・別タブで開きます)を正本に。
いよいよ本物のコードを書きます。前のレッスンでは Compact の部品 — ledger 宣言・circuit・witness・disclose、そしてそれらがどう組み合わさるか — を学びました。今回は、その部品を実際に使います。
このレッスンを終えるころには、あなたは Compact 契約を書き、ゼロ知識回路へコンパイルし、Midnight の Preprod ネットワークへデプロイし、メッセージをオンチェーンに保存し終えています。
やり方は2通りでいきます。
まずは手作業(manual)で。各ステップで何が起きているかを理解するためです。そのあとで、すべてを自動で用意してくれる create-mn-app(外部リンク・別タブで開きます) を見ます。
動画で学ぶ(公式)
何を作るのか
hello-world 契約がやることは1つだけ。メッセージをブロックチェーンに保存して、読み出す。わざとシンプルにしています。ここでの目標は複雑なものを作ることではなく、あなたのツールチェーン一式が端から端まで(end to end)ちゃんと動くことを確かめることです。
これが契約の全体です。
前のレッスンで似たものを見ましたね。今度はこれをビルドして、コンパイルして、オンチェーンに乗せます。
Step 1: プロジェクトを作る
ターミナルを開いて、プロジェクトの土台を用意します。
ディレクトリはこんな形になっているはずです。
Step 2: 契約を書く
契約ファイルを作ります。
contracts/hello-world.compact を VS Code で開いて、次を書き込みます。
実際に書いたところで、もう一度1行ずつ見ていきましょう。
pragma language_version >= 0.20;… この契約は Compact 言語のバージョン 0.20 以上を必要とする、とコンパイラに伝えます。互換性のないバージョンでうっかりコンパイルしてしまうのを防ぎます。import CompactStandardLibrary;… Compact の標準ライブラリを取り込みます。これで組み込みの型や関数が使えます。どの Compact 契約にも必要です。export ledger message: Opaque<"string">;…messageという名前のオンチェーン状態を1つ宣言します。型Opaque<"string">は、回路内ではハッシュとして扱う(中身は覗けない)という意味ですが、TypeScript のフロントエンドからは実際の文字列の値が読めます。exportを付けると DApp のフロントからアクセスでき、ledgerはそれがチェーン上に永続的に存在することを意味します。export circuit storeMessage(newMessage: Opaque<"string">): []…storeMessageという名前の circuit(オンチェーン関数)を定義し、文字列のパラメータを1つ受け取ります。戻り型[]は何も返さないという意味。exportで外部から呼べます。message = disclose(newMessage);…disclose()で囲むことは、「この秘密の値が公開台帳に置かれることで露出するかもしれない」と認めることです。値そのものを公開にするわけではありませんが、コンパイラに「露出の可能性は分かっていて、受け入れます」と伝えます。
Step 3: プロジェクトを設定する
コンパイルの前に、依存パッケージと設定を整えます。
tsconfig.json を作ります。
package.json の中身を、次で置き換えます。
これらの依存について、知っておくとよい点がいくつかあります。
midnight-js-*パッケージ(バージョン 4.0.4)は、契約のデプロイ・証明の生成・indexer への問い合わせ・private state の管理を担う SDK ライブラリ群です。wallet-sdk-*パッケージはウォレットのスタックです。Midnight は multi-wallet(複数ウォレット)アーキテクチャを採用していて、shielded(プライベート)・unshielded(公開)・DUST トークン操作それぞれに別々のウォレットがあります。この仕組みは Unit 3 で詳しく説明します。いまは「契約のデプロイには3種類のウォレットが協調して動く必要がある」とだけ覚えておけば十分です。overridesとresolutionsフィールドは、推移的依存(transitive dependency)の特定バージョンを固定して、バージョン衝突を避けるためのものです。smoldotは空のパッケージに差し替えています — DApp 開発には不要で、一部の環境でビルドの問題を起こすためです。- TypeScript を別途コンパイルする代わりに
tsxを使っています。.tsファイルを直接実行できるので、スクリプトを走らせる前にnpm run buildを挟む必要がありません。
次に、プロジェクトのルートに docker-compose.yml を作ります。
これは、Lesson 22 で出てきた生の docker run コマンドより、proof server をすっきり管理できる方法です。
起動は npm run proof-server:start、停止は npm run proof-server:stop です。
すべてインストールします。
Step 4: 契約をコンパイルする
proof server が動いていることを確かめます。
そしてコンパイルします。
このコマンドは、いくつものことを一度にやります。
- あなたの Compact コードをゼロ知識回路へ変換する
- 暗号の proving key(証明鍵)と verifying key(検証鍵)を生成する
- DApp が契約とやり取りできるよう、JavaScript のバインディングを作る
コンパイル後には、新しいディレクトリ構成が現れます。
いちばん重要な出力は contracts/managed/hello-world/contract/index.js です。これが、契約をデプロイしたり操作したりするために、あなたの TypeScript コードが import するファイルになります。
コンパイルが失敗したら、次を確認します。
- proof server が動いているか(
docker psで確認) pragmaのバージョンが、使っているコンパイラの対応範囲と合っているか(compact --version)- コマンドをプロジェクトのルートで実行しているか(
contracts/の中ではない)
Step 5: デプロイ用スクリプトを書く
デプロイ用スクリプトを作ります。
ここがこのレッスンでいちばんコード量の多い部分です。今は一行一行を理解しようとしなくて大丈夫。SDK のパターンは Unit 3 で分解します。いまの目標は、とにかく契約をオンチェーンに乗せることです。
src/deploy.ts に次を書き込みます。
コードがたくさんありますね。デプロイ用スクリプトが高いレベルで何をしているかを分解しておきましょう。細部を暗記する必要はありませんが、流れは理解しておくとよいです。
- コンパイル済み契約の読み込み:
CompiledContract.make()を使い、コンパイル出力に「空の witness 実装(vacant witnesses)」とファイルベースの ZK アセットを束ねます。 - ウォレットの作成:Midnight は HD ウォレット(hierarchical deterministic)を使い、3種類の鍵を導出します — shielded トランザクション用(Zswap)・unshielded トランザクション用(NightExternal)・DUST 用。これらは
WalletFacadeに組み上げられ、3つすべてを取りまとめます。WalletFacade.init()は各ウォレット種別のファクトリ関数を受け取り、それぞれを独立に設定できます。 - ウォレットへの入金:残高がゼロなら、faucet から tNight トークンを送ってもらうのを待ちます。
- DUST の登録:これは他のブロックチェーンでは見ない手順です。Midnight は契約の実行に DUST トークン(あなたの tNight 保有から生成される)を使います。スクリプトは、あなたの unshielded コインを DUST 生成のために登録し、DUST トークンが使えるようになるまで待ちます。
- 契約のデプロイ:すべての provider(private state・public data・ZK config・proof 生成・wallet)を組み立てて、デプロイ用トランザクションを送信します。
- デプロイ情報の保存:
deployment.jsonに契約アドレスとウォレットの seed を書き出します。これで CLI スクリプトが契約を見つけられます。
Step 6: デプロイする
proof server が動いていることを確かめます。
Preprod へデプロイします。
スクリプトは次のことをします。
- 新しいウォレットを作るか、既存の seed から復元するかを尋ねる(初回はオプション 1 を選ぶ)
- 新しい seed を生成する — これはすぐに保存すること
- ウォレットのアドレスを表示する
- Preprod faucet(外部リンク・別タブで開きます) で入金するのを待つ
- DUST 生成のために登録し、DUST トークンを待つ
- 契約をデプロイする(30〜60秒かかる)
- 契約アドレスと seed を
deployment.jsonに保存する
"Contract deployed successfully!" と契約アドレスが表示されたら、あなたの契約は Preprod ネットワーク上で稼働中です。
Step 7: 契約とやり取りする
では、メッセージを保存して読み出してみましょう。CLI スクリプトを作ります。
src/cli.ts に次を書き込みます。
CLI を実行します。
プロンプトが出たら、ウォレットの seed を入力します(デプロイ時と同じもの。deployment.json にも保存されています)。つながったら、次を試します。
- オプション 1:
"Hello from Midnight!"のようなメッセージを保存する。これはトランザクションを作り、ZK 証明を生成し、ブロックチェーンへ書き込みます(20〜30秒かかる)。 - オプション 2:チェーンから現在のメッセージを読み戻す。これは indexer への無料のクエリで、トランザクションは不要です。
- オプション 3:終了する。
メニューに表示される DUST 残高に注目してください。メッセージを保存するたびに、DUST トークンを消費します。
読み出しが無料なのは、indexer に問い合わせているだけだからです — 証明の生成もトランザクションも無し。
自分のメッセージが返ってきたら、おめでとうございます。あなたはゼロ知識スマートコントラクトをデプロイし、操作したことになります。
もっと速いやり方
ここまでが手作業のやり方です。Midnight には、これら一式を自動で用意してくれる scaffolding(足場づくり)ツールもあります。
これで、Hello World テンプレート(または Counter テンプレート)が入った設定済みプロジェクトが手に入ります。デプロイスクリプト・TypeScript 設定・全依存パッケージ込みです。今後のプロジェクトで手作業のセットアップを飛ばしたいなら、こちらがよい選択です。
いま何が起きたのか
少し立ち止まって、あなたがやり遂げたことを味わいましょう。
- オンチェーン状態と circuit を宣言する Compact 契約を書いた
- コンパイラがそれを、暗号鍵つきのゼロ知識回路へ変換した
- デプロイスクリプトが multi-wallet 構成(shielded + unshielded + DUST)を作り、入金し、DUST 生成のために登録した
- ZK 証明つきのデプロイ用トランザクションを Preprod ネットワークへ送信した
- circuit を呼び出してさらに ZK 証明を生成し、ネットワークがそれを検証してから状態を更新した
- 公開台帳に問い合わせて、自分のメッセージを読み戻した
契約そのものはロジック4行(import を入れて5行)です。けれどその裏では、状態の変化ごとにゼロ知識証明が生成・検証され、3種類のウォレットが協調してトランザクションを成立させていました。
つぎは何か
これで、端から端まで動く一連の流れが手に入りました。次のレッスンでは proof server をさらに詳しく見ます — それが実際に何をしているのか、トランザクションがシステムをどう流れるのか、そして「submit(送信)」から「confirmed(確定)」までの間に何が起きているのか。
開発者として押さえる点
- 流れは 書く →
npm run compile→npm run deploy→npm run cliの4段階。コンパイルは Compact を ZK 回路へ変換し、proving/verifying 鍵と JS バインディング(contract/index.js)を生成する - デプロイ・実行には proof server(
docker compose、ポート 6300)が必須。コンパイルもデプロイも、まずnpm run proof-server:startを確認 - Midnight は multi-wallet:shielded(Zswap)・unshielded(NightExternal)・DUST の3鍵を
WalletFacadeで取りまとめる。デプロイには3つすべてが必要 - 他チェーンに無い手順が DUST 登録。tNight から生成される DUST が契約実行の手数料になる。Preprod の tNight は faucet(外部リンク・別タブで開きます) から入手
- 書き込み(
storeMessage)は有料・ZK 証明つき・20〜30秒。読み出しは indexer へのクエリで無料・トランザクション不要。この非対称性を押さえる - 手作業を飛ばしたいなら
npx create-mn-appで Hello World / Counter テンプレートを足場づくりできる
やさしい版・公式へ
- やさしい版:Counter チュートリアル
- 公式:Academy Courses(外部リンク・別タブで開きます)(Phase 3 / Unit 1 / 1.3)
- 関連 docs:Midnight Developer Docs(外部リンク・別タブで開きます) / Compact リファレンス(外部リンク・別タブで開きます)
つぎに読むページ
➡️ 原文準拠コースの入口へ戻る。このコースについて(次のレッスンは順次追加します)