mcpField
S
setup-sui-contracts

setup-sui-contracts

github.com/openzeppelin开发21975 次安装2 天前更新
代码仓库

关于 setup-sui-contracts

面向使用 OpenZeppelin Contracts for Sui 构建 Sui Move 项目的开发者,指导创建项目、发现和配置依赖、依据示例集成,以及执行构建和测试。

主要功能

  1. 01初始化 Move 包
  2. 02发现 OpenZeppelin 包和示例
  3. 03配置 MVR 与 Git 依赖
  4. 04生成 Agent 指南和 MCP 配置
  5. 05使用必需环境构建和测试

适用场景

  1. 01启动 Sui Move 项目
  2. 02向 Move.toml 添加 OpenZeppelin 包
  3. 03解决依赖版本冲突
  4. 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>" }

不要依赖记忆中的包列表——库不断发展,列表会随之变化。请从库自身的元数据中查找可用内容,因为它是唯一可信来源:

  1. 一如既往,从 AI 发现入口 llms.txt 开始。
  2. 跟随其中的链接,查看它列出的每个包目录——llms.txt 是确定有哪些目录的权威来源,因此应从中读取目录列表,而不是假定列表固定(随着时间推移,会添加新的顶层目录)。每个目录表都列出了 MVR slug、Move 包名、文档和要点。
  3. 阅读各个包的 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" }

实现前先学习示例和文档

请在编写任何集成代码之前完成这一步——大多数用户不了解完整的目录,而且目录会不断扩充,因此请让库展示它提供的内容及其预期的组合方式。对于计划使用的每个包:

  1. 学习其 examples/。 每个包都附带可编译且经 CI 测试的集成示例——这些是标准的组合范例,也是了解实际使用模式(初始化、能力/见证者流程、对象所有权、测试)最快的方式。优先将示例复制并改编到 sources/,而不是从头编写。

    从 main 读取示例和元数据;将依赖固定到某个发行版。 所有 AI 元数据——llms.txt、目录、包 README、STYLEGUIDE.md 和 examples/——都应从 main 分支读取,因为该分支包含最新、最完整的内容(某个包在 main 上可能有示例,但在带标签的发行版中没有)。相反,构建依赖项解析到固定的发行版——MVR slug 或 git rev = "<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"。你不能在依赖项的地址下定义模块,因此未经修改地复制代码无法构建,必须先移入新项目。

  2. 阅读文档。 先从文档站点了解概念和指南,然后查看生成的 API 参考——通过每个目录表格中的 Docs 链接进入(该链接指向正确的版本),路径为 .../contracts-sui/<major>.x/api/<catalog_package>;其中 <major> 与你的 contracts-sui 发行版相符,<catalog_package> 是目录中的简称,而不是 Move 包名。API 参考可用于查找精确的签名、参数和中止条件。

  3. 阅读代码文档。 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 for local"。在刚生成的项目上成功执行 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_constant lint。在测试中,使用 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 中的审查流程会在应用修复后运行同样的格式化步骤。

评论

支持 Markdown