このページは、白紙のディレクトリから自分の手で Counter を打つための実践ノートです。
テンプレート生成で一気に作るのではなく、counter.compact を1文字ずつ書き → わざとエラーを出し → 直し → コンパイル成功まで、ゆっくり一緒に進みます。
ここ、かなり大事です。何度でも繰り返してOKです。むしろ、繰り返すために作ったページです。counter-01 がうまくいったら、次は counter-02、counter-03 …と新しい練習場を作って、同じ流れを手で再生してください。
はじめに:なぜ Counter から始めるのか
最初から Battleship のような複雑なアプリを読むと、ファイルが多すぎて迷子になります。まずは Counter だけに集中します。
Counter は、
- 数字がある
- ボタンを押す
- 数字が増える
という、いちばん小さなアプリです。この最小構造を通して、つぎの感覚を身体に入れます。
まずは全部理解しようとしなくて大丈夫です。今日のゴールは「白紙から書いて、コンパイルが通る」ところまで。それだけで十分な前進です。
今日の合言葉
声に出すくらいの気持ちで、何度も唱えてください。
もう少し噛み砕くと:
順番でいうと:
そして、今日ずっと使う比喩はこれです。
ステップ1:作業用ディレクトリを作る
まずは練習場(空っぽのフォルダ)を作ります。
すでに練習用の場所がある人は、そこに counter-01 を作ってもOKです。
いまどこにいるか確認しましょう。
ls で何も出てこなければ大成功です。
理想のディレクトリ構造(いまの状態):
ステップ2:contract と counter.compact を作る
つぎに、スマートコントラクト本体を置くフォルダを作ります。
確認:
期待結果:
いまの構造:
ステップ3:自力版 Counter を書いてみる
いきなり「正解」を写すのではなく、まずは人間にいちばん分かりやすい自力版を書いてみます。counter.compact に、こう打ってみてください。
一行ずつ、人間の言葉に翻訳します。
export ledger count: Uint<64>; は——
export circuit increment(): [] は——
count = count + 1; は——
この段階のコードは、人間にはとても素直で分かりやすいですね。ここから先で、Compact が「人間の素直さ」と「機械の厳しさ」の間でどう振る舞うかを体験していきます。
つまずき1:Unit と Uint(typo と型の話)
練習中、実際にこういう打ち間違いがありました。
正しくは:
初学者向けにまとめると:
エラーは先生です。Unit と打ってしまっても落ち込まなくて大丈夫。「あ、Uint だった」と気づけたら、それが一歩前進です。
ステップ4:compact のバージョン確認
コンパイルの前に、道具のバージョンを見ておきます。
実際の例:
数字は環境で変わってOKです。「compact というコマンドが反応する」ことが確認できれば十分です。
ステップ5:compact compile の正しい形
最初、こう打ってみました。
すると、使い方(usage)が出ました。
ここから分かることは、Compact の compile には入力ファイルと出力先ディレクトリの両方が必要だ、ということです。
正しい形:
意味:
これも比喩で言うと:
つまずき2:count = count + 1 で型エラー
さて、自力版(count = count + 1;)をコンパイルすると、こんなエラーが出ます。
落ち着いて読みます。
ここでの大事な学び:
スマートコントラクトは「お金や状態がチェーンに残る」世界です。だから「うっかり最大値を超えて壊れる」ことを、コンパイラがあらかじめ止めてくれます。最初は厳しく感じますが、これは守ってくれている厳しさです。
つまずき3:assert を門番にする
「最大値を超えないようにチェックすればいいのでは?」——その直感は正しいです。assert(アサート)を使ってみます。
最初はこう書きました。
でも Compact の assert にはメッセージが必要でした。正しくはこうです。
意味:
直感的に言うと:
つまりこのコードは——
人間語にすると:
…なのですが、それでもさっきの型エラーは消えませんでした。
人間の「だいじょうぶ」と、型システムの「型として保証されている」は別物なんですね。ここ、けっこう深い学びです。
つまずき4:count += 1 も試す
「+= 1 なら?」と試したくなります。やってみましょう。
すると、別のエラーが出ます。
意味:
ここで、つぎの理解にたどり着きます。
もう少し正確に言うと:
比喩でいうと:
ステップ6:最小 Counter 契約の完成
ここまでの遠回りが、いちばんの近道でした。「増やしたいなら Counter を使う」——これがモデル回答です。
counter.compact を、こう書き直します。
一行ずつ分解します。
export ledger round: Counter; は——
さらに細かく:
round.increment(1); は——
ここを強調しておきます。
自力版と Counter版を並べると、ちがいが一目で分かります。
自力版(理解用):
Counter版(実務用):
Counter 型は標準ライブラリ(
CompactStandardLibrary)が用意してくれている「増減の作法」を内側に持っています。だから「上限チェックをどうしよう」と人間が悩まなくても、安全に増やせる入口だけが渡されるわけです。
ステップ7:コンパイル成功
いよいよコンパイルします。
成功すると、こんな出力が出ます。
読み解くと——
Missing public parameters for k=5 ... は「証明に使う公開パラメータを初回だけダウンロードしている」だけなので、エラーではありません。びっくりしなくて大丈夫です。
ステップ8:output の中身を眺める
コンパイルが終わると、contract/ の中に output/ フォルダが自動でできています。VS Code で開くと、こんな感じです。
ここ、初学者にとってすごく大事なポイントです。
だれが何を作るのか、はっきりさせます。
各ファイルのざっくり説明:
ちなみに output/compiler/contract-info.json を開くと、コンパイラ自身のバージョンや、見つかった ledger と circuit が書いてあります(実際の中身の一部):
ledger の欄に round(storage: Counter)が、circuits の欄に increment が、ちゃんと載っていますね。自分が書いた counter.compact が、機械の言葉に変換されたことの「証拠」です。
output は全部読まなくていい
もう一度、比喩で締めます。
index.js や zkir の中身は、今は読まなくていいが、あとで必要になる部品です。デプロイのときに、この部品たちを使います。今は「眺めるだけ」でOKです。
ステップ9:pnpm で cli の土台を作る
ここからは「次の準備」です。生成された部品を使って、いずれチェーンへデプロイし、callTx で increment を呼びます。そのための CLI 用フォルダを作るところまでやります。
まず counter-01 の直下に戻ります。
いまの場所を確認:
CLI フォルダを作って初期化:
pnpm init のあと、package.json はこんな形になります。
つぎに、TypeScript を動かす道具を入れます。
実際の出力例:
こんな警告が出ることがあります。
ステップ10:deploy.ts で TypeScript 実行確認
最後に、TypeScript の CLI が動くかどうかだけ、小さく確かめます。本格的なデプロイ実装は次回にまわします。今日は「土台が動く」ことの確認です。
src フォルダと deploy.ts を作ります。
src/deploy.ts に、まずは1行だけ書きます。
実行:
期待出力:
これが出たら、今日の小ゴール達成です。TypeScript の CLI が、ちゃんとあなたのコードを動かしてくれました。
最終的なディレクトリ構造
ここまでの全体像です。これが今日作った「練習場ひとつぶん」の姿です。
✍️ 白紙練習(何度でも反復)
このページは反復用です。答えを開く前に、いったん自分の手から出してみてください。手で書くと「わかったつもり」がはがれます。counter-02, counter-03 …と練習場を増やして、何度でもどうぞ。
counter.compact を pragma から白紙で書いてみよう(Counter 版・コンパイルが通る形)。
compact compile の正しいコマンドの形と、その意味を自分の言葉で書いてみよう。
counter.compact を入力として読み、output フォルダに実行用・アプリ用の部品一式を自動生成する。入力ファイルと出力先ディレクトリの両方が必要。
ledger・circuit・callTx は、それぞれ何を表す?
ledger は状態(チェーンに残る箱)。circuit は操作(外から呼べる入口=ボタン)。callTx は実行(そのボタンをトランザクションとして押す)。合言葉は「ledger circuit callTx」。
自力版で「count = count + 1」を書くと型エラーになるのはなぜ?
count は Uint<64>。でも count + 1 の結果型は、最大値を超える可能性があるぶん型が広がる(Uint<0..18446744073709551617>)。Compact はそれを「そのまま Uint<64> に入れてよい」とは認めない。型システムが「壊れる可能性」を先に止めてくれている。
Uint<64> と Counter のちがいは? なぜ increment には Counter を使う?
Uint<64> は「生の数字」で、+= のような増やす操作は定義されていない。Counter は「増減のための専用 ledger 型」で、round.increment(1) と書くだけで安全に増やせる。増やしたいなら Counter を使う。
assert の役割は? Compact の assert で忘れがちなことは?
assert(条件, "メッセージ") は門番。条件が true なら先へ進み、false ならそのメッセージで止める。忘れがちなのは——Compact の assert はメッセージ(第2引数)が必須だということ。
compact compile が生成する output には何が入っている? そのうち「自分で書く」のはどれ?
自分で書くのは counter.compact だけ。output/ の中身(contract/index.js・index.d.ts・zkir/*・keys/*・compiler/contract-info.json)は全部コンパイラが自動生成する。今は中身を読まなくてよい。「設計図を渡すと、部品一式ができる」。
今日の呪文
今日の勝利条件
ひとつでもチェックがついたら、今日は前進です。全部ついたら大成功。
今日のまとめ
- Counter は「数字を1ふやすだけ」の最小アプリ。でも
ledger・circuit・compileの感覚が全部入っている。 - 自力版(
Uint<64>+count = count + 1)は人間には分かりやすいが、Compact の型チェックに引っかかる。 - 増やしたいなら Counter 型。
round.increment(1)で、安全に「1増やす」を表現できる。 - 自分で全部書く必要はない。書くのは小さな
counter.compactだけ。compileすればoutputが自動生成される。 - エラーは先生。型エラーも
assertのメッセージ忘れも、ぜんぶ「Compact の安全への厳しさ」を教えてくれている。
次回予告
今日できたこと:
次にやること:
cli/src/deploy.ts の中身を、本物の Midnight デプロイに育てていきます。そのとき、今日「眺めるだけ」にした output/contract/index.js や keys/ が、いよいよ主役になります。
つぎに読むページ
- 同じ道具(
compact compile)で契約を作り変えて慣れる → Counter 改造10本ノック - いよいよチェーンに置く(実験ノート)→ Counter 白紙練習 第二弾:デプロイ
- いまの一周を、もう少し高い視点で見る → Counter(はじめての DApp)
ledgerとcircuitの文法をおさらい → Compact の書きかた(文法ツアー)- 環境がまだの人は → 開発環境の作りかた