mcpField
S
sui-publish

sui-publish

github.com/mystenlabs云与 DevOps19353 次安装今天更新
代码仓库

关于 sui-publish

sui-publish 是介绍发布和升级 Sui Move 包、使用网络及准备部署的参考技能,面向使用 Sui CLI 的开发者,内容涵盖临时测试、本地网络和上线检查。

主要功能

  1. 01发布和升级 Move 包
  2. 02在临时网络测试发布
  3. 03设置升级策略
  4. 04切换不同网络
  5. 05运行本地网络开发流程
  6. 06准备 Mainnet 上线检查

适用场景

  1. 01部署前测试包初始化
  2. 02向不同网络发布包
  3. 03修改代码前检查升级限制
  4. 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)。不要发布单独的计数器包,也不要创建第二个项目目录。

发布前检查清单
  1. 验证当前环境:sui client active-env
  2. 验证是否有 SUI 代币:sui client balance
  3. 确认构建成功: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。可以通过以下几种方式查找:

  1. Published.toml(首选):发布后,工具链会将 cap ID 记录在每个环境对应的 Published.toml 的 upgrade-capability 字段中。
  2. 查询所拥有的对象:列出发布地址所拥有的所有 UpgradeCap 对象:
    sui client objects --type 0x2::package::UpgradeCap
    
  3. 发布交易输出:原始 sui client publish 输出会在创建的对象列表中包含 UpgradeCap 对象 ID。
  4. 浏览器:在 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 命令。恢复意味着发布向前修复升级。

如果发布了有问题的升级
  1. **评估影响范围。**确定哪些函数受到影响。此前版本创建的现有对象仍然有效——其类型锚定在原始软件包 ID 上。
  2. **立即发布修复升级。**编写修正后的代码,运行测试,在 Testnet 上试运行,然后在 Mainnet 上执行 sui client upgrade。新的软件包 ID 将替代旧 ID,用于所有后续调用。
  3. **更新前端。**将 PACKAGE_IDS 指向新的(已修复的)软件包 ID。类型查询仍使用 ORIGINAL_PACKAGE_IDS。
  4. **进行沟通。**如果此漏洞影响了面向用户的行为,请通过应用的渠道通知用户。
如果 UpgradeCap 遭到入侵

持有 UpgradeCap 的攻击者可以在你的软件包下发布任意代码。缓解措施:

  • **如果你仍持有该权限:**立即限制它(only_dep_upgrades 或 make_immutable),以阻止进一步的恶意升级。
  • **如果攻击者持有该权限:**你无法恢复升级权限。请发布一个新软件包、迁移用户,并说明迁移情况。这就是生产软件包需要通过多重签名托管 UpgradeCap 的原因。
如果共享对象遭到破坏

有缺陷的函数可能会将无效状态写入共享对象。由于任何交易都可以修改共享对象:

  • **如果你可以升级:**发布包含修复函数的升级,以修复损坏的状态。通过 AdminCap 对其进行限制。
  • **如果软件包不可变:**唯一的选择是部署一个新软件包,其中包含迁移函数,用于读取旧对象的数据(如果可访问),并创建经过修正的对象。
预防检查清单
  • UpgradeCap 由多重签名持有,或限制为 additive / dep_only
  • 所有升级都在 Testnet 上使用相同代码和相同发布流程进行测试
  • 管理员操作会发出事件以供监控
  • 关键共享对象具有通过能力进行限制的修复函数
  • 前端无需重新部署即可切换软件包 ID(使用环境配置,而非硬编码)

评论

支持 Markdown