mcpField
W
write-contracts

write-contracts

github.com/aptos-labs開発2094 回インストール24 週間前に更新
リポジトリ

write-contracts について

write-contractsは、Aptos Moveのスマートコントラクトを作成する開発者向けのスキルです。デジタルアセット、オブジェクト、セキュリティ、ストレージ選択、テスト可能なアクセサーのパターンを扱います。

主な機能

  1. 01Aptos Moveのコントラクトパターン
  2. 02Digital Asset NFTの指針
  3. 03オブジェクトのライフサイクルと所有権ルール
  4. 04署名者と入力の検証
  5. 05ストレージ選択の指針
  6. 06テスト可能なviewアクセサー

活用シーン

  1. 01NFTコレクションやマーケットプレイスの作成
  2. 02オブジェクトベースのコントラクトモジュールの実装
  3. 03レジストリやログのストレージ選択
  4. 04アクセス制御と入力検証の追加

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/aptos-labs/aptos-agent-skills --skill write-contracts

インストール後、Claude Code で/write-contractsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
access-control.md
complete-example.md
events.md
initialization.md
object-patterns.md
safe-arithmetic.md
storage-decision-tree.md
storage-gas-optimization.md
storage-patterns.md
storage-types.md
v2-syntax.md

詳細

コントラクト作成スキル

コアルール

デジタルアセット(NFT)⭐ 重大
  1. NFT 関連のすべてのコントラクト(コレクション、マーケットプレイス、ミント)では、必ずデジタルアセット(DA)標準を使用してください
  2. 必ず aptos_token_objects::collection モジュールと aptos_token_objects::token モジュールをインポートしてください
  3. NFT の参照には必ず Object<AptosToken> を使用してください(汎用の Object<T> は使用しないでください)
  4. 非推奨の TokenV1 標準や aptos_token::token モジュールは絶対に使用しないでください
  5. NFT のパターン全体については、../../../patterns/move/DIGITAL_ASSETS.md を参照してください
オブジェクトモデル
  1. すべてのオブジェクト参照には必ず Object<T> を使用してください(生のアドレスは絶対に使用しないでください)
  2. ConstructorRef が破棄される前に、コンストラクター内ですべての参照(TransferRef、DeleteRef)を生成してください
  3. コンストラクターからは Object<T> を返してください(ConstructorRef は絶対に返さないでください)
  4. object::owner(obj) == signer::address_of(user) を使って所有権を確認してください
  5. オブジェクトの署名者には object::generate_signer(&constructor_ref) を使用してください
  6. シングルトンには名前付きオブジェクトを使用してください:object::create_named_object(creator, seed)
セキュリティ
  1. エントリー関数では、署名者の権限を必ず検証してください: assert!(signer::address_of(user) == expected, E_UNAUTHORIZED)
  2. 入力は必ず検証してください:ゼロでない数量、アドレスの検証、文字列の長さの確認
  3. 公開関数で &mut 参照を公開することは絶対に避けてください
  4. エントリー関数で署名者の検証を省略することは絶対に避けてください
最新の構文
  1. イテレーションにはインライン関数とラムダを使用してください
  2. レシーバースタイルのメソッド呼び出しを使用してください:obj.is_owner(user)(最初のパラメーターを self として定義)
  3. vector::borrow() の代わりにベクターのインデックス参照を使用してください:vector[index]
  4. 名前付きアドレスを直接使用してください:@marketplace_addr(ヘルパー関数は使用しないでください)
必須パターン
  1. デプロイ時のコントラクト初期化には init_module を使用してください
  2. すべての重要なアクティビティ(作成、転送、更新、削除)で必ずイベントを発行してください
  3. 明確で説明的な名前のエラー定数を定義してください(E_NOT_OWNER、E_INSUFFICIENT_BALANCE)
テスト容易性
  1. 構造体のフィールドにアクセスするアクセサー関数を追加してください。別モジュールのテストからは、構造体のフィールドに直接アクセスできません
  2. 読み取り専用のアクセサー関数には #[view] アノテーションを使用してください
  3. 複数フィールドにアクセスするアクセサー関数では、タプルを返してください:(seller, price, timestamp)
  4. #[view] はドキュメントコメントより前に配置してください。#[view] の前に /// comment を記述すると、コンパイラー警告が発生します。まず #[view] を記述し、その後に /// を記述してください

クイックワークフロー

  1. モジュール構造を作成 → 構造体、イベント、定数、init_module を定義する
  2. オブジェクト作成を実装 → すべての参照をあらかじめ生成する適切なコンストラクターパターンを使用する
  3. アクセス制御を追加 → 所有権を確認し、すべての入力を検証する
  4. セキュリティを確認 → デプロイ前に security-audit スキルを使用する

主な例:オブジェクト作成パターン

struct MyObject has key {
    name: String,
    transfer_ref: object::TransferRef,
    delete_ref: object::DeleteRef,
}

// Error constants
const E_NOT_OWNER: u64 = 1;
const E_EMPTY_STRING: u64 = 2;
const E_NAME_TOO_LONG: u64 = 3;

// Configuration constants
const MAX_NAME_LENGTH: u64 = 100;

/// Create object with proper pattern
public fun create_my_object(creator: &signer, name: String): Object<MyObject> {
    // 1. Create object
    let constructor_ref = object::create_object(signer::address_of(creator));

    // 2. Generate ALL refs you'll need BEFORE constructor_ref is destroyed
    let transfer_ref = object::generate_transfer_ref(&constructor_ref);
    let delete_ref = object::generate_delete_ref(&constructor_ref);

    // 3. Get object signer
    let object_signer = object::generate_signer(&constructor_ref);

    // 4. Store data in object
    move_to(&object_signer, MyObject {
        name,
        transfer_ref,
        delete_ref,
    });

    // 5. Return typed object reference (ConstructorRef automatically destroyed)
    object::object_from_constructor_ref<MyObject>(&constructor_ref)
}

/// Update with ownership verification
public entry fun update_object(
    owner: &signer,
    obj: Object<MyObject>,
    new_name: String
) acquires MyObject {
    // ✅ ALWAYS: Verify ownership
    assert!(object::owner(obj) == signer::address_of(owner), E_NOT_OWNER);

    // ✅ ALWAYS: Validate inputs
    assert!(string::length(&new_name) > 0, E_EMPTY_STRING);
    assert!(string::length(&new_name) <= MAX_NAME_LENGTH, E_NAME_TOO_LONG);

    // Safe to proceed
    let obj_data = borrow_global_mut<MyObject>(object::object_address(&obj));
    obj_data.name = new_name;
}

主な例:テスト用アクセサー関数

struct ListingInfo has store, drop, copy {
    seller: address,
    price: u64,
    listed_at: u64,
}

/// Accessor function - tests cannot access struct fields directly
/// Use tuple returns for multiple fields
#[view]
public fun get_listing_details(nft_addr: address): (address, u64, u64) acquires Listings {
    let listings = borrow_global<Listings>(get_marketplace_address());
    assert!(table::contains(&listings.items, nft_addr), E_NOT_LISTED);
    let listing = table::borrow(&listings.items, nft_addr);
    (listing.seller, listing.price, listing.listed_at)
}

/// Single-field accessor when only one value needed
#[view]
public fun get_staked_amount(user_addr: address): u64 acquires Stakes {
    let stakes = borrow_global<Stakes>(get_vault_address());
    if (table_with_length::contains(&stakes.items, user_addr)) {
        table_with_length::borrow(&stakes.items, user_addr).amount
    } else {
        0
    }
}

モジュール構造テンプレート

module my_addr::my_module {
    // ============ Imports ============
    use std::signer;
    use std::string::String;
    use aptos_framework::object::{Self, Object};
    use aptos_framework::event;

    // ============ Events ============
    #[event]
    struct ItemCreated has drop, store {
        item: address,
        creator: address,
    }

    // ============ Structs ============
    // Define your data structures

    // ============ Constants ============
    const E_NOT_OWNER: u64 = 1;
    const E_UNAUTHORIZED: u64 = 2;

    // ============ Init Module ============
    fun init_module(deployer: &signer) {
        // Initialize global state, registries, etc.
    }

    // ============ Public Entry Functions ============
    // User-facing functions

    // ============ Public Functions ============
    // Composable functions

    // ============ Private Functions ============
    // Internal helpers
}

ストレージタイプの選択

⚠️ ユーザーがストレージについて言及した場合(「store」、「track」、「registry」、「mapping」、「list」、「collection」):

1. 2~3 個の質問をする(references/storage-decision-tree.md を参照)
  • アクセスパターンは?(逐次、キーと値、または両方)
  • 想定サイズは?(小、大、または不明)
  • .length() が必要か?(条件による)
2. パターンから推奨する(references/storage-patterns.md)
パターン推奨ストレージ
ユーザーレジストリTable<address, UserInfo>
ステーキング記録Table<address, StakeInfo>
リーダーボードBigOrderedMap<u64, address>
トランザクションログSmartVector<TxRecord> または Vector
ホワイトリスト(<100)Vector<address>
投票記録TableWithLength<address, bool>
設定(<50)OrderedMap<String, Value>
DAO 提案BigOrderedMap<u64, Proposal>
アセットコレクションVector<Object<T>> または SmartVector
3. ガスに関する簡単なコンテキストを含める

推奨例:

  • 「ステーキングには Table<address, StakeInfo> をおすすめします。ユーザー数に上限がなく、並行操作が発生するためです(スロットを分けると並列アクセスが可能になります)」
  • 「リーダーボードには BigOrderedMap<u64, address> をおすすめします。ソートされた順序での反復処理が必要なためです(O(log n)。本番環境では allocate_spare_slots を使用してください)」
使用可能なストレージ型
  • Vector - 小規模なシーケンシャルデータ(100件未満)
  • SmartVector - 大規模なシーケンシャルデータ(100件以上)
  • Table - 順序なしのキーと値による検索
  • TableWithLength - 件数を追跡する Table
  • OrderedMap - 小規模なソート済みマップ(100件未満)
  • BigOrderedMap - 大規模なソート済みマップ(100件以上)

⚠️ SmartTable は絶対に使用しないでください(非推奨です。BigOrderedMap を使用してください)

詳細: 判断ツリー、型の比較、ガスの最適化については references/ を参照してください。

アンチパターン

  1. ❌ レガシーな TokenV1 標準や aptos_token::token のインポートは絶対に使用しないでください
  2. ❌ リソースアカウントは絶対に使用しないでください(代わりに名前付きオブジェクトを使用してください)
  3. ❌ 公開関数から ConstructorRef を絶対に返さないでください
  4. ❌ エントリー関数で署名者の検証を絶対に省略しないでください
  5. ❌ 入力の検証(数量、アドレス、文字列)を絶対に省略しないでください
  6. ❌ テストカバレッジ 100% を達成せずにデプロイしないでください
  7. ❌ 名前付きアドレスを返すだけのヘルパー関数を絶対に作成しないでください
  8. ❌ 重要なアクティビティに対するイベントの発行を絶対に省略しないでください
  9. ❌ V2 構文が使用可能な場合に、古い構文を絶対に使用しないでください
  10. ❌ 初期化が必要なコントラクトでは、init_module を絶対に省略しないでください
  11. ❌ 実際の秘密鍵やシークレットをコードにハードコードしないでください — @my_addr の名前付きアドレスと "0x..." プレースホルダーを使用してください
  12. ❌ .env や ~/.aptos/config.yaml は絶対に読み込まないでください — これらには秘密鍵が含まれています

対応すべきエッジケース

シナリオチェックエラーコード
ゼロの数量assert!(amount > 0, E_ZERO_AMOUNT)E_ZERO_AMOUNT
過大な数量assert!(amount <= MAX, E_AMOUNT_TOO_HIGH)E_AMOUNT_TOO_HIGH
空のベクターassert!(vector::length(&v) > 0, E_EMPTY_VECTOR)E_EMPTY_VECTOR
空の文字列assert!(string::length(&s) > 0, E_EMPTY_STRING)E_EMPTY_STRING
文字列が長すぎるassert!(string::length(&s) <= MAX, E_STRING_TOO_LONG)E_STRING_TOO_LONG
ゼロアドレスassert!(addr != @0x0, E_ZERO_ADDRESS)E_ZERO_ADDRESS
オーバーフローassert!(a <= MAX_U64 - b, E_OVERFLOW)E_OVERFLOW
アンダーフローassert!(a >= b, E_UNDERFLOW)E_UNDERFLOW
ゼロ除算assert!(divisor > 0, E_DIVISION_BY_ZERO)E_DIVISION_BY_ZERO
権限のないアクセスassert!(signer == expected, E_UNAUTHORIZED)E_UNAUTHORIZED
オブジェクトの所有者ではないassert!(object::owner(obj) == user, E_NOT_OWNER)E_NOT_OWNER

参考資料

詳細なパターン(references/ フォルダー):

  • references/storage-decision-tree.md - ⭐ ストレージタイプ選択フレームワーク(ストレージについて言及されたら確認)
  • references/storage-patterns.md - ⭐ ユースケースのパターンとスマートなデフォルト設定
  • references/storage-types.md - 6 種類すべてのストレージタイプの詳細な比較
  • references/storage-gas-optimization.md - ストレージのガス最適化戦略
  • references/object-patterns.md - 名前付きオブジェクト、コレクション、ネストされたオブジェクト
  • references/access-control.md - RBAC と権限システム
  • references/safe-arithmetic.md - オーバーフロー/アンダーフローの防止
  • references/initialization.md - init_module パターンとレジストリの作成
  • references/events.md - イベント発行のパターン
  • references/v2-syntax.md - Move V2 の最新機能(メソッド呼び出し、インデックス指定、ラムダ)
  • references/complete-example.md - 注釈付きの NFT コレクションコントラクト全体

パターンのドキュメント(patterns/ フォルダー):

  • ../../../patterns/move/DIGITAL_ASSETS.md - Digital Asset(NFT)標準 - NFT では重要
  • ../../../patterns/move/OBJECTS.md - オブジェクトモデルの包括的なガイド
  • ../../../patterns/move/SECURITY.md - セキュリティチェックリストとパターン
  • ../../../patterns/move/MOVE_V2_SYNTAX.md - 最新の構文例

公式ドキュメント:

関連スキル:

  • search-aptos-examples - aptos-core で類似の例を検索します(任意)
  • generate-tests - コントラクトのテストを作成します(コントラクト作成後に使用)
  • security-audit - デプロイ前にコントラクトを監査します

コメント