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. 01创建 NFT 集合或市场
  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. 始终使用 Object<AptosToken> 作为 NFT 引用(不要使用通用的 Object<T>)
  4. 绝不使用旧版 TokenV1 标准或 aptos_token::token 模块(已弃用)
  5. 完整的 NFT 模式请参阅 ../../../patterns/move/DIGITAL_ASSETS.md
对象模型
  1. 所有对象引用始终使用 Object<T>(绝不使用原始地址)
  2. 生成所有引用(TransferRef、DeleteRef):在 ConstructorRef 销毁前,于构造函数中生成
  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. 使用内联函数和 lambda 进行迭代
  2. 使用接收者风格的方法调用:obj.is_owner(user)(将第一个参数定义为 self)
  3. 使用向量索引:vector[index],而不是 vector::borrow()
  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. 简要说明 Gas 上下文

推荐示例:

  • “对于质押,我推荐使用 Table<address, StakeInfo>,因为用户数量没有上限,且会有并发操作 (独立槽位支持并行访问)”
  • “对于排行榜,我推荐使用 BigOrderedMap<u64, address>,因为你需要排序迭代(O(log n),生产环境请使用 allocate_spare_slots)”
可用的存储类型
  • Vector - 小型顺序存储(<100 个条目)
  • SmartVector - 大型顺序存储(100+ 个条目)
  • Table - 无序键值查找
  • TableWithLength - 带计数跟踪的表
  • OrderedMap - 小型有序映射(<100 个条目)
  • BigOrderedMap - 大型有序映射(100+ 个条目)

⚠️ 绝不要使用 SmartTable(已弃用,请使用 BigOrderedMap)

详情: 有关决策树、类型比较和 Gas 优化,请参阅 references/。

反模式

  1. ❌ 绝不要使用旧版 TokenV1 标准或导入 aptos_token::token
  2. ❌ 绝不要使用资源账户(请改用命名对象)
  3. ❌ 绝不要从公共函数返回 ConstructorRef
  4. ❌ 绝不要在 entry 函数中跳过签名者验证
  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 - 存储的 Gas 优化策略
  • 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 功能(方法调用、索引、lambda)
  • references/complete-example.md - 完整且附有注释的 NFT 集合合约

模式文档(patterns/ 文件夹):

  • ../../../patterns/move/DIGITAL_ASSETS.md - 数字资产(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 - 在部署前审计合约

评论

支持 Markdown