Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第 17 章 Move 合约引用 Walrus 数据

Move 合约不应该存大文件本体。它应该保存可验证引用:哪个 Walrus Blob、哪个业务对象、谁能更新、什么时候过期。真正的字节读取发生在客户端、后端或 indexer 中。

引用模型

一个实用的引用结构通常包含:

public struct BlobRef has copy, drop, store {
    blob_object_id: ID,
    blob_id_hash: vector<u8>,
    size: u64,
    end_epoch: u32,
}

这里故意不直接把长字符串塞进每个对象。Move 里可以保存 vector<u8>,但大字段会推高 gas 和对象大小。生产应用常用两种方案:

  • 链上保存短哈希或 bytes 形式的 blob_id,完整字符串由事件和索引器保存。
  • 链上保存完整 blob_id bytes,适合对象数量少、读链上状态方便的应用。

如果你要让前端直接从链上对象拼 aggregator URL,第二种更方便;如果你要管理大量资源,第一种更节省。

最小 Move 示例

本仓库的 examples/move 提供了一个最小示例。它不直接依赖官方 Walrus Move 模块,因为不同网络的包 ID、对象类型和入口函数需要以官方最新配置替换。示例重点是业务对象如何持有 Walrus 引用。

核心结构如下:

public struct Asset has key, store {
    id: UID,
    owner: address,
    blob: BlobRef,
    label: String,
}

发布前你必须替换:

占位项替换来源
walrus = "0x0"当前网络官方 Walrus package ID
WALRUS_SYSTEM_OBJECT_ID当前网络 Walrus system object
blob_object_id上传 Blob 后的 Sui 对象 ID
blob_id_hashblob_id_bytes上传返回的 blob_id 派生值

Mainnet 使用 mainnet-v1.47.1 对应配置,Testnet 使用 testnet-v1.47.1 对应配置。Devnet devnet-v1.48.0 是预发布,只适合实验。

合约边界

Move 能做:

  • 记录某个业务对象引用哪个 Blob。
  • 限制谁可以创建、替换、删除引用。
  • 发事件,让 indexer 建立链上到链下的读取索引。
  • 检查引用字段的基本格式、大小和过期 epoch。

Move 不适合做:

  • 从 Walrus HTTP aggregator 下载字节。
  • 在链上解码大文件。
  • 在每次读内容时发交易。
  • 把完整文件内容写进对象字段。

如果你需要“内容必须符合某个格式”,把校验放在上传前或后端任务里,再把校验结果写成链上状态。例如:图片宽高、MIME、JSON schema 版本、AI 数据集摘要。

创建和更新工作流

推荐的创建流程:

  1. 客户端选择文件并计算业务哈希。
  2. 后端或 SDK 上传到 Walrus,拿到 blob_id 和链上对象 ID。
  3. 客户端发 Sui 交易调用 create_asset,把 Walrus 引用写入业务对象。
  4. 合约发 AssetCreated 事件。
  5. 索引器监听事件,保存完整 blob_id、业务 ID、owner、tx digest。

更新流程不要直接覆盖历史:

  1. 上传新 Blob。
  2. 调用 update_blob_ref 更新业务对象。
  3. 事件里带上 old/new 引用。
  4. 后端决定旧 Blob 是否续期、保留或等待自然过期。

这样做可以把业务状态更新和存储生命周期分开。用户替换头像时,头像对象是一个业务实体;旧图片 Blob 是一次历史内容写入。

权限模式

常见权限模式如下:

模式实现适用场景
用户自管assert!(tx_context::sender(ctx) == asset.owner, E_NOT_OWNER)个人文件、个人主页
管理员能力发布时铸造 AdminCap内容审核、应用配置
集合拥有者业务对象挂在 collection 下NFT、项目资料库
多签或治理由外层合约控制入口协议参数、公共数据集

不要用“知道 blob_id”当权限。Walrus Blob 默认公开可读;访问控制应由加密、Seal、后端授权或业务合约控制。

事件设计

事件是链上索引的主接口。至少包含:

public struct AssetCreated has copy, drop {
    asset_id: ID,
    owner: address,
    blob_object_id: ID,
    size: u64,
    end_epoch: u32,
}

如果完整 blob_id 不写入对象,也可以放进事件。但要记住事件不是对象状态,合约内部后续无法读取历史事件。凡是合约逻辑以后还要判断的字段,必须存在对象里。

测试重点

Move 单测应该覆盖:

  • 非 owner 不能更新引用。
  • end_epoch 早于当前 epoch 的引用不能创建,或者会被标记为已过期。
  • size 超过业务限制时拒绝。
  • 更新事件包含旧引用和新引用。
  • 删除业务对象不会假装删除 Walrus Blob。

最后一条很重要:链上业务对象删除不等于 Walrus 存储立即删除。Walrus 的生命周期由 Blob 存储期和官方合约规则控制。