最小コントラクトを書く
このレッスンでは、前のレッスンで学んだ概念(Package・Module・Object)を使って、実際に動くコントラクトを書きます。身構えなくて大丈夫です。カウンターの struct と entry fun を2つ書くだけです。
前提条件
- Moveプロジェクトを作成 を完了している
- Moveの仕組みを知ろう を読んでいる
- VSCodeを使う場合:Sui Extension がインストールされている
コントラクトを書こう
これがカウンターコントラクトの全体です。まず読んでみて、次のセクションで試す方法を選んでください。
module my_first_package::counter {
/// カウンターオブジェクト
public struct Counter has key {
id: UID,
value: u64,
}
/// カウンターを作成して送信する
entry fun create(ctx: &mut TxContext) {
let counter = Counter {
id: object::new(ctx),
value: 0,
};
transfer::transfer(counter, ctx.sender());
}
/// カウンターの値を1増やす
entry fun increment(counter: &mut Counter) {
counter.value = counter.value + 1;
}
}
試してみよう
2つの方法で試せます。やりやすい方を選んでください。
方法A:Move Playground
ローカルのセットアップ不要。ブラウザ上で編集・コンパイル・パブリッシュまでできます。
Build ready.
方法B:VSCode + CLI
VSCodeで my_first_package を開き、sources/ フォルダの中の .move ファイルを開きます。
sui move new my_first_package コマンドで作成すると、sources/my_first_package.move が自動生成されています。このファイルの内容を丸ごと上のコードに書き換えてください。


コードを書いたら、以下の手順でビルドエラーがないか確認します。
1. devnetのチェーンIDを確認する
sui client chain-identifier
2. Move.toml の末尾に環境設定を追記する
表示されたIDを使って追記します(IDは例です。実際に表示されたものを使用してください):
[environments]
devnet = "a63d14dc"
なお、devnetはリセットされるたびにチェーンIDが変わります。エラーが再発した場合は同じ手順でIDを更新してください。
testnet と mainnet は Sui の標準環境として最初から認識されているため、この設定は不要です。[environments] セクションの追記が必要なのは devnet だけです。
3. ビルドを実行する
cd ~/sui-projects/my_first_package
sui move build
成功すると以下のように表示されます:
INCLUDING DEPENDENCY MoveStdlib
INCLUDING DEPENDENCY Sui
BUILDING my_first_package
Sui Extensionがインストールされていれば、VSCode上でリアルタイムにエラーが赤線で表示されます。エラーの行にカーソルを当てると説明が出ます。
よくある原因:
- セミコロン(
;)の書き忘れ - カッコの閉じ忘れ
コードのポイントを確認しよう
L13で学んだ概念と対応させながら確認します。
entry キーワード
entry fun create(ctx: &mut TxContext) {
entry を付けると、この関数はウォレットやCLI(sui client call)からトランザクションとして直接呼び出せるようになります。
Moveの関数は、先頭に付けるキーワードによって「どこから呼び出せるか」が変わります。
| 書き方 | ウォレット・CLIから | 他のコントラクトから | 使いどころ |
|---|---|---|---|
fun | ❌ | ❌ | 同一module内だけで使う内部ヘルパー |
entry fun | ✅ | ❌ | ウォレット・CLIから直接呼ぶトランザクションの入り口 |
public(package) fun | ❌ | ⚠️ 同一パッケージのみ | パッケージ内の複数モジュールで共有したい関数(よく使う) |
public fun | ✅ | ✅ | 誰でも実行できるため、セキュリティを十分考慮した上で真に必要な場合のみ |
補足:
public funは技術的にはウォレットからも呼び出せますが、主な目的は他のコントラクトやモジュールに関数を公開することです。トランザクションとして直接呼び出したい場合は、entry funを使うのが正しい選択です。
今回の create と increment は「ウォレットから呼ぶだけ、他のコントラクトには使わせない」ので entry fun がぴったりです。
なお entry fun には以下の制約があります:
- 戻り値がある場合、その型は
dropability を持つ必要がある - 同じトランザクション内で
entry funに渡したオブジェクトを、後で別の関数に再利用できない - 参照(
&T/&mut T)を返す関数はウォレットから呼べない
has key について
public struct Counter has key {
L13では has key, store のサンプルを見ました。ここでは key だけ付けています。
key→ Suiオブジェクトとして存在できる(必須)store→ 他のオブジェクトのフィールドに入れられる、public_transferで転送できる
このカウンターは他のオブジェクトに格納する予定がないので、key だけで十分です。後のレッスンで使い分けが出てきたときに改めて確認します。
transfer::transfer について
transfer::transfer(counter, ctx.sender());
作ったオブジェクトを「誰かに渡す」操作です。渡さずに関数を終了しようとするとコンパイルエラーになります(Moveの「Linear Types」という仕組みで、オブジェクトの行方が常に追跡されます)。
ctx.sender() はトランザクションを送信したアドレスを返します。自分自身に送ることで、「自分のオブジェクトとして所有する」状態になります。
成功の確認
以下ができれば、このレッスンは完了です:
-
struct Counterをhas keyで実装できた -
createとincrementをentry funで実装できた - コンパイルがエラーなく成功した(Playground または
sui move build)
このレッスンでやったこと
-
entry funを使ってウォレット・CLIから呼び出せる関数を実装した -
has keyで Suiオブジェクトとして機能するstructを定義した -
transfer::transferでオブジェクトを自分自身に送ることを確認した - コントラクトがエラーなくコンパイルできることを確認した