关于 setup-sui-contracts
面向使用 OpenZeppelin Contracts for Sui 构建 Sui Move 项目的开发者,指导创建项目、发现和配置依赖、依据示例集成,以及执行构建和测试。
主要功能
- 01初始化 Move 包
- 02发现 OpenZeppelin 包和示例
- 03配置 MVR 与 Git 依赖
- 04生成 Agent 指南和 MCP 配置
- 05使用必需环境构建和测试
适用场景
- 01启动 Sui Move 项目
- 02向 Move.toml 添加 OpenZeppelin 包
- 03解决依赖版本冲突
- 04为编码 Agent 准备项目指南
MCP 相关
此技能说明如何配置 OpenZeppelin Sui MCP 服务:https://mcp.openzeppelin.com/contracts/sui/mcp。其工具提供配方发现和包元数据,供设置依赖及集成示例时使用。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ openzeppelin/ openzeppelin-skills --skill setup-sui-contracts安装后在 Claude Code 中输入/setup-sui-contracts确认已加载。
详细介绍
Sui 设置
前置条件
按照 Sui 安装指南 安装 Sui CLI。CLI 包含 Move 工具链和 Move Registry (MVR) 解析器,因此无需单独安装 Move。
每当你需要了解该库时,都从它的 AI 发现入口 llms.txt 开始——它列出了库的内容(架构文档、contracts/ 和 math/ 包目录、各个包的 README、示例和 API 参考,以及审计报告)。请沿着其中的链接查找,而不要猜测路径。
OpenZeppelin Contracts for Sui 在其顶层 README 中指定了特定的 Sui CLI 版本(这是仓库元数据,并未链接到 llms.txt)——请在那里查看所需版本,并将本地安装版本与之核对。指定版本经过测试,匹配该版本最稳妥;较新的补丁或次要版本通常也能正常工作,因此版本略有差异应视为警告,而非阻碍:
sui --version
创建项目
初始化一个新的 Move 包(仅在开始新项目时执行):
sui move new my_project
cd my_project
这会创建 Move.toml、.gitignore,以及注释掉的占位模块 sources/my_project.move 和 tests/my_project_tests.move,使用 Move edition = "2024"。这些模块是无效的占位内容(完全包裹在 /* … */ 中);添加自己的模块时,请替换或删除它们。
OpenZeppelin 依赖
OpenZeppelin 包发布在 Move Registry (MVR) 上,并以 r.mvr 格式添加到 Move.toml 的 [dependencies] 下,将 Move 包名映射到其 MVR slug:
[dependencies]
<move_package_name> = { r.mvr = "@openzeppelin-move/<slug>" }
不要依赖记忆中的包列表——库不断发展,列表会随之变化。请从库自身的元数据中查找可用内容,因为它是唯一可信来源:
- 一如既往,从 AI 发现入口
llms.txt开始。 - 跟随其中的链接,查看它列出的每个包目录——
llms.txt是确定有哪些目录的权威来源,因此应从中读取目录列表,而不是假定列表固定(随着时间推移,会添加新的顶层目录)。每个目录表都列出了 MVR slug、Move 包名、文档和要点。 - 阅读各个包的
README.md,获取准确的r.mvr安装代码片段及其模块列表。确认 slug 可以解析——可以在 moveregistry.com 上查询,或者通过运行构建(见下文)来最终确认;构建会根据 MVR 解析每个 slug。
目录路径是相对于目录文件所在目录的。 位于
<catalog-dir>/README.md的目录文件会以相对于<catalog-dir>/的Path列出每个包,因此包的原始 README 路径为.../main/<catalog-dir>/<path>/README.md——应根据链接到它的目录文件所在目录解析<path>,而不是相对于仓库根目录(直接使用.../main/<path>/README.md会返回 404)。如有疑问,请列出仓库树:gh api 'repos/OpenZeppelin/contracts-sui/git/trees/main?recursive=1'(请为 endpoint 加上引号——在某些 shell 中,不加引号的?会被视为 glob)。
Move 包名(用于
use语句)与 MVR slug 不同——slug@openzeppelin-move/integer-math对应的 Move 包名是openzeppelin_math。只添加项目实际使用的包。
一个包通常包含多个模块。 目录列出的是包;你需要的构建块通常只是一个包中的某个模块。请阅读包 README 中的模块列表及其
examples/,了解包实际提供的内容——不要假定一个包就等于一个组件,也不要依赖固定的概念目录:包和模块的集合会不断扩展。
OpenZeppelin Sui MCP 会通过确定性的工具调用提供相同的元数据——
sui-list-recipes(发现配方)、sui-get-recipe(配方源码及其使用的包,每个包都附带安装行),以及sui-get-package(包的安装行和文档)。如果 MCP 可用,请查询它,而不是手动遍历文件,然后将返回的安装行写入Move.toml,并将配方源码移入你的包中。MCP 返回的是数据,不是可构建的包——只有完成写入配置和迁移源码,才能将其变为可构建的包。
不在 Move Registry 上的包
并非所有包都在 Move Registry 上——有些包根本没有发布到那里。如果某个包的 README.md 没有 r.mvr 安装代码片段,请改用 GitHub release 依赖——将 git 依赖固定到某个 release 标签,而不是会变化的分支:
[dependencies]
<move_package_name> = { git = "https://github.com/OpenZeppelin/contracts-sui.git", subdir = "<catalog-dir>/<path>", rev = "<release-tag>" }
<catalog-dir>/<path> 是包在仓库中的目录(即其目录表行中列出的同一个 Path);请将 rev 固定到已发布的 GitHub release 标签,而不是分支。所有 OpenZeppelin 依赖都应固定到同一个修订版本,以避免下文所述的菱形依赖冲突。
解决版本冲突(菱形依赖)
两个 OZ 包可能会引入共享传递依赖的不同修订版本——例如,一个包内置了某个数学包的修订版本,而你又直接依赖了该数学包。发生这种情况时,构建会中止:
Package depends on multiple versions of the package with ID 0x…
解决方法是在直接声明的依赖项中添加 override = true,强制所有使用方采用该修订版本:
[dependencies]
openzeppelin_math = { r.mvr = "@openzeppelin-move/integer-math", override = true }
openzeppelin_fp_math = { r.mvr = "@openzeppelin-move/fixed-point-math" }
实现前先学习示例和文档
请在编写任何集成代码之前完成这一步——大多数用户不了解完整的目录,而且目录会不断扩充,因此请让库展示它提供的内容及其预期的组合方式。对于计划使用的每个包:
-
学习其
examples/。 每个包都附带可编译且经 CI 测试的集成示例——这些是标准的组合范例,也是了解实际使用模式(初始化、能力/见证者流程、对象所有权、测试)最快的方式。优先将示例复制并改编到sources/,而不是从头编写。从
main读取示例和元数据;将依赖固定到某个发行版。 所有 AI 元数据——llms.txt、目录、包 README、STYLEGUIDE.md和examples/——都应从main分支读取,因为该分支包含最新、最完整的内容(某个包在main上可能有示例,但在带标签的发行版中没有)。相反,构建依赖项解析到固定的发行版——MVR slug 或 gitrev = "<release-tag>"(见上文)——以确保构建可复现。两者可能不同:从main复制的示例可能引用了比固定发行版更新的 API,此时移入新项目时要进行改编(或升级固定的发行版)。将复制的示例移入新项目。 示例模块是在库自身的地址下声明的(例如
module openzeppelin_access::example_reward_treasury;)——它们是库内的演示代码,不是可直接用于使用方的代码。将示例复制到sources/时,请将模块重命名为你的包名(module <your_package>::<module>;,其中<your_package>是Move.toml中的[package]名称)。use openzeppelin_*::…行保持不变——这些行引用的是依赖项。如果示例定义了一次性见证者——即名称为模块名大写形式、由init使用的结构体——也要将该结构体重命名为与你的新模块名一致,否则init会失败,并提示 "Invalid parameter … Expected a one-time witness type"。你不能在依赖项的地址下定义模块,因此未经修改地复制代码无法构建,必须先移入新项目。 -
阅读文档。 先从文档站点了解概念和指南,然后查看生成的 API 参考——通过每个目录表格中的
Docs链接进入(该链接指向正确的版本),路径为.../contracts-sui/<major>.x/api/<catalog_package>;其中<major>与你的 contracts-sui 发行版相符,<catalog_package>是目录中的简称,而不是 Move 包名。API 参考可用于查找精确的签名、参数和中止条件。 -
阅读代码文档。 API 参考由模块的文档注释生成,因此已安装的源代码才是权威依据。使用
sui move build --doc --build-env testnet在本地生成相同的文档(只要存在 MVR 依赖,就必须使用--build-env)——此命令也会为每个 OZ 依赖生成文档,存放在build/<your_package>/docs/dependencies/<move_package_name>/下——或者直接阅读已安装源代码中的文档注释。
从 llms.txt 和各个包的 README 中了解可用的示例,然后在 contracts-sui 仓库中浏览这些示例。
导入约定
通过 Move 包名(Move.toml 条目的左侧)后接模块名来导入模块——use <move_package_name>::<module>;——然后遵循包的 examples/ 和 API 文档中的用法模式。
有些操作通过
public use fun重新导出,以方法语法(value.op(...))提供,因此底层函数可能位于与类型不同的模块中。如果找不到预期的自由函数,请检查包源代码中的public use fun行,并以方法形式调用。
生成 Agent 指南
在项目根目录创建 AGENTS.md,让之后打开此项目的任何 Agent 都知道项目基于 OpenZeppelin Contracts for Sui 构建,以及权威信息源所在的位置。遵循 contracts-sui AGENTS.md 的理念:创建精简的指引文件,不重复说明规则,而是链接到权威信息源。另创建一个仅包含 @AGENTS.md 的 CLAUDE.md,以便 Claude Code 读取(与 contracts-sui 本身的设置方式一致)。
# AGENTS.md
This is a Sui Move project built on **OpenZeppelin Contracts for Sui**.
## Sources of truth — read these first
- AI discovery entry point: https://raw.githubusercontent.com/OpenZeppelin/contracts-sui/main/llms.txt
(points to the package catalogs, each package's `examples/`, the generated API reference, and audits)
- Docs: https://docs.openzeppelin.com/contracts-sui
- API reference: https://docs.openzeppelin.com/contracts-sui/<major>.x/api/<catalog_package> (`<major>` matches your contracts-sui release and `<catalog_package>` is the short catalog name; the catalog's `Docs` links carry the correct version)
- Code docs (local): `sui move build --doc --build-env testnet` → `build/<your_package>/docs/` (named after your own package; includes OZ dependencies)
## Conventions
- Before implementing, study each package's `examples/` (compilable, CI-tested composition recipes) and its API docs; prefer audited library components over custom logic.
- Import modules via the Move package name (`use <move_package_name>::<module>;`); a package typically exposes several modules — check its README and `examples/`.
- MVR-backed builds require a build env: `sui move build --build-env testnet`. If two OZ deps pull different revisions of a shared transitive package, add `override = true` to the directly-declared one.
- For fungible-asset custody, consider Sui [Address Balances](https://docs.sui.io/onchain-finance/asset-custody/address-balances/) (SIP-58) over summing `Coin` objects where it fits.
- Code style follows the OpenZeppelin Sui conventions — see Code Quality below.
## Code quality
The library's conventions are the single source of truth for Move style:
- Rules: https://raw.githubusercontent.com/OpenZeppelin/contracts-sui/main/STYLEGUIDE.md
- Design rationale: https://raw.githubusercontent.com/OpenZeppelin/contracts-sui/main/ARCHITECTURE.md
- Review procedure (reads STYLEGUIDE.md, reports/fixes violations): https://raw.githubusercontent.com/OpenZeppelin/contracts-sui/main/.claude/commands/code-quality.md
## MCP
The OpenZeppelin Sui MCP (https://mcp.openzeppelin.com/contracts/sui/mcp) serves recipes and per-package install lines as deterministic tool calls (`sui-list-recipes`, `sui-get-recipe`, `sui-get-package`) — a queryable alternative to reading `llms.txt` + the catalogs by hand. This project ships it pre-wired in `.mcp.json`. It returns data, not a buildable package — wiring the install lines into `Move.toml` and re-homing the source into this package is what turns it into one.
另外,在项目根目录添加预先配置好的 MCP 配置,让任何 Agent 克隆项目后都能了解 OpenZeppelin 在 Sui 上下文中的信息,无需手动设置。Claude Code 会读取 .mcp.json;其他助手(Cursor、Cline)可在各自的 MCP 配置中使用相同的服务器 URL。
{
"mcpServers": {
"openzeppelin-sui": {
"type": "http",
"url": "https://mcp.openzeppelin.com/contracts/sui/mcp"
}
}
}
构建与测试
由于依赖项通过 MVR 解析,构建和测试必须指定构建环境。开发时使用 testnet(发布构建也可使用 mainnet):
sui move build --build-env testnet
sui move test --build-env testnet
如果不使用
--build-env,CLI 将无法解析r.mvr依赖项,并报错 "Could not determine the correct dependencies to use forlocal"。在刚生成的项目上成功执行build和test,是验收标准。
项目中有实际代码后,添加 --lint --warnings-are-errors,尽早发现代码风格和死代码问题:
sui move test --build-env testnet --lint --warnings-are-errors
需要了解的测试模式
使用这些包构建的代码在测试中会反复遇到一些 Sui 惯例——该库自身 examples/ 中的测试是权威参考:
-
测试中的一次性见证(OTW)。 接收 OTW 的模块会在
init中消耗它,且无法在运行时构造。要在单元测试中测试此类模块,需公开一个#[test_only]初始化函数来构造 OTW(其结构体可在#[test_only]上下文中构造):#[test_only] public fun init_for_testing(ctx: &mut TxContext) { init(MY_OTW {}, ctx) } -
Clock。 依赖时间的模块会接收
sui::clock::Clock。在测试中使用create_for_testing、increment_for_testing和destroy_for_testing。 -
断言。 在
--warnings-are-errors下,字面量中止代码(assert!(cond, 0))会触发abort_without_constantlint。在测试中,使用assert_eq!(来自std::unit_test)或不带中止代码的assert!(cond);在非测试代码中,应使用命名错误常量进行断言,而不是字面量。
组合约束: Sui 的
init函数不会接收Clock(或其他运行时参数)。因此,构造函数需要Clock的组件无法在init中完全构建——应在init中创建并共享对象,然后在接收Clock的后续入口函数中完成设置。该包的examples/展示了预期的两步模式。
代码质量
按照 OpenZeppelin 自身的惯例统一项目的 Move 风格,让集成方的代码与该库保持一致。与依赖项一样,不要重复说明规则,而应指向该库唯一的权威信息源:
STYLEGUIDE.md— 编纂成文的规则(命名、模块与包、章节顺序、导入、结构体、函数、惯用 Move 2024、集合与对象大小、测试、Lint 抑制、文档)。ARCHITECTURE.md— 这些规则所依据的设计原理(基于能力的访问、拥有对象与共享对象、PTB 可组合性、有界状态、升级安全性)。.claude/commands/code-quality.md— 审查流程:读取STYLEGUIDE.md,逐个检查.move文件,并报告或修复违规。该流程采用纯 Markdown 编写,因此任何 Agent 都能遵循,即使斜杠命令仅适用于 Claude。
在生成的 AGENTS.md 中的 Code quality 标题下记录这些内容(见上文),确保下游 Agent 克隆项目后遵循相同惯例。
格式化
contracts-sui 使用 @mysten/prettier-plugin-move Prettier 插件格式化 Move,格式应与之匹配,确保集成方的代码布局一致。安装该插件,并添加与库匹配的 .prettierrc:
npm i -D prettier @mysten/prettier-plugin-move
{
"tabWidth": 4,
"printWidth": 100,
"useModuleLabel": true,
"autoGroupImports": "module",
"plugins": ["@mysten/prettier-plugin-move"]
}
然后从仓库根目录进行格式化(如果无法解析该插件,Prettier 会静默跳过 .move 文件,因此请将其保留在 devDependencies 中):
npx prettier --write "**/*.move"
autoGroupImports: "module"会规范化use分组,因此请运行--write进行格式化——不要手动调整导入并期望--check首次运行就能通过。
code-quality.md 中的审查流程会在应用修复后运行同样的格式化步骤。