关于 write-contracts
write-contracts 是面向 Aptos Move 开发者的合约编写技能,涵盖数字资产、对象、安全、存储选择及便于测试的访问函数。
主要功能
- 01Aptos Move 合约模式
- 02Digital Asset NFT 指引
- 03对象生命周期与所有权规则
- 04签名者与输入验证
- 05存储类型选择建议
- 06便于测试的 view 访问函数
适用场景
- 01创建 NFT 集合或市场
- 02实现基于对象的合约模块
- 03为注册表或日志选择存储
- 04添加访问控制与输入验证
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ aptos-labs/ aptos-agent-skills --skill write-contracts安装后在 Claude Code 中输入/write-contracts确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/access-control.mdcomplete-example.mdevents.mdinitialization.mdobject-patterns.mdsafe-arithmetic.mdstorage-decision-tree.mdstorage-gas-optimization.mdstorage-patterns.mdstorage-types.mdv2-syntax.md详细介绍
编写合约技能
核心规则
数字资产(NFT)⭐ 极其重要
- 所有 NFT 相关合约(集合、市场、铸造)始终使用数字资产(DA)标准
- 始终导入
aptos_token_objects::collection和aptos_token_objects::token模块 - 始终使用
Object<AptosToken>作为 NFT 引用(不要使用通用的Object<T>) - 绝不使用旧版 TokenV1 标准或
aptos_token::token模块(已弃用) - 完整的 NFT 模式请参阅
../../../patterns/move/DIGITAL_ASSETS.md
对象模型
- 所有对象引用始终使用
Object<T>(绝不使用原始地址) - 生成所有引用(TransferRef、DeleteRef):在 ConstructorRef 销毁前,于构造函数中生成
- 从构造函数返回
Object<T>(绝不返回 ConstructorRef) - 验证所有权:
object::owner(obj) == signer::address_of(user) - 为对象签名者使用
object::generate_signer(&constructor_ref) - 单例使用命名对象:
object::create_named_object(creator, seed)
安全性
- 入口函数始终验证签名者权限:
assert!(signer::address_of(user) == expected, E_UNAUTHORIZED) - 始终验证输入:非零金额、地址验证、字符串长度检查
- 绝不在公共函数中公开
&mut引用 - 绝不跳过入口函数中的签名者验证
现代语法
- 使用内联函数和 lambda 进行迭代
- 使用接收者风格的方法调用:
obj.is_owner(user)(将第一个参数定义为self) - 使用向量索引:
vector[index],而不是vector::borrow() - 直接使用命名地址:
@marketplace_addr(不要使用辅助函数)
必需模式
- 使用 init_module 在部署时初始化合约
- 为所有重要活动发出事件(创建、转移、更新、删除)
- 定义清晰的错误常量,并使用描述性名称(E_NOT_OWNER、E_INSUFFICIENT_BALANCE)
可测试性
- 为结构体字段添加访问器函数——独立模块中的测试无法直接访问结构体字段
- 为只读访问器函数使用
#[view]注解 - 多字段访问时,访问器返回元组:
(seller, price, timestamp) - 将
#[view]放在文档注释之前——在#[view]前写/// comment会导致编译器警告。先写#[view], 再写///
快速工作流
- 创建模块结构 → 定义结构体、事件、常量和 init_module
- 实现对象创建 → 使用正确的构造函数模式,并预先生成所有引用
- 添加访问控制 → 验证所有权并验证所有输入
- 安全检查 → 部署前使用
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/。
反模式
- ❌ 绝不要使用旧版 TokenV1 标准或导入
aptos_token::token - ❌ 绝不要使用资源账户(请改用命名对象)
- ❌ 绝不要从公共函数返回 ConstructorRef
- ❌ 绝不要在 entry 函数中跳过签名者验证
- ❌ 绝不要跳过输入验证(金额、地址、字符串)
- ❌ 绝不要在测试覆盖率未达到 100% 时部署
- ❌ 绝不要创建只返回命名地址的辅助函数
- ❌ 绝不要跳过重要活动的事件发出
- ❌ 有 V2 语法可用时,绝不要使用旧语法
- ❌ 需要初始化的合约绝不要跳过 init_module
- ❌ 绝不要在代码中硬编码真实私钥或密钥 — 请使用
@my_addr命名地址和"0x..."占位符 - ❌ 绝不要读取
.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- 现代语法示例
官方文档:
- Digital Asset Standard:https://aptos.dev/build/smart-contracts/digital-asset
- Object Model:https://aptos.dev/build/smart-contracts/object
- Security Guidelines:https://aptos.dev/build/smart-contracts/move-security-guidelines
相关技能:
search-aptos-examples- 在 aptos-core 中查找类似示例(可选)generate-tests- 为合约编写测试(在编写合约后使用)security-audit- 在部署前审计合约
评论
支持 Markdown