メインコンテンツにスキップ

最小コントラクトを書く

このレッスンでは、前のレッスンで学んだ概念(Package・Module・Object)を使って、実際に動くコントラクトを書きます。身構えなくて大丈夫です。カウンターの structentry fun を2つ書くだけです。


前提条件


コントラクトを書こう

これがカウンターコントラクトの全体です。まず読んでみて、次のセクションで試す方法を選んでください。

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

ローカルのセットアップ不要。ブラウザ上で編集・コンパイル・パブリッシュまでできます。

my_first_package
Move.toml
sources
my_first_package.move
Move.toml
README.md
Console
Build ready.

方法B:VSCode + CLI

VSCodeで my_first_package を開き、sources/ フォルダの中の .move ファイルを開きます。

sui move new my_first_package コマンドで作成すると、sources/my_first_package.move が自動生成されています。このファイルの内容を丸ごと上のコードに書き換えてください。

VSCodeでファイルを開いた状態

コード入力後のVSCode画面

コードを書いたら、以下の手順でビルドエラーがないか確認します。

1. devnetのチェーンIDを確認する

sui client chain-identifier

2. Move.toml の末尾に環境設定を追記する

表示されたIDを使って追記します(IDは例です。実際に表示されたものを使用してください):

[environments]
devnet = "a63d14dc"

なお、devnetはリセットされるたびにチェーンIDが変わります。エラーが再発した場合は同じ手順でIDを更新してください。

testnet・mainnet では不要

testnetmainnet は 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)からトランザクションとして直接呼び出せるようになります。

関数の可視性(visibility)まとめ

Moveの関数は、先頭に付けるキーワードによって「どこから呼び出せるか」が変わります。

書き方ウォレット・CLIから他のコントラクトから使いどころ
fun同一module内だけで使う内部ヘルパー
entry funウォレット・CLIから直接呼ぶトランザクションの入り口
public(package) fun⚠️
同一パッケージのみ
パッケージ内の複数モジュールで共有したい関数(よく使う)
public fun誰でも実行できるため、セキュリティを十分考慮した上で真に必要な場合のみ

補足: public fun は技術的にはウォレットからも呼び出せますが、主な目的は他のコントラクトやモジュールに関数を公開することです。トランザクションとして直接呼び出したい場合は、entry fun を使うのが正しい選択です。

今回の createincrement は「ウォレットから呼ぶだけ、他のコントラクトには使わせない」ので entry fun がぴったりです。

なお entry fun には以下の制約があります:

  • 戻り値がある場合、その型は drop ability を持つ必要がある
  • 同じトランザクション内で 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 Counterhas key で実装できた
  • createincremententry fun で実装できた
  • コンパイルがエラーなく成功した(Playground または sui move build

このレッスンでやったこと

  • entry fun を使ってウォレット・CLIから呼び出せる関数を実装した
  • has key で Suiオブジェクトとして機能するstructを定義した
  • transfer::transfer でオブジェクトを自分自身に送ることを確認した
  • コントラクトがエラーなくコンパイルできることを確認した