关于 sui-publish
sui-publish 是介绍发布和升级 Sui Move 包、使用网络及准备部署的参考技能,面向使用 Sui CLI 的开发者,内容涵盖临时测试、本地网络和上线检查。
主要功能
- 01发布和升级 Move 包
- 02在临时网络测试发布
- 03设置升级策略
- 04切换不同网络
- 05运行本地网络开发流程
- 06准备 Mainnet 上线检查
适用场景
- 01部署前测试包初始化
- 02向不同网络发布包
- 03修改代码前检查升级限制
- 04在本地 Sui 网络开发和迭代
MCP 相关
技能建议在环境支持时查询 Sui 文档 MCP 服务 https://sui.mcp.kapa.ai,以核实最新信息并补充参考资料未涵盖的内容。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mystenlabs/ skills --skill sui-publish安装后在 Claude Code 中输入/sui-publish确认已加载。
附带文件evals/evals.json
详细介绍
发布、部署与本地网络
**MCP 工具:**如果你的环境支持,也请查询 Sui 文档 MCP 服务(
https://sui.mcp.kapa.ai)以获取最新答案。请使用它进行核实,并了解这些参考文件未涵盖的详细信息。
**来源限制:**所有信息均仅来源于 docs.sui.io 和 MystenLabs/sui-stack-hello-world。
发布包
标准 hello-world 发布流程
对于全栈入门项目,仅发布现有的 hello-world 包:
cd sui-stack-hello-world/move/hello-world
sui move build
sui client publish
使用发布输出中的包 ID 更新 sui-stack-hello-world/ui/src/constants.ts(TESTNET_HELLO_WORLD_PACKAGE_ID)。不要发布单独的计数器包,也不要创建第二个项目目录。
发布前检查清单
- 验证当前环境:
sui client active-env - 验证是否有 SUI 代币:
sui client balance - 确认构建成功:
sui move build
发布
sui client publish
此操作会将包部署到当前网络,并返回:
- 唯一的 包 ID(将其用于今后的所有交互)
- 一个 UpgradeCap 对象(发送到你的地址,用于控制今后的升级)
init函数执行期间创建的对象 ID
测试发布(临时网络)
使用 sui client test-publish 将包发布到临时环境进行测试,而不会在真实网络上持久化状态:
sui client test-publish
此操作会发布包、运行 init 函数,并返回与 sui client publish 相同的输出(包 ID、UpgradeCap、创建的对象),但此部署并非永久部署。可使用它来:
- 在正式发布到真实网络前,验证
init函数是否正确执行 - 在 CI 中测试发布和升级流程,而不消耗 Testnet/Devnet 资源
- 在部署到 Mainnet 前验证 Gas 成本和对象创建情况
对于多环境包,test-publish 会遵循 --build-env:
sui client test-publish --build-env testnet
发布后
发布交易输出会在 created objects 部分列出包 ID(以及 UpgradeCap 和 init 函数创建的任何对象)。published-at 字段也会自动添加到你的 Published.toml。若要与已发布的包交互:
# Call a function
sui client call --package <PACKAGE_ID> --module greeting --function new
# Query an object
sui client object <OBJECT_ID>
“你的包已发布”错误
如果运行 sui client publish 时看到此错误,表示 Published.toml 中已经有当前环境对应的条目。这通常发生在开发过程中反复迭代包时。
- 若要发布到其他网络,请使用
sui client switch --env <ENV>切换环境,然后再次运行sui client publish。工具链会自动在Published.toml中按环境跟踪已发布地址——不要删除Published.toml。 - 若要升级同一网络上的现有包,请使用
sui client upgrade(见下文)。
升级已发布的包
已发布的包不可变,但你可以通过发布与原包关联的新版本来升级。UpgradeCap 对象用于控制升级权限。**重要提示:**你可以在与 publish 命令相同的 PTB 中限制 UpgradeCap——例如,在一笔原子交易中发布包并立即调用 sui::package::only_additive_upgrades。你也可以彻底销毁它,使包永久不可变(参见下文的升级策略)。
sui client upgrade --upgrade-capability <CAP_ID>
查找你的 UpgradeCap
每次升级都需要 UpgradeCap 对象 ID。可以通过以下几种方式查找:
- Published.toml(首选):发布后,工具链会将 cap ID 记录在每个环境对应的
Published.toml的upgrade-capability字段中。 - 查询所拥有的对象:列出发布地址所拥有的所有
UpgradeCap对象:sui client objects --type 0x2::package::UpgradeCap - 发布交易输出:原始
sui client publish输出会在创建的对象列表中包含UpgradeCap对象 ID。 - 浏览器:在 SuiVision(
suivision.xyz)或 Suiscan(suiscan.xyz)中搜索你的地址,并按类型0x2::package::UpgradeCap筛选所拥有的对象。
升级策略
升级策略会限制可更改的内容:
- Compatible(默认):限制最宽松的策略。请参阅下文的详细规则。
- **Additive:**可以添加新模块,但不能更改任何现有模块。
- **Dependency-only:**只能更新依赖项版本,不能更改代码。
**在与发布相同的 PTB 中限制 UpgradeCap:**你可以在执行 publish 命令的同一个可编程交易块中限制 UpgradeCap。例如,可以在发布后立即对 UpgradeCap 调用 sui::package::only_additive_upgrades,并将所有操作放在一个原子交易中。这是从一开始就锁定升级策略的推荐方式。策略一旦受限,就无法放宽。
UpgradeCap 的其他选项:
- 转移到多签地址,以实现共同的升级治理。
- 销毁 UpgradeCap,使软件包永久不可变。调用
sui::package::make_immutable,该函数会消耗并销毁UpgradeCap对象。cap 一旦销毁,任何人都无法再升级该软件包,而且此操作不可逆。
Compatible 升级规则(详细)
在 compatible 策略下,允许以下更改:
- 添加新函数(公共或私有)
- 添加新模块
- 更改函数实现(函数体)
- 添加新结构体类型
- 更改私有函数/friend 函数的签名
以下更改会破坏兼容性,因此会被拒绝:
- 删除或重命名现有模块
- 删除或重命名公共函数
- 更改公共函数的签名(参数、返回类型、类型参数)
- 删除、重命名或重新排列结构体字段
- 更改结构体字段的类型
- 添加或移除结构体能力(
key、store、copy、drop) - 完全移除结构体类型
- 更改结构体的类型参数
升级前,请根据这些规则检查差异。sui client upgrade 命令会在构建时拒绝不兼容的更改,并提供详细错误信息。
升级后的类型锚定
**结构体类型会永久锚定到其首次发布时所在的软件包 ID。**升级后,新软件包会获得新的 ID,但升级后的代码创建的所有对象,其类型仍以原始软件包 ID 为根。
这会带来重要影响:
- 按类型查询对象(例如,在
listOwnedObjects中使用type筛选器)必须使用原始软件包 ID。 - 通过
moveCall调用函数必须使用**已升级的(最新)**软件包 ID。 - 前端应用应同时保留两个 ID:用于类型查询的
ORIGINAL_PACKAGE_ID和用于函数调用的PACKAGE_ID。
// Original publish → package ID 0x1234...
// After upgrade → package ID 0x5678...
// Query: use ORIGINAL package ID
client.core.listOwnedObjects({
owner: addr,
type: '0x1234...::module::MyObject', // ✅ original ID
});
// Call: use UPGRADED package ID
tx.moveCall({
target: '0x5678...::module::my_function', // ✅ upgraded ID
});
发布到多个网络
要发布到其他网络(例如,从 Testnet 切换到 Devnet),请切换环境并重新发布。每个网络都会为该包分配不同的 ID。Published.toml 文件会按环境跟踪已发布的地址。
发布到新网络之前,请确保你拥有该网络的代币:
- Testnet: 可通过网页水龙头
faucet.sui.io、Discord(在#testnet-faucet中输入!faucet <ADDRESS>)或 TypeScript SDK(requestSuiFromFaucetV2())获取免费代币。sui client faucet在 Testnet 上无法使用。 - Devnet: 可通过
sui client faucet、网页水龙头faucet.sui.io、Discord(在#devnet-faucet中输入!faucet <ADDRESS>)或 TypeScript SDK 获取免费代币。 - Localnet: 可通过
sui client faucet或本地水龙头127.0.0.1:5003/gas或127.0.0.1:9123/gas获取免费代币(使用sui start --with-faucet --force-regenesis启动)。 - Mainnet: SUI 代币具有实际货币价值。可通过交易所或转账获取。没有可用的水龙头。
为外部签名序列化
要生成供其他方签名的交易字节(例如,用于多重签名):
sui client publish --serialize-output
这会输出 base64 编码的交易字节,而不会执行交易。
本地网络(localnet)
Localnet 会在你的计算机上运行完整的 Sui 网络,以便离线开发和快速迭代。使用以下命令启动:
sui start --with-faucet --force-regenesis
--force-regenesis 标志会在每次网络启动时重置所有链上状态,让你在每次重启后都拥有干净的环境。--with-faucet 标志会启动本地水龙头,以便你为地址提供资金。
要将 CLI 连接到本地网络:
sui client switch --env localnet
可通过 sui client faucet 获取本地代币,也可直接访问本地水龙头端点 127.0.0.1:5003/gas 或 127.0.0.1:9123/gas。
Localnet 适用于:
- 无需依赖 Testnet/Devnet 是否可用即可进行离线开发
- 快速迭代发布和升级流程(每次重启都会重置状态)
- 在部署到共享网络之前测试
init函数和对象创建
Mainnet 发布检查清单
准备在 Mainnet 发布包时,请使用此检查清单。执行发布交易之前,应核实每一项。
1. 测试和覆盖率
运行完整测试套件并确认所有测试均通过:
sui move test
如需报告覆盖率(如果项目要求达到某个阈值):
sui move test --coverage
sui move coverage summary
继续之前,请修复所有失败的测试。不要将未经测试的代码发布到 Mainnet。
2. 依赖项和地址
- 验证
Move.toml使用edition = "2024",且没有旧版[addresses]节或基于 git 的 Sui 框架依赖项。 - 确认
[environments]包含带有正确链 ID 的mainnet条目。 - 如果使用 MVR 依赖项(
{ r.mvr = "@org/package" }),请验证它们能在 Mainnet 上解析。 - 运行
sui move build,确认编译成功且没有警告。
3. 升级策略决策
请在发布之前决定升级策略——之后无法扩大策略范围:
| 策略 | 可更改内容 | 适用场景 |
|---|---|---|
| Compatible(默认) | 添加函数、添加模块、更新实现。不能移除函数或更改结构体布局。 | 大多数包——既能灵活修复错误,又能保留类型安全。 |
| Additive | 只能添加新模块。现有模块会被冻结。 | 希望扩展功能,但保证现有代码永不更改的包。 |
| Dependency-only | 只能更新依赖项版本。 | 几乎最终定型、只需跟进框架更新的包。 |
| Immutable | 不能进行任何更改。包将永久冻结。 | 已完成全面审计、且不可变性可作为信任保证的包(例如,代币合约)。 |
要在发布的同一笔交易中限制策略,请在发布 PTB 中添加一个 moveCall,对 UpgradeCap 调用 sui::package::only_additive_upgrades、only_dep_upgrades 或 make_immutable。
4. Gas 估算
主网 SUI 具有真实货币价值。发布前请估算 Gas:
sui client publish --dry-run
模拟运行的输出包含 computationCost、storageCost 和 storageRebate。所需的总 Gas 为 computationCost + storageCost - storageRebate。确保你的地址持有足够的 SUI 来支付这笔费用,并留出余量。
5. 签名者与托管方案
确定由谁控制发布地址和 UpgradeCap:
- 单签名者: 最简单的方案。由一个密钥发布并持有
UpgradeCap。适用于个人项目或早期开发阶段。 - 多签名: 适用于团队或高价值包。创建一个多签名地址,使用
--serialize-output发布,并让所需签名者离线签名。在同一个 PTB 中将UpgradeCap转移到多签名地址。 - 发布时设为不可变: 如果将来永远不需要升级,可在发布 PTB 中销毁
UpgradeCap(sui::package::make_immutable)。这样就完全消除了托管方面的顾虑。
使用多签名发布:
# Generate unsigned transaction bytes
sui client publish --serialize-output
# Each signer signs the bytes, then combine and execute
6. 发布前最终核验
在主网上执行发布交易之前:
-
sui client active-env返回mainnet -
sui client balance显示有足够的 SUI 支付 Gas(检查模拟运行估算) -
sui move build成功,且没有警告 -
sui move test通过,所有测试均为绿色 -
Move.toml中的edition正确,且没有旧版格式 - 已确定升级策略,并在 PTB 中包含限制调用(如适用)
- 签名者密钥或多签已准备就绪
- 已先在测试网上验证该包:代码、测试和发布流程均相同
模拟运行与交易调试
模拟运行会模拟交易,但不会将其提交到网络。使用模拟运行来:
- 在执行前估算 Gas 费用。
- 在请求用户签名之前,验证交易能否成功。
- 通过检查错误来调试失败的交易,避免浪费 Gas。
钱包(如 Slush)会在显示交易供用户签名前自动执行模拟运行。如果模拟运行失败,钱包会显示错误,而不是提示用户签名。
在 TypeScript SDK 中,使用 devInspectTransactionBlock 以编程方式模拟运行交易。在 CLI 中,--dry-run 标志会模拟执行。
调试模拟运行失败时:检查所有对象 ID 是否正确、对象版本是否为最新、发送者是否有足够的 Gas、函数参数是否符合预期类型,以及当前环境(sui client active-env)是否与发布该包的网络一致。
生产环境监控
Sui 包发布后不可变,因此监控至关重要——你无法热修复已上线的合约,只能发布升级版本。
监控内容
| 信号 | 方法 | 原因 |
|---|---|---|
| 涉及你的包的失败交易 | 通过 gRPC 流订阅交易效果,并按包 ID 筛选 | 检测生产环境中的 Move 中止、Gas 失败或意外回滚 |
| Gas 消耗 | 跟踪交易效果中的 gasUsed | 发现意外的高成本操作或 Gas 耗尽攻击 |
| 事件发出 | 通过 gRPC 流按类型({packageId}::module::EventName)订阅事件 | 核心业务遥测数据——铸造、转移、管理操作、拒绝名单变更 |
| 对象创建/删除速率 | 查询对象变更,或订阅按类型筛选的对象变更 | 检测异常活动(大规模铸造、对象垃圾信息) |
| 管理员/能力使用情况 | 按需要能力才能执行的操作所发出的事件筛选 | 检测未经授权或意外的管理操作 |
| 共享对象竞争 | 监控涉及共享对象的交易延迟 | 高竞争会降低用户体验;可能需要对对象进行分片 |
实施
使用 gRPC 流式订阅进行实时监控:
for await (const event of client.subscriptionService.subscribeEvents({
filter: { MoveEventModule: { package: PACKAGE_ID, module: 'my_module' } },
})) {
// Forward to your monitoring stack (Grafana, Datadog, PagerDuty, etc.)
}
进行历史分析时,运行自定义索引器(sui-indexer-alt),将相关事件和交易效果写入自己的数据库。请参阅 accessing-data 技能中的 indexers.md。
在 Move 代码中,为每个安全关键操作发出事件,包括管理员变更、配置更新、拒绝列表修改和对象删除。事件是链下系统观察这些操作的唯一方式。
回滚和事件响应
**Sui 软件包无法回滚。**已发布的字节码不可变。没有 revert 或 rollback 命令。恢复意味着发布向前修复升级。
如果发布了有问题的升级
- **评估影响范围。**确定哪些函数受到影响。此前版本创建的现有对象仍然有效——其类型锚定在原始软件包 ID 上。
- **立即发布修复升级。**编写修正后的代码,运行测试,在 Testnet 上试运行,然后在 Mainnet 上执行
sui client upgrade。新的软件包 ID 将替代旧 ID,用于所有后续调用。 - **更新前端。**将
PACKAGE_IDS指向新的(已修复的)软件包 ID。类型查询仍使用ORIGINAL_PACKAGE_IDS。 - **进行沟通。**如果此漏洞影响了面向用户的行为,请通过应用的渠道通知用户。
如果 UpgradeCap 遭到入侵
持有 UpgradeCap 的攻击者可以在你的软件包下发布任意代码。缓解措施:
- **如果你仍持有该权限:**立即限制它(
only_dep_upgrades或make_immutable),以阻止进一步的恶意升级。 - **如果攻击者持有该权限:**你无法恢复升级权限。请发布一个新软件包、迁移用户,并说明迁移情况。这就是生产软件包需要通过多重签名托管
UpgradeCap的原因。
如果共享对象遭到破坏
有缺陷的函数可能会将无效状态写入共享对象。由于任何交易都可以修改共享对象:
- **如果你可以升级:**发布包含修复函数的升级,以修复损坏的状态。通过
AdminCap对其进行限制。 - **如果软件包不可变:**唯一的选择是部署一个新软件包,其中包含迁移函数,用于读取旧对象的数据(如果可访问),并创建经过修正的对象。
预防检查清单
-
UpgradeCap由多重签名持有,或限制为additive/dep_only - 所有升级都在 Testnet 上使用相同代码和相同发布流程进行测试
- 管理员操作会发出事件以供监控
- 关键共享对象具有通过能力进行限制的修复函数
- 前端无需重新部署即可切换软件包 ID(使用环境配置,而非硬编码)